2015-04-01 34 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