Search code examples
javascripthtml5-canvas

Javascript: Render PNG stored as Uint8Array onto Canvas element without Data URI


I'm trying to render a PNG image that is stored in a javascript Uint8Array. The code that I originally tried was the following:

var imgByteStr = String.fromCharCode.apply(null, this.imgBytes);

var pageImg = new Image();
pageImg.onload = function(e) {

    // Draw onto the canvas
    canvasContext.drawImage(pageImg, 0, 0);

};

// Attempt to generate the Data URI from the binary
// 3rd-party library adds toBase64() as a string function
pageImg.src="data:image/png;base64,"+encodeURIComponent(imgByteStr.toBase64());

However, I found that for some reason the onload function was never running (I had breakpoints set up in chrome and it simply never hit). I found that if I set the src to a URL instead of Data URI, it worked correctly. However, due to some constraints, I cannot directly refer to the image's URL, i.e., it must be loaded from the UInt8Array.

Also, to complicate things slightly more, these images may be megabytes in size. From what I have read, there are compatibility issues with attempting to use Data URIs for very large images. Because of this, I am extremely hesitant to utilize them.

The question, therefore, is how do I render this byte array as a PNG onto a canvas context without using Data URIs or referring directly to the image URL?


I've also tried using something like the following to manipulate the image data directly using the putImageData function. Keep in mind that I don't 100% understand how this function works

var imgdata = canvasContext.createImageData(this.baseHeight, this.baseWidth);
var imgdatalen = imgdata.data.length;
for(var i=0; i<imgdatalen; i++) {
    imgdata.data[i] = _this.imgBytes[i];
}
canvasContext.putImageData(imgdata, 0, 0);

It was lifted from a blog whose tab I have since closed, so apologies to the inspiration for not giving appropriate credit.


Also, while slowly hammering away at this thing, I ran into an error in Chrome SECURITY_ERR: DOM Exception 18. Turns out that, as soon as an image is loaded inot a canvas using drawImage, it cannot be retrieved without some additional workarounds. A Chromium Blog post on the topic was especially useful


Solution

  • If you have the PNG data in memory (which I think is what you're describing) then you can create an image from it. In HTML it would look like:

     <img src="data:image/png;base64,KSjs9JdldhAlisflAkshf==" />
    

    In JavaScript you can do the same:

    var image = document.createElement('img');
        image.src = 'data:image/png;base64,' + base64Data;
    

    Note that you can change the MIME type if you don't have PNG data.

    You could then draw the image onto your canvas using context.drawImage(image, 0, 0) or similar.

    So the remaining part of the puzzle is to encode the contents of your Uint8Array into Base 64 data.

    var array = new Uint8Array(),
        base64Data = btoa(String.fromCharCode.apply(null, array));
    

    The function btoa isn't standard, so some browsers might not support it. However it seems that most do. Otherwise you might find some code I use helpful.

    I haven't tested any of this myself!