如何在画布上填充“相反”的形状?

40

假设我在HTML5画布上有一个圆(弧形),我可以通过以下方式填充它:

ctx.arc(100, 100, 50, 0, 2 * Math.PI);
ctx.fill();

它非常有效。但是,如何填充相反的区域?现在它是白色的,有一个黑色的圆圈,但我希望它更像以下图像(其中白色是背景颜色,黑色是填充颜色):

opposite area

我知道我可以使用黑色背景并绘制一个白色圆圈,但是背景可以是任何东西(之前已经绘制了各种东西,因此无法只交换颜色)。
另一件事是不应该填充整个画布,而是填充一个取消圆形的正方形。
我考虑使用globalCompositeOperation,但似乎它们都不符合我的需求。
那么,如何像示例图像中那样填充“相反”的区域?
4个回答

60

在 OP 中绘制黑色的图形 - 即绘制一个矩形,中间切割出一个圆形。

首先调用 beginPath();然后顺时针绘制圆形;接着逆时针绘制矩形(或者反过来);最后调用 fill()。

var ctx = $('#cv').get(0).getContext('2d');

ctx.fillStyle = "grey";
ctx.beginPath();
ctx.arc(200, 200, 100, 0, 2 * Math.PI);
ctx.rect(400, 0, -400, 400);
ctx.fill();
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<canvas width="400" height="400" id="cv"></canvas>

从大部分文档中可能不太明显,但您可以在单个fill()、stroke()或clip()操作中组合多个子路径。为了在形状中打洞,只需以相反方向绘制孔的形状即可。beginPath()方法重置当前路径。对于fill()和clip(),Canvas使用非零环绕数规则--如果您阅读该规则,它应该会变得更加清晰。


1
谢谢,我没有考虑到这一点。你所说的“逆时针矩形”是指一个负维度吗?在阅读了关于绕数规则的文章后,我的想法是,偶数个负维度可以抵消每一对。最终我得到了这个结果:http://jsfiddle.net/eGjak/685/. - pimvdb
谢谢,这个答案对于在Ejecta中实现HTML5画布非常有帮助,似乎需要使用路径的顺时针/逆时针来“挖出”一个圆。浏览器则更加宽容。 - Zachstronaut
4
@MichaelWaterfall 我也遇到了同样的问题,解决方法是指定一个奇偶填充,像这样:ctx.fill("evenodd"); - eskimomatt
2
为了使它更简单,您可以使用弧度函数的最后一个参数,并将其设置为true。 - user2039981
1
值得注意的是,如果将来有人遇到这个问题,ctx.arc中的200,200是指矩形宽度和高度的一半。在矩形中,由于这是一个正方形,第一个400是外部矩形的宽度,-400是宽度的负值,最后的400是高度。 - user3967379
显示剩余2条评论

24

你可以使用另一个画布作为遮罩来实现:

// This is the canvas where you want to draw
var canvas = document.getElementById('your-canvas');
var ctx = canvas.getContext('2d');

// I'll use a skyblue background that covers everything
// Just to demonstrate
ctx.fillStyle = "skyblue";
ctx.fillRect(0, 0, canvas.width, canvas.height);

// Create a canvas that we will use as a mask
var maskCanvas = document.createElement('canvas');
// Ensure same dimensions
maskCanvas.width = canvas.width;
maskCanvas.height = canvas.height;
var maskCtx = maskCanvas.getContext('2d');

// This color is the one of the filled shape
maskCtx.fillStyle = "black";
// Fill the mask
maskCtx.fillRect(0, 0, maskCanvas.width, maskCanvas.height);
// Set xor operation
maskCtx.globalCompositeOperation = 'xor';
// Draw the shape you want to take out
maskCtx.arc(30, 30, 10, 0, 2 * Math.PI);
maskCtx.fill();

// Draw mask on the image, and done !
ctx.drawImage(maskCanvas, 0, 0);
<canvas id="your-canvas">

这里是演示


非常感谢,但是在你的演示中它是青色而不是白色。你是否也遇到了同样的问题? - pimvdb
1
是的,我有意使用了这种颜色。它是背景色。 - user703016
Heandel:哎呀,对不起。显然你使用的是 skyblue……它完美地工作了,谢谢! - pimvdb
我想强调一下,您是在填充画布之后设置为 xor。我最初是在开始时设置并使用 xor 填充的,但这样很慢。 - pimvdb
我上一条评论的改进(现已删除):http://dev.rgraph.net/tests/2012-11-30/clip.html - Richard

5
通常这可以通过“裁剪区域”完成,其中您可以为绘图操作应用一个区域掩码。HTML5 画布有能力制作圆形本身的裁剪掩码...

http://www.html5canvastutorials.com/tutorials/html5-canvas-clipping-region-tutorial/

...但它缺少Google Gears Canvas中的subtract()操作,我发现了这一点,他们声称这是“来自HTML5”…但显然不是。

有人在这里回答了如何反转剪辑(clip()),但它涉及到离屏画布:

Canvas 'Clip' reverse action?

希望其他人能提出更好的建议。 :-/


0

在画布掩码上使用XOR函数只适用于不透明填充(即与globalAlpha不兼容)。解决此问题的一种方法是使用.clip()函数,并将剪切区域填充为背景中的内容。然后,您可以将剪切区域叠加在原始画布上,在该画布上执行所需的任何填充。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接