canvas元素简易教程(10)(转自火狐,自己只写了简单的代码分析)
冒险上一次首页吧,毕竟不是自己的东西,希望不要被喷。。。
大家好,经过前几次的学习,相信大家对canvas标签的使用已经有了一个大概的思路,也学习到了基本的用法。那么我们今天来学习一点对图像处理的基本方法:变形。
啥叫变形呢?好比你是一个胖子,把你拉高了挤瘦了弄成一个瘦子,这就叫变形。
也就是说,变形就是对图像的形状进行变化,但不对其内容进行改变的过程。
在了解变形之前,我先介绍一下两个在你开始绘制复杂图形就必不可少的方法。
save()
restore()
save和restore方法是用来保存和恢复canvas状态的,都没有参数。Canvas的状态就是当前画面应用的所有样式和变形的一个快照。
啥意思?其实就是用save就是把当前绘画对象状态做一个保留,然后把它入栈,restore就是出栈。。。
事实是这样吗?当然不是。Canvas状态是以堆(stack)的方式保存的,每一次调用save方法,当前的状态就会被推入堆中保存起来。这种状态包括:
当前应用的变形(即移动,旋转和缩放,后面会介绍);
strokeStyle,fillStyle,globalAlpha,lineWidth,lineCap,lineJoin,miterLimit,shadowOffsetX,shadowOffsetY,shadowBlur,shadowColor,globalCompositeOperation的值;
当前的裁切路径(clipping path),后面会介绍的。
你可以调用任意多次save方法。
每一次调用restore方法,上一个保存的状态就从堆中弹出,所有设定都恢复。
所以你可以理解为这个快照就是一个入栈出栈的过程,但是它是用堆实现的。
都写了这么多还没有看到代码,难道你不遗憾吗?很可惜,这章没有源码了,一切都得大家自己去尝试。。。
开个玩笑,还是让我们来一段代码吧:
function draw() { var ctx = document.getElementById('canvas').getContext('2d'); ctx.fillRect(0,0,150,150); // Draw a rectangle with default settings ctx.save(); // Save the default state ctx.fillStyle = '#09F' // Make changes to the settings ctx.fillRect(15,15,120,120); // Draw a rectangle with new settings ctx.save(); // Save the current state ctx.fillStyle = '#FFF' // Make changes to the settings ctx.globalAlpha = 0.5; ctx.fillRect(30,30,90,90); // Draw a rectangle with new settings ctx.restore(); // Restore previous state ctx.fillRect(45,45,60,60); // Draw a rectangle with restored settings ctx.restore(); // Restore original state ctx.fillRect(60,60,30,30); // Draw a rectangle with restored settings }
让我们来分析一下这段代码吧,我们用一个一圈一圈的矩形来分析一下这两个新方法的工作原理。
首先是画一个大的正方形,此时的填充状态是黑色的,然后save一下当前状态,也就是把那个黑色填充状态入栈。
然后再在黑色大正方形里面画一个蓝色的正方形,然后把蓝色填充的状态再入栈。
大家可以看到蓝色是叠加在黑色之上的,这是默认绘制方法,至于怎样改变这个叠加的着色问题我们在后面将会讲解。
再在蓝色中画一个白色透明的正方形,然后出栈状态也就是把最后一次入栈的蓝色填充状态取出,然后在白色正方形中间绘制一个蓝色的正方形,然后出栈黑色填充状态,在最中间画一个小的黑色正方形。
好了,我们简单的做了一个案例来分析save与restore方法,大家都理解了么?
下面我们会介绍一个比较实用的方法:translate。
为什么说这个方法实用呢?我们先来看一下这个方法的用法吧:
translate(x, y)
translate方法用来移动canvas和它的原点到一个不同的位置。translate方法接受两个参数x是左右偏移量,y是上下偏移量。我相信有很多人会说这个和moveTo方法有什么不同啊,不都是移动嘛。好的,让我们先来分析一下这两个方法的区别吧。
moveTo方法是把画笔移到一个点,比如我moveTo(100,100),这时候是将画笔移到这里,然后以这里为绘画的起始点继续进行绘制。但是translate方法是见100,100这一点作为现在的坐标原点,也就是原来的左上角的(0,0)。那有什么用呢?好的,我们来分析一下。许多对称的图形尤其是需要很多次描点的图形,都需要有一个参考中心,然后围绕中心做无数次的描点最后成功绘制。如果用moveTo的话,那么恭喜你,你需要一个点一个点的描,因为你没有一个可以作为参考中心的点。你也许说我可以自己定义啊,那么你可以尝试一下去定义一个参考中心的实施难度以及还需要对该店进行坐标的增减以达到0,0的无力感。
translate的第二个功能是旋转,比如时钟,是围绕中性点旋转的,而在canvas标签的JS方法中是有旋转的方法的,那么就不需要一次一次的描点而只要规定一个中心点就可以了。
我想现在大家或多或少已经了解了这个方法的用处了,但是不要忘记一点,这个规定完坐标原点之后要回去会比较麻烦,因为你的相对坐标改变了,不能再用以前的坐标恢复。但是不用愁,忘记了刚才学了什么么?对啦,save方法会帮你回去。在使用translate方法时,应该反复的调用save方法以达到保留前次的状态,这样可以很方便的用restore方法来返回,不用繁琐的手动恢复。又如果你是在一个循环中做位移但没有保存和恢复canvas的状态,很可能到最后会发现怎么有些东西不见了,那是因为它很可能已经超出canvas范围以外了。
来个例子,这个例子对于图形学的要求较高,反正在绘制过程中我基本是一点一点试着看的,大家主要去体会思想与translate的使用,别的嘛看看就好了。。。
让我们看一下代码:
function draw() { var ctx = document.getElementById('canvas').getContext('2d'); ctx.fillRect(0,0,300,300); for (var i=0;i<3;i++) { for (var j=0;j<3;j++) { ctx.save(); ctx.strokeStyle = "#9CFF00"; ctx.translate(50+j*100,50+i*100); drawSpirograph(ctx,20*(j+2)/(j+1),-8*(i+3)/(i+1),10); ctx.restore(); } } } function drawSpirograph(ctx,R,r,O){ var x1 = R-O; var y1 = 0; var i = 1; ctx.beginPath(); ctx.moveTo(x1,y1); do { if (i>20000) break; var x2 = (R+r)*Math.cos(i*Math.PI/72) - (r+O)*Math.cos(((R+r)/r)*(i*Math.PI/72)) var y2 = (R+r)*Math.sin(i*Math.PI/72) - (r+O)*Math.sin(((R+r)/r)*(i*Math.PI/72)) ctx.lineTo(x2,y2); x1 = x2; y1 = y2; i++; } while (x2 != R-O && y2 != 0 ); ctx.stroke(); }
来,让我们一起分析一下这段代码。首先是drawSpirograph方法,这个方法绘制了一个随着R与r不同而绘画轨迹不同的螺旋曲线,当绘画终点与起点重合时结束。其实这里的“if (i>20000) break;”没有作为出口条件,但是却不可缺少,因为有可能出现细小的调整后终点与起点未能重合,若没有这个出口条件则出现了死循环。
drawSpirograph方法一直进行着微小的绘制,直到绘制出一段螺旋曲线。
而drawSpirograph的最后一个参数也就是O,则是drawSpirograph的绘制弧度参数。越小越直。。。
如果不是使用translate方法,那么每次绘制的图像只能有四分之一在页面上,剩下四分之三在左上角被隐藏了。通过循环使用translate方法来规定原点,从而画出所有的曲线。不要忘记了在每次使用前后save一下再restore一下,这样对轨迹的把握要方便的多。
好了,今天就先到这里,下次将会对旋转缩放进行进一步的讲解,我们下次再见。