I am implementing lighting in a tile engine, and I need a way to darken tiles, based on their light level.

For the ground tiles, I can clear the context to black, then set the globalAlpha and use drawImage normally. But what about foreground tiles, such as trees? I don't want them to be translucent, just darker. Is there any way to filter the output of drawImage?


I figured it out. My solution was to use a "light mask" - an image with a progression of tiles of varying translucency. The darkening is achieved by simply rendering from the light mask over the finished scene.


  • \$\begingroup\$ Note that answers that reference transient external resources should recap the information in them to avoid the inevitable dangling links that render the answer near-useless. \$\endgroup\$ Nov 8 '14 at 18:21

When you want to have full control over the color values of an image, you can draw it to an off-screen canvas like this (NOTE: This code assumes the image is already loaded!)

    var offscreenCanvas = document.createElement('canvas');
    offscreenCanvas.width = image.width;
    offscreenCanvas.height = image.height;
    var context = offscreenCanvas.getContext('2d');
    context.drawImage(image, 0, 0);

You can then use context.getImageData to get an array of raw RGBA values. You can then manipulate these color values in any way you desire and write them back with context.putImageData.

// get raw pixel values
var imageData = context.getImageData(0, 0, image.width, image.height);
var pixels = imageData.data;
// modify each pixel
for(var i = 0; i < pixels.length; i += 4) {
   // red is pixels[i];
   // green is pixels[i + 1];
   // blue is pixels[i + 2];
   // alpha is pixels[i + 3];
   // all values are integers between 0 and 255
   // do with them whatever you like. Here we are reducing the color volume to 75%
   // without affecting the alpha channel
   pixels[i] = pixels[i] * 0.75;
   pixels[i+1] = pixels[i+1] * 0.75;
   pixels[i+2] = pixels[i+2] * 0.75;
// write modified pixels back to canvas
context.putImageData(imageData, 0, 0);

When you want to draw the modified image to the main canvas, you can use the offscreen canvas as a source parameter just like any normal image.

  • \$\begingroup\$ That's a good and simple technique, but I fear it would be too slow to be done on a per-tile basis. \$\endgroup\$
    – alekop
    May 12 '14 at 18:22
  • \$\begingroup\$ @alekop Keep in mind that you only need to do it once on initialization per tileset and light level because you can keep the offscreen canvas with the modified image around and draw it again and again. It's a simple memory/cpu tradeoff which is likely much faster than your method which requires two drawing-passes per frame. \$\endgroup\$
    – Philipp
    May 12 '14 at 19:29
  • \$\begingroup\$ Yeah, it would be faster, but the trouble is, I have 16 light levels, and I am considering going up because the difference from one tile to the next is still rather noticable. That would mean increasing the memory consumption by 16x! I guess I could give this technique a try, it just feels like it wouldn't be fast enough. \$\endgroup\$
    – alekop
    May 12 '14 at 23:39
  • \$\begingroup\$ @alekop When you are planning to write an advanced lighting engine, especially when you want mobile light sources, you might want to look into WebGL and implement it as a pixel shader. \$\endgroup\$
    – Philipp
    May 13 '14 at 10:46

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.