DEV Community

Cover image for JavaScript-30-Day-19
KUMAR HARSH
KUMAR HARSH

Posted on • Edited on

JavaScript-30-Day-19

Unreal Webcam Fun

demo

ss

So what are we going to be building today?

Today we are going to make a photo booth with JavaScript.

First of all, we've got our video being piped in from our webcam, and then we've got our video being piped into a canvas element. Then, once it's in a canvas element, we can start to do all kinds of cool things with it. Like we can take our photo, and then you can go ahead and download them. They'll download to your
computer, and you can see them as real photos. Then, in the scripts, once we have it, we can start to add effects, like different filters where things should be red. Or what we can do is make this really cool RGB split, where you can see my face.

Before we get started today, there is one
thing that we do need to get up and running, and that is a server. So far, in this course we've just been working off of
the file. However, because of security restrictions with
getting a user's webcam, it must be tied to what's called a "secure origin".

A secure origin is a website that
is HTTPS, or in our case localhost is also
a secure origin.

index.html needs to be fed through some sort of server.

Wes included a package.JSON file. If we open that up, you'll see one dependency, which is this called "browser-sync". It allows you to open up your website and start a little server, and it also gives you live reloading and a whole bunch of other stuff.

First we type npm install when that has finished what you can type is npm start.

This is the html we start with:

<div class="photobooth">
      <div class="controls">
        <button onClick="takePhoto()">Take Photo</button>
      </div>

      <canvas class="photo"></canvas>
      <video class="player"></video>
      <div class="strip"></div>
    </div>
Enter fullscreen mode Exit fullscreen mode

We quickly make a couple of selectors:

const video = document.querySelector('.player');
const canvas = document.querySelector('.photo');
const ctx = canvas.getContext('2d');
const strip = document.querySelector('.strip');
const snap = document.querySelector('.snap');
Enter fullscreen mode Exit fullscreen mode

The first thing we want to do is get the video
being piped into that video element.

navigator.mediaDevices.getUserMedia({ video: true, audio: false })
Enter fullscreen mode Exit fullscreen mode

Now that thing this going to return a promise so we call a .then on it.

navigator.mediaDevices.getUserMedia({ video: true, audio: false })
    .then(localMediaStream => {
      console.log(localMediaStream);
Enter fullscreen mode Exit fullscreen mode

Now what we need to do is to take our video, and we need to set the source to be that localMediaStream

Now, that's not going to work automatically, because localMediaStream is an object. In order for our video to work, it actually needs to be converted into some sort of URL.

video.srcObject = localMediaStream;
      video.play();
    })
    .catch(err => {
      console.error(`OH NO!!!`, err);
    });
Enter fullscreen mode Exit fullscreen mode

.createObjectURL, That's going to convert that media stream into something that this video player can understand.

Now what we will see is one or two frames not a continuous video stream.

Why is that?

That's because we set the video to be this media stream. But it's not going to update unless we actually go ahead and play it.

Therefore, underneath that we'll call video.play, which is going to play it.

We need to do a catch here, just in case someone doesn't allow you to access their webcam. We need to handle that error.

Here is the complete getVideo function:

function getVideo() {
  navigator.mediaDevices
    .getUserMedia({ video: true, audio: false })
    .then((localMediaStream) => {
      console.log(localMediaStream);

      video.srcObject = localMediaStream;
      video.play();
    })
    .catch((err) => {
      console.error(`OH NO!!!`, err);
    });
}

getVideo();
Enter fullscreen mode Exit fullscreen mode

The next thing that we need to do is to take a frame from this video, and to paint it onto the actual canvas on the screen.

We'll first re-size our canvas according to height and width of actual video.

Now, we need to make sure that the canvas is the
exact same size before we paint to it. That's really important because if the canvas is not the same size as the video
or if your video webcam has a different width and height to it, then we need to change that.

const width = video.videoWidth;
  const height = video.videoHeight;
  canvas.width = width;
  canvas.height = height;
Enter fullscreen mode Exit fullscreen mode

Now, what we want to do is to every 16 milliseconds (random choice), we are going to take an image from the webcam
and put it into the canvas.

