簡體   English   中英

如何在畫布上繪制“剪切”蒙版區域,然后僅在有蒙版區域的地方繪制內容

[英]How to draw “clip” mask regions to a canvas, then draw content only where there are mask regions

嘗試處理特定的畫布繪制案例時,我發現一些奇怪的行為。 我可以選擇要繪制的區域(“遮罩區域”),定義為任意多邊形。 然后我想繪制一些圖像/形狀/等,裁剪后僅繪制到蒙版區域中。

我想我可以通過以下方法實現此目的:

  • (0, 0, 0, 0) clearRect )填充畫布(使用便捷的clearRect方法)
  • (1, 1, 1, 1) 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測試圖像:

圖片

您想要的是合成,因此不要嘗試使用混合。

您似乎想要的合成模式是"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繪制所有“可見”圖形,然后使用destination-in合成模式將“ masking area”繪制為單個子路徑:

 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 })))) } 
 <canvas id="canvas" width="300" height="200"></canvas> 

再一次,如果您不能將所有“遮罩區域”合並為一個子路徑,則可以簡單地將它們合並,然后再放在屏幕外畫布上,然后在合成步驟中繪制此屏幕外畫布。

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM