Blog postLearn to code web app that takes pictures

Learn to code web app that takes pictures with the webcam

Published September 12, 2018

The application that you will learn to develop in the tutorial uses a little HTML5 and a short snippet of JavaScript to take pictures with the user's webcam in a way which is similar to a mobile apps. The images will be saved on the server side using PHP.

The images that you take with the app will be stacked on the screen. Watch the following video demonstrating in 10 seconds how it is going to work:

After you've watched the video, let's explain in principle how it works:

  • An HTML video tag shows everything that is captured in the webcam.
  • Once a user clicks the save button, the code pastes the current frame into a canvas element.
  • The canvas is converted to data that can be saved as a file.
  • The data is passed to the server side via Ajax, and the server generates an image file.
  • The data that the server returns includes the location of the file, which the app uses as the source of the images that it displays to the user.
  • The image stack effect is achieved using the CSS rotate and z-index features. The rotate feature tilts the images, while the z-index piles the images.

  1. The HTML
  2. The CSS
  3. The JavaScript
  4. The PHP
  5. Conclusion and the source code

# 1. The HTML

The HTML contains the following elements:

index.html

<!doctype html>
<html>
<head>
    <meta charset="utf-8" />
    <title>Web app that takes pictures</title>
    <link rel="stylesheet" href="style.css">
</head>
<body>
<div id="newImages"></div>
<video id="player" autoplay></video>
<canvas id="canvas" width="320px" height="240px"></canvas>
<button class="btn btn-primary" id="capture-btn">Capture</button>
<div id="pick-image">
  <label>Video is not supported. Pick an Image instead</label>
  <input type="file" accept="image/*" id="image-picker">
</div>

<script src="script.js"></script>
</body>
</html>

Click to download the source code

  • The HTML5 video tag shows everything that the camera records before taking a picture. The tag needs to have the autoplay attribute.
  • A canvas element to which you paste the image taken with the camera.
  • A button on which you click to freeze the video and to activate the code that saves the images to the server side.
  • The "#newImages" DIV contains the images that the app generates.
  • The "#pick-image" DIV contains an upload button.

# 2. The CSS

style.css

#newImages {
    height : 300px;
    position : relative;
    max-width : 100%;
}
img.masked {
    position : absolute;
    background-color : #fff;
    border : 1px solid #babbbd;
    padding :10px;
    box-shadow :1px 1px 1px #babbbd;
    margin : 10px auto 0;
}
#player {
    width : 320px;
    height : 240px;
    margin :10px auto;
}
canvas{
    width : 320px;
    height : 240px;
    margin : 10px auto;
}
#capture-btn{
    width : 130px;
    margin : 0 auto;
}
#pick-image{
    display : none;
}

Click to download the source code

  • The "#newImages" DIV must have a relative position so that you can give the images inside it an absolute position which enables us to play with their position relative to the container.
  • The class ".masked" gives the images a nice look thanks to wide margins.
  • The script hides the "#pick-image" DIV because you want to display it only if the browser does not support taking pictures.

# 3. The JavaScript

  • After the page loads, the script calls the startMedia() function that runs the code that is responsible for taking the video.
  • Clicking the button saves the image and displays the stack.

script.js

const videoPlayer = document.querySelector('#player');
const canvasElement = document.querySelector('#canvas');
const captureButton = document.querySelector('#capture-btn');
const imagePicker = document.querySelector('#image-picker');
const imagePickerArea = document.querySelector('#pick-image');
const newImages = document.querySelector('#newImages');

// Image dimensions
const width = 320;
const height = 240;
let   zIndex = 1;


const startMedia = () => {
    // Play the video if possible
};

// Capture the image, save it and then show it in the page
captureButton.addEventListener('click', (event) => {

});

window.addEventListener("load", (event) => startMedia());

Click to download the source code

The startMedia() function activates the methods that the browser use to take video.

const startMedia = () => {
  if (!('mediaDevices' in navigator)) {
    navigator.mediaDevices = {};
  }

  if (!('getUserMedia' in navigator.mediaDevices)) {
    navigator.mediaDevices.getUserMedia = (constraints) => {
      const getUserMedia = navigator.webkitGetUserMedia || navigator.mozGetUserMedia;

       if (!getUserMedia) {
          return Promise.reject(new Error('getUserMedia is not supported'));
        } else {
          return new Promise((resolve, reject) => getUserMedia.call(navigator, constraints, resolve, reject));
        }
    };
  }

  navigator.mediaDevices.getUserMedia({video: true})
    .then((stream) => {
      videoPlayer.srcObject = stream;
      videoPlayer.style.display = 'block';
    })
  .catch((err) => {
    imagePickerArea.style.display = 'block';
  });
};

