Page 2 of 3
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.fillStyle = "rgb(0,200,0)";
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.fillRect (20, 20, 55, 50);
Repeat this another time and we draw a red rectangle
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.
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.
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 know about homogeneous co-ordinates. The transformation can be written in matrix form as:
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:
T=(a c e)
(b d f)
(0 0 1)
(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.
Now to return to the details of the programming. We have a method:
which sets the transformation to the matrix specified and a method
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