Horrible Canvas GetImageData() / PutImageData() performance on mobile

10,148

Solution 1

Phrogz has the right idea. You really just want to paint a half-transparent (or ratio-transparent) black over the whole thing with 'source-atop' globalCompositeOperation.

Like this: http://jsfiddle.net/F4cNg/

There was actually a good question on sophisticated darkening like this but the author deleted it which is a real shame. It's certainly possible to make dark-masks on drawn stuff, even with sophisticated shapes. They may not help you, but for the sake of completeness and for anyone searching for "darken canvas" stuff where some parts are kept light (exclusion zones), that can be done with the 'xor' globalCompositeOperation, like this:

http://jsfiddle.net/k6Xwy/1/

Solution 2

Have you seen this performance tip?: http://ajaxian.com/archives/canvas-image-data-optimization-tip

They talk about reducing calls to the DOM to increase performance.

So, based on this tip you might try:

var pixel = ctx.getImageData(0, 0, tmpCanvas.width, tmpCanvas.height);
    pixelData=pixel.data; // detach the pixel array from DOM
    var length = pixelData.length;
    for (var i = 0; i < length; i+= 4) {
        pixelData[i] = pixelData[i] * ratio;
        pixelData[i + 1] = pixelData[i + 1] * ratio;
        pixelData[i + 2] = pixelData[i + 2] * ratio;
    }

Your .data calls may be slowing you down.

Share:
10,148
recursive9
Author by

recursive9

Founder of Crystal Gears Portfolio: - www.movertrends.com - www.friendshopper.com - www.digitalshow.com - www.translatorscorner.com - www.gigpay.com

Updated on July 29, 2022

Comments

  • recursive9
    recursive9 almost 2 years

    I'm doing a little HTML5 game and, while loading my sprites at the beginning of the map, I do some processing with GetImageData() / looping over all the image / PutImageData().

    This works fantastically great on my PC, however, on my cell phones it's horrendously slow.

    PC: 5-6 ms
    iPhone 4: 300-600 ms
    Android HTC Desire S: 2500-3000 ms
    

    I've been doing some VERY basic benchmarking, and both GetImageData and PutImageData run very fast, what's taking long is the looping through the contents.

    Now, I obviously expect a slowdown on the phone, but 1000x sounds a bit excessive, and the loading takes about 4 minutes on my HTC, so that's not going to work. Also, everything else in the game works at very reasonable speed (mainly because the screen is ridiculously smaller, but still, it works surprisingly fine for JS on a cell phone)


    What I'm doing in this processing is basically "darkening" the sprite to a certain level. I simply loop through all the pixels, and multiply them by a value < 1. That's all.

    Since this is too slow... Is there a better way of doing the same thing, using the Canvas functionality, (compositing, opacity, whatever), without looping through all the pixels one by one?

    NOTE: This layer has some 100% transparent pixels, and some 100% opaque pixels. Both need to remain either 100% opaque or 100% transparent.

    Things I've thought of that wouldn't work:
    1) Painting the sprites in a new canvas, with lower opacity. This won't work because i need the sprites to remain opaque, just darker.
    2) Painting the sprites, and painting a semi-transparent black rect on top of them. This will make them darker, but it'll also make my transparent pixels not transparent anymore...

    Any ideas?

    This is the code I'm using, just in case you see something terribly idiotic in it:

    function DarkenCanvas(baseImage, ratio) {
        var tmpCanvas = document.createElement("canvas");
        tmpCanvas.width = baseImage.width;
        tmpCanvas.height = baseImage.height;
        var ctx = tmpCanvas.getContext("2d");
        ctx.drawImage(baseImage, 0, 0);
    
        var pixelData = ctx.getImageData(0, 0, tmpCanvas.width, tmpCanvas.height);
        var length = pixelData.data.length;
        for (var i = 0; i < length; i+= 4) {
            pixelData.data[i] = pixelData.data[i] * ratio;
            pixelData.data[i + 1] = pixelData.data[i + 1] * ratio;
            pixelData.data[i + 2] = pixelData.data[i + 2] * ratio;
        }
    
        ctx.putImageData(pixelData, 0, 0);
        return tmpCanvas
    }
    

    EDIT: This is an example of what i'm trying to do with the image:
    Original: http://www.crystalgears.com/isoengine/sprites-ground.png
    Darkened: http://www.crystalgears.com/isoengine/sprites-ground_darkened.png

    Thanks!
    Daniel

  • recursive9
    recursive9 over 12 years
    Thank you so much, this is exactly what I needed. I considered darkening out the final result of the drawing. The XOR trick is cool, much simpler than that I was planning to do, but even without it I could just paint a rect with "holes". At that point, I don't care about my transparent pixels anymore. I'm not taking that approach, however, because i'm using an ISO perspective, and my method, while way slower, lets me have a bit more accuracy on a few things I care about. Thanks for the tip, though, i'll definitely come in handy sometime. THANKS!!!
  • Amit
    Amit over 3 years
    Absolutely true. Not a big difference though, but sure saves some milliseconds.👍