2015-04-01 76 views
1

我遇到的問題非常簡單。這是「我怎樣才能在形狀上畫一個洞?」的變體問題,經典答案是「簡單地在同一條路徑中繪製兩個形狀,但是順時針繪製實體,逆時針繪製」孔「。這很棒,但我需要的「洞」往往是複合形狀,由多個圈組成。Javascript畫布 - 在矩形中相交的圓孔或如何合併多個圓弧路徑

視覺描述:http://i.imgur.com/9SuMSWT.png

的jsfiddle:http://jsfiddle.net/d_panayotov/44d7qekw/1/

context = document.getElementsByTagName('canvas')[0].getContext('2d'); 
// green background 
context.fillStyle = "#00FF00"; 
context.fillRect(0,0,context.canvas.width, context.canvas.height); 
context.fillStyle = "#000000"; 
context.globalAlpha = 0.5; 
//rectangle 
context.beginPath(); 
context.moveTo(0, 0); 
context.lineTo(context.canvas.width, 0); 
context.lineTo(context.canvas.width, context.canvas.height); 
context.lineTo(0, context.canvas.height); 
//first circle 
context.moveTo(context.canvas.width/2 + 20, context.canvas.height/2); 
context.arc(context.canvas.width/2 + 20, context.canvas.height/2, 50, 0, Math.PI*2, true); 
//second circle 
context.moveTo(context.canvas.width/2 - 20, context.canvas.height/2); 
context.arc(context.canvas.width/2 - 20, context.canvas.height/2, 50, 0, Math.PI*2, true); 
context.closePath(); 
context.fill(); 

編輯:

多的解決方案已被提出,我覺得我的問題一直是誤導性的。所以這裏有更多信息: 我需要矩形區域作爲陰影。以下是我正在製作的遊戲截圖(希望這不違反規則):http://i.imgur.com/tJRjMXC.png

  • 該矩形應該能夠具有小於1.0的alpha。
  • 顯示在「孔」中的內容是在應用陰影之前在畫布上繪製的任何內容。

@markE:

  • 或者......來 「敲除」(刪除)雙圓... - 「目的地出」 代替畫布內容與設定背景。 http://jsfiddle.net/d_panayotov/ab21yfgd/ - 孔是藍色而不是綠色。
  • 另一方面... - 「source-atop」需要在定義剪貼蒙版後繪製內容。這在我的情況下將效率低下(光被繪製爲同心圓,陰影區域仍然可見)。

@ hobberwickey: 這是一個靜態背景,而不是實際的畫布內容。然而,我可以像使用「source-atop」一樣使用clip(),但效率不高。

我現在已經實施的解決方案:http://jsfiddle.net/d_panayotov/ewdyfnj5/。我只是在主要畫布內容上繪製修剪過的矩形(在內存中)。有更快/更好的解決方案嗎?

+0

什麼是你的代碼? – Xufox 2015-04-01 16:03:51

+1

用一個簡單的例子更新了我的問題。 – 2015-04-01 16:06:15

+0

我認爲你必須做出一個包含兩個相同路徑的方形的圖形...... – Xufox 2015-04-01 16:14:14

回答

1

因爲它的簡單性,我幾乎害怕發佈這個答案的第一部分,但爲什麼不只是在純色背景上填充2個圓?

enter image description here

var canvas=document.getElementById("canvas"); 
 
var ctx=canvas.getContext("2d"); 
 
var cw=canvas.width; 
 
var ch=canvas.height; 
 

 
var r=50; 
 

 
ctx.fillStyle='rgb(0,174,239)'; 
 
ctx.fillRect(0,0,cw,ch); 
 

 
ctx.fillStyle='white' 
 
ctx.beginPath(); 
 
ctx.arc(cw/2-r/2,ch/2,r,0,Math.PI*2); 
 
ctx.closePath(); 
 
ctx.fill(); 
 
ctx.beginPath(); 
 
ctx.arc(cw/2+r/2,ch/2,r,0,Math.PI*2); 
 
ctx.closePath(); 
 
ctx.fill();
body{ background-color: ivory; } 
 
#canvas{border:1px solid red;}
<canvas id="canvas" width=400 height=168></canvas>

備選...到 「敲除」(擦除)雙圓圈...

如果你想在2個圈,以「基因敲除」藍色像素下來,雙圓是透明&顯示網頁背景之下,那麼你可以使用合成來「敲除」的圓圈:context.globalCompositeOperation='destination-out

enter image description here

var canvas=document.getElementById("canvas"); 
 
var ctx=canvas.getContext("2d"); 
 
var cw=canvas.width; 
 
