【问题标题】:How to draw "clip" mask regions to a canvas, then draw content only where there are mask regions如何将“剪辑”蒙版区域绘制到画布上,然后仅在有蒙版区域的地方绘制内容
【发布时间】:2019-03-11 23:41:58
【问题描述】:

在尝试处理特定的画布绘图案例时,我发现了一些奇怪的行为。我有一些要绘制的区域(“遮罩区域”),定义为任意多边形。我有一些图像/形状/等,然后我想绘制,剪裁为仅绘制到蒙版区域。

我想我可以通过以下方式实现:

  • (0, 0, 0, 0) 填充画布(使用方便的clearRect 方法)
  • (1, 1, 1, 1) 填充遮罩区域
  • contextglobalCompositeOperation 设置为"multiply"
  • 绘制内容

(对于那些熟悉的人,我正在尝试完成类似于 GIMP 的“图层蒙版”工具(填充全白)的效果。)

我知道我可以使用画布剪辑来处理这种特定情况,但是如果没有它,我希望使用它的任务会变得更加简单,而且我认为使用乘法来完成它应该是可能的。我想要实现的相反效果(绘制内容然后消隐像素)很容易用类似的想法来实现,无论如何使用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>

这是我的高质量square.png 测试图像:

.

【问题讨论】:

    标签: javascript html5-canvas


    【解决方案1】:

    你想要的是合成,所以不要尝试使用混合。

    您似乎想要的合成模式是"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);
    }
    &lt;canvas id="canvas"&gt;&lt;/canvas&gt;

    但这需要将您的“可见”绘图合并为单个绘图调用(这可以通过首先在屏幕外画布上绘制它们来实现)。

    因此,如果您愿意,您可以反过来:首先使用源覆盖绘制所有“可见”图形,然后使用 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
        }))))
    }
    &lt;canvas id="canvas" width="300" height="200"&gt;&lt;/canvas&gt;

    再一次,如果您不能将所有“遮罩区域”合并为单个子路径,您可以简单地预先在屏幕外画布上合并它们,然后在合成步骤中绘制此屏幕外画布。

    【讨论】:

    • 除非我遗漏了什么,否则这是行不通的。 "source-in" 只允许您在绘制蒙版后绘制一个形状/图像,对吗?我希望能够绘制任意数量的图像/形状,每个图像/形状都应该被剪辑到相同的预先绘制的蒙版区域。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2023-03-31
    • 1970-01-01
    • 1970-01-01
    • 2015-05-19
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多