blob: 0c8fe67b110092aaf40fed1727371380b918d20d [file] [log] [blame]
/*!
2D Transformation Matrix v2.3.2
(c) Epistemex.com 2014-2016
License: MIT, header required.
*/
/* --- To see contributors: please see readme.md and Change.log --- */
/**
* 2D transformation matrix object initialized with identity matrix.
*
* The matrix can synchronize a canvas 2D context by supplying the context
* as an argument, or later apply current absolute transform to an
* existing context.
*
* To synchronize a DOM element you can use [`toCSS()`]{@link Matrix#toCSS} or [`toCSS3D()`]{@link Matrix#toCSS3D}.
*
* @param {CanvasRenderingContext2D} [context] - Optional context to sync with Matrix
* @prop {number} a - scale x
* @prop {number} b - shear y
* @prop {number} c - shear x
* @prop {number} d - scale y
* @prop {number} e - translate x
* @prop {number} f - translate y
* @prop {CanvasRenderingContext2D|null} [context=null] - set or get current canvas context
* @constructor
* @license MIT license (header required)
* @copyright Epistemex.com 2014-2016
*/
function Matrix(context) {
var me = this;
me._t = me.transform;
me.a = me.d = 1;
me.b = me.c = me.e = me.f = 0;
// reset canvas to enable 100% sync.
if (context)
(me.context = context).setTransform(1, 0, 0, 1, 0, 0);
}
/**
* Returns a new matrix that transforms a triangle `t1` into another triangle
* `t2`, or throws an exception if it is impossible.
*
* Note: the method can take both arrays as well as literal objects.
* Just make sure that both arguments (`t1`, `t2`) are of the same type.
*
* @param {{px: number, py: number, qx: number, qy: number, rx: number, ry: number}|Array} t1 - Object or array containing the three points for the triangle.
* For object use obj.px, obj.py, obj.qx, obj.qy, obj.rx and obj.ry. For arrays provide the points in the order [px, py, qx, qy, rx, ry]
* @param {{px: number, py: number, qx: number, qy: number, rx: number, ry: number}|Array} t2 - See description for t1.
* @param {CanvasRenderingContext2D} [context] - optional canvas 2D context to use for the matrix
* @returns {Matrix}
* @throws Exception is matrix becomes not invertible
* @static
*/
Matrix.fromTriangles = function(t1, t2, context) {
var m1 = new Matrix(),
m2 = new Matrix(context),
r1, r2;
if (Array.isArray(t1)) {
var rx1 = t1[4], ry1 = t1[5], rx2 = t2[4], ry2 = t2[5];
r1 = [t1[0] - rx1, t1[1] - ry1, t1[2] - rx1, t1[3] - ry1, rx1, ry1];
r2 = [t2[0] - rx2, t2[1] - ry2, t2[2] - rx2, t2[3] - ry2, rx2, ry2]
}
else {
r1 = [t1.px - t1.rx, t1.py - t1.ry, t1.qx - t1.rx, t1.qy - t1.ry, t1.rx, t1.ry];
r2 = [t2.px - t2.rx, t2.py - t2.ry, t2.qx - t2.rx, t2.qy - t2.ry, t2.rx, t2.ry]
}
m1.setTransform.apply(m1, r1);
m2.setTransform.apply(m2, r2);
return m2.multiply(m1.inverse())
};
/**
* Create a new matrix from a SVGMatrix
*
* @param {SVGMatrix} svgMatrix - source SVG Matrix
* @param {CanvasRenderingContext2D} [context] - optional canvas 2D context to use for the matrix
* @returns {Matrix}
* @static
* @see {@link https://developer.mozilla.org/en-US/docs/Web/API/SVGMatrix|MDN / SVGMatrix}
*/
Matrix.fromSVGMatrix = function(svgMatrix, context) {
return new Matrix(context).multiply(svgMatrix)
};
/**
* Create a matrix from a transform list from an SVG shape. The list
* can be for example baseVal (i.e. `shape.transform.baseVal`).
*
* The resulting matrix has all transformations from that list applied
* in the same order as the list.
*
* @param {SVGTransformList} tList - transform list from an SVG shape.
* @param {CanvasRenderingContext2D} [context] - optional canvas 2D context to use for the matrix
* @returns {Matrix}
* @see {@link https://developer.mozilla.org/en-US/docs/Web/API/SVGTransformList|MDN / SVGTransformList}
*/
Matrix.fromSVGTransformList = function(tList, context) {
var m = new Matrix(context),
i = 0;
while(i < tList.length)
m.multiply(tList[i++].matrix);
return m
};
Matrix.prototype = {
/**
* Concatenates transforms of this matrix onto the given child matrix and
* returns a new matrix. This instance is used on left side.
*
* @param {Matrix} cm - child matrix to apply concatenation to
* @returns {Matrix} - new Matrix instance
*/
concat: function(cm) {
return this.clone().multiply(cm)
},
/**
* Flips the horizontal values.
* @returns {Matrix}
*/
flipX: function() {
return this._t(-1, 0, 0, 1, 0, 0)
},
/**
* Flips the vertical values.
* @returns {Matrix}
*/
flipY: function() {
return this._t(1, 0, 0, -1, 0, 0)
},
/**
* Reflects incoming (velocity) vector on the normal which will be the
* current transformed x axis. Call when a trigger condition is met.
*
* @param {number} x - vector end point for x (start = 0)
* @param {number} y - vector end point for y (start = 0)
* @returns {{x: number, y: number}}
*/
reflectVector: function(x, y) {
var v = this.applyToPoint(0, 1),
d = (v.x * x + v.y * y) * 2;
x -= d * v.x;
y -= d * v.y;
return {x: x, y: y}
},
/**
* Short-hand to reset current matrix to an identity matrix.
* @returns {Matrix}
*/
reset: function() {
return this.setTransform(1, 0, 0, 1, 0, 0)
},
/**
* Rotates current matrix by angle (accumulative).
* @param {number} angle - angle in radians
* @returns {Matrix}
*/
rotate: function(angle) {
var cos = Math.cos(angle),
sin = Math.sin(angle);
return this._t(cos, sin, -sin, cos, 0, 0)
},
/**
* Converts a vector given as `x` and `y` to angle, and
* rotates (accumulative).
* @param x
* @param y
* @returns {Matrix}
*/
rotateFromVector: function(x, y) {
return this.rotate(Math.atan2(y, x))
},
/**
* Helper method to make a rotation based on an angle in degrees.
* @param {number} angle - angle in degrees
* @returns {Matrix}
*/
rotateDeg: function(angle) {
return this.rotate(angle * Math.PI / 180)
},
/**
* Scales current matrix uniformly and accumulative.
* @param {number} f - scale factor for both x and y (1 does nothing)
* @returns {Matrix}
*/
scaleU: function(f) {
return this._t(f, 0, 0, f, 0, 0)
},
/**
* Scales current matrix accumulative.
* @param {number} sx - scale factor x (1 does nothing)
* @param {number} sy - scale factor y (1 does nothing)
* @returns {Matrix}
*/
scale: function(sx, sy) {
return this._t(sx, 0, 0, sy, 0, 0)
},
/**
* Scales current matrix on x axis accumulative.
* @param {number} sx - scale factor x (1 does nothing)
* @returns {Matrix}
*/
scaleX: function(sx) {
return this._t(sx, 0, 0, 1, 0, 0)
},
/**
* Scales current matrix on y axis accumulative.
* @param {number} sy - scale factor y (1 does nothing)
* @returns {Matrix}
*/
scaleY: function(sy) {
return this._t(1, 0, 0, sy, 0, 0)
},
/**
* Apply shear to the current matrix accumulative.
* @param {number} sx - amount of shear for x
* @param {number} sy - amount of shear for y
* @returns {Matrix}
*/
shear: function(sx, sy) {
return this._t(1, sy, sx, 1, 0, 0)
},
/**
* Apply shear for x to the current matrix accumulative.
* @param {number} sx - amount of shear for x
* @returns {Matrix}
*/
shearX: function(sx) {
return this._t(1, 0, sx, 1, 0, 0)
},
/**
* Apply shear for y to the current matrix accumulative.
* @param {number} sy - amount of shear for y
* @returns {Matrix}
*/
shearY: function(sy) {
return this._t(1, sy, 0, 1, 0, 0)
},
/**
* Apply skew to the current matrix accumulative.
* @param {number} ax - angle of skew for x
* @param {number} ay - angle of skew for y
* @returns {Matrix}
*/
skew: function(ax, ay) {
return this.shear(Math.tan(ax), Math.tan(ay))
},
/**
* Apply skew for x to the current matrix accumulative.
* @param {number} ax - angle of skew for x
* @returns {Matrix}
*/
skewX: function(ax) {
return this.shearX(Math.tan(ax))
},
/**
* Apply skew for y to the current matrix accumulative.
* @param {number} ay - angle of skew for y
* @returns {Matrix}
*/
skewY: function(ay) {
return this.shearY(Math.tan(ay))
},
/**
* Set current matrix to new absolute matrix.
* @param {number} a - scale x
* @param {number} b - shear y
* @param {number} c - shear x
* @param {number} d - scale y
* @param {number} e - translate x
* @param {number} f - translate y
* @returns {Matrix}
*/
setTransform: function(a, b, c, d, e, f) {
var me = this;
me.a = a;
me.b = b;
me.c = c;
me.d = d;
me.e = e;
me.f = f;
return me._x()
},
/**
* Translate current matrix accumulative.
* @param {number} tx - translation for x
* @param {number} ty - translation for y
* @returns {Matrix}
*/
translate: function(tx, ty) {
return this._t(1, 0, 0, 1, tx, ty)
},
/**
* Translate current matrix on x axis accumulative.
* @param {number} tx - translation for x
* @returns {Matrix}
*/
translateX: function(tx) {
return this._t(1, 0, 0, 1, tx, 0)
},
/**
* Translate current matrix on y axis accumulative.
* @param {number} ty - translation for y
* @returns {Matrix}
*/
translateY: function(ty) {
return this._t(1, 0, 0, 1, 0, ty)
},
/**
* Multiplies current matrix with new matrix values. Also see [`multiply()`]{@link Matrix#multiply}.
*
* @param {number} a2 - scale x
* @param {number} b2 - shear y
* @param {number} c2 - shear x
* @param {number} d2 - scale y
* @param {number} e2 - translate x
* @param {number} f2 - translate y
* @returns {Matrix}
*/
transform: function(a2, b2, c2, d2, e2, f2) {
var me = this,
a1 = me.a,
b1 = me.b,
c1 = me.c,
d1 = me.d,
e1 = me.e,
f1 = me.f;
/* matrix order (canvas compatible):
* ace
* bdf
* 001
*/
me.a = a1 * a2 + c1 * b2;
me.b = b1 * a2 + d1 * b2;
me.c = a1 * c2 + c1 * d2;
me.d = b1 * c2 + d1 * d2;
me.e = a1 * e2 + c1 * f2 + e1;
me.f = b1 * e2 + d1 * f2 + f1;
return me._x()
},
/**
* Multiplies current matrix with source matrix.
* @param {Matrix|SVGMatrix} m - source matrix to multiply with.
* @returns {Matrix}
*/
multiply: function(m) {
return this._t(m.a, m.b, m.c, m.d, m.e, m.f)
},
/**
* Divide this matrix on input matrix which must be invertible.
* @param {Matrix} m - matrix to divide on (divisor)
* @throws Exception is input matrix is not invertible
* @returns {Matrix}
*/
divide: function(m) {
if (!m.isInvertible())
throw "Matrix not invertible";
return this.multiply(m.inverse())
},
/**
* Divide current matrix on scalar value != 0.
* @param {number} d - divisor (can not be 0)
* @returns {Matrix}
*/
divideScalar: function(d) {
var me = this;
me.a /= d;
me.b /= d;
me.c /= d;
me.d /= d;
me.e /= d;
me.f /= d;
return me._x()
},
/**
* Get an inverse matrix of current matrix. The method returns a new
* matrix with values you need to use to get to an identity matrix.
* Context from parent matrix is not applied to the returned matrix.
*
* @param {boolean} [cloneContext=false] - clone current context to resulting matrix
* @throws Exception is input matrix is not invertible
* @returns {Matrix} - new Matrix instance
*/
inverse: function(cloneContext) {
var me = this,
m = new Matrix(cloneContext ? me.context : null),
dt = me.determinant();
if (me._q(dt, 0))
throw "Matrix not invertible.";
m.a = me.d / dt;
m.b = -me.b / dt;
m.c = -me.c / dt;
m.d = me.a / dt;
m.e = (me.c * me.f - me.d * me.e) / dt;
m.f = -(me.a * me.f - me.b * me.e) / dt;
return m
},
/**
* Interpolate this matrix with another and produce a new matrix.
* `t` is a value in the range [0.0, 1.0] where 0 is this instance and
* 1 is equal to the second matrix. The `t` value is not clamped.
*
* Context from parent matrix is not applied to the returned matrix.
*
* Note: this interpolation is naive. For animation containing rotation,
* shear or skew use the [`interpolateAnim()`]{@link Matrix#interpolateAnim} method instead
* to avoid unintended flipping.
*
* @param {Matrix} m2 - the matrix to interpolate with.
* @param {number} t - interpolation [0.0, 1.0]
* @param {CanvasRenderingContext2D} [context] - optional context to affect
* @returns {Matrix} - new Matrix instance with the interpolated result
*/
interpolate: function(m2, t, context) {
var me = this,
m = context ? new Matrix(context) : new Matrix();
m.a = me.a + (m2.a - me.a) * t;
m.b = me.b + (m2.b - me.b) * t;
m.c = me.c + (m2.c - me.c) * t;
m.d = me.d + (m2.d - me.d) * t;
m.e = me.e + (m2.e - me.e) * t;
m.f = me.f + (m2.f - me.f) * t;
return m._x()
},
/**
* Interpolate this matrix with another and produce a new matrix.
* `t` is a value in the range [0.0, 1.0] where 0 is this instance and
* 1 is equal to the second matrix. The `t` value is not constrained.
*
* Context from parent matrix is not applied to the returned matrix.
*
* To obtain easing `t` can be preprocessed using easing-functions
* before being passed to this method.
*
* Note: this interpolation method uses decomposition which makes
* it suitable for animations (in particular where rotation takes
* places).
*
* @param {Matrix} m2 - the matrix to interpolate with.
* @param {number} t - interpolation [0.0, 1.0]
* @param {CanvasRenderingContext2D} [context] - optional context to affect
* @returns {Matrix} - new Matrix instance with the interpolated result
*/
interpolateAnim: function(m2, t, context) {
var m = new Matrix(context ? context : null),
d1 = this.decompose(),
d2 = m2.decompose(),
t1 = d1.translate,
t2 = d2.translate,
s1 = d1.scale,
rotation = d1.rotation + (d2.rotation - d1.rotation) * t,
translateX = t1.x + (t2.x - t1.x) * t,
translateY = t1.y + (t2.y - t1.y) * t,
scaleX = s1.x + (d2.scale.x - s1.x) * t,
scaleY = s1.y + (d2.scale.y - s1.y) * t
;
// QR order (t-r-s-sk)
m.translate(translateX, translateY);
m.rotate(rotation);
m.scale(scaleX, scaleY);
//todo test skew scenarios
return m._x()
},
/**
* Decompose the current matrix into simple transforms using either
* QR (default) or LU decomposition.
*
* The result must be applied in the following order to reproduce the current matrix:
*
* QR: translate -> rotate -> scale -> skew
* LU: translate -> skew -> scale -> skew
*
* @param {boolean} [useLU=false] - set to true to use LU rather than QR algorithm
* @returns {*} - an object containing current decomposed values (rotate, skew, scale, translate)
* @see {@link http://www.maths-informatique-jeux.com/blog/frederic/?post/2013/12/01/Decomposition-of-2D-transform-matrices|Adoption based on this code}
*/
decompose: function(useLU) {
var me = this,
a = me.a,
b = me.b,
c = me.c,
d = me.d,
acos = Math.acos,
atan = Math.atan,
sqrt = Math.sqrt,
pi = Math.PI,
translate = {x: me.e, y: me.f},
rotation = 0,
scale = {x: 1, y: 1},
skew = {x: 0, y: 0},
determ = a * d - b * c; // determinant(), skip DRY here...
if (useLU) {
if (a) {
skew = {x: atan(c / a), y: atan(b / a)};
scale = {x: a, y: determ / a};
}
else if (b) {
rotation = pi * 0.5;
scale = {x: b, y: determ / b};
skew.x = atan(d / b);
}
else { // a = b = 0
scale = {x: c, y: d};
skew.x = pi * 0.25;
}
}
else {
// Apply the QR-like decomposition.
if (a || b) {
var r = sqrt(a * a + b * b);
rotation = b > 0 ? acos(a / r) : -acos(a / r);
scale = {x: r, y: determ / r};
skew.x = atan((a * c + b * d) / (r * r));
}
else if (c || d) {
var s = sqrt(c * c + d * d);
rotation = pi * 0.5 - (d > 0 ? acos(-c / s) : -acos(c / s));
scale = {x: determ / s, y: s};
skew.y = atan((a * c + b * d) / (s * s));
}
else { // a = b = c = d = 0
scale = {x: 0, y: 0}; // = invalid matrix
}
}
return {
scale : scale,
translate: translate,
rotation : rotation,
skew : skew
}
},
/**
* Returns the determinant of the current matrix.
* @returns {number}
*/
determinant: function() {
return this.a * this.d - this.b * this.c
},
/**
* Apply current matrix to `x` and `y` of a point.
* Returns a point object.
*
* @param {number} x - value for x
* @param {number} y - value for y
* @returns {{x: number, y: number}} A new transformed point object
*/
applyToPoint: function(x, y) {
var me = this;
return {
x: x * me.a + y * me.c + me.e,
y: x * me.b + y * me.d + me.f
}
},
/**
* Apply current matrix to array with point objects or point pairs.
* Returns a new array with points in the same format as the input array.
*
* A point object is an object literal:
*
* {x: x, y: y}
*
* so an array would contain either:
*
* [{x: x1, y: y1}, {x: x2, y: y2}, ... {x: xn, y: yn}]
*
* or
*
* [x1, y1, x2, y2, ... xn, yn]
*
* @param {Array} points - array with point objects or pairs
* @returns {Array} A new array with transformed points
*/
applyToArray: function(points) {
var i = 0, p, l,
mxPoints = [];
if (typeof points[0] === 'number') {
l = points.length;
while(i < l) {
p = this.applyToPoint(points[i++], points[i++]);
mxPoints.push(p.x, p.y);
}
}
else {
while(p = points[i++]) {
mxPoints.push(this.applyToPoint(p.x, p.y));
}
}
return mxPoints
},
/**
* Apply current matrix to a typed array with point pairs. Although
* the input array may be an ordinary array, this method is intended
* for more performant use where typed arrays are used. The returned
* array is regardless always returned as a `Float32Array`.
*
* @param {*} points - (typed) array with point pairs [x1, y1, ..., xn, yn]
* @param {boolean} [use64=false] - use Float64Array instead of Float32Array
* @returns {*} A new typed array with transformed points
*/
applyToTypedArray: function(points, use64) {
var i = 0, p,
l = points.length,
mxPoints = use64 ? new Float64Array(l) : new Float32Array(l);
while(i < l) {
p = this.applyToPoint(points[i], points[i + 1]);
mxPoints[i++] = p.x;
mxPoints[i++] = p.y;
}
return mxPoints
},
/**
* Apply to any canvas 2D context object. This does not affect the
* context that optionally was referenced in constructor unless it is
* the same context.
*
* @param {CanvasRenderingContext2D} context - target context
* @returns {Matrix}
*/
applyToContext: function(context) {
var me = this;
context.setTransform(me.a, me.b, me.c, me.d, me.e, me.f);
return me
},
/**
* Returns true if matrix is an identity matrix (no transforms applied).
* @returns {boolean}
*/
isIdentity: function() {
var me = this;
return me._q(me.a, 1) &&
me._q(me.b, 0) &&
me._q(me.c, 0) &&
me._q(me.d, 1) &&
me._q(me.e, 0) &&
me._q(me.f, 0)
},
/**
* Returns true if matrix is invertible
* @returns {boolean}
*/
isInvertible: function() {
return !this._q(this.determinant(), 0)
},
/**
* Test if matrix is valid (here meaning the scale values are non-zero).
* @returns {boolean}
*/
isValid: function() {
return !(this.a * this.d)
},
/**
* Compares current matrix with another matrix. Returns true if equal
* (within epsilon tolerance).
* @param {Matrix} m - matrix to compare this matrix with
* @returns {boolean}
*/
isEqual: function(m) {
var me = this,
q = me._q;
return q(me.a, m.a) &&
q(me.b, m.b) &&
q(me.c, m.c) &&
q(me.d, m.d) &&
q(me.e, m.e) &&
q(me.f, m.f)
},
/**
* Clones current instance and returning a new matrix.
* @param {boolean} [noContext=false] don't clone context reference if true
* @returns {Matrix} - a new Matrix instance with identical transformations as this instance
*/
clone: function(noContext) {
return new Matrix(noContext ? null : this.context).multiply(this)
},
/**
* Returns an array with current matrix values.
* @returns {Array}
*/
toArray: function() {
var me = this;
return [me.a, me.b, me.c, me.d, me.e, me.f]
},
/**
* Returns a binary typed array, either as 32-bit (default) or
* 64-bit.
* @param {boolean} [use64=false] chose whether to use 32-bit or 64-bit typed array
* @returns {*}
*/
toTypedArray: function(use64) {
var a = use64 ? new Float64Array(6) : new Float32Array(6),
me = this;
a[0] = me.a;
a[1] = me.b;
a[2] = me.c;
a[3] = me.d;
a[4] = me.e;
a[5] = me.f;
return a
},
/**
* Generates a string that can be used with CSS `transform`.
* @example
* element.style.transform = m.toCSS();
* @returns {string}
*/
toCSS: function() {
return "matrix(" + this.toArray() + ")"
},
/**
* Generates a `matrix3d()` string that can be used with CSS `transform`.
* Although the matrix is for 2D use you may see performance benefits
* on some devices using a 3D CSS transform instead of a 2D.
* @example
* element.style.transform = m.toCSS3D();
* @returns {string}
*/
toCSS3D: function() {
var me = this;
return "matrix3d(" + me.a + "," + me.b + ",0,0," + me.c + "," + me.d + ",0,0,0,0,1,0," + me.e + "," + me.f + ",0,1)"
},
/**
* Returns a JSON compatible string of current matrix.
* @returns {string}
*/
toJSON: function() {
var me = this;
return '{"a":' + me.a + ',"b":' + me.b + ',"c":' + me.c + ',"d":' + me.d + ',"e":' + me.e + ',"f":' + me.f + '}'
},
/**
* Returns a string with current matrix as comma-separated list.
* @param {number} [fixLen=4] - truncate decimal values to number of digits
* @returns {string}
*/
toString: function(fixLen) {
var me = this;
fixLen = fixLen || 4;
return "a=" + me.a.toFixed(fixLen) +
" b=" + me.b.toFixed(fixLen) +
" c=" + me.c.toFixed(fixLen) +
" d=" + me.d.toFixed(fixLen) +
" e=" + me.e.toFixed(fixLen) +
" f=" + me.f.toFixed(fixLen)
},
/**
* Returns a string with current matrix as comma-separated values
* string with line-end (CR+LF).
* @returns {string}
*/
toCSV: function() {
return this.toArray().join() + "\r\n"
},
/**
* Convert current matrix into a `SVGMatrix`. If `SVGMatrix` is not
* supported, a `null` is returned.
*
* Note: BETA
*
* @returns {SVGMatrix}
* @see {@link https://developer.mozilla.org/en-US/docs/Web/API/SVGMatrix|MDN / SVGMatrix}
*/
toSVGMatrix: function() {
// as we can not set transforms directly on SVG matrices we need
// to decompose our own matrix first:
var dc = this.decompose(),
translate = dc.translate,
scale = dc.scale,
skew = dc.skew,
eq = this._q,
svgMatrix = document.createElementNS("http://www.w3.org/2000/svg", "svg").createSVGMatrix();
if (!svgMatrix) return null;
// apply transformations in the correct order (see decompose()), QR: translate -> rotate -> scale -> skew
svgMatrix = svgMatrix.translate(translate.x, translate.y);
svgMatrix = svgMatrix.rotate(dc.rotation / Math.PI * 180); // SVGMatrix uses degrees
svgMatrix = svgMatrix.scaleNonUniform(scale.x, scale.y);
if (!eq(0, skew.x))
svgMatrix = svgMatrix.skewX(skew.x);
if (!eq(0, skew.y))
svgMatrix = svgMatrix.skewY(skew.y);
return svgMatrix
},
/**
* Compares floating point values with some tolerance (epsilon)
* @param {number} f1 - float 1
* @param {number} f2 - float 2
* @returns {boolean}
* @private
*/
_q: function(f1, f2) {
return Math.abs(f1 - f2) < 1e-14
},
/**
* Apply current absolute matrix to context if defined, to sync it.
* @returns {Matrix}
* @private
*/
_x: function() {
var me = this;
if (me.context)
me.context.setTransform(me.a, me.b, me.c, me.d, me.e, me.f);
return me
}
};