diff --git a/CHANGELOG.md b/CHANGELOG.md index 945877590..253b73ce7 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -9,6 +9,7 @@ project adheres to [Semantic Versioning](http://semver.org/). ================== ### Changed ### Added +* Added support for `inverse()` and `invertSelf()` to `DOMMatrix` (#1648) ### Fixed 2.7.0 diff --git a/lib/DOMMatrix.js b/lib/DOMMatrix.js index 5c43c62a7..379c78c7a 100644 --- a/lib/DOMMatrix.js +++ b/lib/DOMMatrix.js @@ -425,7 +425,7 @@ DOMMatrix.prototype.skewYSelf = function (sy) { return this } -DOMMatrix.prototype.flipX = function () { +DOMMatrix.prototype.flipX = function () { return newInstance(multiply([ -1, 0, 0, 0, 0, 1, 0, 0, @@ -446,8 +446,135 @@ DOMMatrix.prototype.inverse = function () { return newInstance(this._values).invertSelf() } DOMMatrix.prototype.invertSelf = function () { - // If not invertible, set all attributes to NaN and is2D to false - throw new Error('Not implemented') + var m = this._values; + var inv = m.map(v => 0); + + inv[0] = m[5] * m[10] * m[15] - + m[5] * m[11] * m[14] - + m[9] * m[6] * m[15] + + m[9] * m[7] * m[14] + + m[13] * m[6] * m[11] - + m[13] * m[7] * m[10]; + + inv[4] = -m[4] * m[10] * m[15] + + m[4] * m[11] * m[14] + + m[8] * m[6] * m[15] - + m[8] * m[7] * m[14] - + m[12] * m[6] * m[11] + + m[12] * m[7] * m[10]; + + inv[8] = m[4] * m[9] * m[15] - + m[4] * m[11] * m[13] - + m[8] * m[5] * m[15] + + m[8] * m[7] * m[13] + + m[12] * m[5] * m[11] - + m[12] * m[7] * m[9]; + + inv[12] = -m[4] * m[9] * m[14] + + m[4] * m[10] * m[13] + + m[8] * m[5] * m[14] - + m[8] * m[6] * m[13] - + m[12] * m[5] * m[10] + + m[12] * m[6] * m[9]; + + // If the determinant is zero, this matrix cannot be inverted, and all + // values should be set to NaN, with the is2D flag set to false. + + var det = m[0] * inv[0] + m[1] * inv[4] + m[2] * inv[8] + m[3] * inv[12]; + + if (det === 0) { + this._values = m.map(v => NaN); + this._is2D = false; + return this; + } + + inv[1] = -m[1] * m[10] * m[15] + + m[1] * m[11] * m[14] + + m[9] * m[2] * m[15] - + m[9] * m[3] * m[14] - + m[13] * m[2] * m[11] + + m[13] * m[3] * m[10]; + + inv[5] = m[0] * m[10] * m[15] - + m[0] * m[11] * m[14] - + m[8] * m[2] * m[15] + + m[8] * m[3] * m[14] + + m[12] * m[2] * m[11] - + m[12] * m[3] * m[10]; + + inv[9] = -m[0] * m[9] * m[15] + + m[0] * m[11] * m[13] + + m[8] * m[1] * m[15] - + m[8] * m[3] * m[13] - + m[12] * m[1] * m[11] + + m[12] * m[3] * m[9]; + + inv[13] = m[0] * m[9] * m[14] - + m[0] * m[10] * m[13] - + m[8] * m[1] * m[14] + + m[8] * m[2] * m[13] + + m[12] * m[1] * m[10] - + m[12] * m[2] * m[9]; + + inv[2] = m[1] * m[6] * m[15] - + m[1] * m[7] * m[14] - + m[5] * m[2] * m[15] + + m[5] * m[3] * m[14] + + m[13] * m[2] * m[7] - + m[13] * m[3] * m[6]; + + inv[6] = -m[0] * m[6] * m[15] + + m[0] * m[7] * m[14] + + m[4] * m[2] * m[15] - + m[4] * m[3] * m[14] - + m[12] * m[2] * m[7] + + m[12] * m[3] * m[6]; + + inv[10] = m[0] * m[5] * m[15] - + m[0] * m[7] * m[13] - + m[4] * m[1] * m[15] + + m[4] * m[3] * m[13] + + m[12] * m[1] * m[7] - + m[12] * m[3] * m[5]; + + inv[14] = -m[0] * m[5] * m[14] + + m[0] * m[6] * m[13] + + m[4] * m[1] * m[14] - + m[4] * m[2] * m[13] - + m[12] * m[1] * m[6] + + m[12] * m[2] * m[5]; + + inv[3] = -m[1] * m[6] * m[11] + + m[1] * m[7] * m[10] + + m[5] * m[2] * m[11] - + m[5] * m[3] * m[10] - + m[9] * m[2] * m[7] + + m[9] * m[3] * m[6]; + + inv[7] = m[0] * m[6] * m[11] - + m[0] * m[7] * m[10] - + m[4] * m[2] * m[11] + + m[4] * m[3] * m[10] + + m[8] * m[2] * m[7] - + m[8] * m[3] * m[6]; + + inv[11] = -m[0] * m[5] * m[11] + + m[0] * m[7] * m[9] + + m[4] * m[1] * m[11] - + m[4] * m[3] * m[9] - + m[8] * m[1] * m[7] + + m[8] * m[3] * m[5]; + + inv[15] = m[0] * m[5] * m[10] - + m[0] * m[6] * m[9] - + m[4] * m[1] * m[10] + + m[4] * m[2] * m[9] + + m[8] * m[1] * m[6] - + m[8] * m[2] * m[5]; + + inv.forEach((v,i) => inv[i] = v/det); + this._values = inv; + return this } DOMMatrix.prototype.setMatrixValue = function (transformList) { @@ -471,11 +598,11 @@ DOMMatrix.prototype.transformPoint = function (point) { return new DOMPoint(nx, ny, nz, nw) } -DOMMatrix.prototype.toFloat32Array = function () { +DOMMatrix.prototype.toFloat32Array = function () { return Float32Array.from(this._values) } -DOMMatrix.prototype.toFloat64Array = function () { +DOMMatrix.prototype.toFloat64Array = function () { return this._values.slice(0) } diff --git a/test/dommatrix.test.js b/test/dommatrix.test.js index ec84016d0..8e1571713 100644 --- a/test/dommatrix.test.js +++ b/test/dommatrix.test.js @@ -23,6 +23,7 @@ function assertApproxDeep(actual, expected, tolerance) { describe('DOMMatrix', function () { var Avals = [4,5,1,8, 0,3,6,1, 3,5,0,9, 2,4,6,1] var Bvals = [1,5,1,0, 0,3,6,1, 3,5,7,2, 2,0,6,1] + var Xvals = [1,0,0,0, 0,1,0,0, 0,0,1,0, 0,0,0,0] var AxB = new Float64Array([7,25,31,22, 20,43,24,58, 37,73,45,94, 28,44,8,71]) var BxA = new Float64Array([23,40,89,15, 20,39,66,16, 21,30,87,14, 22,52,74,17]) @@ -374,7 +375,7 @@ describe('DOMMatrix', function () { }) describe('skewYSelf', function () {}) - + describe('flipX', function () { it('works', function () { var x = new DOMMatrix() @@ -403,8 +404,127 @@ describe('DOMMatrix', function () { }) }) - describe('inverse', function () {}) - describe('invertSelf', function () {}) + describe('invertSelf', function () { + it('works for invertible matrices', function() { + var d = new DOMMatrix(Avals) + d.invertSelf() + assertApprox(d.m11, 0.9152542372881356) + assertApprox(d.m12, -0.01694915254237288) + assertApprox(d.m13, -0.7966101694915254) + assertApprox(d.m14, -0.13559322033898305) + assertApprox(d.m21, -1.8305084745762712) + assertApprox(d.m22, -0.9661016949152542) + assertApprox(d.m23, 1.5932203389830508) + assertApprox(d.m24, 1.271186440677966) + assertApprox(d.m31, 0.7966101694915254) + assertApprox(d.m32, 0.559322033898305) + assertApprox(d.m33, -0.711864406779661) + assertApprox(d.m34, -0.5254237288135594) + assertApprox(d.m41, 0.711864406779661) + assertApprox(d.m42, 0.5423728813559322) + assertApprox(d.m43, -0.5084745762711864) + assertApprox(d.m44, -0.6610169491525424) + }) + + it('works for non-invertible matrices', function() { + var d = new DOMMatrix(Xvals) + d.invertSelf() + assert.strictEqual(isNaN(d.m11), true) + assert.strictEqual(isNaN(d.m12), true) + assert.strictEqual(isNaN(d.m13), true) + assert.strictEqual(isNaN(d.m14), true) + assert.strictEqual(isNaN(d.m21), true) + assert.strictEqual(isNaN(d.m22), true) + assert.strictEqual(isNaN(d.m23), true) + assert.strictEqual(isNaN(d.m24), true) + assert.strictEqual(isNaN(d.m31), true) + assert.strictEqual(isNaN(d.m32), true) + assert.strictEqual(isNaN(d.m33), true) + assert.strictEqual(isNaN(d.m34), true) + assert.strictEqual(isNaN(d.m41), true) + assert.strictEqual(isNaN(d.m42), true) + assert.strictEqual(isNaN(d.m43), true) + assert.strictEqual(isNaN(d.m44), true) + assert.strictEqual(d.is2D, false) + }) + }) + + describe('inverse', function () { + it('preserves the original DOMMatrix', function() { + var d = new DOMMatrix(Avals) + var d2 = d.inverse() + assert.strictEqual(d.m11, Avals[0]) + assert.strictEqual(d.m12, Avals[1]) + assert.strictEqual(d.m13, Avals[2]) + assert.strictEqual(d.m14, Avals[3]) + assert.strictEqual(d.m21, Avals[4]) + assert.strictEqual(d.m22, Avals[5]) + assert.strictEqual(d.m23, Avals[6]) + assert.strictEqual(d.m24, Avals[7]) + assert.strictEqual(d.m31, Avals[8]) + assert.strictEqual(d.m32, Avals[9]) + assert.strictEqual(d.m33, Avals[10]) + assert.strictEqual(d.m34, Avals[11]) + assert.strictEqual(d.m41, Avals[12]) + assert.strictEqual(d.m42, Avals[13]) + assert.strictEqual(d.m43, Avals[14]) + assert.strictEqual(d.m44, Avals[15]) + assertApprox(d2.m11, 0.9152542372881356) + assertApprox(d2.m12, -0.01694915254237288) + assertApprox(d2.m13, -0.7966101694915254) + assertApprox(d2.m14, -0.13559322033898305) + assertApprox(d2.m21, -1.8305084745762712) + assertApprox(d2.m22, -0.9661016949152542) + assertApprox(d2.m23, 1.5932203389830508) + assertApprox(d2.m24, 1.271186440677966) + assertApprox(d2.m31, 0.7966101694915254) + assertApprox(d2.m32, 0.559322033898305) + assertApprox(d2.m33, -0.711864406779661) + assertApprox(d2.m34, -0.5254237288135594) + assertApprox(d2.m41, 0.711864406779661) + assertApprox(d2.m42, 0.5423728813559322) + assertApprox(d2.m43, -0.5084745762711864) + assertApprox(d2.m44, -0.6610169491525424) + }) + + it('preserves the original DOMMatrix for non-invertible matrices', function() { + var d = new DOMMatrix(Xvals) + var d2 = d.inverse() + assert.strictEqual(d.m11, Xvals[0]) + assert.strictEqual(d.m12, Xvals[1]) + assert.strictEqual(d.m13, Xvals[2]) + assert.strictEqual(d.m14, Xvals[3]) + assert.strictEqual(d.m21, Xvals[4]) + assert.strictEqual(d.m22, Xvals[5]) + assert.strictEqual(d.m23, Xvals[6]) + assert.strictEqual(d.m24, Xvals[7]) + assert.strictEqual(d.m31, Xvals[8]) + assert.strictEqual(d.m32, Xvals[9]) + assert.strictEqual(d.m33, Xvals[10]) + assert.strictEqual(d.m34, Xvals[11]) + assert.strictEqual(d.m41, Xvals[12]) + assert.strictEqual(d.m42, Xvals[13]) + assert.strictEqual(d.m43, Xvals[14]) + assert.strictEqual(d.m44, Xvals[15]) + assert.strictEqual(isNaN(d2.m11), true) + assert.strictEqual(isNaN(d2.m12), true) + assert.strictEqual(isNaN(d2.m13), true) + assert.strictEqual(isNaN(d2.m14), true) + assert.strictEqual(isNaN(d2.m21), true) + assert.strictEqual(isNaN(d2.m22), true) + assert.strictEqual(isNaN(d2.m23), true) + assert.strictEqual(isNaN(d2.m24), true) + assert.strictEqual(isNaN(d2.m31), true) + assert.strictEqual(isNaN(d2.m32), true) + assert.strictEqual(isNaN(d2.m33), true) + assert.strictEqual(isNaN(d2.m34), true) + assert.strictEqual(isNaN(d2.m41), true) + assert.strictEqual(isNaN(d2.m42), true) + assert.strictEqual(isNaN(d2.m43), true) + assert.strictEqual(isNaN(d2.m44), true) + assert.strictEqual(d2.is2D, false) + }) + }) describe('transformPoint', function () { it('works', function () { @@ -437,7 +557,7 @@ describe('DOMMatrix', function () { ])) }) }) - + describe('toFloat64Array', function () { it('works', function () { var x = new DOMMatrix()