# Transformable 类

# 概述

提供变换扩展, 平移、旋转、缩放等功能

# 基于 canvas 的原理

canvas 官方文档

简单点理解,变换的是坐标系;默认情况下,canvas 的坐标系是左上角为 (0, 0) ,如果将坐标系的圆点移动变换位置,就是实现了 translate ,如果坐标系按照一定的角度进行旋转,则是实现了 rotate ,如果将单位放大,则实现了 scale; 再复杂一些,如果坐标中的 x 和 y 不再垂直了会怎用? 一个矩形可以变成一个平行四边形,也就是斜切。

translate

注意 rotate 的圆点是 (0, 0) 如果想实现在物体上旋转,则需要将 (0, 0) 移动到物体上

# 变换与矩阵

var [a, b, c, d, e, f] = [1, 0, 0, 1, 0, 0];

对应的矩阵表示:

fsdfsd

# translate

e 表示 x 的平移

f 表示 y 的平移

# scale

a 表示 x 的缩放

d 表示 y 的缩放

其实 b, c 也需要参与运算,只不过原始状态下, bc 都为 0,如果 ctx 发生了旋转的话,需要计算水平和竖直方向的缩放

# rotate

旋转的角度为 θ

var rad = θ / 180 * Math.PI;
var matrix = [Math.cos(rad), Math.sin(rad), -Math.sin(rad), Math.cos(rad), 0, 0]

# 关系

# 派生

# Element

# 构造函数

根据参数初始化平移、旋转、缩放等默认值。

# position

位移,类型: number[]。

# rotation

旋转的弧度,类型: number。

# scale

缩放,类型: number[]。

# origin

旋转和缩放的原点,类型: number[]。

# transform

矩阵形式表示位移、旋转、缩放,类型: zrender.matrix

src\Element.js

// drift
this.transform = [1, 0, 0, 1, 0, 0];

# 类方法

# getLocalTransform

将各种变换转换为矩阵表示,当修改了原点的时候,处理方式很用意思。

先将原点移动,转换后,再移动回来。

生成的 transformcanvas 的要求并不相同,是因为,按照目前的变化,不会改变 x, y 的值

getLocalTransform = function (target, m) {
    m = m || [];
    mIdentity(m);

    var origin = target.origin;
    var scale = target.scale || [1, 1];
    var rotation = target.rotation || 0;
    var position = target.position || [0, 0];

    if (origin) {
        // Translate to origin
        m[4] -= origin[0];
        m[5] -= origin[1];
    }
    matrix.scale(m, m, scale);
    if (rotation) {
        matrix.rotate(m, m, rotation);
    }
    if (origin) {
        // Translate back from origin
        m[4] += origin[0];
        m[5] += origin[1];
    }

    m[4] += position[0];
    m[5] += position[1];

    return m;
}

# 原型方法

# needLocalTransform

判断是否需要有坐标变换

如果有坐标变换, 则从position, rotation, scale以及父节点的transform计算出自身的transform矩阵

needLocalTransform = function () {
    return isNotAroundZero(this.rotation)
        || isNotAroundZero(this.position[0])
        || isNotAroundZero(this.position[1])
        || isNotAroundZero(this.scale[0] - 1)
        || isNotAroundZero(this.scale[1] - 1);
}

# updateTransform

更新变换数据

  1. 将 translate rotation scale 等属性转换为矩阵
  2. 合并 Group 的矩阵数据
  3. 根据 globalScaleRatio 来缩小或放大矩阵数据
  4. 获取逆矩阵并保存
updateTransform = function () {
    
    var parent = this.parent;
    var parentHasTransform = parent && parent.transform;
    var needLocalTransform = this.needLocalTransform();
    var m = this.transform;
    if (!(needLocalTransform || parentHasTransform)) {
        m && mIdentity(m);
        return;
    }

    m = m || matrix.create();

    if (needLocalTransform) {
        this.getLocalTransform(m);
    }
    else {
        mIdentity(m);
    }

    // 应用父节点变换
    if (parentHasTransform) {
        if (needLocalTransform) {
            matrix.mul(m, parent.transform, m);
        }
        else {
            matrix.copy(m, parent.transform);
        }
    }
    // 保存这个变换矩阵
    this.transform = m;

    var globalScaleRatio = this.globalScaleRatio;
    if (globalScaleRatio != null && globalScaleRatio !== 1) {
        this.getGlobalScale(scaleTmp);
        var relX = scaleTmp[0] < 0 ? -1 : 1;
        var relY = scaleTmp[1] < 0 ? -1 : 1;
        var sx = ((scaleTmp[0] - relX) * globalScaleRatio + relX) / scaleTmp[0] || 0;
        var sy = ((scaleTmp[1] - relY) * globalScaleRatio + relY) / scaleTmp[1] || 0;

        m[0] *= sx;
        m[1] *= sx;
        m[2] *= sy;
        m[3] *= sy;
    }

    this.invTransform = this.invTransform || matrix.create();
    matrix.invert(this.invTransform, m);
}

