A Programmer's Guide to Canvas
Written by Ian Elliot   
Thursday, 24 January 2019
Article Index
A Programmer's Guide to Canvas
Paths
Active and Passive

Paths

In general the drawing methods work by creating a path which you can then fill or stroke (draw as an outline). 

A path is an abstract geometric thing and to make it visible you have to assign a stroke to it color and thickness. If you also assign a fill then the interior of the path is filled with the specified color. 

You start a path using the beginPath() method or simply moveTo(x,y). There are various line drawing commands such as lineTo(x,y) and once you have a path completed you can render it using the stroke() and/or fill().

The path is drawn using the current set of drawing attributes - lineWidth, fillStyle and so on.

There are also the fillRect and strokeRec methods which can be used as a shortcut to a path and drawing it.

Stack of states

So far so good.

We can now create a drawing context which is essentially a bitmap with an associated drawing state - the canvas state. This is a set of properties that determines how graphics primitives will be drawn.

The state consists of

  • the current transformation matrix
  • the current clipping region
  • all of the drawing attributes such as fillStyle, lineWidth and so on.

In short everything that determines what the result of a drawing operation actually produces. Notice that any drawing operation that is in progress, such as the current path or current bitmap, are not part of the state.

Why are we concerned with defining the context state?

The answer is that there is a save method which saves the current state to an internal stack of states and a restore method that sets the state to the current top of stack. 

So for example you can set a fill color and save it on the stack of states:

ctx.fillStyle = "rgb(200,0,0)";
ctx.save();
ctx.fillStyle = "rgb(0,200,0)";
ctx.save();
ctx.fillStyle = "rgb(0,0,200)";
ctx.fillRect (10, 10, 55, 50);

At this point the current fill colour is blue with green and red on the stack. Hence we have just drawn a blue rectangle.

If we now restore from the top of the stack  and draw a rectangle it will be green:

ctx.restore();
ctx.fillRect (20, 20, 55, 50);

Repeat this another time and we draw a red rectangle

ctx.restore();
ctx.fillRect (30, 30, 55, 50);

Notice that in this case we are only changing the fill color but in practice the entire drawing state is saved and restored.

squares

You can use the state stack to change the drawing state to draw a sub object and then restore the state to continue with drawing the main object.

 

 

  graphicsicon

Transformations

The drawing context has a transformation matrix associated with it and every pair of co-ordinates is multiplied by this matrix before drawing occurs.

When the context is created the matrix is set to the identity which means you are drawing using the default pixel co-ordinates. However, there are a set of methods that can be used to set the transform to anything you like.

A general transformation takes the form:

x'= ax + cy + e
y'= bx + dy + f

The values of a,c,b, d specify a rotation, a scaling or a skew depending on their values. The values e and f specify a shift of the origin to the new location e,f.

This is all you need to know but to understand the way that these transformations are presented is it worth knowing about homogeneous co-ordinates.

The transformation can be written in matrix form as:

    p'=Ap+t

where in terms of the previous tranformation values we have:

= (a c)
    (b d)

p'=(x') p=(x) t=(e)
   (y')   (y)   (f)

 

Notice that the rotation/scale/skew part of the transformation can be written as a matrix multiplication, but the translation is an untidy part that we have to add.

The whole transformation can be written as a matrix multiplication if we add an extra dummy dimension, set to 1, that we simply ignore when actually drawing.

That is the transformation can be written:

    p'=Tp

where

T = (a c e)
    (b d f)
    (0 0 1)

p'=(x') p=(x)
   (y')   (y)
   (1 )   (1)

 

So now you know that homogenous co-ordinates are just a trick that let us treat translation, along with rotation etc, as part of a matrix multiplication.

This is how the Canvas transformation works - you specify a 3x3 matrix in homogenous co-ordinates - which is used to multiple the co-ordinates you specify before any drawing operation.

Now to return to the details of the programming. We have a method:

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

which sets the transformation to the matrix specified and a method

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

which multiplies the existing transformation matrix by the one specified.

Setting the transformation in this general way is powerful but also a bit abstract and difficult.

To make things easier we also have:

  • scale(x,y) which applies a scaling in the x and y direction to the transformation matrix
  • rotate(angle) which applies a rotation angle in the clockwise direction; the angle is measured in radians
  • translate(x,y) which performs a translation by x,y

Banner

<ASIN:0137054890>
<ASIN:0596517742>



Last Updated ( Thursday, 24 January 2019 )