Background Setup
I have a web application that deals with creating images from a set of other images. The way I've chosen to do that is by reading in a set of images and placing them on an HTML canvas. Then, I export each canvas as a jpeg to a third-party API using toDataURL
and converting it into a Blob. The issue I am facing is that I have many of these canvases all exporting data as a jpg and it is consuming a lot of resources. The application slows and becomes unresponsive as each canvas attempts to call toDataURL
.
Question
I've found that calling a canvas's toDataUrl()
or toBlob()
can be very slow especially for large canvas sizes. I'd like to utilize the multi-threaded nature of web workers.
First, I tried passing in the canvas object but an error was thrown. It turns out objects are a problem and that it seems they either get converted into strings or fail when they can't be cloned. Either way, I found that passing a context's image data does work. The data is passed in the form of raw RGB values as a Uint8ClampedArray
from the canvas context's method getImageData()
.
Main.js
var canvas = document.createElement('canvas');
var context = canvas.getContext('2d');
var worker = new Worker('myWorker.js');
worker.postMessage({
image: context.getImageData(0, 0, canvas.width, canvas.height)
});
myWorker.js
this.onmessage = function(e) {
// GOAL: turn e.data.image into an image blob or dataUrl and return it.
// e.g. this.postMessage(new Blob([e.data.image.data], {type: 'image/jpg'});
}
I think it comes down to knowing how to convert a Uint8ClampedArray
which holds the RGB information into the jpg/png data.
The reason why I think this might be useful is that I believe that getImageData
just copies an existing data structure from the canvas context and therefore is not as costly as toDataUrl
. I captured the cpu profile while calling something similar to the code block below:
var image = context.getImageData(0, 0, canvas.width, canvas.height)
var dataUrl = canvas.toDataURL('image/jpeg');
and got:
So, given that, I'd like to offload the brunt of the process into a web worker. I don't even mind if it takes longer inside the web worker as long as its happening in another process.
Couple of extra thoughts about it:
- Adding an extra library to do the conversion is okay, but bonus points for offering how to add an external library as a dependency to web worker files. Right now I'm using browserify for the application. Perhaps create another browserified bundle for the web worker?
- I need a jpeg in the end (for a third party API) so converting it to a png is only so good as to be a step in the conversion to a jpeg.
- I've tried lowering the
encoderOptions
, the second option intoDataURL
, as a way to speed up the process, but I haven't seen much of a change
ctx.getImageData()
. I'm not sure you'll win anything by passing the compression part to a Web Worker. – Flint