« How much energy does a 100kg human pack? | Main | Gmail Autologout and Timed Session »

Client-side image crop and then upload

Earlier this week, Mathangi wanted to know if it's possible to crop an image in the browser and then upload it to the server for her website. I promised I'll think about it over this weekend. So I got up early today and spent a good hour looking around for a solution. There seems to be a ton of code out there that lets you crop an image using JavaScript and PHP, including the excellent JCrop library. However, these all seem to be written for pre-HTML5 days. While you can define the crop area on the client, actual cropping is done on the server. See for example, at the time of writing, the disclaimer in the JCrop manual.

Jcrop creates an interface to crop an image. However, actually creating a new, cropped image file is obviously beyond the scope of a client-side plugin. It may also be the most challenging part of implementing such a process in your web application (now that you've found Jcrop).

Unfortunately, this defeats the whole purpose of the upload for my wife. She wants the image cropping to be done on the client because the upload will be much smaller, and only consist of the area of interest that the user wants to upload. Not the whole 10MB JPEG file. But fortunately, the task may not be as challenging as the disclaimer suggests.

So I put together a quick and tiny solution using a combination of JCrop, JQuery, HTML5's neat File and Canvas APIs, and a "clever" trick. Here is the full code in its awesome smallness (only about 32 lines for the HTML file and 55 lines for the JS including comments). Obviously you need to embellish it considerably.

You can see this code in action at this demo here. Of course, it only works in HTML5 compatible modern browsers, such as Chrome, Firefox and Safari.

If you're just interested in the code, feel free to grab it by Viewing Page Source, and then linking through to the JavaScript in the file crop.js.

How it works

Jcrop is fantastic, but I couldn't figure out a way to get it to latch on to the HTML5 canvas object. Apparently it could only crop images directly. To access the individual pixels within the image, we need the image to be in an HTML5 canvas. What we need, therefore, is some way to map the Jcrop-provided crop-coordinates into corresponding canvas coordinates so we can extract the crop region as a data stream (image data). There's a few different ways to do this, including having a hidden canvas into which the image is secretly loaded. But I just took the easy shortcut and overlaid the canvas directly on top of the raw image in a div of the exact same dimensions :-)

HTML5 drag/drop hooks allow us to drag an image into the div. Using the FILE api, we then read the image contents from the local file system into a data URL and display it in both the div and the canvas. Note that I scale the longer side of the image to fit within the 200 pixel div I use to display it. You may wish to play around with all these settings.

Jcrop is attached to the div, but because the image in the canvas is of the exact same display dimensions, Jcrop's crop coordinates apply exactly to the canvas as well. So we are able to extract the requested slice of the image from the canvas, transfer it to a second "preview" canvas for the user to verify. When the user is happy with what they see in the second panel, they can click the Upload button, which will then transmit to the server only the contents of the second (cropped) panel. Oh, I forget to mention, you still need server side code, but it doesn't have to do any image processing. Even more importantly, you don't have to suffer the overhead of having to receive a whole entire large image, only a small portion of which is actually intended for display by the uploading user!

Here are the 15 lines of PHP code that receive the image and save it into an image file. Obviously you need to customize it to build in error handling and save the image in a location that your application requires, not /dev/null :-)

   <?php
   if (isset($_POST['i'])) {
     $data=$_POST['i'];
     $filteredData=substr($data, strpos($data, ",")+1);
     $decodedData=base64_decode($filteredData);
     $fp = fopen('/dev/null', 'wb');
     fwrite($fp, $decodedData);
     fclose($fp);
     echo "Upload successful\n";
   } else {
     echo "No uploaded data\n";
   }
   ?>

TODO

This is clearly only a proof of concept. I'm happy to spend a few more cycles on this when I can next manage to tear myself away from my day-job. But in the meantime if you happen to address any issues or make enhancements this basic approach, I'd be interested to hear about it, cohack or use your mods, perhaps for the following:

  • Progress meter while reading the image file
  • Error checks of various kinds
  • Bells and whistles as needed
  • Better smoothing/interpolation when shrinking a large image into the div and cropping a small part of it.

Enjoy!

&

TrackBack

TrackBack URL for this entry:
http://www.pandamatak.com/cgi-bin/mt/mt-tb.cgi/75

Post a comment

(If you haven't left a comment here before, you may need to be approved by the site owner before your comment will appear. Until then, it won't appear on the entry. Thanks for waiting.)

About

This page contains a single entry from the blog posted on November 6, 2010 8:44 AM.

The previous post in this blog was How much energy does a 100kg human pack?.

The next post in this blog is Gmail Autologout and Timed Session.

Many more can be found on the main index page or by looking through the archives.

Powered by
Movable Type 3.35