var _constants = require("../../utils/constants");
var mathSqrt = _constants.mathSqrt;
var mathMin = _constants.mathMin;
var mathMax = _constants.mathMax;
var mathAbs = _constants.mathAbs;
var matrixUtil = require("../../utils/affine_matrix_util");
var vectorUtil = require("../../utils/vector_util");
var dataUtil = require("../../utils/data_structure_util");
/**
* @abstract
* @class qrenderer.graphic.transform.Transformable
*
* Provide transformation functions for Element class,
* such as translate, scale, skew, rotation, shape, style.
*
*
* 为 Element 类提供变换功能,例如:平移、缩放、扭曲、旋转、翻转、形状、样式。
*
* TODO:用新的事件机制和继承机制,把 Element 类里面与变形有关的逻辑移到本类中来,保持 Element 干净整洁。
*
* @see https://developer.mozilla.org/en-US/docs/Web/API/Canvas_API/Tutorial/Transformations
* @author pissang (https://www.github.com/pissang)
* @docauthor 大漠穷秋 <damoqiongqiu@126.com>
*/
var scaleTmp = [];
/**
* @method constructor Transformable
*/
var Transformable = function Transformable() {
var options = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : {};
/**
* @property {Array<Number>} origin
* The origin point of transformation, default as (0,0) of canvas.
*
*
* 几何变换的原点,默认为 canvas 最左上角的(0,0)点。
*/
this.origin = options.origin === null || options.origin === undefined ? [0, 0] : options.origin;
/**
* @property {Array<Number>} rotation
* The rotation in radian.
*
*
* 旋转弧度。
*/
this.rotation = options.rotation === null || options.rotation === undefined ? 0 : options.rotation;
/**
* @property {Array<Number>} position
* The translate array, for better understanding, we use position to replace the
* word translate defined in W3C canvas standard.
*
*
* 平移,数组。为了方便理解,用 position 这个名字来替代 W3C canvas 标准里面的 translate 。
*/
this.position = options.position === null || options.position === undefined ? [0, 0] : options.position;
/**
* @property {Array<Number>} scale
* The scale array.
*
*
* 缩放,数组。
*/
this.scale = options.scale === null || options.scale === undefined ? [1, 1] : options.scale;
/**
* @property {Array<Number>} skew
* The skew array.
*
*
* 斜切,数组。
*/
this.skew = options.skew === null || options.skew === undefined ? [0, 0] : options.skew;
/**
* @property {Matrix} transform
* The transform matri. To work with the animation system better,
* do NOT modify transform directly, except SVGPainter.
*
*
* 变换矩阵。为了能和动画机制很好地配合,请不要直接修改 transform 属性, SVGPainter 除外。
*/
this.transform = matrixUtil.create();
/**
* @property {Matrix} inverseTransform
* The inverse transform matrix.
*
*
* 逆变换矩阵。
*/
this.inverseTransform = null;
/**
* @property {Number} globalScaleRatio
* The global scale ratio.
*
*
* 全局缩放比例
*/
this.globalScaleRatio = 1;
};
Transformable.prototype = {
constructor: Transformable,
/**
* @method needLocalTransform
* If the change is less than 5e-5(0.00005), there is no need to do any transform.
*
*
* 如果变化的值小于5e-5(0.00005),则不需要变换。
*
* @return {Boolean}
*/
needLocalTransform: function needLocalTransform() {
return dataUtil.isNotAroundZero(this.rotation) || dataUtil.isNotAroundZero(this.position[0]) || dataUtil.isNotAroundZero(this.position[1]) || dataUtil.isNotAroundZero(this.scale[0] - 1) || dataUtil.isNotAroundZero(this.scale[1] - 1) || dataUtil.isNotAroundZero(this.skew[0] - 1) || dataUtil.isNotAroundZero(this.skew[1] - 1);
},
/**
* @method applyTransform
*
* Apply this.transform matrix to canvas context.
*
*
* 将 this.transform 应用到 canvas context 上。
*
* @param {CanvasRenderingContext2D} ctx
*/
applyTransform: function applyTransform(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);
}
},
/**
* @method restoreTransform
* Restore the transform matrix.
*
*
* 重置变换矩阵。
* @param {Context} ctx
*/
restoreTransform: function restoreTransform(ctx) {
var dpr = ctx.dpr || 1;
ctx.setTransform(dpr, 0, 0, dpr, 0, 0);
},
/**
* @method getLocalTransform
* Get local transform matrix.
*
* Note: This implementation did NOT consider the matrix multiplication order of
* affine, because the API invoker will not notice the transform order when provide
* the config object, but always use the transform order of intuitive sense, that is:
* skew->scale->rotation->position.
*
* @example
* rect.animate()
* .when(1000,{
* position:[100,100],
* skew:[2,2],
* scale:[2,2],
* rotate:Math.PI
* })
* .when(2000,{
* position:[200,100],
* scale:[1,1],
* skew:[1,1],
* rotate:-Math.PI
* })
* .start();
*
* There is a big disadvantage of this implementation, it can not coordinate with the
* transform attribute in SVG tags.
* For example: <path transform="rotation(Math.PI);scale(2,2);">,
* means apply some rotation first, then apply scale, this require
* strict operation orders of affine, but the implementation here can NOT support it.
*
*
* 获取本地变换矩阵。
*
* 注意:这里的实现没有考虑仿射变换中的矩阵乘法顺序,因为 API 调用者
* 在提供配置项时并不会留意数学意义上的变换顺序,而总是采用的直觉意义
* 上的变换顺序,也就是:skew->scale->rotation->position 。
*
* @example
* rect.animate()
* .when(1000,{
* position:[100,100],
* skew:[2,2],
* scale:[2,2],
* rotate:Math.PI
* })
* .when(2000,{
* position:[200,100],
* scale:[1,1],
* skew:[1,1],
* rotate:-Math.PI
* })
* .start();
*
*
* 这种实现方式有一个重大的缺点,它不能很好地对应 SVG 中的 transform 机制,
* 比如:<path transform="rotation(Math.PI);scale(2,2);">
* 这个 transform 属性表达的意思是:先 rotation ,然后 scale ,这就要求严格按照
* 仿射变换的顺序来进行矩阵运算,但是这里的实现不能支持这种操作。
*/
getLocalTransform: function getLocalTransform() {
var origin = this.origin || [0, 0];
var rotation = this.rotation || 0;
var position = this.position || [0, 0];
var scale = this.scale || [1, 1];
var skew = this.skew || [0, 0];
var m = matrixUtil.create(); // move origin point
m[4] -= origin[0];
m[5] -= origin[1];
m = matrixUtil.skew(m, skew);
m = matrixUtil.scale(m, scale);
m = matrixUtil.rotate(m, rotation); // move origin back
m[4] += origin[0];
m[5] += origin[1]; // translate
m[4] += position[0];
m[5] += position[1];
return m;
},
/**
* @method composeParentTransform
* This method is designed to compose the transform matrix from parent.
* We needt this method to compose the transfromation from parent when the elements are nested.
*
*
* 此方法的主要作用是复合父层的变换矩阵,当元素出现嵌套时,需要此方法来复合父层上的变换。
*/
composeParentTransform: function composeParentTransform() {
var needLocalTransform = this.needLocalTransform();
var m = matrixUtil.identity(this.transform); // transformation of self
if (needLocalTransform) {
m = this.getLocalTransform();
} // transformation of parent element
var parent = this.parent;
var parentHasTransform = parent && parent.transform;
if (parentHasTransform) {
if (needLocalTransform) {
m = matrixUtil.mul(parent.transform, m);
} else {
matrixUtil.copy(m, parent.transform);
}
} // global scale
if (this.globalScaleRatio != null && this.globalScaleRatio !== 1) {
this.getGlobalScale(scaleTmp);
var relX = scaleTmp[0] < 0 ? -1 : 1;
var relY = scaleTmp[1] < 0 ? -1 : 1;
var sx = ((scaleTmp[0] - relX) * this.globalScaleRatio + relX) / scaleTmp[0] || 0;
var sy = ((scaleTmp[1] - relY) * this.globalScaleRatio + relY) / scaleTmp[1] || 0;
m[0] *= sx;
m[1] *= sx;
m[2] *= sy;
m[3] *= sy;
}
this.transform = m;
this.inverseTransform = this.inverseTransform || matrixUtil.create();
this.inverseTransform = matrixUtil.invert(this.inverseTransform, m);
return this.transform;
},
/**
* @method getGlobalScale
* Get global scale.
*
*
* 获取全局缩放比例。
* @return {Array<Number>}
*/
getGlobalScale: function getGlobalScale() {
var out = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : [];
var m = this.transform;
out[0] = mathSqrt(m[0] * m[0] + m[1] * m[1]); // scale in X axis
out[1] = mathSqrt(m[2] * m[2] + m[3] * m[3]); // scale in Y axis
if (m[0] < 0) {
out[0] = -out[0];
}
if (m[3] < 0) {
out[1] = -out[1];
}
return out;
},
/**
* @method globalToLocal
* Tanslate global coordinate to local space of shape.
*
*
* 变换坐标位置到 shape 的局部坐标空间。
* @param {Number} x
* @param {Number} y
* @return {Array<Number>}
*/
globalToLocal: function globalToLocal(x, y) {
var v2 = [x, y];
var inverseTransform = this.inverseTransform;
if (inverseTransform) {
vectorUtil.applyTransform(v2, v2, inverseTransform);
}
return v2;
},
/**
* @method localToGlobal
* Translate local coordinate of element to global space.
*
*
* 变换局部坐标位置到全局坐标空间。
* @param {Number} x
* @param {Number} y
* @return {Array<Number>}
*/
localToGlobal: function localToGlobal(x, y) {
var v2 = [x, y];
var transform = this.composeParentTransform();
vectorUtil.applyTransform(v2, v2, transform);
return v2;
},
/**
* @method getOuterBoundingRect
* Get the bounding rect in global space, this rect will not apply transformation itself, but it will
* surround the transformed element.
*
*
* 全局坐标系中的边界矩形,此矩形本身不进行几何变换,但是会包围变形之后的元素。
*/
getOuterBoundingRect: function getOuterBoundingRect() {
var rect = this.getBoundingRect();
var points = [];
points[0] = [rect.x1, rect.y1];
points[1] = [rect.x2, rect.y1];
points[2] = [rect.x2, rect.y2];
points[3] = [rect.x1, rect.y2];
points[0] = matrixUtil.transformVector(points[0], this.transform);
points[1] = matrixUtil.transformVector(points[1], this.transform);
points[2] = matrixUtil.transformVector(points[2], this.transform);
points[3] = matrixUtil.transformVector(points[3], this.transform);
var minX = mathMin(points[0][0], points[1][0], points[2][0], points[3][0]);
var maxX = mathMax(points[0][0], points[1][0], points[2][0], points[3][0]);
var minY = mathMin(points[0][1], points[1][1], points[2][1], points[3][1]);
var maxY = mathMax(points[0][1], points[1][1], points[2][1], points[3][1]);
return {
x1: minX,
y1: minY,
x2: maxX,
y2: maxY,
width: mathAbs(maxX - minX),
height: mathAbs(maxY - minY)
};
}
};
var _default = Transformable;
module.exports = _default;