详解HTML5 Canvas绘制不规则图形时的非零环绕原则
路径方向与非零环绕原则
平时我们画的图形都是规规矩矩的,那么如果我们用线条画了个抽象派作品,就像下面这图一样,童鞋们知道怎么用fill()染色呢?
这里就要用到数学上的一个方法——非零环绕原则,来判断哪块区域是里面,哪块区域是外面。接下来,我们具体来看下什么是非零环绕原则。
首先,我们得给图形确定一条路径,只要“一笔画”并且“不走重复路线”就可以了。如图,标出的是其中的一种路径方向。我们先假定路径的正方向为1(其实为-1啥的也都可以,正负方向互为相反数,不是0就行),那么反方向就是其相反数-1。
然后,我们在子路径切割的几块区域内的任意一点各取一条方向任意的射线,这里我只取了三个区域的射线为例,来判断这三块区域是“里面”还是“外面”。
接下来,我们就来判断了。s1中引出的射线l1,与s1的子路径的正方向相交,那么我们就给计数器+1,结果为+1,在外面。
s2中引出的射线l2,与两条子路径的正方向相交,计数器+2,结果为+2,在外面。
s3中引出的射线l3,与两条子路径相交,但是其中有一条的反方向,计数器+1-1,结果为0,在里面。没错,只要结果不为0,该射线所在的区域就在外面。
绘制圆环
记得arc方法吗?它的最后一个参数就是判断是路径方向的,如果是路径相反的两个同心圆在一起,图上色会有什么神奇的效果呢?
下面我们通过代码来实现它。
- <!doctype html>
- <html lang="zh">
- <head>
- <meta charset="utf-8">
- <title>圆环</title>
- <style>
- body { background: url("./images/bg3.jpg") repeat; }
- #canvas { border: 1px solid #aaaaaa; display: block; margin: 50px auto; }
- </style>
- </head>
- <body>
- <div id="canvas-warp">
- <canvas id="canvas">
- 你的浏览器居然不支持canvas?!赶快换一个吧!!
- </canvas>
- </div>
- <script>
- window.onload = function(){
- var canvas = document.getelementbyid("canvas");
- canvas.width = 800;
- canvas.height = 600;
- var context = canvas.getcontext("2d");
- context.fillstyle = "#fff";
- context.fillrect(0,0,800,600);
- context.shadowcolor = "#545454";
- context.shadowoffsetx = 5;
- context.shadowoffsety = 5;
- context.shadowblur = 2;
- context.arc(400, 300, 200, 0, math.pi * 2 ,false);
- context.arc(400, 300, 230, 0, math.pi * 2 ,true);
- context.fillstyle = "#00aaaa";
- context.fill();
- };
- </script>
- </body>
- </html>
运行结果:
镂空剪纸效果
接下来,我们利用非零环绕原则和阴影来绘制一个镂空的剪纸效果。
- <!doctype html>
- <html lang="zh">
- <head>
- <meta charset="utf-8">
- <title>镂空剪纸效果</title>
- <style>
- body { background: url("./images/bg3.jpg") repeat; }
- #canvas { border: 1px solid #aaaaaa; display: block; margin: 50px auto; }
- </style>
- </head>
- <body>
- <div id="canvas-warp">
- <canvas id="canvas">
- 你的浏览器居然不支持canvas?!赶快换一个吧!!
- </canvas>
- </div>
- <script>
- window.onload = function(){
- var canvas = document.getelementbyid("canvas");
- canvas.width = 800;
- canvas.height = 600;
- var context = canvas.getcontext("2d");
- context.fillstyle = "#fff";
- context.fillrect(0,0,800,600);
- context.beginpath();
- context.rect(200,100,400,400);
- drawpathrect(context, 250, 150, 300, 150);
- drawpathtriangle(context, 345, 350, 420, 450, 270, 450);
- context.arc(500, 400, 50, 0, math.pi * 2, true);
- context.closepath();
- context.fillstyle = "#058";
- context.shadowcolor = "gray";
- context.shadowoffsetx = 10;
- context.shadowoffsety = 10;
- context.shadowblur = 10;
- context.fill();
- };
- //逆时针绘制矩形
- function drawpathrect(cxt, x, y, w, h){
- /**
- * 这里不能使用beginpath和closepath,
- * 不然就不属于子路径而是另一个全新的路径,
- * 无法使用非零环绕原则
- */
- cxt.moveto(x, y);
- cxt.lineto(x, y + h);
- cxt.lineto(x + w, y + h);
- cxt.lineto(x + w, y);
- cxt.lineto(x, y);
- }
- //逆时针绘制三角形
- function drawpathtriangle(cxt, x1, y1, x2, y2, x3, y3){
- cxt.moveto(x1,y1);
- cxt.lineto(x3,y3);
- cxt.lineto(x2,y2);
- cxt.lineto(x1,y1);
- }
- </script>
- </body>
- </html>
运行结果:
这里手动绘制矩形的原因是我们想要得到逆时针路径的矩形,而且api提供的rect()方法绘制是顺时针矩形。另外,需要注意的是,这个剪纸是一个图形,一个路径。不能在绘制镂空三角形和绘制镂空矩形的方法里使用beginpath()和closepath(),不然它们就会是新的路径、新的图形,而不是剪纸的子路径、子图形,就无法使用非零环绕原则。