Transformation Matrix

Basic Math

Let's try to understand how transformation matrixes are working in 2D.

Here is the typical 2D transformation matrix, it is widely used in CSS, SVG, Figma API, and most 2D graphics engines.

\begin{bmatrix} a & b & dx \\ c & d & dy \end{bmatrix}

First of all let's start from the beginning and write down the underlying matrix multiplication math.

\begin{bmatrix} a & b & dx \\ c & d & dy \end{bmatrix} * \begin{bmatrix} x \\ y \end{bmatrix} = \begin{bmatrix} x*a + y*b + dx \\ x*c + y*d + dy \end{bmatrix}



Playground

Check out this interactive playground! Use scroll to adjust values with 0.1 precision:

With this matrix we can do any affine transformation: rotate, scale, translate, flip, skew. This is why it is so widely used.



How does it work

In the above example I am just using canvas context setTransform method and pass a, b, c, d, dx, dy as arguments before drawing an image.

Most functions are just matrix multiplication of original matrix and the transformation one

Rotation

For building rotation matrix we need to use radians and build the rotation transformation matrix

\begin{bmatrix} x' \\ y' \end{bmatrix} = \begin{bmatrix} \cos \phi & -\sin \phi \\ \sin \phi & \cos \phi \end{bmatrix} \begin{bmatrix} x \\ y \end{bmatrix}

Here is the code that just do this

var rotate = function(rotateAngle){
  var rad = rotateAngle * Math.PI / 180;

// current matrix is stored as a,b, dx, c,d, dy in this example
  var mat1 = [[a,b], [c,d]];
  var mat2 = [[Math.cos(rad), -Math.sin(rad)], [Math.sin(rad), Math.cos(rad)]];

// we multiply only a,b,c,d (2x2 matrix) and keep the dx and dy
  var mul = [
    [
      mat1[0][0]*mat2[0][0] + mat1[0][1]*mat2[1][0],
      mat1[0][0]*mat2[0][1] + mat1[0][1]*mat2[1][1],
      dx
    ],
    [
      mat1[1][0]*mat2[0][0] + mat1[1][1]*mat2[1][0],
      mat1[1][0]*mat2[0][1] + mat1[1][1]*mat2[1][1],
      dy
    ]
  ];
  
  return mul;
};
Scaling

Scaling is one of the easiest — we can just multiply a,b,c,d by some number. If we want to separate the axis — a, c gives us the X axis scaling, and b, dY

In terms of matrix it looks like this:

\begin{bmatrix} X & 0 \\ 0 & Y \end{bmatrix}
var scale = function(x, y){
  var mat1 = [[a,b], [c,d]];

// if y is not specified — do a proportional scaling by x
  var mat2 = y === void 0 ? [[x,0], [0,x]] : [[x,0], [0,y]];

  var mul = [
    [
      mat1[0][0]*mat2[0][0] + mat1[0][1]*mat2[1][0],
      mat1[0][0]*mat2[0][1] + mat1[0][1]*mat2[1][1],
      dx
    ],
    [
      mat1[1][0]*mat2[0][0] + mat1[1][1]*mat2[1][0],
      mat1[1][0]*mat2[0][1] + mat1[1][1]*mat2[1][1],
      dy
    ]
  ];

  return mul;
};
Translate\move

For moving the image adjusting dx, dy would do the trick (in the above example we are working in global coordinate system). If we want to move image in relative coordinates — multiply a, b, c, d by the translation vector and add the result to dx, dy

var move = function(x, y){
  return [[a, b, dx+x], [c, d, dy+y]]
};
Flip

Flipping is similar to scaling, but we are using negative number. So flipping by X look like this:

\begin{bmatrix} -1 & 0 \\ 0 & 1 \end{bmatrix}

Just call scale(-1, 1) and it would do the trick.

Shear\skew

Skew is done by this matrix:

\begin{bmatrix} 1 & X \\ Y & 1 \end{bmatrix}
How is the origin working?

For changing the origin — I just draw the image at different position:



Relative transformation matrix

Global, and local coordinates

Animating transformation matrix changes