Masking in Canvas

Because the canvas requires manual drawing through scripting we need to leverage the 2d canvas API, through the canvas’ 2d context.

Clip paths in canvas are pretty straight forward. First we simply draw a path using the beginPath method on our context, followed by the drawing commands, then closePath.

ctx.beginPath();
ctx.arc(canvas.width / 2, canvas.height / 2, 200, 0, Math.PI * 2, false);
ctx.closePath();
ctx.clip();

Then we call clip() on the context. All the content we draw after this will be clipped by the path we just drew.

Masking in canvas works a little different in that there’s no specific mask property. Instead you leverage the canvas context’s globalCompositeOperation property. This can take a variety of values depending on how you want new content to influence the alpha channel or colors of the existing content. For a full list of the supported compositing operations check out MDN (https://developer.mozilla.org/en-US/docs/Web/API/CanvasRenderingContext2D/globalCompositeOperation).

For normal luminance masks we use the value ‘destination-in’ and then proceed to draw our grayscale mask.

ctx.globalCompositeOperation = 'destination-in';
ctx.drawImage(‘mylumamask’, 0, 0);

It’s important to remember canvas retains this as a global value. So when you’re done with your mask, you’ll need to set it back to the default, ‘source-over’ in order for things to draw correctly later.

 

ctx.globalCompositeOperation = ’source-over';

The real power of canvas is that you can use it to dynamically draw content through interaction as well as use video. Combining these with masks can create some really interesting results. The issue with using video for masks though is that many browsers still do not support alpha channels in their videos. This means that we have to manually convert a luminance mask video into an alpha mask in order to have a video mask content in canvas.

This can be done by drawing the video on a separate offscreen canvas for processing. We then draw the video onto the offscreen canvas. Then using getImageData we can collect and iterate over the pixels, determining their color values. Since it’s a grayscale image we can simply pick the red color value and set each pixel’s alpha to red value. Then we can call putImageData back on that offscreen canvas to draw the processed image. We now have a video playing in our canvas converted from a luminance mask to an alpha mask. Then we can continue to draw that back into our original canvas with the ‘destination-in’ globalCompositeOperation. This is more performance intensive since we have to do per pixel reading and writing so it may have performance issues for large footage, but in lieu of alpha video support this will get the job done. Of course if the browser supports alpha video you can skip this step altogether.

function drawMask() {
  videoctx.drawImage(video, 0, 0);
  var imgd = videoctx.getImageData(0, 0, videocanvas.width, videocanvas.height),
      pix = imgd.data,
      i;

  for (i = 0, n = pix.length; i < n; i += 4) {
    pix[i + 3] = pix[i];
  }

  videoctx.putImageData(imgd, 0, 0);
}

Click here to download the demo.