# CanvasContext2D Transform Math for Beginners

It’s easy to go to find a canvas tutorial, and often it will show a developer how to move the canvas context around to draw images using the specialized transform functions on the CanvasContext2DPrototype. These tutorials are absolutely fine for beginners and often, development teams will look no further to optimize their code. However, these tutorials overlook some very simple math that could make the canvas operations faster.

For example, typical canvas transforms normally look like this for sprites:

`//draw a sprite and skip the mathctx.save();ctx.translate(x, y); //move to canvas coordinatesctx.rotate(rotation); //rotate in radiansctx.scale(sx, sy); //scale in sizectx.translate(-cx, -cy); //center the image relative to itselfctx.drawImage(img, 0, 0, img.width, img.height);ctx.restore();`

However, this isn’t the fastest way to draw an image on canvas. In the prior example, the ctx.save() and ctx.restore() functions have quite a bit of overhead, especially when combined with 4 extra function calls. It’s probably better to just do a bit of the math yourself.

Doing matrix math can be complicated, but it also enables us to use the ctx.setTransform() function instead of the save and restore functions, which does turn out to be less cpu intensive. The following code snippet is equivalent to the sprite example above:

`//don't skip the math!let cos = Math.cos(rotation), sin = Math.sin(rotation);`
`//Transform the canvas contextctx.setTransform(  sx * cos,  sy * sin,  sx * -sin,  sy * cos,  x,  y);`
`//draw an imagectx.drawImage(img, -cx, -cy, img.width, img.height);`
`//restore the canvas statectx.setTransform(1, 0, 0, 1, 0, 0);`

Wow! The amount of function calls have been reduced, and we no longer have to save or restore the canvas context. Scaling it inside a for loop can even look like this:

`let cos, sin;for (let box of this.boxes) {  cos = Math.cos(box.rotation); //calculate the cosine ratio  sin = Math.sin(box.rotation); //calculate the sine ratio  ctx.setTransform(    box.size[0] * cos, //box.size[0] is the width    box.size[1] * sin, //box.size[1] is the height    box.size[0] * -sin,    box.size[1] * cos,    box.position[0], //box.position[0] is the x position    box.position[1] //box.position[1] is the y position  );  ctx.beginPath();  ctx.rect(-0.5, -0.5, 1, 1); //set the rectangle path  ctx.setTransform(1, 0, 0, 1, 0, 0); //restore the transform stack  ctx.stroke(); //then draw the rectangle}`
Note: Remember to set the transform back to [1, 0, 0, 1, 0, 0] after defining the position of the box object to make sure the line widths are consistent, when they are stroked with the API.

So what’s happening under the hood? Why does this work?

### Enter The Matrix

So to begin, let’s describe what a matrix is. The initial matrix is a (3x3) “identity matrix” that looks like this:

This matrix is very important, because it’s the exact starting point for every canvas operation.

Note: the order of the letters and positions in the matrix are row first: i.e. [y, x]. This means item b is at [2, 1].

One of the magical rules about Canvas operations is that all the stretching, rotation, and translating happens in two dimensional space. As a result, the bottom row, because of mathematical reasons is always [0, 0, 1].

### Starting Off Basic — Save and Restore

Now that we have a starting point, we want to be able to save and restore canvas state in a way that makes sense.

For the sake of example, follow along in code and derive your own canvas functions.

`//set currentMatrix to the identity matrixvar currentMatrix = [1, 0, 0, 1, 0, 0];var cache = new Float64Array(6); //reuse this variable a lot!`

Using a Float64Array for number caching is a wise choice, especially for number arrays that get re-used a lot. When creating new TypedArrays, it uses more memory and more CPU cycles up front to make read and write operations faster later, so allocate what you can up front, and reuse a lot later.

Next, we are going to treat the state of those six numbers as a stackable value. Knowing that canvas transforms are stackable allows you to create an array of transforms, then push and pop them as we need to.

In this example, we create a stack, and then describe a push operation.

`var stack = [currentMatrix.slice()];//slice copies the currentMatrix`
`//This is a pushing operationstack.push(currentMatrix.slice());ctx.setTransform( currentMatrix[0], currentMatrix[1], currentMatrix[2], currentMatrix[3], currentMatrix[4], currentMatrix[5]);`

Great! Now let’s describe a popping operation.

`//remove the top valuestack.pop();`
`//restore the next valuecurrentMatrix = stack[stack.length — 1]; //restore previous valuectx.setTransform( currentMatrix[0], currentMatrix[1], currentMatrix[2], currentMatrix[3], currentMatrix[4], currentMatrix[5]);`

Excelent! We have push and pop defined very well! Let’s start manipulating that stack with some example math.

### Lost In Translation!

In order to describe certain kinds of canvas transforms, we need to give them a matrix representation, and indeed, those representations are very simple! A translate matrix looks like this:

The following example uses tx and ty in the calculations required for translation.

`currentMatrix[4] += currentMatrix[0] * tx + currentMatrix[2] * ty;currentMatrix[5] += currentMatrix[1] * tx + currentMatrix[3] * ty;push();`
Note: Translation only moves the e and f values of the current matrix. This is because a translation does not change how a drawn image is rotated or stretched.

In the side example, the green square is translated from the location of the blue square.

### Scaling Your Operation

Next we investigate how scaling operations work. This is what a scale matrix looks like:

The following example uses sx and sy in the calculations required for scaling.

`currentMatrix[0] *= sx;currentMatrix[1] *= sx;currentMatrix[2] *= sy;currentMatrix[3] *= sy;push();`
Note: Since scaling doesn't affect the current [x,y] coordinates of the transform, scaling is applied to the first four items in the matrix.

As the name scaling implies, scaling makes your images and path coordinates bigger, smaller, or reflects them over a given axis.

In the side example, the blue square is reflected over the x and y axis, then made bigger by the scaling operation.

### Rotating The Point Of View

Investigating rotation is a bit harder, because it involves trigonometry functions.

The following example uses the cosine and sine functions to calculate a few transform values. (Note that the a variable is rotation in radians and not degrees)

`var cosa = Math.cos(a), sinr = Math.sin(a);cache[0] = currentMatrix[0];cache[1] = currentMatrix[1];cache[2] = currentMatrix[2];cache[3] = currentMatrix[3];currentMatrix[0] = cache[0] * cosa + cache[2] * sina;currentMatrix[1] = cache[1] * cosa + cache[3] * sina;currentMatrix[2] = cache[0] * -sina + cache[2] * cosa;currentMatrix[3] = cache[1] * -sina + cache[3] * cosa;push();`

In conventional math, rotations happen counter-clockwise, but because the y axis is flipped, they happen clockwise on canvas operations.

Do not forget to convert degrees to radians for Math.cos(), and Math.sin().

`const rads = (degrees) => Math.PI * 2 * degrees / 360;`

### Transformers! Mathematics in disguise!(Extra Credit!)

Last but not least, the full matrix transform is in the example below. Every other transform is derived from this formula, only simplified.

`cache.set(currentMatrix);currentMatrix[0] = cache[0] * t[0] + cache[2] * t[1]; //a...currentMatrix[1] = cache[1] * t[0] + cache[3] * t[1]; //b...currentMatrix[2] = cache[0] * t[2] + cache[2] * t[3]; //c...currentMatrix[3] = cache[1] * t[2] + cache[3] * t[3]; //d...currentMatrix[4] = cache[0] * t[4] + cache[2] * t[5] + cache[4]; currentMatrix[5] = cache[1] * t[4] + cache[3] * t[5] + cache[5];`

Good luck,

-Josh