Make a Camera App in Web (Part 2): Capturing the image
Tags: vue, javascript
Published: 2020-02-12 03:20:23

From the past article in Part 1, we already succeeded in accessing the webcam with `mediaDevices.getUserMedia()` and stop it with a single button. You may want to stop there if you just want to stream something without any action required after. But I doubt you just leave it like that because, what the hell that kind of thing going to be useful, right?

So now we're going to move to the next step. What did the camera do? Capture the image, yes. So after we successfully accessing the user permission to use the camera and stream it to our webpage, then we're going to add some spaces to store and display our captured image and of course, a button to capture it.

First, let's put `<canvas></canvas>` tag to display our image. Don't forget to make it the same size as the `<video></video>` so our image won't be cropped.

```html
 <div v-if="isCameraOpen" class="camera-box">
    <video v-show="!isPhotoTaken" ref="camera" :width="450" :height="337.5" autoplay></video>
    <canvas v-show="isPhotoTaken" id="photoTaken" ref="canvas" :width="450" :height="337.5"></canvas>
 </div>
```

See that there is a condition put there which is `isPhotoTaken`. Why put the condition there? Think about the camera. It streams when we haven't captured any image yet. Then the moving picture gone replaced by the static image that we captured after clicking a button. So we're going to make just like a camera, the `<video></video>` will be hidden and `<canvas></canvas>` would appear after we click the shutter.

There you might think again why using `v-show` instead of `v-if-else`. It simply because `v-show` just hid the element, not delete it, so we're not going to call `createCameraElement()` multiple times if we want to take another photo.

Let's declare `isPhotoTaken` inside `data()` and handle it together with `isCameraOpen` in the `toggleCamera()` method so when we close the camera and open it again, it doesn't show the last captured image.

```javascript
data() {
  return {
    isCameraOpen: false,
    isPhotoTaken: false
  }
},
  
methods: {
  toggleCamera() {
    if(this.isCameraOpen) {
      this.isCameraOpen = false;
      this.isPhotoTaken = false;
      this.stopCameraStream();
    } else {
      this.isCameraOpen = true;
      this.createCameraElement();
    }
  }
}
```

Nah now what we need after that is the shutter button. Let's add it to our HTML and give it an action to called a method that will capture our photo. Let's call our method `takePhoto()`.

```html
<div v-if="isCameraOpen" class="camera-shoot">
  <button type="button" class="button" @click="takePhoto">
    <img src="https://img.icons8.com/material-outlined/50/000000/camera--v2.png">
  </button>
</div>
```

I use the free image icons to fill the button and make it round with setting `border-radius` to 100% so it will look like a shutter button on a phone camera. Here's the styling if you wanna take a look.

```scss
button {
  height: 60px;
  width: 60px;
  display: flex;
  align-items: center;
  justify-content: center;
  border-radius: 100%;
      
  img {
    height: 35px;
    object-fit: cover;
  }
}
```

Now we have the shutter button. Great! Next is let's define the method `takePhoto()`. This method will basically grab the image on the `<video></video>` and put it inside `<canvas></canvas>`.

```javascript
takePhoto() {
  this.isPhotoTaken = !this.isPhotoTaken;
      
  const context = this.$refs.canvas.getContext('2d');
  context.drawImage(this.$refs.camera, 0, 0, 450, 337.5);
}
```

In the method above, we have this toggle action on `isPhotoTaken`. This action will make the button not only as a shutter but also as a switch to replace the image and the camera stream. So when the stream is active, you click the shutter, it will capture your photo. To the opposite, when you already captured a photo and click the shutter again, the camera will stream and the existing photo will be gone.

For saving the image to the canvas, we first have to catch the canvas element by pointer it with `$refs` and get a drawing context on the canvas with `getContext('2d')`. And then we can assign the image from camera (`<video></video>'`) to the canvas with `drawImage()`. For this part make sure that you draw with the correct size. Here's I draw starting from point `x = 0`, `y = 0`, and with `450` width and `337.5` height, the same size as the `<video></video>`.

That's it! Now you may want to run it and try to take a photo to see if it's work or not.

Is it all? No. We want to do something with the photo. You may want to upload it to the server or download it. Now let's try to put a download button below the shutter button like this. We're going to use anchor instead a button because we will assign `href` attribute later. Don't forget to put `download` attribute to define your file name that will be downloaded.

```html
<div v-if="isPhotoTaken && isCameraOpen" class="camera-download">
  <a id="downloadPhoto" download="my-photo.jpg" class="button" role="button" @click="downloadImage">
    Download
  </a>
</div>
```

Giving the condition above, the button will be hidden when we click the close button. Actually, the close camera button will completely hide all the elements except the button itself. Now we have to define the `downloadImage` on javascript.

```javascript
downloadImage() {
  const download = document.getElementById("downloadPhoto");
  const canvas = document.getElementById("photoTaken").toDataURL("image/jpeg")
    .replace("image/jpeg", "image/octet-stream");
  
  download.setAttribute("href", canvas);
}
```

`downloadImage()` will get data URI containing a representation of the image in the `image/jpeg` format then assign it to `href` attribute in download anchor.