8
votes

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

2
please show us before and after photos of the effect you are trying to achieve?Simon Sarris
Just added links at the bottom of the question. Thanks!Daniel Magliola
You're just trying to darken some sprites? For goodness sakes, don't use per-pixel image data for that. Just draw semi-transparent black overtop (with a different globalCompositeOperation to preserve alpha).Phrogz
Thank you! That's the exact kind of answer i'm looking for! I'm doing it this way because it's how I used to do it in the pre-web times, so it's all I know. Which composition should I use? Also, could you post this as an answer so I can upvote you as much as I can? Thanks!Daniel Magliola

2 Answers

11
votes

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/

11
votes

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.