# getLocalTransform

getLocalTransform = function (m) {
    return Transformable.getLocalTransform(this, m);
}

# setTransform

将自己的transform应用到context上

setTransform = function (ctx) {
    var m = this.transform;
    var dpr = ctx.dpr || 1;
    if (m) {
        ctx.setTransform(dpr * m[0], dpr * m[1], dpr * m[2], dpr * m[3], dpr * m[4], dpr * m[5]);
    }
    else {
        ctx.setTransform(dpr, 0, 0, dpr, 0, 0);
    }
}

# restoreTransform

还原变形矩阵

restoreTransform = function (ctx) {
    var dpr = ctx.dpr || 1;
    ctx.setTransform(dpr, 0, 0, dpr, 0, 0);
}

# setLocalTransform

将变形矩阵转换为变形的属性

setLocalTransform = function (m) {
    if (!m) {
        // TODO return or set identity?
        return;
    }
    var sx = m[0] * m[0] + m[1] * m[1];
    var sy = m[2] * m[2] + m[3] * m[3];
    var position = this.position;
    var scale = this.scale;
    if (isNotAroundZero(sx - 1)) {
        sx = Math.sqrt(sx);
    }
    if (isNotAroundZero(sy - 1)) {
        sy = Math.sqrt(sy);
    }
    if (m[0] < 0) {
        sx = -sx;
    }
    if (m[3] < 0) {
        sy = -sy;
    }

    position[0] = m[4];
    position[1] = m[5];
    scale[0] = sx;
    scale[1] = sy;
    this.rotation = Math.atan2(-m[1] / sy, m[0] / sx);
}

# decomposeTransform

分解 transform 矩阵到 positionrotationscale。通常用于修改 transform 后同步 positionrotationscale 属性。

transformableProto.decomposeTransform = function () {
    if (!this.transform) {
        return;
    }
    var parent = this.parent;
    var m = this.transform;
    if (parent && parent.transform) {
        // Get local transform and decompose them to position, scale, rotation
        matrix.mul(tmpTransform, parent.invTransform, m);
        m = tmpTransform;
    }
    var origin = this.origin;
    if (origin && (origin[0] || origin[1])) {
        originTransform[4] = origin[0];
        originTransform[5] = origin[1];
        matrix.mul(tmpTransform, m, originTransform);
        tmpTransform[4] -= origin[0];
        tmpTransform[5] -= origin[1];
        m = tmpTransform;
    }

    this.setLocalTransform(m);
}

# getGlobalScale

获取全局缩放值

getGlobalScale = function (out) {
    var m = this.transform;
    out = out || [];
    if (!m) {
        out[0] = 1;
        out[1] = 1;
        return out;
    }
    out[0] = Math.sqrt(m[0] * m[0] + m[1] * m[1]);
    out[1] = Math.sqrt(m[2] * m[2] + m[3] * m[3]);
    if (m[0] < 0) {
        out[0] = -out[0];
    }
    if (m[3] < 0) {
        out[1] = -out[1];
    }
    return out;
}

# transformCoordToLocal

变换坐标位置到 shape 的局部坐标空间

  1. 这个方法特别的有用,将全局的坐标位置转换为图形中的局部坐标,在缩放移动后,可以获取变换前的数据。在定点缩放时很有用。
  2. 在获取图形是否包含坐标时也会用到。

这个方法的核心就是应用了逆变换矩阵

transformCoordToLocal = function (x, y) {
    var v2 = [x, y];
    var invTransform = this.invTransform;
    if (invTransform) {
        vector.applyTransform(v2, v2, invTransform);
    }
    return v2;
}

# transformCoordToGlobal

变换局部坐标位置到全局坐标空间

transformCoordToGlobal = function (x, y) {
    var v2 = [x, y];
    var transform = this.transform;
    if (transform) {
        vector.applyTransform(v2, v2, transform);
    }
    return v2;
}