Canvas Rendering Context 2D: Transforms

This is the fourth article of an ongoing series about game development for the Web. In the previous article, we discussed the Canvas 2D Context API interface and went over the functions in charge of displaying primitives and text.

In this delivery, we will focus on the set of functions meant to alter the drawn geometry or text location and orientation within the <canvas>.


The Math

I have created an article that goes over some math concepts relevant to the topics discussed in this article. This is not intended to be the definitive math guide, but at least provides an overview of the magic going on behind the scenes for the API functions discussed here.


Transforms & the Canvas 2D Context API

The Canvas 2D Context API, like many other 2-dimensional APIs, has support for standard transformations like translation, rotation and scaling. This allows us to draw transformed elements around the screen without having to do the calculations of the new points by hand. If needed you can also combine transforms by calling them in order. On the Canvas 2D Context API transformations work by first transforming the canvas context, and then drawing onto it.

Translate Transform

To translate the Canvas 2D Context, we use the translate() interface method of the API. Translations enable us to move entire pieces of the canvas with just one call.

translate(x, y ) - remaps the (0,0) origin on a canvas. You can specify one or both parameters.

Example:

            // Get a reference to the canvas 
            var canvas = document.getElementById('canvas');

            // Get a reference to the drawing context 
            var ctx = canvas.getContext('2d');

            var rectWidth = 150;
            var rectHeight = 75;

            // translate context to center of canvas
            ctx.translate(canvas.width / 2, canvas.height / 2);

            ctx.fillStyle = 'blue';
            ctx.fillRect(rectWidth / -2, rectHeight / -2, rectWidth, rectHeight);

translate

Rotate Transform

To rotate the Canvas 2D Context, we use the rotate() interface method of the API. The rotate transformation requires an angle in radians. To define the rotation point, we need to first translate the canvas context such that the top left corner of the context lies on the desired rotation point.

rotate(angle) - rotates the context based on the current origin. The rotation angle must be given in radians.

Example:

            // Get a reference to the canvas 
            var canvas = document.getElementById('canvas');

            // Get a reference to the drawing context 
            var ctx = canvas.getContext('2d');

            var rectWidth = 150;
            var rectHeight = 75;

            // translate context to center of canvas
            ctx.translate(canvas.width / 2, canvas.height / 2);

            // rotate 45 degrees clockwise
            ctx.rotate(Math.PI / 4);

            ctx.fillStyle = 'blue';
            ctx.fillRect(rectWidth / -2, rectHeight / -2, rectWidth, rectHeight);

rotate

Scale Transform

To scale the Canvas 2D Context, we use the scale() transform method of the API. The method only requires the x and y scaling factors as the parameters.

scale(x, y) - scales the context based on the specified scaling factors. You can specify one or both parameters.

Example:

            // Get a reference to the canvas 
            var canvas = document.getElementById('canvas');

            // Get a reference to the drawing context 
            var ctx = canvas.getContext('2d');

            var rectWidth = 150;
            var rectHeight = 75;

            // translate context to center of canvas
            ctx.translate(canvas.width / 2, canvas.height / 2);

            // scale y component
            ctx.scale(1, 0.5);

            ctx.fillStyle = 'blue';
            ctx.fillRect(rectWidth / -2, rectHeight / -2, rectWidth, rectHeight);

scale

Custom Transform

To apply a custom transformation matrix to the Canvas 2D Context, we use the transform() method of the API. This method follows a convention defined on the SVGMatrix interface, where the 3x3 transformation matrix components are laid out as follows:

image

transform(a, b, c, d, e, f)

Example:

            // Get a reference to the canvas 
            var canvas = document.getElementById('canvas');

            // Get a reference to the drawing context 
            var ctx = canvas.getContext('2d');

            var rectWidth = 150;
            var rectHeight = 75;

            // translation matrix:
            // 1  0  tx
            // 0  1  ty
            // 0  0  1

            var tx = canvas.width / 2;
            var ty = canvas.height / 2;

            // translate context to center of canvas
            ctx.transform(1, 0, 0, 1, tx, ty);

            ctx.fillStyle = 'blue';
            ctx.fillRect(rectWidth / -2, rectHeight / -2, rectWidth, rectHeight);       

transform

State Management

When drawing to the Canvas using its 2D Context API, the 2D Context itself is in a certain state. Every time we manipulate the context by using a drawing method or applying a transformation, we modify the contexts state.

We can think of the canvas drawing state as a snapshot of all the styles and transformations that we have applied. The Canvas modifications persisted per state are:

  • The transformations such as translate, rotate and scale etc.
  • The current clipping region.
  • The current values of the following attributes: strokeStyle, fillStyle, globalAlpha, lineWidth, lineCap, lineJoin, miterLimit, shadowOffsetX, shadowOffsetY, shadowBlur, shadowColor, globalCompositeOperation, font, textAlign, textBaseline.

Often, we need to transform independently several elements at once while drawing to the Canvas. For instance, we might want to have rectangles with different stroke styles or orientations. The 2D Context API provides a state stack, which allows easy switching between states depending on our needs. This is a quick way of resuming earlier states after temporary changes.

In order to push and pop states from the Canvas 2D Context we need to use the following methods:

  • save(), pushes the current state to the stack.
  • restore(), pops the top state from the stack and sets it as the current state of the context.

Example:

            // Get a reference to the canvas 
            var canvas = document.getElementById('canvas');

            // Get a reference to the drawing context 
            var ctx = canvas.getContext('2d');

            var rectWidth = 150;
            var rectHeight = 75;

            ctx.save();

            // save state 1
            ctx.translate(canvas.width / 2, canvas.height / 2);

            ctx.save();
            // save state 2
            ctx.rotate(Math.PI / 4);

            ctx.save();
            // save state 3
            ctx.scale(2, 2);

            ctx.fillStyle = 'blue';
            ctx.fillRect(rectWidth / -2, rectHeight / -2, rectWidth, rectHeight);

            ctx.restore();
            // restore state 3
            ctx.fillStyle = 'red';
            ctx.fillRect(rectWidth / -2, rectHeight / -2, rectWidth, rectHeight);

            ctx.restore();
            // restore state 2
            ctx.fillStyle = 'yellow';
            ctx.fillRect(rectWidth / -2, rectHeight / -2, rectWidth, rectHeight);

            ctx.restore();
            // restore state 1
            ctx.fillStyle = 'green';
            ctx.fillRect(rectWidth / -2, rectHeight / -2, rectWidth, rectHeight); 

state stack


Examples

The source code for these examples is hosted onGitHub and you can download it and do whatever you want with it. Likewise, the live demos can be accessed by clicking this link.


Conclusion

In this article we took a closer look to the set of methods meant to alter the Canvas Context orientation and location. We took a look into the math behind the transformation methods and some basic concepts which will be the base of other developmenttechniquesoften used in simple and complex games.

Moving forward in the series, we will take what we already know about the CanvasRenderingContext2D API and put them together to some basic 2D games.


References

Weisstein, Eric W. "Scalar." From MathWorld—A Wolfram Web Resource.
http://mathworld.wolfram.com/Scalar.html

Weisstein, Eric W. "Linear Algebra." From MathWorld—A Wolfram Web Resource.
http://mathworld.wolfram.com/LinearAlgebra.html

Weisstein, Eric W. "Affine Transformation." From MathWorld—A Wolfram Web Resource.
http://mathworld.wolfram.com/AffineTransformation.html