图形编辑器开发:为什么我选择用 transform 矩阵表达图形的变形?

大家好,我是前端西瓜哥。

前段时间对自己的图形编辑器项目做了一次改造。

改用 transform 表达图形的变形,并废弃掉了原来的 rotation、x、y 属性。

然后再补上了图形的翻转的支持,以及斜切的支持。图形的变形操作算是补完了。

这里我简单说说这么做的原因。

我正在开发的 suika 图形设计工具:

https://github.com/F-star/suika

线上体验:

https://blog.fstars.wang/app/suika/

旋转、缩放和斜切

首先图形有基础的属性 x、y、width、height。

有些图形没有这 4 个属性,但其实也有,因为可以通过计算包围盒计算出来(比如贝塞尔曲线)。

然后我们要支持旋转,于是加了一个 rotation 属性(通常基于中心旋转。如果想随意指定,可以再补一个 pivot 属性指定旋转中心点)。

后来又要支持图形缩放。一般情况下,我们更改 width 和 height 就好了。

但有一种情况就不够用了,那就是 “翻转”,有两种情况:水平翻转和垂直翻转。

这时候我们就要引入缩放属性 scaleX 和 scaleY。

scaleX 如果是 1 表示不翻转,如果是 -1,表示水平翻转;scaleY 同理,不同的是它是垂直翻转。

如果都是 -1,那其实就是旋转了 180 度。

最后我们可能要 支持斜切 ,一般来说这种形变的情况是很少见的,甚至说有些编辑器极力避免这种情况的发生。

比如 Canva 图片编辑器会避免斜切的出现。如果同时缩放多个图形,图形只会改宽和高。

图片

但如果一定要支持斜切(比如 Figma),我们只能上 transform 了。

虽说貌似可以补上一个 skewX 和 skewY 属性,但和 rotation 有一些冲突,后面会说为什么。

下面是 Figma 缩放多个图形的效果。

图片

transform 矩阵

上面这些图形的变形属性,其实都可以用 transform 矩阵表示出来。或者叫模型矩阵。

变形矩阵用 6 个数值表示。

| a | c | tx|
| b | d | ty|
| 0 | 0 | 1 |

a 和 d 对应缩放值 scaleX、scaleY。

tx 和 ty 表示位移量,x 和 y 表示图形的位置。所以这里我把图形的 x 和 y 属性也丢掉了,默认为 (0, 0),放到 tx 和 ty 上了。

rotation 值如果对应旋转矩阵,可根据特性求。但 transfrom 不保证符合旋转矩阵的特征。

旋转矩阵其实是斜切中的特例。

所以还是不要太依赖旋转矩阵的特性。

计算 rotation,我们可以选择对一个基准方向的向量(比如 (1, 0)),应用 transform 得到新向量,作为这个图形的方向向量,计算出对应的 rotation。

const getTransformAngle = (tf, angleBase = { x: 1, y: 0 }) => {
  // 丢掉位移的影响,因为向量和点不同,位移后还是原来的向量
  tf = new Matrix(tf.a, tf.b, tf.c, tf.d, 0, 0);
  const angleVec = tf.apply(angleBase);
  return getSweepAngle(angleBase, angleVec);
};

最后是 skewX、skewY,对应是 c 和 b。基本没有什么用。

transform 有很多好处,首先它是底层属性,所有渲染引擎(比如 SVG、Canvas 2D)都支持用矩阵对图形表示形变。

其次也方便做多个形变的复合运算。

比如对一个已经形变的图形做中心旋转,只要给原来的变形矩阵左乘一个 “位移-旋转-位移” 的复合矩阵就可以了。

const dRotate = (dRotation, originTf, center) => {
  const rotateMatrix = new Matrix()
    .translate(-center.x, -center.y)
    .rotate(dRotation)
    .translate(center.x, center.y);

 // 记得要 “左乘” 新的矩阵
  return rotateMatrix.append(originTf);
}

这个思路的方向,要比带着一堆 rotation、scaleX 乱七八糟的属性基于几何算法写出一套复杂的逻辑简单多了。当然前提是你得理解矩阵到底在干什么,这个是基础,建议你花时间弄懂。

最后

选择 transform 矩阵的一些优点:

  1. 它是更底层的表达,能够非常精炼地表达一个图形的形变(虽然一眼看过去不是很直观);

  2. 同时基于矩阵运算,也很方便计算二次形变结果,左乘一个新的变形矩阵即可;

  3. 更容易兼容其他的用了 transform 风格的图形数据,比如 SVG;

我是前端西瓜哥,关注我,学习更多图形编辑器知识。


相关阅读,

计算机图形学:变换矩阵

学到了!Figma 原来是这样表示矩形的

平面几何:求向量 a 到向量 b扫过的夹角

图形编辑器开发:缩放和旋转控制点

图形编辑器:旋转选中的元素


这是一个从 https://juejin.cn/post/7368288987641675816 下的原始话题分离的讨论话题