Drawing transparent glyphs on the HTML canvas

The HTML canvas has a set of methods, createImageData and putImageData, that look like they will enable you to draw transparent shapes pixel by pixel. The data structures that you manipulate with these methods are pseudo-arrays of pixels, with four bytes per pixel. One byte for red, one for green, one for blue and one for alpha. This alpha byte makes one believe that you are going to be able to manage transparency, but that’s a lie.

Here is a little script that attempts to overlay a simple generated pattern on top of a uniform background:

var wrong = document.getElementById("wrong").getContext("2d");
wrong.fillStyle = "#ffd42a";
wrong.fillRect(0, 0, 64, 64);
var overlay = wrong.createImageData(32, 32),
    data = overlay.data;
fill(data);
wrong.putImageData(overlay, 16, 16);

where the fill method is setting the pixels in the lower-left half of the overlay to opaque red, and the rest to transparent black.

And here’s how it renders:putImageData replaces existing pixels

As you can see, the transparency byte was completely ignored. Or was it? in fact, what happens is more subtle. What happens is that the pixels from the image data, including their alpha byte, replaced the existing pixels of the canvas. So the alpha byte is not lost, it’s just that it wasn’t used by putImageData to combine the new pixels with the existing ones.

This is in fact a clue to how to write a putImageData that works: we can first dump that image data into an intermediary canvas, and then compose that temporary canvas onto our main canvas. The method that we can use for this composition is drawImage, which works not only with image objects, but also with canvas objects.

var right = document.getElementById("right").getContext("2d");
right.fillStyle = "#ffd42a";
right.fillRect(0, 0, 64, 64);
var overlay = wrong.createImageData(32, 32),
    data = overlay.data;
fill(data);
var overlayCanvas = document.createElement("canvas");
overlayCanvas.width = overlayCanvas.height = 32;
overlayCanvas.getContext("2d").putImageData(overlay, 0, 0);
right.drawImage(overlayCanvas, 16, 16);

And there is is, a version of putImageData that works like it should always have:Using an intermediary canvas gets the transparency right

No Comments