本节书摘来自异步社区《HTML5 Canvas开发详解》一书中的第2章,第2.7节,作者: 【美】Steve Fulton , Jeff Fulton 更多章节内容可以访问云栖社区“异步社区”公众号查看。
2.7 简单画布变换
画布变换是指用数学方法调整所绘形状的物理属性。缩放和旋转是常用的两个形状变换,本节将专门讨论。
所有变换都依赖于后台数学矩阵运算。幸运的是,读者只需使用变换功能而不必去理解这些运算。接下来,本书将讨论如何通过调整Canvas属性来应用旋转和缩放变换。
2.7.1 旋转和平移变换
首先指定画布上的对象面向左侧时处于角度为0的旋转状态(如果对象有“面”,这很重要,如果对象没有“面”,也可将其作为参照)。例如画一个方框(四边等长),它并没有一个与其他边相比而言初始就朝向左侧的“面”。现在画出来作为参考。
//绘制一个红色正方形
context.fillStyle = "red";
context.fillRect(100,100,50,50);
现在,如果将画布旋转45°,那么需要进行以下几步操作。首先,将Canvas变换设置为“identity”(或“reset”)矩阵。
context.setTransform(1,0,0,1,0,0);
由于Canvas使用弧度,而不是角度,在设定变换时应将45°角转成弧度。
var angleInRadians = 45 * Math.PI / 180;
context.rotate(angleInRadians);
第1课:变换在调用setTransform()或其他变换函数后应用到形状和路径上。
如果照抄这段代码运行,将发生很有趣的运行结果:屏幕上什么也没有!这是因为只有对画布应用setTransform()函数后才对形状起作用。示例中先绘制了一个正方形,然后设置变换属性。这将不会对这个使正方形发生改变(或者变换)。例2-7给出了能产生预想结果的正确代码顺序,结果如图2-12所示。
例2-7 简单旋转变换
function drawScreen(){
//绘制一个红色正方形
context.setTransform(1,0,0,1,0,0);
var angleInRadians = 45 * Math.PI / 180;
context.rotate(angleInRadians);
context.fillStyle = "red";
context.fillRect(100,100,50,50);
}
这次得到了变换结果,但是估计和读者想要的结果有所不同。虽然红色的方框旋转了,但是好像画布也跟着一起旋转了。但实际上画布并没有旋转,context.rotate()函数调用后只有部分被绘制出来。那么,为什么这个正方形旋转到了屏幕外?因为旋转原点设在了“nontranslated”(0,0)点,所以导致了正方形从整个画布的左上角旋转。
例2-8提供了一个略微不同的场景:先画一个黑色方块,然后设置旋转变换,最后再画这个红色方块。结果如图2-13所示。
例2-8 旋转以及Canvas状态
function drawScreen(){
//绘制黑色正方形
context.fillStyle = "black";
context.fillRect(20,20,25,25);
//绘制红色正方形
context.setTransform(1,0,0,1,0,0);
var angleInRadians = 45 * Math.PI / 180;
context.rotate(angleInRadians);
context.fillStyle = "red";
context.fillRect(100,100,50,50);
}
https://yqfile.alicdn.com/1e6128d4e58b795aefe6912c28d8e748eb7cd780.png" >
这个小黑方块没受旋转的影响,因为只有调用context.rotate()函数之后绘制的形状才受到影响。
同样,红方块移到了左侧之外。重申一下,这是因为画布不知道旋转的原点在哪儿而发生的。如果没有平移到实际的原点,Canvas会认为就在点(0,0),结果导致context.rotate()函数围绕点(0,0)旋转。这将会使读者领会到下面的内容。
第2课:只有将原点平移至形状中心,对象才会围着自己转。
将例2-8加以改变,使红色正方形能在旋转45°的同时保持位置不变。
首先,设置好控制红色正方形属性的fillRect()函数的变量。虽然不必这样做,但是这会使代码更易于阅读和调整。
var x = 100;
var y = 100;
var width = 50;
var height = 50;
接下来,使用context.translate()函数,把画布原点平移到红色正方形的中心点。这个函数可以将画布原点移到(x,y)处。这里将原点x坐标值设为红色正方形左上角的x值(100)加上其一半宽度。使用前面创建的变量即可控制这个红色正方形的属性,如下所示:
x+0.5*width
接下来,确定原点平移的y坐标值。这次使用左上角的y值和形状的高度值:
y+.05*height
translate()函数语句如下:
context.translate(x+.05*width, y+.05*height)
既然画布已经平移到了正确的原点,下面就可以进行旋转变换了,代码不变。
context.rotate(angleInRadians);
最后,绘制出形状。由于画布原点已经移动到将要绘制形状的位置,那么不能简单重复使用例2-8中同样的数值。现在要把(125,125)作为一切绘制操作的原点。125是从正方形左上角x值(100)加上一半宽度(25)而得来。y值同上。调用translate()方法实现这一目的。
绘制对象需要从正确的左上角坐标值(x,y)开始,进而从原点x值减去宽的一半,从y值减去高的一半。
context.fillRect(-0.5*width,-0.5*height, width, height);
为什么要这样?如图2-14所示。
试想一下,从左上角开始绘制正方形,如果原点在(125,125),左上角实际上是(100,100),然而原点已经平移过了,也就是说(125,125)现在相当于(0,0)。如果在未平移的画布上画这个方块,则应从(-25,-25)点开始。
这样必须把绘制正方形当成从(0,0)开始,而不是从(125,125)开始。因此,当实际绘图的时候必须使用这样的坐标,如图2-15所示。
小结:变换需要将原点平移到正方形的中心,以使其围绕自己旋转,绘图的时候需要使代码将(125,125)当作实际的(0,0)点。如果不平移原点,则也可以使用(125,125)作为正方形的中心(如图2-14所示)。例2-9说明了其如何运行,结果如图2-16所示。
例2-9 围绕中心点旋转
function drawScreen(){
//绘制黑色正方形
context.fillStyle = "black";
context.fillRect(20,20 ,25,25);
//绘制红色正方形
context.setTransform(1,0,0,1,0,0);
var angleInRadians = 45 * Math.PI / 180;
var x = 100;
var y = 100;
var width = 50;
var height = 50;
context.translate(x+.5*width, y+.5*height);
context.rotate(angleInRadians);
context.fillStyle = "red";
context.fillRect(-.5*width,-.5*height , width, height);
}
再看一个旋转的例子。例2-10在例2-9的基础上增加了4个单独的40×40的正方形,每个稍稍进行旋转,结果如图2-17所示。
例2-10 旋转多个正方形
function drawScreen(){
//绘制一个红色正方形
context.setTransform(1,0,0,1,0,0);
var angleInRadians = 45 * Math.PI / 180;
var x = 50;
var y = 100;
var width = 40;
var height = 40;
context.translate(x+.5*width, y+.5*height);
context.rotate(angleInRadians);
context.fillStyle = "red";
context.fillRect(-.5*width,-.5*height , width, height);
context.setTransform(1,0,0,1,0,0);
var angleInRadians = 75 * Math.PI / 180;
var x = 100;
var y = 100;
var width = 40;
var height = 40;
context.translate(x+.5*width, y+.5*height);
context.rotate(angleInRadians);
context.fillStyle = "red";
context.fillRect(-.5*width,-.5*height , width, height);
context.setTransform(1,0,0,1,0,0);
var angleInRadians = 90 * Math.PI / 180;
var x = 150;
var y = 100;
var width = 40;
var height = 40;
context.translate(x+.5*width, y+.5*height);
context.rotate(angleInRadians);
context.fillStyle = "red";
context.fillRect(-.5*width,-.5*height , width, height);
context.setTransform(1,0,0,1,0,0);
var angleInRadians = 120 * Math.PI / 180;
var x = 200;
var y = 100;
var width = 40;
var height = 40;
context.translate(x+.5*width, y+.5*height);
context.rotate(angleInRadians);
context.fillStyle = "red";
context.fillRect(-.5*width,-.5*height , width, height);
}
下面讲解缩放变换。
2.7.2 缩放变换
context.scale()函数有两个参数:第一个是x轴的缩放属性,第二个是y轴的缩放属性。一个对象的正常缩放大小数值是1。因此,如果要将一个对象放大一倍,可以将两个参数都设为2。在drawScreen()中使用下面这段代码可以产生这个红色正方形,如图2-18所示:
context.setTransform(1,0,0,1,0,0);
context.scale(2,2);
context.fillStyle = "red";
context.fillRect(100,100 ,50,50);
https://yqfile.alicdn.com/4df44e05f59c614e1012919fe16020d2ec553145.png" >
如果测试这段代码,读者将会发现缩放的工作方式与旋转差不多。由于没有平移原点来对正方形进行缩放,而是仍用画布左上角作为画布原点,结果红色的正方形向右下方移动了。如果从正方形的中心缩放,这就需要在缩放之前将原点平移到正方形中心,然后再围绕这个中心点绘图(如例2-9所示)。参见例2-11,结果如图2-19所示。
例2-11 从中心点缩放
function drawScreen(){
//绘制一个红色正方形
context.setTransform(1,0,0,1,0,0);
var x = 100;
var y = 100;
var width = 50;
var height = 50;
context.translate(x+.5*width, y+.5*height);
context.scale(2,2);
context.fillStyle = "red";
context.fillRect(-.5*width,-.5*height , width, height);
}
2.7.3 缩放和旋转组合变换
如果对对象进行缩放和旋转操作,Canvas变换可以轻松地组合并生成想要的效果(如图2-20所示),例2-12显示了如何在前面的示例中使用scale(2,2)和rotate(angleInRadians)进行组合变换。
例2-12 缩放和旋转组合
function drawScreen(){
context.setTransform(1,0,0,1,0,0);
var angleInRadians = 45 * Math.PI / 180;
var x = 100;
var y = 100;
var width = 50;
var height = 50;
context.translate(x+.5*width, y+.5*height);
context.scale(2,2);
context.rotate(angleInRadians);
context.fillStyle = "red";
context.fillRect(-.5*width,-.5*height , width, height);
}
例2-13也组合了旋转和缩放,这个例子是对矩形进行操作,如图2-21所示。
例2-13 非正方形对象的缩放和旋转
function drawScreen(){
//绘制一个红色矩形
context.setTransform(1,0,0,1,0,0);
var angleInRadians = 90 * Math.PI / 180;
var x = 100;
var y = 100;
var width = 100;
var height = 50;
context.translate(x+.5*width, y+.5*height);
context.rotate(angleInRadians);
context.scale(2,2);
context.fillStyle = "red";
context.fillRect(-.5*width,-.5*height , width, height);
}
找到任何形状的中心
对矩形或其他形状进行旋转和缩放与对正方形非常类似,实际上只要在缩放、旋转或者组合缩放旋转前将原点平移到形状的中心,都可以得到想要的效果。记住,任何形状的中心点都是半宽的x值和半高的y值!这需要使用边界框理论找到中心点。
图2-22说明了这个理论,尽管不是简单形状也可以找到包含对象任一点的边界框。图2-22接近正方形,但是同样符合矩形的边界框理论。
https://yqfile.alicdn.com/9cb2715a82873b4a97abf229a1f9119648489881.png" >