var ch=canvas.height; 
 

 
var r=50; 
 

 

 
// draw the blue background 
 
// The background will be visible only outside the double-circles 
 
ctx.fillStyle='rgb(0,174,239)'; 
 
ctx.fillRect(0,0,cw,ch); 
 

 

 
// use destination-out compositing to "knockout" 
 
// the double-circles and thereby revealing the 
 
// ivory webpage background below 
 
ctx.globalCompositeOperation='destination-out'; 
 

 
// draw the double-circles 
 
// and effectively "erase" the blue background 
 
ctx.fillStyle='white' 
 
ctx.beginPath(); 
 
ctx.arc(cw/2-r/2,ch/2,r,0,Math.PI*2); 
 
ctx.closePath(); 
 
ctx.fill(); 
 
ctx.beginPath(); 
 
ctx.arc(cw/2+r/2,ch/2,r,0,Math.PI*2); 
 
ctx.closePath(); 
 
ctx.fill(); 
 

 
// always clean up! Set compositing back to its default 
 
ctx.globalCompositeOperation='source-over';
body{ background-color: ivory; } 
 
#canvas{border:1px solid red;}
<canvas id="canvas" width=400 height=168></canvas>

在th另一方面...

如果您需要將這些雙圓像素隔離爲包含路徑,那麼您可以使用合成繪製到雙圓而不繪製到藍色背景中。

再舉一例:

enter image description here

var canvas=document.getElementById("canvas"); 
 
var ctx=canvas.getContext("2d"); 
 
var cw=canvas.width; 
 
var ch=canvas.height; 
 

 
var r=50; 
 

 
var img=new Image(); 
 
img.onload=start; 
 
img.src="https://dl.dropboxusercontent.com/u/139992952/multple/mm.jpg"; 
 
function start(){ 
 

 
    // fill the double-circles with any color 
 
    ctx.fillStyle='white' 
 
    ctx.beginPath(); 
 
    ctx.arc(cw/2-r/2,ch/2,r,0,Math.PI*2); 
 
    ctx.closePath(); 
 
    ctx.fill(); 
 
    ctx.beginPath(); 
 
    ctx.arc(cw/2+r/2,ch/2,r,0,Math.PI*2); 
 
    ctx.closePath(); 
 
    ctx.fill(); 
 

 
    // set compositing to source-atop 
 
    // New drawings are only drawn where they 
 
    // overlap existing (non-transparent) pixels 
 
    ctx.globalCompositeOperation='source-atop'; 
 

 

 
    // draw your new content 
 
    // The new content will be visible only inside the double-circles 
 
    ctx.drawImage(img,0,0); 
 

 
    // set compositing to destination-over 
 
    // New drawings will be drawn "behind" 
 
    // existing (non-transparent) pixels 
 
    ctx.globalCompositeOperation='destination-over'; 
 

 
    // draw the blue background 
 
    // The background will be visible only outside the double-circles 
 
    ctx.fillStyle='rgb(0,174,239)'; 
 
    ctx.fillRect(0,0,cw,ch); 
 

 
    // always clean up! Set compositing back to its default 
 
    ctx.globalCompositeOperation='source-over'; 
 

 
}
body{ background-color: ivory; } 
 
#canvas{border:1px solid red;}
<canvas id="canvas" width=400 height=168></canvas>

技術角度給出{除了回答更多的想法}:xor合成作品通過翻轉像素上的alpha值,但不會將像素的r,g,b部分置零。在某些情況下,着色像素的alpha值將被取消清零,rgb將再次顯示。像素值(r,g,b,a)的所有部分都被清零,所以它們不會意外返回來困擾你,所以最好使用「目標輸出」合成。

是的......雖然它不是在你的榜樣至關重要的,你應該總是開始您的路徑與maskCtx.beginPath()繪製命令。這表示任何先前繪圖的結束和新路徑的開始。

其中一個選項:我看到您正在使用同心圓來在您的圈子中心產生更大的「揭示」。如果你想要更漸進的揭示,那麼你可以用修剪陰影(或徑向漸變)而不是同心圓敲掉你的記憶中的圓圈。

除此之外,覆蓋內存中畫布的解決方案應該可以正常工作(以用於內存畫布的內存爲代價)。

祝您好運與您的遊戲!

+1

首先感謝您的時間和廣泛的答覆!由於我有一些問題,我目前正在撰寫回復。我最初認爲目標出局正是我需要的解決方案。 – 2015-04-01 17:37:08

+1

感謝您的建議。將「目標出」替換爲「xor」。我真的很喜歡徑向漸變的圓圈。 – 2015-04-01 19:44:58

0

