louisinhongkong louisinhongkong - 1 month ago 8x
Javascript Question

Canvas getImageData() For optimal performance. To pull out all data or one at a time?

I need to scan through every pixel in a canvas image and do some fiddling with the colors etc. For optimal performance, should I grab all the data in one go and work on it through the array? Or should I call each pixel as I work on it.

So basically...

data = context.getImageData(x, y, height, width);


data = context.getImageData(x, y, 1, 1); //in a loop height*width times.


You'll get much higher performances by grabbing the image all at once since : a) a (contiguous) acces to an array is way faster than a function call. b) especially when this function isa method of a DOM object having some overhead. c) and there might be buffer refresh issues that might delay response (if canvas is on sight... or not depending on double buffering implementation).

So go for a one-time grab.

I'll suggest you look into Javascript Typed Arrays to get the most of the imageData result.

If i may quote myself, look at how you can handle pixels fast in this old post of mine (look after 2) ):

Nice ellipse on a canvas?

(i quoted the relevant part below : )

You can get a UInt32Array view on your ImageData with :

var myGetImageData = myTempCanvas.getImageData(0,0,sizeX, sizeY);
var sourceBuffer32     = new Uint32Array(myGetImageData.data.buffer);

then sourceBuffer32[i] contains Red, Green, Blue, and transparency packed into one unsigned 32 bit int. Compare it to 0 to know if pixel is non-black ( != (0,0,0,0) )

OR you can be more precise with a Uint8Array view :

var myGetImageData = myTempCanvas.getImageData(0,0,sizeX, sizeY);
var sourceBuffer8     = new Uint8Array(myGetImageData.data.buffer);

If you deal only with shades of grey, then R=G=B, so watch for


and you can set the i-th pixel to black in one time using the UInt32Array view :


set to any color/alpha with :

sourceBuffer32[i]= (A<<24) | (B<<16) | (G<<8) | R ;

or just to any color :

sourceBuffer32[i]= 0xff000000 | (B<<16) | (G<<8) | R ;

(be sure R is rounded).

Listening to @Ken's comment, yes endianness can be an issue when you start fighting with bits 32 at a time. Most computer are using little-endian, so RGBA becomes ABGR when dealing with them 32bits a once.
Since it is the vast majority of systems, if dealing with 32bit integer assume this is the case, and you can -for compatibility- reverse your computation before writing the 32 bits results on Big endian systems. Let me share those two functions :

function isLittleEndian() {     
// from TooTallNate / endianness.js.   https://gist.github.com/TooTallNate/4750953
    var b = new ArrayBuffer(4);
    var a = new Uint32Array(b);
    var c = new Uint8Array(b);
    a[0] = 0xdeadbeef;
    if (c[0] == 0xef) { isLittleEndian = function() {return true }; return true; }
    if (c[0] == 0xde) { isLittleEndian = function() {return false }; return false; }
    throw new Error('unknown endianness');

function reverseUint32 (uint32) {
    var s32 = new Uint32Array(4);
    var s8 = new Uint8Array(s32.buffer);
    var t32 = new Uint32Array(4);
    var t8 = new Uint8Array(t32.buffer);        
    reverseUint32 = function (x) {
        s32[0] = x;
        t8[0] = s8[3];
        t8[1] = s8[2];
        t8[2] = s8[1];
        t8[3] = s8[0];
        return t32[0];
    return reverseUint32(uint32);