在尝试处理特定的画布绘制案例时,我发现一些奇怪的行为。我可以选择要绘制的区域(“遮罩区域”),定义为任意多边形。然后我想绘制一些图像/形状/等,裁剪后仅绘制到遮罩区域中。
我认为我可以通过以下方式实现这一目标
(0, 0, 0, 0)
(使用方便的clearRect
方法)填充画布(1, 1, 1, 1)
填充遮罩区域context
的{{1}}设置为globalCompositeOperation
(对于熟悉的人,我实际上是在尝试完成与GIMP的“图层蒙版”工具(用全白色填充)类似的东西。)
我知道我可以使用画布裁剪来处理这种特殊情况,但是如果没有画布,我希望使用它的任务将变得更加简单,我认为使用乘法来实现它应该是可能的。无论如何,使用"multiply"
来实现类似目标的相反效果(先绘制内容,然后将像素消隐)。
代码如下:
globalCompositeOperation = "destination-out"
这是我的高质量<html>
<head><style>
html { width: 100%; height: 100%; }
body { margin: 0; width: 100%;
height: 100%; background: #777; }
canvas { border: 1px solid black;
margin: 10px auto; display: block; }
</style></head>
<body><canvas id="can" width="1800" height="900"></canvas></body>
<script>
const img = new Image;
img.onload = () => requestAnimationFrame(drawFrame);
img.src = "square.png";
const canvas = document.getElementById("can");
const ctx = canvas.getContext("2d");
let then = 0;
function drawFrame (now) {
const deltaTime = now - then;
then = now;
// fill canvas with 0 in all channels
ctx.clearRect(0, 0, canvas.width, canvas.height);
// draw FPS
ctx.fillStyle = "black";
ctx.font = "16px serif";
ctx.fillText(`FPS: ${Math.round(1 / (deltaTime / 1000))}`, 0, 16);
// fill mask area with 1 in all channels
ctx.fillStyle = "#01010101";
ctx.fillRect(100, 100, 400, 400);
// further draws should multiply current canvas values
ctx.globalCompositeOperation = "multiply";
// draw image (100*100 resolution image; half the image should be visible)
// Each channel of each pixel **should** multiply together
// "0"'d regions: 0 in all channels
// "1"'d regions: 1 * imgChannelValue = imgChannelValue
ctx.drawImage(img, 50, 100);
// draw a box, which should have its top-left corner blanked
ctx.strokeStyle = "#ff0000";
ctx.strokeRect(300, 300, 500, 300);
// (reset compositing)
ctx.globalCompositeOperation = "source-over";
requestAnimationFrame(drawFrame);
}
</script>
</html>
测试图片:
答案 0 :(得分:0)
您想要的是合成,所以不要尝试使用混合。
您似乎想要的合成模式是"source-in"
,它将仅在已绘制目标的位置保留新内容。
因此,您要做的就是在蒙版区域上完全不透明地绘制,然后在绘制图像之前将gCO切换为"source-in"
。
const ctx = canvas.getContext("2d");
const img = new Image();
img.onload = draw;
img.src = "https://i.stack.imgur.com/TFJu0.png";
function draw() {
canvas.width = img.width;
canvas.height = img.height;
// draw the masking areas with full opacity
drawShapes();
// in a timeout just for demo
setTimeout(()=> {
ctx.globalCompositeOperation = 'source-in';
ctx.drawImage(img, 0,0);
// reset to default
ctx.globalCompositeOperation = 'source-over';
}, 1000)
}
function drawShapes() {
// draw some 'mask' shapes
ctx.beginPath();
ctx.moveTo(130,20);
ctx.arc(100, 20, 30, 0, Math.PI*2);
ctx.moveTo(10, 45);
ctx.lineTo(60, 95);
ctx.lineTo(8, 120);
ctx.fill();
ctx.fillRect(40, 0, 20, 20);
}
<canvas id="canvas"></canvas>
但这需要将您的“可见”图形合并为一个图形调用(可以通过首先在屏幕外的画布上进行绘制来实现)。
因此,如果需要,您可以采用另一种方法:首先使用source-over绘制所有“可见”图形,然后使用{{1}作为单个子路径绘制“遮罩区域” }合成模式:
destination-in
const ctx = canvas.getContext("2d");
const urls = ["https://i.stack.imgur.com/TFJu0.png", "https://upload.wikimedia.org/wikipedia/commons/4/47/PNG_transparency_demonstration_1.png"];
loadImages(urls).then(imgs => {
// draw 'visibles'
imgs.forEach((img) => ctx.drawImage(img, 0, 0, canvas.width, canvas.height));
// Now do the compositing
ctx.globalCompositeOperation = 'destination-in';
drawMasks();
ctx.globalCompositeOperation = 'source-over';
});
function drawMasks() {
// draw your masks as a single sub-path
ctx.beginPath();
ctx.moveTo(130, 30);
ctx.arc(100, 30, 30, 0, Math.PI * 2);
ctx.moveTo(30, 45);
ctx.lineTo(90, 95);
ctx.lineTo(38, 120);
ctx.rect(40, 0, 20, 20);
ctx.fill();
}
function loadImages(urls) {
return Promise.all(urls.map(u => new Promise((res, rej) => Object.assign(
(new Image()), {
src: u,
onload: e => res(e.target),
onerror: rej
}))))
}
再一次,如果您不能将所有“遮盖区域”合并为一个子路径,则可以简单地将它们合并,然后再放在屏幕外的画布上,然后在合成步骤中绘制此屏幕外的画布。