Frédéric Wang Yet another non-exponentially growing weblog

About Me  Blog Archive

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.

This also recalled me a small algorithm to decompose an arbitrary SVG transform into a composition of basic transforms (Scale, Rotate, Translate and Skew) that I wrote 5 years ago for the Amaya SVG editor. I translated it into Javascript and I make it available here. Feel free to copy it on MDN or anywhere else. The convention used to represent transforms as 3-by-3 matrices is the one of the SVG specification.

Live demo

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: .

CSS

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:

SVG

After simplification (and modulo rounding errors), a CSS decomposition into simple transformations is ? and it renders like this:

CSS

A matrix decomposition of the original transform is:

Mathematical Description

The decomposition algorithm is based on the classical LU and QR decompositions. First remember the SVG specification: the transform matrix(a,b,c,d,e,f) is represented by the matrix

(a c e b d f 0 0 1)

and corresponds to the affine transformation

(x y)(a c b d)(x y)+(e f)

which shows the classical factorization into a composition of a linear transformation (a c b d) and a translation (e f). Now let's focus on the matrix (a c b d) and denote Δ=adbc its determinant. We first consider the LDU decomposition. If a0, we can use it as a pivot and apply one step of Gaussian's elimination:

(1 0 b/a 1)(a c b d)=(a c 0 Δ/a)

and thus the LDU decomposition is

(a c b d)=(1 0 b/a 1)(a 0 0 Δ/a)(1 c/a 0 1)

Hence if a0, 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 a=0 and b0 then we have Δ=cb and we can write (this is approximately "LU with full pivoting"):

(0 c b d)=(0 1 1 0)(b d 0 c)=(cos(π/2) sin(π/2) sin(π/2) cos(π/2))(b 0 0 Δ/b)(1 d/b 0 1)

and so the transform becomes translate(e,f) rotate(90°) scale(b, Δ/b) skewX(d/b). Finally, if a=b=0, then we already have an LU decomposition and we can just write

(0 c 0 d)=(c 0 0 d)(1 1 0 1)(0 0 0 1)

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 skewX and skewY instead of preserving the nice factors.

We thus consider instead the QR decomposition. If Δ0, then by applying the Gram–Schmidt process to the columns (a b),(c d) we obtain

(a c b d)=(a/r b/r b/r a/r)(r (ac+bd)/r 0 Δ/r)=(a/r b/r b/r a/r)(r 0 0 Δ/r)(1 (ac+bd)/r 2 0 1)

where r=a 2+b 20. 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 ac+bd=(a b)(c d)=0 and Δ=(a b)(c d)cos(π/2)=r 2. 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 r0 (that is a0 or b0) in the expression above and so the decomposition applies in that case too. Similarly, if we let s=c 2+d 2 and instead assume c0 or d0 we get

(a c b d)=(cos(π/2) sin(π/2) sin(π/2) cos(π/2))(c/s d/s d/s c/s)(Δ/s 0 0 s)(1 0 (ac+bd)/s 2 1)

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 a=b=c=d=0, then the transform is just scale(0,0).

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 Δ=0) but in practice they are not really useful (try NonInvertible).