During the past few days, I played around a bit with an SVG file, trying to minimize its size. One thing I stumbled upon was this transformation:

<path d="..." transform="translate(186 188) rotate(-63) scale(41)" />

I had forgotten everything I ever knew about affine transformations, therefore I didn't know how to combine them into a single transformation matrix. Two days later, I had this program:

public enum AffineTransformations {

    public static void main(String[] args) {
        System.out.println(new Transformer()
                .translate(186, 188)

    static class Transformer {

        private double a = 1, b, c, d = 1, e, f;

        public Transformer scale(double x) {
            a *= x;
            b *= x;
            c *= x;
            d *= x;
            return this;

        public Transformer translate(double x, double y) {
            e += a * x + c * y;
            f += b * x + d * y;
            return this;

        public Transformer rotate(double deg) {
            double alpha = deg * Math.PI / 180.0;
            double sin = StrictMath.sin(alpha);
            double cos = StrictMath.cos(alpha);
            double aa = a, bb = b, cc = c, dd = d;
            a = aa * cos - cc * sin;
            b = bb * cos - dd * sin;
            c = aa * sin + cc * cos;
            d = bb * sin + dd * cos;
            return this;

        public String toString() {
            return String.format("matrix(%s %s %s %s %s %s)",
                    rd(a), rd(b), rd(c), rd(d), rd(e), rd(f));

        private double rd(double x) {
            return Math.floor(1000 * x + 0.5) / 1000;

It provides a minimalistic API to combine arbitrary scalings, rotations and translations. It outputs a transformation matrix suitable to be inserted into an SVG file.

Having 3 decimal places seemed enough to me for use in SVGs, therefore the rounding. I chose half-up rounding instead of half-even since I was annoyed some days ago by the Android renderer that uses half-even rounding, which created some visual artifacts (the coordinates 1.5 and 2.5 map to the same screen coordinate, even though their distance is 1.0).

I started the above code by copying a standard matrix multiplication routine, but then I thought about the many zeroes and ones that are involved in the matrixes, so I inlined all calculations to avoid the useless multiplications by 1.0 and 0.0. Now there's hardly anything left to inline. This optimization was just for fun rather than because of an actual need.

The short variable names follow the SVG specification.

Anything else left to criticize?

| improve this question | | | | |
  • \$\begingroup\$ Nice! Only thing I'd add is a static factory method, so that you can start out with Transformer.create().scale(...) instead of new Transformer(). (Matters of taste) \$\endgroup\$ – mtj Oct 27 '17 at 9:07

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Browse other questions tagged or ask your own question.