Thursday, June 20, 2013

How to Upload Canvas Data to Server?

In a recent assignment of mine I had to deal with HTML5 Canvas. I built a simple editor. The problem was that I needed some nice way to upload the data to the server. As it wasn't as easy problem as anticipated I thought to write this post. Perhaps someone else struggling with the same things will find it useful. :)

The simplest way to upload Canvas data to the server is simply to encode it using toDataUrl and then pass it as a part of your query string. After that you might want to decode it again on the server again and store somewhere. This approach might be alright for small images but I do not recommend this approach for bigger ones.

You are better off using FormData and Blobs. These are quite recent additions but seem to work well in modern browsers. The biggest advantage of this approach is that it allows you to treat your Canvas data as if it was a regular file. You also avoid having to encode the data at the server side so that's a nice bonus. And it is possible to implement progress bars and all that jazz on top of this.

Unfortunately certain method we need to make this work, namely Canvas.toBlob, isn't that widely supported yet. Especially the lack of support in Chrome is problematic although Firefox seems to work just fine.

As it happens, there's a way to work around this issue. The fix might not be that nice due to the amount of processing required. Still, it's way better than nothing. We simply need a custom function to convert data URI to blob. I came by a solution at Stack Overflow. It required a bit of tweaking as BlobBuilder was eliminated from the specification in favor of Blob. I have the fixed version below:
function dataURItoBlob(dataURI) {
    // convert base64 to raw binary data held in a string
    // doesn't handle URLEncoded DataURIs - see SO answer #6850276 for code that does this
    var byteString = atob(dataURI.split(',')[1]);

    // separate out the mime component
    var mimeString = dataURI.split(',')[0].split(':')[1].split(';')[0];

    // write the bytes of the string to an ArrayBuffer
    var ab = new ArrayBuffer(byteString.length);
    var dw = new DataView(ab);
    for(var i = 0; i < byteString.length; i++) {
        dw.setUint8(i, byteString.charCodeAt(i));
    }

    // write the ArrayBuffer to a blob, and you're done
    return new Blob([ab], {type: mimeString});
}
EDIT: Updated to use DataView.