mirror of
https://github.com/scratchfoundation/paper.js.git
synced 2025-01-01 02:38:43 -05:00
Implement proper matrix decomposition and use it in SvgExport.
This commit is contained in:
parent
aec87f4ed1
commit
e438ac8223
3 changed files with 114 additions and 86 deletions
|
@ -468,33 +468,61 @@ var Matrix = this.Matrix = Base.extend(/** @lends Matrix# */{
|
|||
);
|
||||
},
|
||||
|
||||
decompose: function() {
|
||||
// http://dev.w3.org/csswg/css3-2d-transforms/#matrix-decomposition
|
||||
// http://stackoverflow.com/questions/4361242/
|
||||
var a = this._a, b = this._b, c = this._c, d = this._d;
|
||||
if (Numerical.isZero(a * d - b * c))
|
||||
return {};
|
||||
|
||||
var scaleX = Math.sqrt(a * a + b * b);
|
||||
a /= scaleX;
|
||||
b /= scaleX;
|
||||
|
||||
var shear = a * c + b * d;
|
||||
c -= a * shear;
|
||||
d -= b * shear;
|
||||
|
||||
var scaleY = Math.sqrt(c * c + d * d);
|
||||
c /= scaleY;
|
||||
d /= scaleY;
|
||||
shear /= scaleY;
|
||||
|
||||
// a * d - b * c should now be 1 or -1
|
||||
if (a * d < b * c) {
|
||||
a = -a;
|
||||
b = -b;
|
||||
// We don't use c & d anymore, but this would be correct:
|
||||
// c = -c;
|
||||
// d = -d;
|
||||
shear = -shear;
|
||||
scaleX = -scaleX;
|
||||
}
|
||||
|
||||
return {
|
||||
scaling: Point.create(scaleX, scaleY),
|
||||
rotation: -Math.atan2(b, a) * 180 / Math.PI,
|
||||
shearing: shear,
|
||||
translation: Point.create(this._tx, this._ty)
|
||||
};
|
||||
},
|
||||
|
||||
getTranslation: function() {
|
||||
return Point.create(this._tx, this._ty);
|
||||
return this.decompose().translation;
|
||||
},
|
||||
|
||||
getScaling: function() {
|
||||
// http://math.stackexchange.com/questions/13150/
|
||||
// http://stackoverflow.com/questions/4361242/
|
||||
var hor = Math.sqrt(this._a * this._a + this._b * this._b),
|
||||
ver = Math.sqrt(this._c * this._c + this._d * this._d);
|
||||
return Point.create(
|
||||
this._a < 0 ? -hor : hor,
|
||||
this._d < 0 ? -ver : ver);
|
||||
return this.decompose().scaling;
|
||||
},
|
||||
|
||||
/**
|
||||
* The rotation angle of the matrix. If a non-uniform rotation is applied as
|
||||
* a result of a shear() or scale() command, undefined is returned, as the
|
||||
* resulting transformation cannot be expressed in one rotation angle.
|
||||
* The rotation angle of the matrix.
|
||||
*
|
||||
* @type Number
|
||||
* @bean
|
||||
*/
|
||||
getRotation: function() {
|
||||
var angle1 = -Math.atan2(this._b, this._a),
|
||||
angle2 = Math.atan2(this._c, this._d);
|
||||
return Math.abs(angle1 - angle2) < /*#=*/ Numerical.EPSILON
|
||||
? angle1 * 180 / Math.PI : undefined;
|
||||
return this.decompose().rotation;
|
||||
},
|
||||
|
||||
/**
|
||||
|
|
|
@ -44,7 +44,8 @@ new function() {
|
|||
|
||||
function getTransform(item, coordinates) {
|
||||
var matrix = item._matrix,
|
||||
trans = matrix.getTranslation(),
|
||||
decomposed = matrix.decompose(),
|
||||
trans = decomposed.translation,
|
||||
attrs = {};
|
||||
if (coordinates) {
|
||||
// If the item suppports x- and y- coordinates, we're taking out the
|
||||
|
@ -61,27 +62,22 @@ new function() {
|
|||
}
|
||||
if (matrix.isIdentity())
|
||||
return attrs;
|
||||
// See if we can formulate this matrix as simple scale / rotate commands
|
||||
// Note: getScaling() returns values also when it's not a simple scale,
|
||||
// but angle is only != null if it is, so check for that.
|
||||
// TODO: We disable transformation detection for now, until
|
||||
// Matrix#getRotation() and Matrix#getScaling() work correctly for all
|
||||
// angles and values of scaling.
|
||||
var angle = null, // matrix.getRotation(),
|
||||
parts = [];
|
||||
if (angle != null) {
|
||||
matrix = matrix.clone().scale(1, -1);
|
||||
// See if we can formulate the decomposed matrix as a simple
|
||||
// translate/scale/rotate command sequence.
|
||||
if (!decomposed.shearing) {
|
||||
var parts = [],
|
||||
angle = decomposed.rotation,
|
||||
scale = decomposed.scaling;
|
||||
if (trans && !trans.isZero())
|
||||
parts.push('translate(' + formatPoint(trans) + ')');
|
||||
if (angle)
|
||||
parts.push('rotate(' + formatFloat(angle) + ')');
|
||||
var scale = matrix.getScaling();
|
||||
if (!Numerical.isZero(scale.x - 1) || !Numerical.isZero(scale.y - 1))
|
||||
parts.push('scale(' + formatPoint(scale) +')');
|
||||
if (angle)
|
||||
parts.push('rotate(' + formatFloat(angle) + ')');
|
||||
attrs.transform = parts.join(' ');
|
||||
} else {
|
||||
parts.push('matrix(' + matrix.getValues().join(',') + ')');
|
||||
attrs.transform = 'matrix(' + matrix.getValues().join(',') + ')';
|
||||
}
|
||||
attrs.transform = parts.join(' ');
|
||||
return attrs;
|
||||
}
|
||||
|
||||
|
|
|
@ -11,67 +11,56 @@
|
|||
*/
|
||||
|
||||
module('Matrix');
|
||||
test('getRotation()', function() {
|
||||
equals(function() {
|
||||
return new Matrix().rotate(45).getRotation();
|
||||
}, 45);
|
||||
test('Decomposition: rotate()', function() {
|
||||
function testAngle(angle, expected) {
|
||||
equals(new Matrix().rotate(angle).getRotation(),
|
||||
Base.pick(expected, angle),
|
||||
'new Matrix().rotate(' + angle + ').getRotation()',
|
||||
Numerical.TOLERANCE);
|
||||
equals(new Matrix().rotate(angle).getScaling(),
|
||||
new Point(1, 1),
|
||||
'new Matrix().rotate(' + angle + ').getScaling()');
|
||||
}
|
||||
|
||||
equals(function() {
|
||||
return new Matrix().rotate(90).getRotation();
|
||||
}, 90);
|
||||
|
||||
equals(function() {
|
||||
return new Matrix().rotate(180).getRotation();
|
||||
}, 180);
|
||||
|
||||
equals(function() {
|
||||
return new Matrix().rotate(270).getRotation();
|
||||
}, -90, null, Numerical.TOLERANCE);
|
||||
|
||||
equals(function() {
|
||||
return new Matrix().rotate(-45).getRotation();
|
||||
}, -45);
|
||||
|
||||
equals(function() {
|
||||
return new Matrix().rotate(-90).getRotation();
|
||||
}, -90);
|
||||
|
||||
equals(function() {
|
||||
return new Matrix().rotate(-180).getRotation();
|
||||
}, -180);
|
||||
|
||||
equals(function() {
|
||||
return new Matrix().rotate(-270).getRotation();
|
||||
}, 90, null, Numerical.TOLERANCE);
|
||||
testAngle(0);
|
||||
testAngle(1);
|
||||
testAngle(45);
|
||||
testAngle(90);
|
||||
testAngle(135);
|
||||
testAngle(180);
|
||||
testAngle(270, -90);
|
||||
testAngle(-1);
|
||||
testAngle(-45);
|
||||
testAngle(-90);
|
||||
testAngle(-135);
|
||||
testAngle(-180);
|
||||
testAngle(-270, 90);
|
||||
});
|
||||
|
||||
test('getScaling()', function() {
|
||||
equals(function() {
|
||||
return new Matrix().scale(1, 1).getScaling();
|
||||
}, new Point(1, 1));
|
||||
test('Decomposition: scale()', function() {
|
||||
function testScale(sx, sy) {
|
||||
var flipped = sx < 0 && sy < 0;
|
||||
equals(new Matrix().scale(sx, sy).getScaling(),
|
||||
new Point(flipped ? -sx : sx, flipped ? -sy : sy),
|
||||
'new Matrix().scale(' + sx + ', ' + sy + ').getScaling()');
|
||||
equals(new Matrix().scale(sx, sy).getRotation(),
|
||||
flipped ? 180 : 0,
|
||||
'new Matrix().scale(' + sx + ', ' + sy + ').getRotation()',
|
||||
Numerical.TOLERANCE);
|
||||
}
|
||||
|
||||
equals(function() {
|
||||
return new Matrix().scale(1, -1).getScaling();
|
||||
}, new Point(1, -1));
|
||||
|
||||
equals(function() {
|
||||
return new Matrix().scale(-1, 1).getScaling();
|
||||
}, new Point(-1, 1));
|
||||
|
||||
equals(function() {
|
||||
return new Matrix().scale(2, -4).getScaling();
|
||||
}, new Point(2, -4));
|
||||
|
||||
equals(function() {
|
||||
return new Matrix().scale(-4, 2).getScaling();
|
||||
}, new Point(-4, 2));
|
||||
|
||||
equals(function() {
|
||||
return new Matrix().scale(-4, -4).getScaling();
|
||||
}, new Point(-4, -4));
|
||||
testScale(1, 1);
|
||||
testScale(1, -1);
|
||||
testScale(-1, 1);
|
||||
testScale(-1, -1);
|
||||
testScale(2, 4);
|
||||
testScale(2, -4);
|
||||
testScale(4, 2);
|
||||
testScale(-4, 2);
|
||||
testScale(-4, -4);
|
||||
});
|
||||
|
||||
test('getRotation() & getScaling()', function() {
|
||||
test('Decomposition: rotate() & scale()', function() {
|
||||
equals(function() {
|
||||
return new Matrix().scale(2, 4).rotate(45).getScaling();
|
||||
}, new Point(2, 4));
|
||||
|
@ -88,4 +77,19 @@ test('getRotation() & getScaling()', function() {
|
|||
return new Matrix().scale(2, -4).rotate(45).getRotation();
|
||||
}, 45);
|
||||
|
||||
equals(function() {
|
||||
return new Matrix().scale(-2, 4).rotate(45).getScaling();
|
||||
}, new Point(-2, 4));
|
||||
|
||||
equals(function() {
|
||||
return new Matrix().scale(-2, 4).rotate(45).getRotation();
|
||||
}, 45);
|
||||
|
||||
equals(function() {
|
||||
return new Matrix().scale(-2, -4).rotate(45).getScaling();
|
||||
}, new Point(-2, -4));
|
||||
|
||||
equals(function() {
|
||||
return new Matrix().scale(-2, -4).rotate(45).getRotation();
|
||||
}, 45);
|
||||
});
|
||||
|
|
Loading…
Reference in a new issue