【Canvas技法】利用复数找控制点,利用贝塞尔二次曲线绘制环状橄榄枝
【关键点】
下图左侧的直线橄榄枝权当是练手,右侧的环状橄榄枝才是关键,注意其中控制点的求法与直线型有根本区别。
【图示】
【利用复数计算控制点的过程】
在绘制环形橄榄枝的树叶中,控制点是通过复数计算得到的。
起止点,中心点,控制点的示意图如下:
上图中,起止点start、end是循环中通过角度递增得到的,中心点center坐标可以通过起止点坐标之和的一半得到,控制点c1,c2的连线垂直通过中心点,且四点离中心点距离一致。换句话说,起止点和控制点正好组成一个正方形,中心点就是正方形的中心。
向量OE=(end.x-center.x)+i(end.y-center.y)
向量OC1=OE*i=(center.y-end.y)+i(end.x-center.x)
在向量基础上加上center的x,y坐标就得到了C1的坐标为(center.x+center.y-end.y,end.x-center.x+center.y)
同样,向量OC2=OS*i,得到C2的坐标为(center.x+center.y-start.y,start.x-center.x+center.y)
得到了控制点的坐标,树叶的轮廓就可以用以下代码画出了。
ctx.beginPath();
ctx.lineTo(start.x,start.y);
ctx.quadraticCurveTo(c1.x,c1.y,end1.x,end1.y);
ctx.quadraticCurveTo(c2.x,c2.y,start.x,start.y);
ctx.closePath();
【代码】
<!DOCTYPE html> <html lang="utf-8"> <meta http-equiv="Content-Type" content="text/html; charset=utf-8"/> <head> <title>橄榄枝</title> <style type="text/css"> .centerlize{ margin:0 auto; width:1200px; } </style> </head> <body onload="init();"> <div class="centerlize"> <canvas id="myCanvas" width="12px" height="12px" style="border:1px dotted black;"> 如果看到这段文字说您的浏览器尚不支持HTML5 Canvas,请更换浏览器再试. </canvas> <img id="myImg" src="116-1.jpg" style="display:none;"/> <img id="myImg2" src="116-2.jpg" style="display:none;"/> </div> </body> </html> <script type="text/javascript"> <!-- /***************************************************************** * 将全体代码(从<!DOCTYPE到script>)拷贝下来,粘贴到文本编辑器中, * 另存为.html文件,再用chrome浏览器打开,就能看到实现效果。 ******************************************************************/ // canvas的绘图环境 var ctx; // 高宽 const WIDTH=1024; const HEIGHT=512; // 舞台对象 var stage; //------------------------------- // 初始化 //------------------------------- function init(){ // 获得canvas对象 var canvas=document.getElementById('myCanvas'); canvas.width=WIDTH; canvas.height=HEIGHT; // 初始化canvas的绘图环境 ctx=canvas.getContext('2d'); ctx.translate(WIDTH/2,HEIGHT/2);// 原点平移到画布中央 // 准备 stage=new Stage(); stage.init(); // 开幕 animate(); } // 播放动画 function animate(){ stage.update(); stage.paintBg(ctx); stage.paintFg(ctx); // 循环 if(true){ window.requestAnimationFrame(animate); } } // 舞台类 function Stage(){ // 初始化 this.init=function(){ } // 更新 this.update=function(){ } // 画背景 this.paintBg=function(ctx){ ctx.clearRect(-WIDTH/2,-HEIGHT/2,WIDTH,HEIGHT);// 清屏 // 以下为大小一致的直线橄榄枝 // 长横枝 ctx.beginPath(); ctx.moveTo(-500,-200); ctx.lineTo(0,-200); ctx.lineWidth=1; ctx.strokeStyle="black"; ctx.stroke(); // 旁枝 for(var i=0;i<10;i++){ var start={}; start.x=i*50-500; start.y=-200; var end={}; end.x=start.x+50; end.y=Math.cos(Math.PI*i)*50-200; ctx.beginPath(); ctx.moveTo(start.x,start.y); ctx.lineTo(end.x,end.y); ctx.lineWidth=1; ctx.strokeStyle="black"; ctx.stroke(); } // 利用贝塞尔曲线画叶子 for(var i=0;i<10;i++){ var start={}; start.x=i*50-500; start.y=-200; var end={}; end.x=start.x+50; end.y=Math.cos(Math.PI*i)*50-200; var c1={}; c1.x=start.x; c1.y=end.y; var c2={}; c2.x=end.x; c2.y=start.y; ctx.beginPath(); ctx.lineTo(start.x,start.y); ctx.quadraticCurveTo(c1.x,c1.y,end.x,end.y); ctx.quadraticCurveTo(c2.x,c2.y,start.x,start.y); ctx.closePath(); ctx.lineWidth=1; ctx.strokeStyle="black"; ctx.stroke(); } // 以下为逐渐变细的直线橄榄枝 // 长横枝 ctx.beginPath(); ctx.moveTo(-500,-100); ctx.lineTo(0,-100); ctx.lineWidth=1; ctx.strokeStyle="black"; ctx.stroke(); // 旁枝 for(var i=0;i<10;i++){ var start={}; start.x=i*50-500; start.y=-100; var end={}; var ratio=(20-i)/20; end.x=start.x+50*ratio; end.y=Math.cos(Math.PI*i)*50*ratio-100; ctx.beginPath(); ctx.moveTo(start.x,start.y); ctx.lineTo(end.x,end.y); ctx.lineWidth=1; ctx.strokeStyle="black"; ctx.stroke(); } // 利用贝塞尔曲线画叶子 for(var i=0;i<10;i++){ var start={}; start.x=i*50-500; start.y=-100; var end={}; var ratio=(20-i)/20; end.x=start.x+50*ratio; end.y=Math.cos(Math.PI*i)*50*ratio-100; var c1={}; c1.x=start.x; c1.y=end.y; var c2={}; c2.x=end.x; c2.y=start.y; ctx.beginPath(); ctx.lineTo(start.x,start.y); ctx.quadraticCurveTo(c1.x,c1.y,end.x,end.y); ctx.quadraticCurveTo(c2.x,c2.y,start.x,start.y); ctx.closePath(); ctx.fillStyle="green"; ctx.fill(); } // 以下为逐渐变细的双侧直线橄榄枝 // 长横枝 ctx.beginPath(); ctx.moveTo(-500,0); ctx.lineTo(0,0); ctx.lineWidth=1; ctx.strokeStyle="black"; ctx.stroke(); // 旁枝 for(var i=0;i<10;i++){ var start={}; start.x=i*50-500; start.y=0; var end={}; var ratio=(20-i)/20; end.x=start.x+50*ratio; end.y=Math.cos(Math.PI*i)*50*ratio+0; ctx.beginPath(); ctx.moveTo(start.x,start.y); ctx.lineTo(end.x,end.y); ctx.lineWidth=1; ctx.strokeStyle="black"; ctx.stroke(); end.y*=-1; ctx.beginPath(); ctx.moveTo(start.x,start.y); ctx.lineTo(end.x,end.y); ctx.lineWidth=1; ctx.strokeStyle="black"; ctx.stroke(); } // 利用贝塞尔曲线画叶子 for(var i=0;i<10;i++){ var start={}; start.x=i*50-500; start.y=0; var end={}; var ratio=(20-i)/20; end.x=start.x+50*ratio; end.y=Math.cos(Math.PI*i)*50*ratio-0; var c1={}; c1.x=start.x; c1.y=end.y; var c2={}; c2.x=end.x; c2.y=start.y; ctx.beginPath(); ctx.lineTo(start.x,start.y); ctx.quadraticCurveTo(c1.x,c1.y,end.x,end.y); ctx.quadraticCurveTo(c2.x,c2.y,start.x,start.y); ctx.closePath(); ctx.fillStyle="green"; ctx.fill(); end.y*=-1; var c1={}; c1.x=start.x; c1.y=end.y; var c2={}; c2.x=end.x; c2.y=start.y; ctx.beginPath(); ctx.lineTo(start.x,start.y); ctx.quadraticCurveTo(c1.x,c1.y,end.x,end.y); ctx.quadraticCurveTo(c2.x,c2.y,start.x,start.y); ctx.closePath(); ctx.fillStyle="green"; ctx.fill(); } // 半圆 const radius=200; const leaveLength=40; ctx.beginPath(); ctx.arc(150,0,radius,Math.PI*(-3/2),Math.PI*(-1/2),true); ctx.lineWidth=1; ctx.strokeStyle="black"; ctx.stroke(); // 外侧绿叶子 for(var i=0;i<20;i++){ var ratio=(i+30)/60; var theta=-Math.PI/2+i*Math.PI/20; var start={}; start.x=radius*Math.cos(theta)+150; start.y=radius*Math.sin(theta); var end1={}; end1.x=start.x+ratio*leaveLength*Math.cos(-Math.PI/4+theta); end1.y=start.y+ratio*leaveLength*Math.sin(-Math.PI/4+theta); var center={}; center.x=(start.x+end1.x)/2; center.y=(start.y+end1.y)/2; // 利用复数求c1 var c1={}; c1.x=center.x+center.y-end1.y; c1.y=end1.x-center.x+center.y; // 利用复数求c2 var c2={}; c2.x=center.x+center.y-start.y; c2.y=start.x-center.x+center.y; ctx.beginPath(); ctx.lineTo(start.x,start.y); ctx.quadraticCurveTo(c1.x,c1.y,end1.x,end1.y); ctx.quadraticCurveTo(c2.x,c2.y,start.x,start.y); ctx.closePath(); ctx.fillStyle="green"; ctx.fill(); } // 内侧黄叶子 for(var i=0;i<20;i++){ var ratio=(i+30)/60; var theta=-Math.PI/2+i*Math.PI/20; var start={}; start.x=radius*Math.cos(theta)+150; start.y=radius*Math.sin(theta); var end1={}; end1.x=start.x+ratio*leaveLength*Math.cos(-Math.PI/4*3+theta); end1.y=start.y+ratio*leaveLength*Math.sin(-Math.PI/4*3+theta); var center={}; center.x=(start.x+end1.x)/2; center.y=(start.y+end1.y)/2; // 利用复数求c1 var c1={}; c1.x=center.x+center.y-end1.y; c1.y=end1.x-center.x+center.y; // 利用复数求c2 var c2={}; c2.x=center.x+center.y-start.y; c2.y=start.x-center.x+center.y; ctx.beginPath(); ctx.lineTo(start.x,start.y); ctx.quadraticCurveTo(c1.x,c1.y,end1.x,end1.y); ctx.quadraticCurveTo(c2.x,c2.y,start.x,start.y); ctx.closePath(); ctx.fillStyle="yellow"; ctx.fill(); } // 作者 ctx.textBaseline="bottom"; ctx.textAlign="center"; ctx.font = "8px consolas"; ctx.fillStyle="black"; ctx.fillText("逆火原创",WIDTH/2-40,HEIGHT/2-10); } // 画前景 this.paintFg=function(ctx){ } } /*--------------------------------------------- 三件事情是做了极少有人后悔的: 1.锻炼身体;2.创业了,无论成败;3.孩子生下来了。 第一件事是给你的人生打基础; 第二件事是让你认知真实的世界和人生; 第三件事是给你生活的想象和希望。 ----------------------------------------------*/ //--> </script>
END