21

I am trying to do some dynamic visual effects using the HTML 5 canvas' pixel manipulation, but I am running into a problem where setting pixels in the CanvasPixelArray is ridiculously slow.

For example if I have code like:

imageData = ctx.getImageData(0, 0, 500, 500);

for (var i = 0; i < imageData.length; i += 4){
    imageData.data[i] = buffer[i];
    imageData.data[i + 1] = buffer[i + 1];
    imageData.data[i + 2] = buffer[i + 2];
}

ctx.putImageData(imageData, 0, 0);

Profiling with Chrome reveals, it runs 44% slower than the following code where CanvasPixelArray is not used.

tempArray = new Array(500 * 500 * 4);
imageData = ctx.getImageData(0, 0, 500, 500);

for (var i = 0; i < imageData.length; i += 4){
    tempArray[i] = buffer[i];
    tempArray[i + 1] = buffer[i + 1];
    tempArray[i + 2] = buffer[i + 2];
}

ctx.putImageData(imageData, 0, 0);

My guess is that the reason for this slowdown is due to the conversion between the Javascript doubles and the internal unsigned 8bit integers, used by the CanvasPixelArray.

  1. Is this guess correct?
  2. Is there anyway to reduce the time spent setting values in the CanvasPixelArray?
Nixuz
  • 3,439
  • 4
  • 39
  • 44
  • Old question and probably out of date, by in your second example you don't seem to be doing anything to `imageData` (e.g. you're not setting the values from `tempArray` to `imageData`)? – ZachB Jul 29 '15 at 18:53
  • @ZachB The example is correct. It doesn't use the CanvasPixelArray which seemed to be the bottle neck at the time. See below for some great responses which solved the problem. – Nixuz Jul 31 '15 at 00:41
  • Hah, I misread your question. :) – ZachB Jul 31 '15 at 00:47

4 Answers4

12

Try caching a reference to the data pixel array. Your slowdown could be attributed to the additional property accesses to imageData.data. See this article for more explanation.

E.g. This should be faster that what you currently have.

var imageData = ctx.getImageData(0, 0, 500, 500),
    data = imageData.data,
    len = data.length;

for (var i = 0; i < len; i += 4){
 data[i] = buffer[i];
 data[i + 1] = buffer[i + 1];
 data[i + 2] = buffer[i + 2];
}

ctx.putImageData(imageData, 0, 0);
jimr
  • 11,080
  • 1
  • 33
  • 32
  • You will also find a reference to the above article on ajaxian, with more comments suggesting that it is an issue of reference access. http://ajaxian.com/archives/canvas-image-data-optimization-tip – Jean Hominal Jun 03 '10 at 09:52
  • 1
    I can't understand what is buffer, did you mean imageData? – kilianc Jan 25 '12 at 17:34
  • It's the data that Nixuz wanted to copy into the image - defined somewhere else. It's not imageData, as the "data" variable is the image data - it wouldn't make sense to try to copy it to itself. – jimr Feb 02 '12 at 05:48
4

I don't know if this helps you because you want to manipulate pixels, but for me, in Firefox 3.6.8, just the call to putImageData was very, very slow, without doing any pixel manipulation. In my case, I just wanted to restore a previous version of the image that had been saved with getImageData. Too slow.

Instead, I got it to work well using toDataUrl/drawImage instead. For me it's working fast enough that I can call it within handling a mousemove event:

To save:

savedImage = new Image()  
savedImage.src = canvas.toDataURL("image/png")

The to restore:

ctx = canvas.getContext('2d')  
ctx.drawImage(savedImage,0,0)
Corey Trager
  • 22,649
  • 18
  • 83
  • 121
1

Looks like you're doing some kind of "blitting", so maybe drawImage or all-at-once putImageData could help. Looping a quarter million times to copy pixels individually, rather than using massive "blitting" operations, tends to be much slower -- and not just in Javascript;-).

Alex Martelli
  • 854,459
  • 170
  • 1,222
  • 1,395
  • 1
    The actual drawing happens all at once when putImageData is called. The method which processes the buffer data by looping over each "pixel" (block in the buffer) and doing some advance operations on each of them is reasonably fast, at least MUCH faster than the method described which basically is intended to get the data onto the screen. It shocks me that processing the data takes a lot less time then simply copying it into the canvasPixelArray for drawing. – Nixuz Apr 04 '10 at 01:59
1

Oddly, loops through 2d object arrays are faster than a 1d array offset calcs and no objects. Format accordingly and see if that helps (in my tests, it was 20x faster).

(heads up: this script could crash your browser! If you run it, sit tight for few minutes and let it do its thing) http://jsfiddle.net/hc52jx04/16/

function arrangeImageData (target) {

var imageCapture = target.context.getImageData(0, 0, target.width, target.height);
var imageData = {
    data: []
};
imageData.data[0] = [];
var x = 0;
var y = 0;
var imageLimit = imageCapture.data.length;

for (var index = 0; index < imageLimit; index += 4) {

    if (x == target.width) {
        y++;
        imageData.data[y] = [];
        x = 0;
    }

    imageData.data[y][x] = {
        red: imageCapture.data[index],
        green: imageCapture.data[index + 1],
        blue: imageCapture.data[index + 2],
        alpha: imageCapture.data[index + 3]
    };
    x++;
}
return imageData;

}


function codifyImageData (target, data) {

var imageData = data.data;

var index = 0;
var codedImage = target.context.createImageData(target.width, target.height);

for (var y = 0; y < target.height; y++) {

    for (var x = 0; x < target.width; x++) {

        codedImage.data[index] = imageData[y][x].red;
        index++;
        codedImage.data[index] = imageData[y][x].green;
        index++;
        codedImage.data[index] = imageData[y][x].blue;
        index++;
        codedImage.data[index] = imageData[y][x].alpha;
        index++;
    }

}

return codedImage;

}

More information: http://discourse.wicg.io/t/why-a-straight-array-for-canvas-getimagedata/1020/6