Masking shapes in HTML5 canvas?
Asked Answered
C

2

9

Apologies if this has been asked elsewhere but it's pretty hard to phrase as it is so I couldn't find anything.

Is there any way to implement masks in canvas?

For example, using shapes only (no images) I draw a house with a window. I also have a shape representing a person. I want that person to appear at the window - but obviously only so much as the window allows should be visible of the person. The rest would be masked.

I thought about emptying the part of the house occupied by the window, such that there was a genuine hole in the layer, which makes the problem easy to solve.

But I'm conscious you can't delete shapes or parts of shapes in canvas, only draw new stuff over old stuff. So in a multi-layered environment (I'm making a game in Kinetic.JS), what exactly can I do?

Sorry if any of this is poorly explained - new to the whole graphic thing.

Censer answered 18/8, 2012 at 18:33 Comment(1)
Maybe some suggestions are given here linkDamson
Z
31

You should learn about clipping and compositing soon, but neither of these are what you actually need here.

Instead you need to learn how to make paths using the non-zero winding number rule, which is what HTML5 canvas uses.

If you draw part of your path clockwise and another part counter-clockwise, you can "cut out" shapes from your path.

Here's an example with a window:

http://jsfiddle.net/simonsarris/U5bXf/


edit: Here's a bit of a visualization for you of how the nonzero winding number rule works:

enter image description here

Subpaths are drawn in a direction, and where the paths cross you'll get filled (or not) spaces.

If you put your finger on any part of the figure, and imagine a line going from your finger out into the empty space, that line crosses the path a number of times. If you start at zero and add 1 for every clockwise path, and subtract 1 for every counterclockwise path, the filled areas are all of the areas that have a non-zero number. The numbers for the areas are given in the above diagram.

Zhao answered 18/8, 2012 at 20:44 Comment(8)
Thanks for the help, and for the Fiddle. The cut-out thing is something of a revelation to me. Never realised you could effectively mark certain areas as transparent just by drawing shapes on an open path and not filling them (which appears to be what you're doing). What is the design choice behind that, or is it a hack? Slightly off-topic question: how come in my edit of your Fiddle, I cannot get simply a stroked rectangle - it insists on being filled (orange currently)?Censer
Also, does this cut-out technique work only with line-drawn paths? I updated your Fiddle to try to draw the initial blue square via rect() rather than with lineTo() commands, but the cut-outs no longer appeared.Censer
It's not open versus closed paths exactly, its the direction that the paths are drawn. Let me do another fiddle to give some examplesZhao
Here you go: jsfiddle.net/simonsarris/zGQTA You know how when you draw a circle on paper you can either draw it clockwise or counter-clockwise? All paths are like that. Any time a clockwise path and a counter-clockwise path cross, it will leave unfilled areas. See the edit for a visualizationZhao
This is fascinating - because there doesn't seem to be logical basis for this behaviour. If you did the same thing with a pen you wouldn't get the same result, so is this feature designed purely for the convenience of helping with situations like mine? Or am I missing some mathematical, logical reason for it happening? I get that it's useful, just not why it happens exactly.Censer
Yeah its more or less arbitrary. A long time ago (70's or earlier?) graphical systems designers needed ways to come up with rules to adequately describe what to do when figures criss-cross themselves multiple times, and also a way to "cut out" shapes from the figures they make. They came up with two ways of doing it, and this is one of them.Zhao
Thanks for the explanation. Incidentally, what's the other way, and does canvas support it? You've piqued my interest in this stuff now.Censer
the other way is the even-odd rule, and in some drawing systems (like GDI+ in windows or SVG in your browser) you can pick between the two. Here's a really simple example of one difference: en.wikipedia.org/wiki/…)Zhao
T
1

You just need to create a clipping path and draw your shape in there. The Mozilla Developer Network is a great starting place for learning canvas. Here's the section on clipping.

I've created a basic fiddle with an example of what I think you are trying to create.

var ctx = document.getElementById('canvas').getContext('2d');
ctx.fillRect(0, 0, 150, 150);

// create a clipping path
ctx.beginPath();
ctx.moveTo(20, 20);
ctx.lineTo(20, 130);
ctx.lineTo(130, 130);
ctx.lineTo(130, 20);
ctx.clip();

// backgroud in clipped area
ctx.fillStyle = "#11c";
ctx.fillRect(0, 0, 150, 150);

// draw shapes inside clipped area
ctx.translate(75, 90);

ctx.fillStyle = '#f00';

ctx.fillRect(-15, -40, 40, 40);
ctx.fillRect(0, 0, 10, 10);
ctx.fillRect(-25, 10, 60, 60);

Hope this helps, good luck with your project!

Trantham answered 18/8, 2012 at 19:40 Comment(2)
I accepted the other answer just because he opened my eyes to the non-zero winding number rule concept, which was something of a revelation, but I'm grateful for your answer and for the detailed Fiddle - it was most helpful. +1 to you.Censer
Haha thanks, just happy you figured out the solution. I didn't know about the non-zero winding rule concept either so I managed to learn something too.Trantham

© 2022 - 2024 — McMap. All rights reserved.