Search code examples
javascriptcanvaspixel

Canvas imagedata multiple pixel insert


I am retrieving pixels in a canvas imagedata and I'm doing that a lot.

I think the inserting and retrieving from and to the canvas imagedata is expensive in cpu time, so I want to make as few of those as possible.

One way of cutting that would be to make a single insert that would insert multiple pixels in a single sequence, but so far I have not been able to see how that would be done. All the examples I have seen so far retrieve and insert only a single pixel.

So the question is, in order to speed up canvas imagedata pixel manipulation, how do I insert/retrieve multiple pixels simultaneously?


Solution

  • Just select a larger region when retrieving a pixel buffer:

    var imageData = ctx.getImageData(x, y, width, height);
                                           ^^^^^^^^^^^^ not limited to one
    

    Now your data buffer will contain all pixels for the given region. To get the whole canvas:

    var imageData = ctx.getImageData(0, 0, ctx.canvas.width, ctx.canvas.height);
    

    Adjust them and put back into the same position:

    ctx.putImageData(imageData, x, y);
    

    and you're done.

    Remember that each pixel consists of four bytes (RGBA). To address a larger buffer you can do:

    function getPixelIndex(x, y) {
        return (y * width + x) * 4; // width used when getting buffer
    }
    

    Tips:

    • if you plan to update the same buffer often simply retrieve the buffer once and store a pointer to it, update it when you need and put back, then reuse the same buffer. This way you save the time getting the buffer. This won´t work if you in the mean time apply graphics to the canvas with standard methods.
    • You can also start with an empty buffer using createImageData() instead of getImageData().
    • If your pixel color data is more or less static you can update the buffer using a Uint32Array instead of the Uint8ClampedArray. You get a 32-bit version like this after getting the imageData:

      var buffer32 = new Uint32Array(imageData.data.buffer);

    Your new buffer32 will point to the same underlying byte buffer so no significant memory overhead, but it allows you to read and write 32-bit values instead of just 8-bit. Just be aware of that the byte order is (typically) little-endian so order the bytes as ABGR. Then do as before, call ctx.putImageData(imageData, x, y); when you need to update.