Resizing images before upload using HTML5 canvas

You often see people uploading huge 4000x3000px images to webapps, only to be resized there to 100x75px. That's obiously a huge waste of bandwidth and users' time. The solution is to resize the image before upload. But many regular users don't know how to resize photos, and don't want to know. That's OK, it's our, developers, responsibility to deal with it.

There are several blog posts outlining how to do it. I wanted to share with you a more complete fully working solution, including the backend. So here we go.

This is the code I will be explaining: https://github.com/josefrichter/resize/blob/master/public/preprocess.js.

Now let me walk you through it:

  1. We use a multifile input form field and observe it's onchange event (so it all starts on line 64, please).
  2. We read the files using the HTML5 FileReader API. We need to use .readAsArrayBuffer, because using .readAsDataURL is quite inefficient (slow) for this use case.
  3. As we used .readAsArrayBuffer, we now need to construct a Blob from this data, and then get it's URL using window.URL.createObjectURL(blob)
  4. Now we are able to create new Image element and set it's src to this new blob url. We don't actually display this image (don't append it to DOM), we just need it as a source for the canvas element.
  5. So now we send the image to the canvas. The canvas size is set to desired output size. (I used data-maxwidth and data-maxheight directly in the input element).
  6. Now that the image is drawn into scaled-down canvas, we can get the data back. You can do more processing in canvas, like turning the image to greyscale, etc.
  7. We read the data from canvas via canvas.toDataURL("image/jpeg",0.7) - feel free to set your own output format and quality.
  8. Now we attach new hidden inputs to the original form and transfer the dataURI images basically as normal text.
  9. On backend, we than just read the dataURI and save it to file using f.write(Base64.decode64(imagedata)). And that's it.

Now there's room for improvement:

  • Transfering still quite big images using dataURI is probably also not the most efficient way, but at least we are transferring much less data than initially.
  • You could transfer the data asynchronously using XHR2, which would require slightly more complex backend approach. I went for classic oldsyle synchronous form here, because it's easy to implement into my existing apps for now.
  • This code is still quite raw, I am using it in restricted admin interface. Using it in wild public might require a few more security measures.
  • My knowledge of javascript is not endless, so I know it's not a top notch code. I just wanted to share my proof of concept. I welcome any comments, suggestions, forks, pull requests, etc. Thanks for your help!

The whole repository is at https://github.com/josefrichter/resize

Thanks

I am looking forward to your comments and suggestions at josef.richter[at]me.com

Please see also my homepage and share this article! Thank you.