mirror of
https://github.com/scratchfoundation/paper.js.git
synced 2025-01-01 02:38:43 -05:00
Matrix: Switch to a better implementation of #decompose()
This now also correctly handles skewing in SVG export.
This commit is contained in:
parent
e35a55fe66
commit
3ee46ffc5c
4 changed files with 52 additions and 48 deletions
|
@ -652,40 +652,39 @@ var Matrix = Base.extend(/** @lends Matrix# */{
|
||||||
*/
|
*/
|
||||||
decompose: function() {
|
decompose: function() {
|
||||||
// http://dev.w3.org/csswg/css3-2d-transforms/#matrix-decomposition
|
// http://dev.w3.org/csswg/css3-2d-transforms/#matrix-decomposition
|
||||||
// http://stackoverflow.com/questions/4361242/
|
// 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
|
// https://github.com/wisec/DOMinator/blob/master/layout/style/nsStyleAnimation.cpp#L946
|
||||||
var a = this._a, b = this._c, c = this._b, d = this._d;
|
var a = this._a,
|
||||||
if (Numerical.isZero(a * d - b * c))
|
b = this._b,
|
||||||
return null;
|
c = this._c,
|
||||||
|
d = this._d,
|
||||||
var scaleX = Math.sqrt(a * a + b * b);
|
det = a * d - b * c,
|
||||||
a /= scaleX;
|
sqrt = Math.sqrt,
|
||||||
b /= scaleX;
|
atan2 = Math.atan2,
|
||||||
|
degrees = 180 / Math.PI,
|
||||||
var shear = a * c + b * d;
|
rotate,
|
||||||
c -= a * shear;
|
scale,
|
||||||
d -= b * shear;
|
skew;
|
||||||
|
if (a !== 0 || b !== 0) {
|
||||||
var scaleY = Math.sqrt(c * c + d * d);
|
var r = sqrt(a * a + b * b);
|
||||||
c /= scaleY;
|
rotate = Math.acos(a / r) * (b > 0 ? 1 : -1);
|
||||||
d /= scaleY;
|
scale = [r, det / r];
|
||||||
shear /= scaleY;
|
skew = [atan2(a * c + b * d, r * r), 0];
|
||||||
|
} else if (c !== 0 || d !== 0) {
|
||||||
// a * d - b * c should now be 1 or -1
|
var s = sqrt(c * c + d * d);
|
||||||
if (a * d < b * c) {
|
// rotate = Math.PI/2 - (d > 0 ? Math.acos(-c/s) : -Math.acos(c/s));
|
||||||
a = -a;
|
rotate = Math.asin(c / s) * (d > 0 ? 1 : -1);
|
||||||
b = -b;
|
scale = [det / s, s];
|
||||||
// We don't need c & d anymore, but if we did, we'd have to do this:
|
skew = [0, atan2(a * c + b * d, s * s)];
|
||||||
// c = -c;
|
} else { // a = b = c = d = 0
|
||||||
// d = -d;
|
rotate = 0;
|
||||||
shear = -shear;
|
skew = scale = [0, 0];
|
||||||
scaleX = -scaleX;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return {
|
return {
|
||||||
scaling: new Point(scaleX, scaleY),
|
translation: this.getTranslation(),
|
||||||
rotation: -Math.atan2(b, a) * 180 / Math.PI,
|
rotation: rotate * degrees,
|
||||||
shearing: shear
|
scaling: new Point(scale),
|
||||||
|
skewing: new Point(skew[0] * degrees, skew[1] * degrees)
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
|
|
||||||
|
|
|
@ -1032,7 +1032,7 @@ new function() { // // Scope to inject various item event handlers
|
||||||
beans: true,
|
beans: true,
|
||||||
|
|
||||||
_decompose: function() {
|
_decompose: function() {
|
||||||
return this._decomposed = this._matrix.decompose();
|
return this._decomposed || (this._decomposed = this._matrix.decompose());
|
||||||
},
|
},
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -1043,7 +1043,7 @@ new function() { // // Scope to inject various item event handlers
|
||||||
* @type Number
|
* @type Number
|
||||||
*/
|
*/
|
||||||
getRotation: function() {
|
getRotation: function() {
|
||||||
var decomposed = this._decomposed || this._decompose();
|
var decomposed = this._decompose();
|
||||||
return decomposed && decomposed.rotation;
|
return decomposed && decomposed.rotation;
|
||||||
},
|
},
|
||||||
|
|
||||||
|
@ -1067,7 +1067,7 @@ new function() { // // Scope to inject various item event handlers
|
||||||
* @type Point
|
* @type Point
|
||||||
*/
|
*/
|
||||||
getScaling: function(_dontLink) {
|
getScaling: function(_dontLink) {
|
||||||
var decomposed = this._decomposed || this._decompose(),
|
var decomposed = this._decompose(),
|
||||||
scaling = decomposed && decomposed.scaling,
|
scaling = decomposed && decomposed.scaling,
|
||||||
ctor = _dontLink ? Point : LinkedPoint;
|
ctor = _dontLink ? Point : LinkedPoint;
|
||||||
return scaling && new ctor(scaling.x, scaling.y, this, 'setScaling');
|
return scaling && new ctor(scaling.x, scaling.y, this, 'setScaling');
|
||||||
|
|
|
@ -39,17 +39,22 @@ new function() {
|
||||||
// See if we can decompose the matrix and can formulate it as a
|
// See if we can decompose the matrix and can formulate it as a
|
||||||
// simple translate/scale/rotate command sequence.
|
// simple translate/scale/rotate command sequence.
|
||||||
var decomposed = matrix.decompose();
|
var decomposed = matrix.decompose();
|
||||||
if (decomposed && !decomposed.shearing) {
|
if (decomposed) {
|
||||||
var parts = [],
|
var parts = [],
|
||||||
angle = decomposed.rotation,
|
angle = decomposed.rotation,
|
||||||
scale = decomposed.scaling;
|
scale = decomposed.scaling,
|
||||||
|
skew = decomposed.skewing;
|
||||||
if (trans && !trans.isZero())
|
if (trans && !trans.isZero())
|
||||||
parts.push('translate(' + formatter.point(trans) + ')');
|
parts.push('translate(' + formatter.point(trans) + ')');
|
||||||
|
if (angle)
|
||||||
|
parts.push('rotate(' + formatter.number(angle) + ')');
|
||||||
if (!Numerical.isZero(scale.x - 1)
|
if (!Numerical.isZero(scale.x - 1)
|
||||||
|| !Numerical.isZero(scale.y - 1))
|
|| !Numerical.isZero(scale.y - 1))
|
||||||
parts.push('scale(' + formatter.point(scale) +')');
|
parts.push('scale(' + formatter.point(scale) +')');
|
||||||
if (angle)
|
if (skew && skew.x)
|
||||||
parts.push('rotate(' + formatter.number(angle) + ')');
|
parts.push('skewX(' + formatter.number(skew.x) + ')');
|
||||||
|
if (skew && skew.y)
|
||||||
|
parts.push('skewY(' + formatter.number(skew.y) + ')');
|
||||||
attrs.transform = parts.join(' ');
|
attrs.transform = parts.join(' ');
|
||||||
} else {
|
} else {
|
||||||
attrs.transform = 'matrix(' + matrix.getValues().join(',') + ')';
|
attrs.transform = 'matrix(' + matrix.getValues().join(',') + ')';
|
||||||
|
|
|
@ -48,19 +48,19 @@ test('Decomposition: scale()', function() {
|
||||||
}
|
}
|
||||||
|
|
||||||
testScale(1, 1);
|
testScale(1, 1);
|
||||||
testScale(1, -1, -1, 1, -180); // Decomposing results in correct flipping
|
testScale(1, -1);
|
||||||
testScale(-1, 1);
|
testScale(-1, 1, 1, -1, -180); // Decomposing results in correct flipping
|
||||||
testScale(-1, -1, 1, 1, 180); // Decomposing results in correct flipping
|
testScale(-1, -1, 1, 1, -180); // Decomposing results in correct flipping
|
||||||
testScale(2, 4);
|
testScale(2, 4);
|
||||||
testScale(2, -4, -2, 4, -180); // Decomposing results in correct flipping
|
testScale(2, -4);
|
||||||
testScale(4, 2);
|
testScale(4, 2);
|
||||||
testScale(-4, 2);
|
testScale(-4, 2, 4, -2, -180); // Decomposing results in correct flipping
|
||||||
testScale(-4, -4, 4, 4, 180); // Decomposing results in correct flipping
|
testScale(-4, -4, 4, 4, -180); // Decomposing results in correct flipping
|
||||||
});
|
});
|
||||||
|
|
||||||
test('Decomposition: rotate() & scale()', function() {
|
test('Decomposition: scale() & rotate()', function() {
|
||||||
function testAngleAndScale(sx, sy, a, ex, ey, ea) {
|
function testAngleAndScale(sx, sy, a, ex, ey, ea) {
|
||||||
var m = new Matrix().scale(sx, sy).rotate(a),
|
var m = new Matrix().rotate(a).scale(sx, sy),
|
||||||
s = 'new Matrix().scale(' + sx + ', ' + sy + ').rotate(' + a + ')';
|
s = 'new Matrix().scale(' + sx + ', ' + sy + ').rotate(' + a + ')';
|
||||||
equals(m.getRotation(), ea || a,
|
equals(m.getRotation(), ea || a,
|
||||||
s + '.getRotation()');
|
s + '.getRotation()');
|
||||||
|
@ -69,7 +69,7 @@ test('Decomposition: rotate() & scale()', function() {
|
||||||
}
|
}
|
||||||
|
|
||||||
testAngleAndScale(2, 4, 45);
|
testAngleAndScale(2, 4, 45);
|
||||||
testAngleAndScale(2, -4, 45, -2, 4, -135);
|
testAngleAndScale(2, -4, 45);
|
||||||
testAngleAndScale(-2, 4, 45);
|
testAngleAndScale(-2, 4, 45, 2, -4, -135);
|
||||||
testAngleAndScale(-2, -4, 45, 2, 4, -135);
|
testAngleAndScale(-2, -4, 45, 2, 4, -135);
|
||||||
});
|
});
|
||||||
|
|
Loading…
Reference in a new issue