mirror of
https://github.com/scratchfoundation/paper.js.git
synced 2025-01-06 04:42:15 -05:00
f50a81e089
We can minify some codes which relate matrix.decompose(), because matrix.decompose() must return non-null object
833 lines
26 KiB
JavaScript
833 lines
26 KiB
JavaScript
/*
|
|
* Paper.js - The Swiss Army Knife of Vector Graphics Scripting.
|
|
* http://paperjs.org/
|
|
*
|
|
* Copyright (c) 2011 - 2016, Juerg Lehni & Jonathan Puckey
|
|
* http://scratchdisk.com/ & http://jonathanpuckey.com/
|
|
*
|
|
* Distributed under the MIT license. See LICENSE file for details.
|
|
*
|
|
* All rights reserved.
|
|
*/
|
|
|
|
// Based on goog.graphics.AffineTransform, as part of the Closure Library.
|
|
// Copyright 2008 The Closure Library Authors. All Rights Reserved.
|
|
//
|
|
// Licensed under the Apache License, Version 2.0 (the "License");
|
|
|
|
/**
|
|
* @name Matrix
|
|
*
|
|
* @class An affine transformation matrix performs a linear mapping from 2D
|
|
* coordinates to other 2D coordinates that preserves the "straightness" and
|
|
* "parallelness" of lines.
|
|
*
|
|
* Such a coordinate transformation can be represented by a 3 row by 3
|
|
* column matrix with an implied last row of `[ 0 0 1 ]`. This matrix
|
|
* transforms source coordinates `(x, y)` into destination coordinates `(x',y')`
|
|
* by considering them to be a column vector and multiplying the coordinate
|
|
* vector by the matrix according to the following process:
|
|
*
|
|
* [ x ] [ a c tx ] [ x ] [ a * x + c * y + tx ]
|
|
* [ y ] = [ b d ty ] [ y ] = [ b * x + d * y + ty ]
|
|
* [ 1 ] [ 0 0 1 ] [ 1 ] [ 1 ]
|
|
*
|
|
* Note the locations of b and c.
|
|
*
|
|
* This class is optimized for speed and minimizes calculations based on its
|
|
* knowledge of the underlying matrix (as opposed to say simply performing
|
|
* matrix multiplication).
|
|
*/
|
|
var Matrix = Base.extend(/** @lends Matrix# */{
|
|
_class: 'Matrix',
|
|
|
|
/**
|
|
* Creates a 2D affine transformation matrix that describes the identity
|
|
* transformation.
|
|
*
|
|
* @name Matrix#initialize
|
|
*/
|
|
/**
|
|
* Creates a 2D affine transformation matrix.
|
|
*
|
|
* @name Matrix#initialize
|
|
* @param {Number} a the a property of the transform
|
|
* @param {Number} b the b property of the transform
|
|
* @param {Number} c the c property of the transform
|
|
* @param {Number} d the d property of the transform
|
|
* @param {Number} tx the tx property of the transform
|
|
* @param {Number} ty the ty property of the transform
|
|
*/
|
|
/**
|
|
* Creates a 2D affine transformation matrix.
|
|
*
|
|
* @name Matrix#initialize
|
|
* @param {Number[]} values the matrix values to initialize this matrix with
|
|
*/
|
|
/**
|
|
* Creates a 2D affine transformation matrix.
|
|
*
|
|
* @name Matrix#initialize
|
|
* @param {Matrix} matrix the matrix to copy the values from
|
|
*/
|
|
initialize: function Matrix(arg, _dontNotify) {
|
|
var count = arguments.length,
|
|
ok = true;
|
|
if (count >= 6) { // >= 6 to pass on optional _dontNotify argument.
|
|
this._set.apply(this, arguments);
|
|
} else if (count === 1 || count === 2) {
|
|
// Support both Matrix and Array arguments through #_set(), and pass
|
|
// on the optional _dontNotify argument:
|
|
if (arg instanceof Matrix) {
|
|
this._set(arg._a, arg._b, arg._c, arg._d, arg._tx, arg._ty,
|
|
_dontNotify);
|
|
} else if (Array.isArray(arg)) {
|
|
this._set.apply(this,
|
|
_dontNotify ? arg.concat([_dontNotify]) : arg);
|
|
} else {
|
|
ok = false;
|
|
}
|
|
} else if (!count) {
|
|
this.reset();
|
|
} else {
|
|
ok = false;
|
|
}
|
|
if (!ok) {
|
|
throw new Error('Unsupported matrix parameters');
|
|
}
|
|
return this;
|
|
},
|
|
|
|
/**
|
|
* Sets the matrix to the passed values. Note that any sequence of
|
|
* parameters that is supported by the various {@link Matrix()} constructors
|
|
* also work for calls of `set()`.
|
|
*
|
|
* @function
|
|
*/
|
|
set: '#initialize',
|
|
|
|
// See Point#_set() for an explanation of #_set():
|
|
_set: function(a, b, c, d, tx, ty, _dontNotify) {
|
|
this._a = a;
|
|
this._b = b;
|
|
this._c = c;
|
|
this._d = d;
|
|
this._tx = tx;
|
|
this._ty = ty;
|
|
if (!_dontNotify)
|
|
this._changed();
|
|
return this;
|
|
},
|
|
|
|
_serialize: function(options, dictionary) {
|
|
return Base.serialize(this.getValues(), options, true, dictionary);
|
|
},
|
|
|
|
_changed: function() {
|
|
var owner = this._owner;
|
|
if (owner) {
|
|
// If owner has #applyMatrix set, directly bake the change in now.
|
|
if (owner._applyMatrix) {
|
|
owner.transform(null, true);
|
|
} else {
|
|
owner._changed(/*#=*/Change.MATRIX);
|
|
}
|
|
}
|
|
},
|
|
|
|
/**
|
|
* @return {Matrix} a copy of this transform
|
|
*/
|
|
clone: function() {
|
|
return new Matrix(this._a, this._b, this._c, this._d,
|
|
this._tx, this._ty);
|
|
},
|
|
|
|
/**
|
|
* Checks whether the two matrices describe the same transformation.
|
|
*
|
|
* @param {Matrix} matrix the matrix to compare this matrix to
|
|
* @return {Boolean} {@true if the matrices are equal}
|
|
*/
|
|
equals: function(mx) {
|
|
return mx === this || mx && this._a === mx._a && this._b === mx._b
|
|
&& this._c === mx._c && this._d === mx._d
|
|
&& this._tx === mx._tx && this._ty === mx._ty;
|
|
},
|
|
|
|
/**
|
|
* @return {String} a string representation of this transform
|
|
*/
|
|
toString: function() {
|
|
var f = Formatter.instance;
|
|
return '[[' + [f.number(this._a), f.number(this._c),
|
|
f.number(this._tx)].join(', ') + '], ['
|
|
+ [f.number(this._b), f.number(this._d),
|
|
f.number(this._ty)].join(', ') + ']]';
|
|
},
|
|
|
|
/**
|
|
* Resets the matrix by setting its values to the ones of the identity
|
|
* matrix that results in no transformation.
|
|
*/
|
|
reset: function(_dontNotify) {
|
|
this._a = this._d = 1;
|
|
this._b = this._c = this._tx = this._ty = 0;
|
|
if (!_dontNotify)
|
|
this._changed();
|
|
return this;
|
|
},
|
|
|
|
/**
|
|
* Attempts to apply the matrix to the content of item that it belongs to,
|
|
* meaning its transformation is baked into the item's content or children.
|
|
*
|
|
* @param {Boolean} recursively controls whether to apply transformations
|
|
* recursively on children
|
|
* @return {Boolean} {@true if the matrix was applied}
|
|
*/
|
|
apply: function(recursively, _setApplyMatrix) {
|
|
var owner = this._owner;
|
|
if (owner) {
|
|
owner.transform(null, true, Base.pick(recursively, true),
|
|
_setApplyMatrix);
|
|
// If the matrix was successfully applied, it will be reset now.
|
|
return this.isIdentity();
|
|
}
|
|
return false;
|
|
},
|
|
|
|
/**
|
|
* Concatenates this matrix with a translate transformation.
|
|
*
|
|
* @name Matrix#translate
|
|
* @function
|
|
* @param {Point} point the vector to translate by
|
|
* @return {Matrix} this affine transform
|
|
*/
|
|
/**
|
|
* Concatenates this matrix with a translate transformation.
|
|
*
|
|
* @name Matrix#translate
|
|
* @function
|
|
* @param {Number} dx the distance to translate in the x direction
|
|
* @param {Number} dy the distance to translate in the y direction
|
|
* @return {Matrix} this affine transform
|
|
*/
|
|
translate: function(/* point */) {
|
|
var point = Point.read(arguments),
|
|
x = point.x,
|
|
y = point.y;
|
|
this._tx += x * this._a + y * this._c;
|
|
this._ty += x * this._b + y * this._d;
|
|
this._changed();
|
|
return this;
|
|
},
|
|
|
|
/**
|
|
* Concatenates this matrix with a scaling transformation.
|
|
*
|
|
* @name Matrix#scale
|
|
* @function
|
|
* @param {Number} scale the scaling factor
|
|
* @param {Point} [center] the center for the scaling transformation
|
|
* @return {Matrix} this affine transform
|
|
*/
|
|
/**
|
|
* Concatenates this matrix with a scaling transformation.
|
|
*
|
|
* @name Matrix#scale
|
|
* @function
|
|
* @param {Number} hor the horizontal scaling factor
|
|
* @param {Number} ver the vertical scaling factor
|
|
* @param {Point} [center] the center for the scaling transformation
|
|
* @return {Matrix} this affine transform
|
|
*/
|
|
scale: function(/* scale, center */) {
|
|
var scale = Point.read(arguments),
|
|
center = Point.read(arguments, 0, { readNull: true });
|
|
if (center)
|
|
this.translate(center);
|
|
this._a *= scale.x;
|
|
this._b *= scale.x;
|
|
this._c *= scale.y;
|
|
this._d *= scale.y;
|
|
if (center)
|
|
this.translate(center.negate());
|
|
this._changed();
|
|
return this;
|
|
},
|
|
|
|
/**
|
|
* Concatenates this matrix with a rotation transformation around an
|
|
* anchor point.
|
|
*
|
|
* @name Matrix#rotate
|
|
* @function
|
|
* @param {Number} angle the angle of rotation measured in degrees
|
|
* @param {Point} center the anchor point to rotate around
|
|
* @return {Matrix} this affine transform
|
|
*/
|
|
/**
|
|
* Concatenates this matrix with a rotation transformation around an
|
|
* anchor point.
|
|
*
|
|
* @name Matrix#rotate
|
|
* @function
|
|
* @param {Number} angle the angle of rotation measured in degrees
|
|
* @param {Number} x the x coordinate of the anchor point
|
|
* @param {Number} y the y coordinate of the anchor point
|
|
* @return {Matrix} this affine transform
|
|
*/
|
|
rotate: function(angle /*, center */) {
|
|
angle *= Math.PI / 180;
|
|
var center = Point.read(arguments, 1),
|
|
// Concatenate rotation matrix into this one
|
|
x = center.x,
|
|
y = center.y,
|
|
cos = Math.cos(angle),
|
|
sin = Math.sin(angle),
|
|
tx = x - x * cos + y * sin,
|
|
ty = y - x * sin - y * cos,
|
|
a = this._a,
|
|
b = this._b,
|
|
c = this._c,
|
|
d = this._d;
|
|
this._a = cos * a + sin * c;
|
|
this._b = cos * b + sin * d;
|
|
this._c = -sin * a + cos * c;
|
|
this._d = -sin * b + cos * d;
|
|
this._tx += tx * a + ty * c;
|
|
this._ty += tx * b + ty * d;
|
|
this._changed();
|
|
return this;
|
|
},
|
|
|
|
/**
|
|
* Concatenates this matrix with a shear transformation.
|
|
*
|
|
* @name Matrix#shear
|
|
* @function
|
|
* @param {Point} shear the shear factor in x and y direction
|
|
* @param {Point} [center] the center for the shear transformation
|
|
* @return {Matrix} this affine transform
|
|
*/
|
|
/**
|
|
* Concatenates this matrix with a shear transformation.
|
|
*
|
|
* @name Matrix#shear
|
|
* @function
|
|
* @param {Number} hor the horizontal shear factor
|
|
* @param {Number} ver the vertical shear factor
|
|
* @param {Point} [center] the center for the shear transformation
|
|
* @return {Matrix} this affine transform
|
|
*/
|
|
shear: function(/* shear, center */) {
|
|
// Do not modify point, center, since that would arguments of which
|
|
// we're reading from!
|
|
var shear = Point.read(arguments),
|
|
center = Point.read(arguments, 0, { readNull: true });
|
|
if (center)
|
|
this.translate(center);
|
|
var a = this._a,
|
|
b = this._b;
|
|
this._a += shear.y * this._c;
|
|
this._b += shear.y * this._d;
|
|
this._c += shear.x * a;
|
|
this._d += shear.x * b;
|
|
if (center)
|
|
this.translate(center.negate());
|
|
this._changed();
|
|
return this;
|
|
},
|
|
|
|
/**
|
|
* Concatenates this matrix with a skew transformation.
|
|
*
|
|
* @name Matrix#skew
|
|
* @function
|
|
* @param {Point} skew the skew angles in x and y direction in degrees
|
|
* @param {Point} [center] the center for the skew transformation
|
|
* @return {Matrix} this affine transform
|
|
*/
|
|
/**
|
|
* Concatenates this matrix with a skew transformation.
|
|
*
|
|
* @name Matrix#skew
|
|
* @function
|
|
* @param {Number} hor the horizontal skew angle in degrees
|
|
* @param {Number} ver the vertical skew angle in degrees
|
|
* @param {Point} [center] the center for the skew transformation
|
|
* @return {Matrix} this affine transform
|
|
*/
|
|
skew: function(/* skew, center */) {
|
|
var skew = Point.read(arguments),
|
|
center = Point.read(arguments, 0, { readNull: true }),
|
|
toRadians = Math.PI / 180,
|
|
shear = new Point(Math.tan(skew.x * toRadians),
|
|
Math.tan(skew.y * toRadians));
|
|
return this.shear(shear, center);
|
|
},
|
|
|
|
/**
|
|
* Appends the specified matrix to this matrix. This is the equivalent of
|
|
* multiplying `(this matrix) * (specified matrix)`.
|
|
*
|
|
* @param {Matrix} matrix the matrix to append
|
|
* @return {Matrix} this matrix, modified
|
|
*/
|
|
append: function(mx, _dontNotify) {
|
|
if (mx) {
|
|
var a1 = this._a,
|
|
b1 = this._b,
|
|
c1 = this._c,
|
|
d1 = this._d,
|
|
a2 = mx._a,
|
|
b2 = mx._c,
|
|
c2 = mx._b,
|
|
d2 = mx._d,
|
|
tx2 = mx._tx,
|
|
ty2 = mx._ty;
|
|
this._a = a2 * a1 + c2 * c1;
|
|
this._c = b2 * a1 + d2 * c1;
|
|
this._b = a2 * b1 + c2 * d1;
|
|
this._d = b2 * b1 + d2 * d1;
|
|
this._tx += tx2 * a1 + ty2 * c1;
|
|
this._ty += tx2 * b1 + ty2 * d1;
|
|
if (!_dontNotify)
|
|
this._changed();
|
|
}
|
|
return this;
|
|
},
|
|
|
|
/**
|
|
* Prepends the specified matrix to this matrix. This is the equivalent of
|
|
* multiplying `(specified matrix) * (this matrix)`.
|
|
*
|
|
* @param {Matrix} matrix the matrix to prepend
|
|
* @return {Matrix} this matrix, modified
|
|
*/
|
|
prepend: function(mx, _dontNotify) {
|
|
if (mx) {
|
|
var a1 = this._a,
|
|
b1 = this._b,
|
|
c1 = this._c,
|
|
d1 = this._d,
|
|
tx1 = this._tx,
|
|
ty1 = this._ty,
|
|
a2 = mx._a,
|
|
b2 = mx._c,
|
|
c2 = mx._b,
|
|
d2 = mx._d,
|
|
tx2 = mx._tx,
|
|
ty2 = mx._ty;
|
|
this._a = a2 * a1 + b2 * b1;
|
|
this._c = a2 * c1 + b2 * d1;
|
|
this._b = c2 * a1 + d2 * b1;
|
|
this._d = c2 * c1 + d2 * d1;
|
|
this._tx = a2 * tx1 + b2 * ty1 + tx2;
|
|
this._ty = c2 * tx1 + d2 * ty1 + ty2;
|
|
if (!_dontNotify)
|
|
this._changed();
|
|
}
|
|
return this;
|
|
},
|
|
|
|
/**
|
|
* Returns a new matrix as the result of appending the specified matrix to
|
|
* this matrix. This is the equivalent of multiplying
|
|
* `(this matrix) * (specified matrix)`.
|
|
*
|
|
* @param {Matrix} matrix the matrix to append
|
|
* @return {Matrix} the newly created matrix
|
|
*/
|
|
appended: function(mx) {
|
|
return this.clone().append(mx);
|
|
},
|
|
|
|
/**
|
|
* Returns a new matrix as the result of prepending the specified matrix
|
|
* to this matrix. This is the equivalent of multiplying
|
|
* `(specified matrix) s* (this matrix)`.
|
|
*
|
|
* @param {Matrix} matrix the matrix to prepend
|
|
* @return {Matrix} the newly created matrix
|
|
*/
|
|
prepended: function(mx) {
|
|
return this.clone().prepend(mx);
|
|
},
|
|
|
|
/**
|
|
* Inverts the matrix, causing it to perform the opposite transformation.
|
|
* If the matrix is not invertible (in which case {@link #isSingular()}
|
|
* returns true), `null` is returned.
|
|
*
|
|
* @return {Matrix} this matrix, or `null`, if the matrix is singular.
|
|
*/
|
|
invert: function() {
|
|
var a = this._a,
|
|
b = this._b,
|
|
c = this._c,
|
|
d = this._d,
|
|
tx = this._tx,
|
|
ty = this._ty,
|
|
det = a * d - b * c,
|
|
res = null;
|
|
if (det && !isNaN(det) && isFinite(tx) && isFinite(ty)) {
|
|
this._a = d / det;
|
|
this._b = -b / det;
|
|
this._c = -c / det;
|
|
this._d = a / det;
|
|
this._tx = (c * ty - d * tx) / det;
|
|
this._ty = (b * tx - a * ty) / det;
|
|
res = this;
|
|
}
|
|
return res;
|
|
},
|
|
|
|
/**
|
|
* Creates a new matrix that is the inversion of this matrix, causing it to
|
|
* perform the opposite transformation. If the matrix is not invertible (in
|
|
* which case {@link #isSingular()} returns true), `null` is returned.
|
|
*
|
|
* @return {Matrix} this matrix, or `null`, if the matrix is singular.
|
|
*/
|
|
inverted: function() {
|
|
return this.clone().invert();
|
|
},
|
|
|
|
/**
|
|
* @deprecated use use {@link #append(matrix)} instead.
|
|
*/
|
|
concatenate: '#append',
|
|
/**
|
|
* @deprecated use use {@link #prepend(matrix)} instead.
|
|
*/
|
|
preConcatenate: '#prepend',
|
|
/**
|
|
* @deprecated use use {@link #appended(matrix)} instead.
|
|
*/
|
|
chain: '#appended',
|
|
|
|
/**
|
|
* A private helper function to create a clone of this matrix, without the
|
|
* translation factored in.
|
|
*
|
|
* @return {Matrix} a clone of this matrix, with {@link #tx} and {@link #ty}
|
|
* set to `0`.
|
|
*/
|
|
_shiftless: function() {
|
|
return new Matrix(this._a, this._b, this._c, this._d, 0, 0);
|
|
},
|
|
|
|
_orNullIfIdentity: function() {
|
|
return this.isIdentity() ? null : this;
|
|
},
|
|
|
|
/**
|
|
* @return {Boolean} whether this matrix is the identity matrix
|
|
*/
|
|
isIdentity: function() {
|
|
return this._a === 1 && this._b === 0 && this._c === 0 && this._d === 1
|
|
&& this._tx === 0 && this._ty === 0;
|
|
},
|
|
|
|
/**
|
|
* Checks whether the matrix is invertible. A matrix is not invertible if
|
|
* the determinant is 0 or any value is infinite or NaN.
|
|
*
|
|
* @return {Boolean} whether the matrix is invertible
|
|
*/
|
|
isInvertible: function() {
|
|
var det = this._a * this._d - this._c * this._b;
|
|
return det && !isNaN(det) && isFinite(this._tx) && isFinite(this._ty);
|
|
},
|
|
|
|
/**
|
|
* Checks whether the matrix is singular or not. Singular matrices cannot be
|
|
* inverted.
|
|
*
|
|
* @return {Boolean} whether the matrix is singular
|
|
*/
|
|
isSingular: function() {
|
|
return !this.isInvertible();
|
|
},
|
|
|
|
/**
|
|
* Transforms a point and returns the result.
|
|
*
|
|
* @name Matrix#transform
|
|
* @function
|
|
* @param {Point} point the point to be transformed
|
|
* @return {Point} the transformed point
|
|
*/
|
|
/**
|
|
* Transforms an array of coordinates by this matrix and stores the results
|
|
* into the destination array, which is also returned.
|
|
*
|
|
* @name Matrix#transform
|
|
* @function
|
|
* @param {Number[]} src the array containing the source points
|
|
* as x, y value pairs
|
|
* @param {Number[]} dst the array into which to store the transformed
|
|
* point pairs
|
|
* @param {Number} count the number of points to transform
|
|
* @return {Number[]} the dst array, containing the transformed coordinates
|
|
*/
|
|
transform: function(/* point | */ src, dst, count) {
|
|
return arguments.length < 3
|
|
// TODO: Check for rectangle and use _tranformBounds?
|
|
? this._transformPoint(Point.read(arguments))
|
|
: this._transformCoordinates(src, dst, count);
|
|
},
|
|
|
|
/**
|
|
* A faster version of transform that only takes one point and does not
|
|
* attempt to convert it.
|
|
*/
|
|
_transformPoint: function(point, dest, _dontNotify) {
|
|
var x = point.x,
|
|
y = point.y;
|
|
if (!dest)
|
|
dest = new Point();
|
|
return dest._set(
|
|
x * this._a + y * this._c + this._tx,
|
|
x * this._b + y * this._d + this._ty,
|
|
_dontNotify);
|
|
},
|
|
|
|
_transformCoordinates: function(src, dst, count) {
|
|
for (var i = 0, max = 2 * count; i < max; i += 2) {
|
|
var x = src[i],
|
|
y = src[i + 1];
|
|
dst[i] = x * this._a + y * this._c + this._tx;
|
|
dst[i + 1] = x * this._b + y * this._d + this._ty;
|
|
}
|
|
return dst;
|
|
},
|
|
|
|
_transformCorners: function(rect) {
|
|
var x1 = rect.x,
|
|
y1 = rect.y,
|
|
x2 = x1 + rect.width,
|
|
y2 = y1 + rect.height,
|
|
coords = [ x1, y1, x2, y1, x2, y2, x1, y2 ];
|
|
return this._transformCoordinates(coords, coords, 4);
|
|
},
|
|
|
|
/**
|
|
* Returns the 'transformed' bounds rectangle by transforming each corner
|
|
* point and finding the new bounding box to these points. This is not
|
|
* really the transformed rectangle!
|
|
*/
|
|
_transformBounds: function(bounds, dest, _dontNotify) {
|
|
var coords = this._transformCorners(bounds),
|
|
min = coords.slice(0, 2),
|
|
max = min.slice();
|
|
for (var i = 2; i < 8; i++) {
|
|
var val = coords[i],
|
|
j = i & 1;
|
|
if (val < min[j]) {
|
|
min[j] = val;
|
|
} else if (val > max[j]) {
|
|
max[j] = val;
|
|
}
|
|
}
|
|
if (!dest)
|
|
dest = new Rectangle();
|
|
return dest._set(min[0], min[1], max[0] - min[0], max[1] - min[1],
|
|
_dontNotify);
|
|
},
|
|
|
|
/**
|
|
* Inverse transforms a point and returns the result.
|
|
*
|
|
* @param {Point} point the point to be transformed
|
|
*/
|
|
inverseTransform: function(/* point */) {
|
|
return this._inverseTransform(Point.read(arguments));
|
|
},
|
|
|
|
_inverseTransform: function(point, dest, _dontNotify) {
|
|
var a = this._a,
|
|
b = this._b,
|
|
c = this._c,
|
|
d = this._d,
|
|
tx = this._tx,
|
|
ty = this._ty,
|
|
det = a * d - b * c,
|
|
res = null;
|
|
if (det && !isNaN(det) && isFinite(tx) && isFinite(ty)) {
|
|
var x = point.x - this._tx,
|
|
y = point.y - this._ty;
|
|
if (!dest)
|
|
dest = new Point();
|
|
res = dest._set(
|
|
(x * d - y * c) / det,
|
|
(y * a - x * b) / det,
|
|
_dontNotify);
|
|
}
|
|
return res;
|
|
},
|
|
|
|
/**
|
|
* Decomposes the affine transformation described by this matrix into
|
|
* `scaling`, `rotation` and `skewing`, and returns an object with
|
|
* these properties.
|
|
*
|
|
* @return {Object} the decomposed matrix
|
|
*/
|
|
decompose: function() {
|
|
// http://dev.w3.org/csswg/css3-2d-transforms/#matrix-decomposition
|
|
// http://www.maths-informatique-jeux.com/blog/frederic/?post/2013/12/01/Decomposition-of-2D-transform-matrices
|
|
// https://github.com/wisec/DOMinator/blob/master/layout/style/nsStyleAnimation.cpp#L946
|
|
var a = this._a,
|
|
b = this._b,
|
|
c = this._c,
|
|
d = this._d,
|
|
det = a * d - b * c,
|
|
sqrt = Math.sqrt,
|
|
atan2 = Math.atan2,
|
|
degrees = 180 / Math.PI,
|
|
rotate,
|
|
scale,
|
|
skew;
|
|
if (a !== 0 || b !== 0) {
|
|
var r = sqrt(a * a + b * b);
|
|
rotate = Math.acos(a / r) * (b > 0 ? 1 : -1);
|
|
scale = [r, det / r];
|
|
skew = [atan2(a * c + b * d, r * r), 0];
|
|
} else if (c !== 0 || d !== 0) {
|
|
var s = sqrt(c * c + d * d);
|
|
// rotate = Math.PI/2 - (d > 0 ? Math.acos(-c/s) : -Math.acos(c/s));
|
|
rotate = Math.asin(c / s) * (d > 0 ? 1 : -1);
|
|
scale = [det / s, s];
|
|
skew = [0, atan2(a * c + b * d, s * s)];
|
|
} else { // a = b = c = d = 0
|
|
rotate = 0;
|
|
skew = scale = [0, 0];
|
|
}
|
|
return {
|
|
translation: this.getTranslation(),
|
|
rotation: rotate * degrees,
|
|
scaling: new Point(scale),
|
|
skewing: new Point(skew[0] * degrees, skew[1] * degrees)
|
|
};
|
|
},
|
|
|
|
/**
|
|
* The value that affects the transformation along the x axis when scaling
|
|
* or rotating, positioned at (0, 0) in the transformation matrix.
|
|
*
|
|
* @name Matrix#a
|
|
* @type Number
|
|
*/
|
|
|
|
/**
|
|
* The value that affects the transformation along the y axis when rotating
|
|
* or skewing, positioned at (1, 0) in the transformation matrix.
|
|
*
|
|
* @name Matrix#b
|
|
* @type Number
|
|
*/
|
|
|
|
/**
|
|
* The value that affects the transformation along the x axis when rotating
|
|
* or skewing, positioned at (0, 1) in the transformation matrix.
|
|
*
|
|
* @name Matrix#c
|
|
* @type Number
|
|
*/
|
|
|
|
/**
|
|
* The value that affects the transformation along the y axis when scaling
|
|
* or rotating, positioned at (1, 1) in the transformation matrix.
|
|
*
|
|
* @name Matrix#d
|
|
* @type Number
|
|
*/
|
|
|
|
/**
|
|
* The distance by which to translate along the x axis, positioned at (2, 0)
|
|
* in the transformation matrix.
|
|
*
|
|
* @name Matrix#tx
|
|
* @type Number
|
|
*/
|
|
|
|
/**
|
|
* The distance by which to translate along the y axis, positioned at (2, 1)
|
|
* in the transformation matrix.
|
|
*
|
|
* @name Matrix#ty
|
|
* @type Number
|
|
*/
|
|
|
|
/**
|
|
* The matrix values as an array, in the same sequence as they are passed
|
|
* to {@link #initialize(a, b, c, d, tx, ty)}.
|
|
*
|
|
* @bean
|
|
* @type Number[]
|
|
*/
|
|
getValues: function() {
|
|
return [ this._a, this._b, this._c, this._d, this._tx, this._ty ];
|
|
},
|
|
|
|
/**
|
|
* The translation of the matrix as a vector.
|
|
*
|
|
* @bean
|
|
* @type Point
|
|
*/
|
|
getTranslation: function() {
|
|
// No decomposition is required to extract translation.
|
|
return new Point(this._tx, this._ty);
|
|
},
|
|
|
|
/**
|
|
* The scaling values of the matrix, if it can be decomposed.
|
|
*
|
|
* @bean
|
|
* @type Point
|
|
* @see #decompose()
|
|
*/
|
|
getScaling: function() {
|
|
return this.decompose().scaling;
|
|
},
|
|
|
|
/**
|
|
* The rotation angle of the matrix, if it can be decomposed.
|
|
*
|
|
* @bean
|
|
* @type Number
|
|
* @see #decompose()
|
|
*/
|
|
getRotation: function() {
|
|
return this.decompose().rotation;
|
|
},
|
|
|
|
/**
|
|
* Applies this matrix to the specified Canvas Context.
|
|
*
|
|
* @param {CanvasRenderingContext2D} ctx
|
|
*/
|
|
applyToContext: function(ctx) {
|
|
if (!this.isIdentity()) {
|
|
ctx.transform(this._a, this._b, this._c, this._d,
|
|
this._tx, this._ty);
|
|
}
|
|
}
|
|
}, Base.each(['a', 'b', 'c', 'd', 'tx', 'ty'], function(key) {
|
|
// Create getters and setters for all internal attributes.
|
|
var part = Base.capitalize(key),
|
|
prop = '_' + key;
|
|
this['get' + part] = function() {
|
|
return this[prop];
|
|
};
|
|
this['set' + part] = function(value) {
|
|
this[prop] = value;
|
|
this._changed();
|
|
};
|
|
}, {}));
|