First, the code checks that the browser has video capturing capabilities which are based on the mediaDevices interface which provides access to connected media devices like cameras and microphones. If the browser does not support video capturing, the code tries to create the object using the features webkitGetUserMedia or mozGetUserMedia. If the support exists, the video is displayed. Otherwise, the file upload input field takes command.

Clicking the "Capture" button results in the following:

  • Updating the canvas with the picture that the app has just taken.
  • Converting the canvas into data that the app can later save as an image file with the following method:
canvasElement.toDataURL()
  • And passing the data to the server side with the fetch method. The data returned from the server includes the location of the image file created and saved by the server. The script uses the location as the source of the image that it pastes back to the DOM.
// Capture the image, save it and show it in the page
captureButton.addEventListener('click', (event) => {
  // Draw the image from the video player on the canvas
  canvasElement.style.display = 'block';
  const context = canvasElement.getContext('2d');
  context.drawImage(videoPlayer, 0, 0, canvas.width, canvas.height);

   videoPlayer.srcObject.getVideoTracks().forEach((track) => {
    // track.stop(); 
  });

  // Convert the data so it can be saved as a file
  let picture = canvasElement.toDataURL();

  // Save the file by posting it to the server
  fetch('/api/save_image.php', {
    method : 'post',
    body   : JSON.stringify({data: picture })
  })
  .then((res) => res.json())
  .then((data) => {
    if(data.success){
      // Create the image and give it the CSS style with a random tilt 
      //  and a z-index which gets bigger
      //  each time that an image is added to the div
      let newImage = createImage(data.path, "new image", "new image", width, height, "masked");
      let tilt = -(20 + 60 * Math.random());
      newImage.style.transform = "rotate("+tilt+"deg)";
      zIndex++;
      newImage.style.zIndex    = zIndex;
      newImages.appendChild(newImage);
      canvasElement.classList.add('masked');
    }
  })
  .catch((error) => console.log(error))
});
  • The images that the script stack on the user side are created by the createImage() function:
const createImage = (src, alt, title, width, height, className) => {
    let newImg = document.createElement("img");

    if(src !== null)       newImg.setAttribute("src", src);
    if(alt !== null)       newImg.setAttribute("alt", alt);
    if(title !== null)     newImg.setAttribute("title", title);
    if(width !== null)     newImg.setAttribute("width", width);
    if(height !== null)    newImg.setAttribute("height", height);
    if(className !== null) newImg.setAttribute("class", className);

    return newImg;
}

# 4. The PHP

The PHP code saves the images in the "uploads/images" folder on the server side then returns a JSON with the data about the location of the newly created image.

api/save_image.php

<?php
$folder = "/uploads/images/";
$destinationFolder = $_SERVER['DOCUMENT_ROOT'] . $folder;
$maxFileSize         = 2*1024*1024;

// Get the posted data
$postdata = file_get_contents("php://input");

if(!isset($postdata) || empty($postdata))
    exit(json_encode(["success"=>false, "reason"=>"Not a post data"]));

// Extract the data
$request = json_decode($postdata);

// Validate
if(trim($request->data) === "")
    exit(json_encode(["success"=>false, "reason"=>"Not a post data"]));


$file = $request->data;

// getimagesize is used to get the file extension
// Only png / jpg mime types are allowed
$size = getimagesize ($file);
$ext  = $size['mime'];
if($ext == 'image/jpeg')
    $ext = '.jpg';
elseif($ext == 'image/png')
    $ext = '.png';
else
    exit(json_encode(['success'=>false, 'reason'=>'only png and jpg mime types are allowed']));

// Prevent the upload of large files
if(strlen(base64_decode($file)) > $maxFileSize)
    exit(json_encode(['success'=>false, 'reason'=>"file size exceeds {$maxFileSize} Mb"]));

// Remove inline tags and spaces
$img = str_replace('data:image/png;base64,', '', $file);
$img = str_replace('data:image/jpeg;base64,', '', $img);
$img = str_replace(' ', '+', $img);

// Read base64 encoded string as an image
$img = base64_decode($img);

// Give the image a unique name. Don't forget the extension
$filename = date("d_m_Y_H_i_s")."-".time().$ext;

// The path to the newly created file inside the upload folder
$destinationPath = "$destinationFolder$filename";

// Create the file or return false
$success = file_put_contents($destinationPath, $img);

if(!$success)
    exit(json_encode(['success'=>false, 'reason'=>'the server failed in creating the image']));

// Inform the browser about the path to the newly created image
exit(json_encode(['success'=>true,'path'=>"$folder$filename"]));

Click to download the source code

# Conclusion and the source code

In the tutorial, you learned how to use external devices, like the camera through HTML5 APIs. The bonus was the real life use of the fetch API a new feature introduced into JavaScript with the ES6 standard.

Happy coding!

Click to download the source code for the tutorial

comments powered by Disqus