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;