矩阵变换

在了解矩阵变换之前,需要知道什么是矩阵。在大学线性代数中我们可以得到答案,详细请参考 矩阵基础知识

scale()rotate()translate() 这三个方法提供了一种简便的手段,用于操作绘图环境对象的变换矩阵(Transformation Matrix)。默认情况下,这个变换矩阵就是单位矩阵(Identity Matrix),它并不会影响所要绘制的物体。当调用了scale()、rotate()或translate()方法之后,变换矩阵就会被修改,从而也会影响到所有后续的绘图操作。

transform 和 setTransform

在Canvas中,可以使用transformssetTransform对Canvas坐标系进行矩阵变换。

  • transform:在当前的变换矩阵之上叠加运用另外的变换效果

  • setTransform:将当前的变换矩阵设置为默认的单位矩阵,然后在单位矩阵之上运用用户指定的变换效果

WARNING

多次调用transform()方法所造成的变换效果是累积的,而每次只要调用setTransform()方法,它就会将上一次的变换矩阵彻底清除。

使用transform()和setTransform()有两个好处:

  • 可以实现scale()、rotate()和translate()方法所达到的效果,比如错切效果

  • 只需调用一次transform()或setTransform()方法,就可以做出结合了缩放、旋转、平移及错切等诸多操作的效果

使用transform()和setTransform()方法的主要缺点:

  • 这两个方法不像scale()、rotate()和translate()方法那样直观。

transforms

transforms 直接对变形矩阵作修改。

transform(m11, m12, m21, m22, dx, dy)

这个方法必须将当前的变形矩阵乘上下面的矩阵:

m11 m21 dx
m12 m22 dy
0   0   1

这个方法必须重置当前的变形矩阵为单位矩阵,然后以相同的参数调用 transform 方法。如果任意一个参数是无限大,那么变形矩阵也必须被标记为无限大,否则会抛出异常。

a (m11) Horizontal scaling.

b (m12) Horizontal skewing.

c (m21) Vertical skewing.

d (m22) Vertical scaling.

e (dx) Horizontal moving.

f (dy) Vertical moving.

ctx.transform(1, 0, 1, 1, 0, 0);
ctx.fillRect(0, 0, 100, 100);

理解 Canvas 中的 transform、setTransform

  • transform() 是 Canvas 2D API 使用矩阵多次叠加当前变换的方法,矩阵由方法的参数进行描述。可以实现缩放、旋转、移动和倾斜等效果。
  • setTransform() 是 Canvas 2D API 使用单位矩阵重新设置(覆盖)当前的变换并调用变换的方法,此变换由方法的变量进行描述。

这两个方法都接受六个参数:

ctx.transform(a, b, c, d, e, f);
ctx.setTransform(a, b, c, d, e, f);

其中这六个参数可以使用一个变换矩阵来描述:

其具体代表的是:

  • a: 表示水平缩放scaleX
  • b: 表示水平倾斜skewX
  • c: 表示垂直倾斜skewY
  • d: 表示垂直缩放scaleY
  • e: 表示水平移动translateX
  • f: 表示垂直移动translateY

使用transform实现平移、缩放、旋转

平移

了解矩阵运算的原理之后,就可以很容易的实现坐标的平移。

比如要从一个点 (x0, y0) 移动到 (x, y) , 可以算出位移量:

在坐标轴上表示为:

反之,知道位移量可以算出目标点坐标:

按照矩阵的运算,可以知道:

所以,可以写为:

ctx.transform(1, 0, 0, 1, dx, dy)

这跟使用 translate 是等效的

ctx.translate(dx, dy)

setTransform() 的使用原理也一样。

缩放

比如要将一个点 (x, y) 放大 (k1, k2) 倍,变为 (x', y') , 使用公式可知:

使用矩阵表示为:

所以,可以写为:

ctx.transform(k1, 0, 0, k2, 0, 0)

这跟使用 scale 是等效的

ctx.scale(k1, k2)

setTransform() 的使用原理也一样。

旋转

假定有一个点P(x0,y0)相对坐标原点顺时针旋转θ到达点P(x,y),同时假定P点离坐标原点的距离为r,如下图所示:

先看看两角和差的公式:

sin(α + θ) = sin(α)*cos(θ) + cos(α)*sin(θ)
cos(α + θ) = cos(α)*cos(θ) - sin(α)*sin(θ)
sin(α - θ) = sin(α)*cos(θ) - cos(α)*sin(θ)
cos(α - θ) = cos(α)*cos(θ) + sin(α)*sin(θ)

根据两角和差的公式,可以推导出

x = r * cos(α + θ) = r*cos(α)*cos(θ) - r*sin(α)*sin(θ)
y = r * sin(α + θ) = r*sin(α)*cos(θ) + r*cos(α)*sin(θ)

用矩阵表示为

如果是旋转90度,由于 cos(90)=0,sin(90)=1,所以矩阵应该写成:

ctx.transform(0,1,-1,0,0,0)

就是旋转90度

如果就这样旋转是看不到任何图像的,因为旋转之后坐标点全部移到画布之外,因此通常加一个偏移实现

ctx.transform(0,1,-1,0, dx, dy)

斜切

先用一组图说明什么是斜切

各点的y坐标保持不变,但其x坐标则按比例发生了平移。这种情况将发生水平斜切:

各点的x坐标保持不变,但其y坐标则按比例发生了平移。这种情况将发生垂直斜切:

与旋转变换相比,旋转变换是旋转坐标系中的点,而斜切内里是转坐标轴,坐标轴旋转之后,要保持各点的坐标值不变,所有各点的位置就变了:

变换后,点在新坐标系中的位置不变,在原坐标系中的位置是:

f(x) = x * tan(α)
f(y) = y * tan(α)

回到Canvas当中来,假定有一个点P0(x0,y0)经过斜切变换后得到P(x,y),对于水平斜切,它们之关的关系是:

x = x0 + k*y0
y = y0

用矩阵表示就是:

同理,对于垂直斜切,有:

同时进行水平、垂直斜切,那么矩阵就变成:

transform 表示为

// 水平斜切
ctx.transform(1, 0, k, 1, 0, 0)
ctx.setTransform(1, 0, k, 1, 0, 0)
// 垂直斜切
ctx.transform(1, k, 0, 1, 0, 0)
ctx.setTransform(1, k, 0, 1, 0, 0)
// 水平和垂直方向斜切
ctx.transform(1, k1, k2, 1, 0, 0)
ctx.setTransform(1, k1, k2, 1, 0, 0)

可以看出,其实旋转只是斜切的一种特殊形式。

对称变换

比如,某点P0(x0,y0)结过对称变换后得到P(x,y)。如果对称轴是 x 轴,那么:

x = x0
y = -y0

用矩阵表示为:

如果对称轴是 y 轴,那么:

x = -x0
y = y0

用矩阵表示为:

如果对称轴是 y = x

解得:

x = x0
y = y0

用矩阵表示为:

同理,如果关于 y = -x 对称,则

如果是关于 y = kx 或者 y = kx + b 对称请参考 Canvas学习:自定义的坐标变换,自行演算,本文不多做讨论。

MIT Licensed | Copyright © 2018-present 滇ICP备16006294号

Design by Quanzaiyu | Power by VuePress