更簡單的就是使用剪切和完整的圓圈。除非有某種原因,否則你需要通過單一路徑來完成此操作。

var cutoutCircle = function(x, y, r, ctx){ 
    ctx.save() 
    ctx.arc(x, y, r, 0, Math.PI * 2, false) 
    ctx.clip() 
    ctx.clearRect(0, 0, ctx.canvas.width, ctx.canvas.height) 
    ctx.restore(); 
} 

var myCircles = [{x: 75, y: 100, r: 50}, {x: 125, y: 100, r: 50}], 
    ctx = document.getElementById("canvas").getContext('2d'); 

ctx.fillRect(0, 0, ctx.canvas.width, ctx.canvas.height); 
for (var i=0; i<myCircles.length; i++){ 
    cutoutCircle(myCircles[i].x, myCircles[i].y, myCircles[i].r, ctx) 
} 

編輯:添加背景,例如更好的更好的示範

http://jsfiddle.net/v9qven9w/1/

+0

@hobberwickey ...剪輯在邏輯上更容易理解,但與使用合成相比,'context.clip'是一個昂貴的操作。 ;-) – markE 2015-04-01 17:27:49

+0

這是,但優化並不總是必要的。如果你裁剪3或4圈,整個事情需要1ms運行,爲什麼優化? – hobberwickey 2015-04-01 17:30:20

+0

是的...在這種簡單的情況下裁剪是好的。 :-)即使對於簡單的情況,我也傾向於優化代碼......它只是強化了我腦海中的「最佳實踐」。 ;-p – markE 2015-04-01 17:34:59

1

如果我理解正確:你想擁有一個面具在遊戲頂部的外觀,使兩個相交的圓圈會突出顯示,而其他所有內容都會變暗?

我會建議保持簡單 - 在透明的黑色背景上創建一個與圈子撞出的屏幕外畫布。

然後,只需在需要的時候在遊戲頂部繪製屏幕外畫布。這要比爲每個幀重新合成更高效 - 只需重複一次即可。

演示

面具在下面的演示窗口中顯示(滾動或使用整頁來查看所有)。通常情況下,你會創建一個屏幕外的畫布,並使用它。

// create mask 
 

 
// for off-screen, use createElement("canvas") 
 
var mask = document.getElementById("mask"), 
 
    ctxm = mask.getContext("2d"), 
 
    w = mask.width, h = mask.height, x, y, radius = 80; 
 

 
ctxm.fillStyle = "rgba(0,0,0,0.5)"; 
 
ctxm.fillRect(0, 0, w, h);       // fill mask with 50% transp. black 
 
ctxm.globalCompositeOperation = "destination-out"; // knocks out background 
 
ctxm.fillStyle = "#000";       // some solid color 
 

 
x = w/2 - radius/1.67; 
 
y = h/2; 
 
ctxm.moveTo(x, y);         // circle 1 
 
ctxm.arc(x, y, radius, 0, Math.PI*2); 
 
x = w/2 + radius/1.67; 
 
ctxm.moveTo(x, y);         // circle 2 
 
ctxm.arc(x, y, radius, 0, Math.PI*2); 
 
ctxm.fill();          // knock em' out, DONE! 
 

 
// ----- Use mask for the game, pseudo action below ------ 
 
var canvas = document.getElementById("game"), ctx = canvas.getContext("2d"); 
 

 
(function loop() { 
 
    ctx.fillStyle = "#742"; 
 
    ctx.fillRect(0, 0, w, h);      // clear background 
 
    ctx.fillStyle = "#960"; 
 
    for(x = 0; x < w; x += 8)      // some random action 
 
    ctx.fillRect(x, h * Math.random(), 8, 8); 
 

 
    ctx.drawImage(mask, 0, 0);      // use MASK on top 
 
    
 
    requestAnimationFrame(loop) 
 
})();
<canvas id="mask" width=500 height=220></canvas> 
 
<canvas id="game" width=500 height=220></canvas>

+0

感謝您的好主意!這可能是真正有益的。不幸的是,當玩家移動時需要更新燈光,所以在我的情況下,這種「緩存」僅適用於玩家「火炬」熄滅時。如果我在HTML中定義蒙版畫布,它會有什麼區別嗎? – 2015-04-01 21:30:55

+0

@DeanPanayotov html/css也是完全有效的(並且在很多情況下它可以更快),只要你得到你想要的結果(並且它希望能在不同的瀏覽器/系統上很好地工作)。 – K3N 2015-04-01 21:37:11

+0

@DeanPanayotov你也可以爲每個場景使用幾個蒙版,或者在實際需要時更新一個蒙版。無論如何,祝你的比賽順利! :) – K3N 2015-04-01 21:52:00