return setInterval(() => {
    ctx.drawImage(video, 0, 0, width, height);
Enter fullscreen mode Exit fullscreen mode

Here is the complete paintToCanvas function:

function paintToCanvas() {
  const width = video.videoWidth;
  const height = video.videoHeight;
  canvas.width = width;
  canvas.height = height;

  return setInterval(() => {
    ctx.drawImage(video, 0, 0, width, height);
}
Enter fullscreen mode Exit fullscreen mode

The way that dramImage works is that you pass it an image, or a video element, and it will paint it right to it.

We're going to start at 00. Start at the top left-hand corner of the canvas, and then paint the width and the height. That's exactly why we resized our canvas.

We return that interval here, because if you ever need to stop this from painting, you can have access to that interval and
you can call clearInterval on it.

It's kind of a pain to have to manually run this paintToCanvas. So what we're going to do is, We're going to listen for an event
on the video element called canplay

canplay - That's an event that the video will emit.

video.addEventListener("canplay", paintToCanvas);
Enter fullscreen mode Exit fullscreen mode

Now, what we want to do is let's work on the takePhoto function.

First of all we add capture sound to it for effect.

snap.currentTime = 0;
  snap.play();
Enter fullscreen mode Exit fullscreen mode

What we now need to do is take the data out of the canvas.
We can do this, const data = canvas.toDataURL.

Then, you pass it, "image/jpeg".

The image that we currently have is in a text-based representation so we need to convert in into a link.

const link = document.createElement("a");

  link.href = data;
  link.setAttribute("download", "handsome");
Enter fullscreen mode Exit fullscreen mode

We can now not only click photos but download them as well.

Now we want the photos to be visible on the screen as well:

link.innerHTML = `<img src="${data}" alt="Handsome Man" />`;
strip.insertBefore(link, strip.firstChild);
Enter fullscreen mode Exit fullscreen mode

Here is the complete take photo function:

function takePhoto() {
  // played the sound
  snap.currentTime = 0;
  snap.play();

  // take the data out of the canvas
  const data = canvas.toDataURL("image/jpeg");

  const link = document.createElement("a");

  link.href = data;
  link.setAttribute("download", "handsome");

  link.innerHTML = `<img src="${data}" alt="Handsome Man" />`;

  strip.insertBefore(link, strip.firstChild);
}
Enter fullscreen mode Exit fullscreen mode

The last thing that we want to do is do some filters on them.
So the way that a filter works is that you get the pixels out of the canvas, and then you mess with them, changing the
RGB values, and put them back in.

So let's go back up to our paintToCanvas:

Here are the changes we make:

// take the pixels out
let pixels = ctx.getImageData(0, 0, width, height);

// mess with them

pixels = redEffect(pixels); //red filter

// pixels = greenScreen(pixels); //green screen effect

// pixels = rgbSplit(pixels); //rgb split effect

// ctx.globalAlpha = 0.8; //for ghosting effect

// put them back
ctx.putImageData(pixels, 0, 0);
  }, 16);
Enter fullscreen mode Exit fullscreen mode

Here is the completed function:

function paintToCanvas() {
  const width = video.videoWidth;
  const height = video.videoHeight;
  canvas.width = width;
  canvas.height = height;

  return setInterval(() => {
    ctx.drawImage(video, 0, 0, width, height);

// take the pixels out
let pixels = ctx.getImageData(0, 0, width, height);

// mess with them

pixels = redEffect(pixels); //red filter

// pixels = greenScreen(pixels); //green screen effect

// pixels = rgbSplit(pixels); //rgb split effect

// ctx.globalAlpha = 0.8; //for ghosting effect

// put them back
ctx.putImageData(pixels, 0, 0);
  }, 16);
}
Enter fullscreen mode Exit fullscreen mode

and now you create the functions for effects:

function redEffect(pixels) {
  for (let i = 0; i < pixels.data.length; i += 4) {
    pixels.data[i + 0] = pixels.data[i + 0] + 200; // RED
    pixels.data[i + 1] = pixels.data[i + 1] - 50; // GREEN
    pixels.data[i + 2] = pixels.data[i + 2] * 0.5; // Blue
  }
  return pixels;
}
Enter fullscreen mode Exit fullscreen mode
function rgbSplit(pixels) {
  for (let i = 0; i < pixels.data.length; i += 4) {
    pixels.data[i - 150] = pixels.data[i + 0]; // RED
    pixels.data[i + 500] = pixels.data[i + 1]; // GREEN
    pixels.data[i - 550] = pixels.data[i + 2]; // Blue
  }
  return pixels;
}
Enter fullscreen mode Exit fullscreen mode
function greenScreen(pixels) {
  const levels = {};

  document.querySelectorAll(".rgb input").forEach((input) => {
    levels[input.name] = input.value;
  });

  for (i = 0; i < pixels.data.length; i = i + 4) {
    red = pixels.data[i + 0];
    green = pixels.data[i + 1];
    blue = pixels.data[i + 2];
    alpha = pixels.data[i + 3];

    if (
      red >= levels.rmin &&
      green >= levels.gmin &&
      blue >= levels.bmin &&
      red <= levels.rmax &&
      green <= levels.gmax &&
      blue <= levels.bmax
    ) {
      // take it out!
      pixels.data[i + 3] = 0;
    }
  }

  return pixels;
}
Enter fullscreen mode Exit fullscreen mode

With this we are done with the project.

GitHub repo:

Blog on Day-18 of javascript30

Blog on Day-17 of javascript30

Blog on Day-16 of javascript30

Follow me on Twitter

Follow me on Linkedin

DEV Profile

cenacr007_harsh image

You can also do the challenge at javascript30

Thanks @wesbos , WesBos to share this with us! 😊💖

Please comment and let me know your views

Thank You!

Top comments (2)

Collapse
 
rash123 profile image
RASHMI VERMA

Awasome color effect

Collapse
 
cenacr007_harsh profile image
KUMAR HARSH

thanks