[英]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)填充遮罩區域 context
的globalCompositeOperation
設置為"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.