Introduction

Most of the web applications allow users to upload media, especially images – random photographs, avatars etc. Sometimes we might want to provide our users with the feature of cropping their avatars. This is a legacy requirement which has been well translated into pragmatic code. We’re not going to re-invent the wheel here. We’re going to take a novel/different approach to this requirement. The question is, why do we need to do this? Well, as the title of the article suggests, we’re going to crop images client side i.e. in the browser before sending out the image file to the server. This way we’re actually able to forgo quite a bit of server side implementation, thereby (comparatively) reducing load on the server.

Image manipulation

There has been considerable work done in the field of image manipulation – cropping, filtering etc. As far as cropping/clipping is concerned, what we require is to let the user select a particular area on the image (that’s the area on the image that we need to preserve) and then chop off all remaining areas. Usually this has been achieved by sending the file along with the area to be cropped to the server and then the server does the honors for us.

Traditional Server side implementation

Let’s have a look at a sample server side implementation using PHP:

Our use-case stands as: we’ve uploaded our file onto the server, stored on the file system and then we refer the file and crop it.

<?php

/* crop method takes the fileName on the filesystem along with

* the starting coordinates from where to begin cropping and

* the crop area defined by its width and height

*/

function crop($fileName, $startX, $startY, $width, $height){

$fileContents = file_get_contents($fileName);

if($fileContents){

/*create a blank image with dimensions as that of the cropped area

*this is going to be our final image

*/

$dest = imagecreatetruecolor($width, $height);

$destX = 0;

$destY = 0;

/** create an image identifier from file titled $fileName */

$src = imagecreatefromjpeg($fileName);

/** copy the portion of the $src which needs to be cropped into the $dest */

imagecopyresampled ($dest, $src, $destX, $destY, $startX, $startY, $width, $height, $width, $height);

/** save the file */

imagejpeg($dest, “thumbnail_”.$fileName);

}

}

?>

code walkthrough

First we create a blank image whose dimensions are same as that of the selected area by the user. Then we create an image resource which references the image file uploaded to the server. Refer to the image below for visualizing the following cropping process:

/wp-content/uploads/2014/08/finalcrop_519946.png

We’ve the top-left coordinates of the selected area on the image along with the extent of the selection. All we need to do is to copy all pixels within the selection area onto our blank image. We’ll begin pasting these pixels onto the blank image from its top-left corner i.e. (0, 0). That’s exactly what imagecopyresampled method does for us. Note that in case our source image’s width/height didn’t match that of the destination’s width/height, then appropriate shrinking/stretching of the selected area will be done. Once that is done, we’re ready to save the new cropped file. Note that this is a very basic implementation of the requirement.

Client Side implementation

The same process needs to done on the client side as well. But as of now, we didn’t have enough resources/APIs available which could make such situations conducive to image cropping on the client side. But with the HTML5 <canvas> element, this is definitely possible now. We’ll use a canvas element to perform all low level image manipulations. Before we begin, we need to get ourselves acquainted with a few important canvas APIs.

  1. drawImage(image, dx, dy, dw, dh) – It’s a method on the context of the canvas. It takes an image, scales it to a width of dw and a height of dh, and draws it on the canvas at coordinates (dx, dy).
  2. getImageData(dx, dy, dw, dh) – A method on the canvas context  that returns an ImageData object representing the underlying pixel data for the area of the canvas denoted by the rectangle which starts at (dx, dy) and has an dw width and dh height.
  3. putImageData(imageData, dx, dy) – Again, a method on the context that draws/puts the image data retrieved using getImageData method onto a canvas at coordinates (dx, dy)
  4. toDataURL() – This method works on the canvas element itself  and creates the image data URL for the canvas contents

    

For selecting an area on the image we’ll be using a very useful jQuery plugin called imgAreaSelect. We’ll also be referring to the HTML5 FileReader API as well. If you’re not familiar with the HTML5 FileReader API, please refer to this article. Okay let’s begin with designing our HTML.

HTML

<input type=’file’ name=’avatar’ /><img src=” width=’400px’ id=’preview’ />

JavaScript

$(“input[type=’file’]”).bind(‘change’, function(evt){

var file = this.files[0];

var fileReader = new FileReader();

fileReader.onload = function(event){                      

      var originalImage = event.target.result;

      $(‘#preview’)

    .attr(‘src’, originalImage)

.imgAreaSelect({

            aspectRatio: ‘1:1’, //square area selection

            handles: true,

            onSelectEnd: function(img, selection){

  /*selection is an object containing the width, height of selection along with the top-left and

   *bottom-right coordinates of the selected rectangle

   *For e.g. selection = {

   *                     x1: 12, //top-left X

   *                     y1: 10, // top-left Y

   *                     x2: 145, //bottom-right X

   *                     y2: 178, //bottom-right Y

   *                  width: 133, //selection width

   *                 height: 168 // selection height

   *          }

      var image = new Image();

      image.src = originalImage;

      image.onload = function(){

                   var imgObj = this;

        var canvas = document.createElement(‘canvas’);

        $(canvas)

            .attr(‘width’, imgObj.width)

            .attr(‘height’, imgObj.height);

       

        var ctx = canvas.getContext(‘2d’);

        ctx.drawImage(imageObj, 0, 0,  imgObj.width,  imgObj.height);

       

        var imageData = ctx.getImageData(

selection.x1,

selection.y1,

selection.width,

selection.height

);

             

        $(canvas)

            .attr(‘width’, selection.width)

            .attr(‘height’, selection.height);

        ctx = canvas.getContext(‘2d’);

ctx.clearRect(0, 0, selection.width, selection.height); 

        ctx.putImageData(imageData, 0, 0);

       

        var dataUrl = canvas.toDataURL();       

/* the dataUrl variable could be set as ‘src’ attribute of an image and could be sent

* to the server where it could be stored.

*/       

     }   

            }

             });           

    }

fileReader.readAsDataURL(file);

});

code walkthrough

We first select a file following which we read it using the FileReader API. Note that the file object is a blob and that it’s being accessed/read via the readAsDataURL method which converts the blob into a base64 encoded string. Then we create an Image resource and load the file contents onto the resource. When the image resource is loaded, we allow the user to select an area to crop. Upon the completion of the user’s selection we capture the area of selection and load the Image resource onto a canvas element using the drawImage method. From, the canvas we cut out the area whose coordinates match that of the area selected on the original image. Finally we reset our canvas and put back the cropped area (retrieved previously) onto it. The toDataURL method converts the contents rendered on the canvas into a base64 encoded string representing the cropped image. By default, this method generates data in the PNG format which however could be altered. Once we have the data URL representation of the image, this could be sent to the server which then could be stored as a blob or could be written onto a file and then saved in the file system.

Conclusion

This article doesn’t preach that image manipulation should be shifted client side. Server side libraries are more exhaustive and provide plethora of features for e.g. ImageMagick PHP extension. However certain features as cropping, applying filters etc. could definitely be shifted downtown and the server could be made more available. We need to bet more on HTML5, at the same time gracefully degrading to other technologies in case of unfavorable environments.

Browser Support

We used the following HTML5 goodies in this article. They are supported in most of the browsers as of now. Further information could be found here:

  1. Canvas – http://caniuse.com/canvas
  2. FileReader – http://caniuse.com/filereader
To report this post you need to login first.

Be the first to leave a comment

You must be Logged on to comment or reply to a post.

Leave a Reply