问题
Is there a way to combine multiple draw operations to a 2d canvas rendering context in such a way that they their combined result is composed onto the previous content of the canvas, as opposed to each drawing operation being composed by itself?
One application: I'd like to draw a translucent line with an arrow head, and I'd like to avoid increased opacity in those areas where the line and the arrow head overlap.
Many other rendering models support such features. SVG has a group opacity setting, described in section 14.5. The PDF reference describes “Transparency Groups” in section 7.3. In many graphics applications a user can build layers and then compose them as a whole.
I guess I could set up a second invisible canvas to be used as an off-screen image, render my content to that and then use globalAlpha to compose the result onto the main canvas with the desired translucency. But I hope that there is some more elegant solution, even if I couldn't find it in the docs so far.
Merge multiple paths in CanvasRenderingContext2D to fill and stroke as a set apparently has a similar goal in mind. But the focus there appears to be how to perform boolean operations on paths, probably the way clipper does it. So for this post here, I'm not interested in manipulating the paths up front; I want to be able to draw each stroke as I usually would.
回答1:
No, that's currently not possible without using an intermediate canvas.
Unless all of your draw operations in question can be combined into a single path, thereby using a single call to stroke()
and fill()
. I assume that's not the case here, but people searching later may be looking for that information.
// two rectangles composed into a single draw operation:
ctx.rect(0, 0, 10, 10);
ctx.rect(5, 5, 10, 10);
ctx.fill();
// With transparency, the above produces a different result from:
ctx.rect(0, 0, 10, 10);
ctx.fill();
ctx.rect(5, 5, 10, 10);
ctx.fill();
回答2:
Maybe because I'm used to the idea of layers, I do believe the off-sreen canvas is still the most elegant way to do it.
But note that in the exceptional* case of an empty canvas, you can also achieve it on a single canvas element, thanks to the globalCompositeOperation property.
The idea is to first draw your group of overlapping paths opaque, then use the gCO (see comments and down of post edit for a better way of doing it, still with gCO) : "destination-in"
and draw a rectangle covering the whole area of the group with the wanted color
var ctx = c.getContext('2d');
// first draw your overlapping paths opaque
ctx.lineWidth = 10;
ctx.beginPath();
ctx.arc(40, 40, 35, 0, Math.PI * 2);
ctx.stroke();
ctx.beginPath();
ctx.arc(60, 60, 35, 0, Math.PI * 2);
ctx.stroke();
// then set the fill color to the one you want
ctx.fillStyle = 'rgba(0,255,0,.5)';
// set the gCo so we only draw on the existing pixels
ctx.globalCompositeOperation = 'source-in';
// draw a large rect
ctx.fillRect(0, 0, c.width, c.height);
p{position: absolute;z-index:-1}
body{background-color: ivory;}
<p>Is this transparent ?</p>
<canvas id="c"></canvas>
* Actually, the only one case I can think of where it could be the case, would be for an off-screen canvas ;-)
Edit
As noted by @K3N, it can even be simplified and improved by using the "copy"
composite operation.
来源:https://stackoverflow.com/questions/36578651/transparency-groups-in-canvasrenderingcontext2d