Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

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

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

like image 760
Daniel Magliola Avatar asked Dec 10 '11 01:12

Daniel Magliola


2 Answers

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/

like image 171
Simon Sarris Avatar answered Nov 05 '22 08:11

Simon Sarris


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.

like image 41
Jacquie Avatar answered Nov 05 '22 10:11

Jacquie