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

前端 未结 3 716
长情又很酷
长情又很酷 2021-02-02 04:06

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 throu

相关标签:
3条回答
  • 2021-02-02 04:40

    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

    sourceBuffer8[4*i]>Threshold
    

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

    sourceBuffer32[i]=0xff000000;
    

    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);
    };
    
    0 讨论(0)
  • 2021-02-02 04:40

    It depends on what exactly you're doing, but I'd suggest grabbing it all at once, and then looping through it.

    Grabbing it all at once is faster than grabbing it pixel by pixel, since searching through an array is a lot faster than searching through a canvas, once for each pixel.

    If you're really in need of speed, look into web workers. You can set each one to grab a specific section of the canvas, and since they can run simultaneously, they'll make much better use out of your CPU.

    getImageData() isn't really slow enough for you to notice the difference if you were to grab it all at once or individually, in my experiences using the function.

    0 讨论(0)
  • 2021-02-02 04:50

    Additionally to what GameAlchemist said, if you want to get or set all the colors of a pixel simultaneously, but you don't want to check endianness, you can use a DataView:

    var data = context.getImageData(0, 0, canvas.width, canvas.height);
    var view = new DataView(data.data.buffer);
    
    // Read or set pixel (x,y) as #RRGGBBAA (big endian)
    view.getUint32(4 * (x + y*canvas.width));
    view.setUint32(4 * (x + y*canvas.width), 0xRRGGBBAA);
    
    // Read or set pixel (x,y) as #AABBGGRR (little endian)
    view.getUint32(4 * (x + y*canvas.width), true);
    view.setUint32(4 * (x + y*canvas.width), 0xAABBGGRR, true);
    
    // Save changes
    ctx.putImageData(data, 0, 0);
    
    0 讨论(0)
提交回复
热议问题