图形变换。
一、画一片星空
先画一片canvas.width宽canvas.height高的黑色星空,再画200个随机位置,随机大小,随机旋转角度的星星。
window.οnlοad=function(){ var canvas=document.getElementById("canvas"); canvas.width=800; canvas.height=800; var context=canvas.getContext("2d"); context.fillStyle="black"; context.fillRect(0,0,canvas.width,canvas.height); for(var i=0;i<200;i++){ var r=Math.random()*10+10; var x=Math.random()*canvas.width; var y=Math.random()*canvas.height; var a=Math.random()*360; drawStar(context,x,y,r,r/2.0,a); } } //rot顺时针旋转的角度 function drawStar(ctx,x,y,r,R,rot){ ctx.beginPath(); //角度转弧度:除以180*PI for(var i=0;i<5;i++){ ctx.lineTo(Math.cos((18+i*72-rot)/180*Math.PI)*R+x, -Math.sin((18+i*72-rot)/180*Math.PI)*R+y); ctx.lineTo(Math.cos((54+i*72-rot)/180*Math.PI)*r+x, -Math.sin((54+i*72-rot)/180*Math.PI)*r+y); } ctx.closePath(); ctx.fillStyle="#fb3"; ctx.strokeStyle="#fd5"; ctx.lineWidth=3; ctx.lineJoin="round"; ctx.fill(); ctx.stroke(); }
产生一个扁平化设计中200个星星的效果。
二、图像变换和状态保存
1、用标准路径+图形变换思想重构
上面drawStar函数承载的功能太多来,整个绘制路径的指定,同时把五角星的位移,大小,旋转多少度全部揉合在一个函数里了。
假如需要变为画一个四角形?六角形?代码改起来就比较麻烦了。
标准做法:修改函数结构。
接口不变,省去了旋转角度,画一个标准星星。假设外圆半径是内圆半径的两倍,所以只需要传入一个小r。drawStar里调用一个startPath()函数来绘制一个标准五角星的路径。
标准的五角星路径:只传入一个context,在(0,0)的位置绘制来一个大圆半径为1,同时没有任何偏移,任何旋转的的五角星。
在drawStar里勾绘出标准五角星后再通过图形变换使得标准五角星的位移变成在(x,y)的位置,大小变成R这么大,同时旋转rot角度。再进行具体的绘制。
这样一个设计的结构可以避免之前的问题。比如需求变成要画六角形,四角形,只需要把starPath()里面路径勾绘的代码进行相应的更改即可。
更高级的复用:starPath()函数以参数的形式传入drawStar()中。这样drawStar可以叫drawSheap用户可以绘制任意的图形,只需要传入绘制图形的标准路径,变更的位移量,大小量,旋转量即可。
//rot顺时针旋转的角度 function drawStar(ctx,x,y,r,R,rot){ starPath(ctx); //绘制在(x,y)大小为R,旋转rot度的五角星 //... }
function starPath(ctx){ ctx.beginPath(); //角度转弧度:除以180*PI for(var i=0;i<5;i++){ ctx.lineTo(Math.cos((18+i*72)/180*Math.PI), -Math.sin((18+i*72)/180*Math.PI)); ctx.lineTo(Math.cos((54+i*72)/180*Math.PI), -Math.sin((54+i*72)/180*Math.PI)); } ctx.closePath(); }
总结:图形学里绘制先绘制标准路径,再通过图形变换成需求大小。
2,图形变换
三种基本操作:
- 位移translate(x,y)
- 旋转rotate(deg)
- 缩放 scale(sx,sy)
translate会叠加
绿色正方形位置经过2次translate后到达了(200,200)。并不是代码里看起来的(150,150)。
window.οnlοad=function(){ var canvas=document.getElementById("canvas"); canvas.width=400; canvas.height=400; var context=canvas.getContext("2d"); context.fillStyle="red"; context.translate(50,50); context.fillRect(0,0,200,200); context.fillStyle="green"; context.translate(150,150); context.fillRect(0,0,200,200); }
为了避免上述问题,最佳实践是使用图形变换之后,再反向操作把图形变换的结果逆转过来。如下:
window.οnlοad=function(){ var canvas=document.getElementById("canvas"); canvas.width=400; canvas.height=400; var context=canvas.getContext("2d"); context.fillStyle="red"; context.translate(50,50); context.fillRect(0,0,200,200); context.translate(-50,-50);//反向操作 context.fillStyle="green"; context.translate(150,150); context.fillRect(0,0,200,200); context.translate(-150,-150);//反向操作 }
3,canvas状态的保存save()和恢复restore()
逆转图形变换太麻烦了,canvas提供了一个save()API,保存当前的图形状态,状态包括所有我们设置的状态,自然也包括图形变换的状态。
在完成图形变换并且具体绘制以后,在最后再调用一次context.restore()。
restore()和save()是成对出现的,restore()返回在save()时候canvas的所有状态, 这是一个非常好的保持canvas绘图状态的方法,在save()和restore()之间可以随意的更改canvas的状态而不影响后续的绘制效果。
window.οnlοad=function(){ var canvas=document.getElementById("canvas"); canvas.width=400; canvas.height=400; var context=canvas.getContext("2d"); context.save(); context.fillStyle="red"; context.translate(50,50); context.fillRect(0,0,200,200); //context.translate(-50,-50);//反向操作 context.restore(); context.save() context.fillStyle="green"; context.translate(150,150); context.fillRect(0,0,200,200); // context.translate(-150,-150);//反向操作 context.restore(); }
Note:绘制整体元素,特别是在其中使用图形变换的时候,都应该先save()一下,最终结束绘制时再restore()一下以保证canvas图形绘制的正确。
三、应用translate,rotate和scale
1、使用translate和rotate绘制固定大小星星的星空
没有用scale.
window.οnlοad=function(){ var canvas=document.getElementById("canvas"); canvas.width=800; canvas.height=800; var context=canvas.getContext("2d"); context.fillStyle="black"; context.fillRect(0,0,canvas.width,canvas.height); for(var i=0;i<200;i++){ var r=Math.random()*10+10; var x=Math.random()*canvas.width; var y=Math.random()*canvas.height; var a=Math.random()*360; drawStar(context,x,y,r,a); } } //rot顺时针旋转的角度 function drawStar(ctx,x,y,R,rot){ ctx.save(); ctx.translate(x,y); ctx.rotate(rot/180*Math.PI); starPath(ctx); //绘制在(x,y)大小为R,旋转rot度的五角星 ctx.fillStyle="#fb3"; ctx.strokeStyle="#fd5"; ctx.lineWidth=3; ctx.lineJoin="round"; ctx.fill(); ctx.stroke(); ctx.restore(); } function starPath(ctx){ ctx.beginPath(); //角度转弧度:除以180*PI for(var i=0;i<5;i++){ ctx.lineTo(Math.cos((18+i*72)/180*Math.PI)*20, -Math.sin((18+i*72)/180*Math.PI)*20); ctx.lineTo(Math.cos((54+i*72)/180*Math.PI)*0.5*20, -Math.sin((54+i*72)/180*Math.PI)*0.5*20); } ctx.closePath(); }
效果和上面图片一样。
2、scale副作用
不仅放在大小,还会放大坐标,边框等。
var canvas=document.getElementById("canvas"); canvas.width=400; canvas.height=400; var context=canvas.getContext("2d"); context.save(); context.scale(1,1); context.strokeRect(10,10,100,100); context.restore(); context.save() context.scale(2,2,); context.strokeRect(10,10,100,100); context.restore(); context.save() context.scale(3,3,); context.strokeRect(10,10,100,100); context.restore(); }
3, 应用scale绘制星空
坐标是通过translate变换的,始终是(0,0)所以scale后还是(0,0)。
放弃外边框的绘制。
window.οnlοad=function(){ var canvas=document.getElementById("canvas"); canvas.width=800; canvas.height=800; var context=canvas.getContext("2d"); context.fillStyle="black"; context.fillRect(0,0,canvas.width,canvas.height); for(var i=0;i<200;i++){ var r=Math.random()*10+10; var x=Math.random()*canvas.width; var y=Math.random()*canvas.height; var a=Math.random()*360; drawStar(context,x,y,r,a); } } //rot顺时针旋转的角度 function drawStar(ctx,x,y,R,rot){ ctx.save(); ctx.translate(x,y); ctx.rotate(rot/180*Math.PI); ctx.scale(R,R); starPath(ctx); //绘制在(x,y)大小为R,旋转rot度的五角星 ctx.fillStyle="#fb3"; //放弃外边框的绘制 // ctx.strokeStyle="#fd5"; // ctx.lineWidth=3; // ctx.lineJoin="round"; ctx.fill(); // ctx.stroke(); ctx.restore(); } function starPath(ctx){ ctx.beginPath(); //角度转弧度:除以180*PI for(var i=0;i<5;i++){ ctx.lineTo(Math.cos((18+i*72)/180*Math.PI), -Math.sin((18+i*72)/180*Math.PI)); ctx.lineTo(Math.cos((54+i*72)/180*Math.PI)*0.5, -Math.sin((54+i*72)/180*Math.PI)*0.5); } ctx.closePath(); }
星星没有外边框。
四、深入理解图形变换
图形变换的实质是对图形的顶点坐标的再计算。计算过程通过变换矩阵来完成。
二维的变换矩阵是3*3,三维的变换矩阵是4*4。
使用transform(a,b,c,d,e,f)设置变换矩阵每次设置是在之前的基础上设置的。
可以用setTransform(a,b,c,d,e,f))忽略掉之前所有的变换矩阵。先设置为单位矩阵再变换。
window.οnlοad=function(){ var canvas=document.getElementById("canvas"); canvas.width=400; canvas.height=400; var context=canvas.getContext("2d"); context.fillStyle="red"; context.strokeStyle="#058"; context.lineWidth=5; ///// // a c e // b d f // 0 0 1 ///// // a,d 水平,垂直缩放 // b,c 水平,垂直倾斜 // e,f 水平,垂直位移 ///// context.save(); // context.transform(1,0,0,1,0,0); //transform级联操作 context.transform(1,0,0,1,50,100); context.transform(2,0,0,1.5,0,0); context.transform(1,-0.2,-0.2,1,0,0); //setTransform()只使用当前变换 context.setTransform(1,0,0,1,100,100); context.fillRect(50,50,100,100); context.strokeRect(50,50,100,100); context.restore(); }
这部分内容和css3的动画的内容本质都是一样的,都是图形学的内容。
css3动画可以参考我之前的博客:
本文作者starof,因知识本身在变化,作者也在不断学习成长,文章内容也不定时更新,为避免误导读者,方便追根溯源,请诸位转载注明出处:http://www.cnblogs.com/starof/p/8626422.html 有问题欢迎与我讨论,共同进步。