canvas 中的变换矩阵

4,017 阅读7分钟

上一期的《canvas 基础及实现贝塞尔曲线动画》,我们回顾了 canvas 的基础操作和贝塞尔曲线实现原理;这一次,我们来补充下前一篇遗留下来的基础知识点之一:transform 变换矩阵。

变形 transform

transform(a, b, c, d, dx, dy) 方法是将当前的变换矩阵乘上参数的矩阵:

a c dx
b d dy
0 0 1

参数各自代表如下:

  • a:水平方向的缩放
  • b:水平方向的倾斜偏移
  • c:竖直方向的倾斜偏移
  • d:竖直方向的缩放
  • dx:水平方向的移动
  • dy:竖直方向的移动

setTransform(a, b, c, d, dx, dy) 方法会将当前的变形矩阵重置为单位矩阵,然后用相同的参数调用 transform 方法,即该方法取消当前变形,然后设置为指定的变形。
resetTransform() 重置当前变形为单位矩阵,相当于调用 setTransform(1, 0, 0, 1, 0, 0),即下面的默认矩阵(单位矩阵):

1 0 0
0 1 0
0 0 1

下面是示例图和代码:

ctx.fillStyle = 'red';
ctx.fillRect(0, 0, 100, 50);

ctx.transform(1, 0, 0, 1, 50, 50);
ctx.fillStyle = 'orange';
ctx.fillRect(0, 0, 100, 50);

ctx.transform(0, 1, 1, 0, 50, 50);
ctx.fillStyle = 'yellow';
ctx.fillRect(0, 0, 100, 50);

ctx.transform(1, -1, 1, 1, 0, 0);
ctx.fillStyle = 'lime';
ctx.fillRect(0, 0, 100, 50);
  1. 在默认的矩阵下,绘制了一个红色矩形。
  2. ctx.transform(1, 0, 0, 1, 50, 50) 相当于 ctx.translate(50, 50),橙色矩形相对于红色矩形平移了,现在的矩阵是:
    | 1 0 0 |   | 1 0 50 |   | 1 0 50 |
    | 0 1 0 | · | 0 1 50 | = | 0 1 50 |
    | 0 0 1 |   | 0 0 1  |   | 0 0 1  |
    
  3. ctx.transform(0, 1, 1, 0, 50, 50) 改变的矩阵是:
    | 1 0 50 |   | 0 1 50 |   | 1*0+0*1+50*0 1*1+0*0+50*0 1*50+0*50+50*1 |   | 0 1 100 |
    | 0 1 50 | · | 1 0 50 | = | 0*0+1*1+50*0 0*1+1*0+50*0 0*50+1*50+50*1 | = | 1 0 100 |
    | 0 0 1  |   | 0 0 1  |   | 0*0+0*1+1*0  0*1+0*0+1*0  0*50+0*50+1*1  |   | 0 0  1  |
    
    相当于在最初的状态下,先平移 ctx.translate(100, 100),接着在水平竖直方向倾斜偏移,也就是现在黄色矩形的位置。
  4. ctx.transform(1, -1, 1, 1, 0, 0),矩阵变换为:
    | 0 1 100 |   | 1  1 0 |         | -1 1 100 |
    | 1 0 100 | · | -1 1 0 | = ... = | 1  1 100 |
    | 0 0  1  |   | 0  0 1 |         | 0  0  1  |
    
    相当于 ctx.setTransform(-1, 1, 1, 1, 100, 100)。

齐次坐标

canvas 中的 transform 变换矩阵和 CSS3 transform 的 matrix() 是一样的,函数用的是齐次坐标。

什么是齐次坐标

让我们来看看维基百科中关于齐次坐标的描述:

在数学里,齐次坐标(homogeneous coordinates),或投影坐标(projective coordinates)是指一个用于投影几何里的坐标系统,如同用于欧氏几何里的笛卡儿坐标一般。该词由奥古斯特·费迪南德·莫比乌斯于1827年在其著作《Der barycentrische Calcul》一书内引入。齐次坐标可让包括无穷远点的点坐标以有限坐标表示。使用齐次坐标的公式通常会比用笛卡儿坐标表示更为简单,且更为对称。齐次坐标有着广泛的应用,包括电脑图形及 3D 电脑视觉。使用齐次坐标可让电脑进行仿射变换,并通常,其投影变换能简单地使用矩阵来表示。
如一个点的齐次坐标乘上一个非零标量,则所得之坐标会表示同一个点。因为齐次坐标也用来表示无穷远点,为此一扩展而需用来标示坐标之数值比投影空间之维度多一。例如,在齐次坐标里,需要两个值来表示在投影线上的一点,需要三个值来表示投影平面上的一点。
实投影平面可以看作是一个具有额外点的欧氏平面,这些点称之为无穷远点,并被认为是位于一条新的线上(该线称之为无穷远线)。每一个无穷远点对应至一个方向(由一条线之斜率给出),可非正式地定义为一个点自原点朝该方向移动之极限。在欧氏平面里的平行线可看成会在对应其共同方向之无穷远点上相交。给定欧氏平面上的一点 (x, y),对任意非零实数 Z,三元组 (xZ, yZ, Z) 即称之为该点的齐次坐标。依据定义,将齐次坐标内的数值乘上同一个非零实数,可得到同一点的另一组齐次坐标。例如,笛卡儿坐标上的点 (1,2) 在齐次坐标中即可标示成 (1,2,1) 或 (2,4,2)。原来的笛卡儿坐标可透过将前两个数值除以第三个数值取回。因此,与笛卡儿坐标不同,一个点可以有无限多个齐次坐标表示法。 一条通过原点 (0, 0) 的线之方程可写作 nx + my = 0,其中 n 及 m 不能同时为 0。以参数表示,则能写成 x = mt, y = − nt。令 Z=1/t,则线上的点之笛卡儿坐标可写作 (m/Z, − n/Z)。在齐次坐标下,则写成 (m, − n, Z)。当 t 趋向无限大,亦即点远离原点时,Z 会趋近于 0,而该点的齐次坐标则会变成 (m, −n, 0)。因此,可定义 (m, −n, 0) 为对应 nx + my = 0 这条线之方向的无穷远点之齐次坐标。因为欧氏平面上的每条线都会与透过原点的某一条线平行,且因为平行线会有相同的无穷远点,欧氏平面每条线上的无穷远点都有其齐次坐标。
概括来说: 投影平面上的任何点都可以表示成一三元组 (X, Y, Z),称之为该点的齐次坐标或投影坐标,其中 X、Y 及 Z 不全为 0。 以齐次坐标表表示的点,若该坐标内的数值全乘上一相同非零实数,仍会表示该点。 相反地,两个齐次坐标表示同一点,当且仅当其中一个齐次坐标可由另一个齐次坐标乘上一相同非零常数得取得。
当 Z 不为 0,则该点表示欧氏平面上的该 (X/Z, Y/Z)。
当 Z 为 0,则该点表示一无穷远点。

简单来说,齐次坐标就是用 N+1 维来代表 N 维坐标。 相比笛卡儿坐标,多了一个变量 w,当这个变量 w 不为0时,(x, y, w) 表示点 (x/w, y/w); 当这个变量 w 为 0时,表示为无穷远的点,所以 (x, y, 0) 也能表示向量 (x, y)。

如果不能理解变量 w,可以想象在你的面前有一个坐标系,你与坐标系的垂直距离就是变量 w。

为什么要用齐次坐标

为什么要用齐次坐标呢?主要是方便计算机图形学进行仿射几何变换。简单来说,就是用矩阵相乘同时实现图形的平移、旋转、缩放和偏斜等效果。

结合上面的示例,如果使用齐次坐标,在矩形中的点经过变换后的坐标都可以通过以下矩阵公式得出:

| a c e |   | x |   | ax+cy+e |
| b d f | · | y | = | bx+dy+f |
| 0 0 1 |   | 1 |   |    1    |

x' = ax + cy + e
y' = bx + dy + f

根据矩阵公式,我们来看下 translate()、rotate()、scale() 等方法与 transform() 对应的关系:

  1. 平移:上面示例也讲了,translate(x, y) 对应的是 transform(1, 0, 0, 1, x, y);
  2. 旋转:旋转 a 弧度,可使用下面两种方法
    let a;
    let sin = Math.sin(a);
    let cos = Math.cos(a);
    ctx.rotate(a);
    // 等同
    ctx.transform(cos, sin, -sin, cos, 0, 0);
    
  3. 缩放:scale(x, y) 等同于 transform(x, 0, 0, y, 0, 0)。
  4. 偏斜:即类似 CSS3 中的 skew(x, y) 方法。
    let x = Math.PI / 6;
    let y = Math.PI / 3;
    let tanx = Math.tan(x);
    let tany = Math.tan(y);
    ctx.transform(1, tanx, tany, 1, 0, 0);
    

总结

好了,上面就是计算机图形学中基础(2D)的齐次坐标,CSS3 和 canvas 通用的 transform 变形原理。(如果文中有错误的地方,欢迎大家指出!)