Decomposition of 2D-transform matrices
I recently took a look at the description of the CSS 2D / SVG transform
matrix(a, b, c, d, e, f) on MDN and I added a concrete example showing the effect of such a transform on an SVG line, in order to make this clearer for people who are not familiar with affine transformations or matrices.
Enter the CSS 2D transform you want to reduce and decompose or pick one example from the list . You can also choose between LU-like or QR-like decomposition: .
Here is the reduced CSS/SVG matrix as computed by your rendering engine ? and its matrix representation:
After simplification (and modulo rounding errors), an SVG decomposition into simple transformations is ? and it renders like this:
After simplification (and modulo rounding errors), a CSS decomposition into simple transformations is ? and it renders like this:
A matrix decomposition of the original transform is:
and corresponds to the affine transformation
which shows the classical factorization into a composition of a linear transformation and a translation . Now let's focus on the matrix and denote its determinant. We first consider the LDU decomposition. If , we can use it as a pivot and apply one step of Gaussian's elimination:
and thus the LDU decomposition is
Hence if , the transform
matrix(a,b,c,d,e,f) can be written
translate(e,f) skewY(atan(b/a)) scale(a, Δ/a) skewX(c/a). If and then we have and we can write (this is approximately "LU with full pivoting"):
and so the transform becomes
translate(e,f) rotate(90°) scale(b, Δ/b) skewX(d/b). Finally, if , then we already have an LU decomposition and we can just write
and so the transform is
translate(e,f) scale(c, d) skewX(45°) scale(0, 1).
As a consequence, we have proved that any transform
matrix(a,b,c,d,e,f) can be decomposed into a product of simple transforms. However, the decomposition is not always what we want, for example
scale(2) rotate(30°) will be decomposed into a product that involves
skewY instead of preserving the nice factors.
We thus consider instead the QR decomposition. If , then by applying the Gram–Schmidt process to the columns we obtain
where . In that case, the transform becomes
translate(e,f) rotate(sign(b) * acos(a/r)) scale(r, Δ/r) skewX(atan((a c + b d)/r^2)). In particular, a similarity transform preserves orthogonality and length ratio and so and . Hence for a similarity transform we get
translate(e,f) rotate(sign(b) * acos(a/r)) scale(r) as wanted. We also note that it is enough to assume the weaker hypothesis (that is or ) in the expression above and so the decomposition applies in that case too. Similarly, if we let and instead assume or we get
Hence in that case the transform is
translate(e,f) rotate(90° - sign(d) * acos(-c/s)) scale(Delta/s, s) skewY(atan((a c + b d)/s^2)). Finally if , then the transform is just
The decomposition algorithms are now easy to write. We note that none of them gives the best result in all the cases (compare for example how they factor Rotate2 and Skew1). Also, for completeness we have included the noninvertible transforms in our study (that is ) but in practice they are not really useful (try NonInvertible).