JavaScript Canvas - Graphics State
Written by Ian Elliot   
Monday, 27 September 2021

Canvas Graphics' State sounds complicated, but it is very easy to use and very useful.  In this extract from Ian Elliot's book on JavaScript Graphics we look at how to use state.

Now available as a paperback or ebook from Amazon.

JavaScript Bitmap Graphics
With Canvas

largecover360

 

Contents

  1. JavaScript Graphics
  2. Getting Started With Canvas
  3. Drawing Paths
      Extract: Basic Paths 
      Extract: Bezier Curves 
  4. Stroke and Fill
      Extract: Stroke Properties 
      Extract: Fill and Holes
      Extract: Gradient & Pattern Fills **NEW!
  5. Transformations
      Extract: Transformations
      Extract: Custom Coordinates 
      Extract  Graphics State
  6. Text
      Extract: Text, Typography & SVG 
      Extract: Unicode 
  7. Clipping, Compositing and Effects
      Extract: Clipping & Basic Compositing 
  8. Generating Bitmaps
      Extract:  Introduction To Bitmaps
      Extract :  Animation 
  9. WebWorkers & OffscreenCanvas
      Extract: OffscreenCanvas
  10. Bit Manipulation In JavaScript
      Extract: Bit Manipulation  
  11. Typed Arrays
  12. Files, blobs, URLs & Fetch
      Extract: Blobs & Files
      Extract: Read/Writing Local Files 
  13. Image Processing
      Extract: ImageData
      Extract: The Filter API
  14. 3D WebGL
  15. 2D WebGL
    Extract: WebGL Convolutions 

<ASIN:1871962625>

<ASIN:B07XJQDS4Z>

<ASIN:1871962579>

<ASIN:1871962560>

<ASIN:1871962501>

<ASIN:1871962528>

In Chapter But Not In This Extract

  • Transformations
  • Transformation Functions
  • A Logical Approach to Transforms
  • Plotting A Graph

Stack of States

Typically drawing lots of shapes involves changing the transformation and attributes that are used to draw the path. After changing things you often want to restore the original canvas state. The canvas state 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 – see later
  • all of the drawing attributes such as fillStyle, lineWidth and so on.

In short, it includes 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 color 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.

redquare

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.

Active Transformations and State

There is another use for the stack of states - another overall approach to systematic drawing.

We have already met the idea of defining a path to draw a "standard" shape. You draw all of your paths starting at 0,0 and with a unit size. Then when you want a shape at x,y and size s you translate the co-ordinate system to x,y and scale by s. Of course, if you save the state before drawing and restore it after then nothing has changed and you are ready to draw the next standard shape.

For example, if you define a unit square:

var path1 = new Path2D();
path1.rect(0,0,1,1);

you can draw it at 100,200 and size 400 using:

ctx.save();     
ctx.translate(100,200);
ctx.scale(400,400);
ctx.lineWidth=1/400;    
ctx.stroke(path1);
ctx.restore();

Notice that you have to do the transformation in the opposite order to the one you might think and you do have to remember to specify the line width in the new units. Because of the save and restore you can draw the next standard shape using the same method.

This is a particularly useful approach to working with a library of complex shapes. For example, the space ship SVG string used earlier can be edited so that it is drawn starting at 0,0:

path1=new Path2D( "m 0,0 c 2.891926,-25.77092 -1.958475,
-65.0136 -13.037221,-73.92172 -11.072474,
8.90817 -17.526591,48.1508 -14.634563,
73.92172 -9.479011,8.46903 -9.015897,
17.40218 -9.068381,29.71617 l 5.92022,
-0.074 c 0,0 2.493141,-15.15787 5.105513,
-16.98251 l 3.15542,8.06751 -1.537022,
9.15649 c 13.277647,-0.17974 7.242537,
-0.17974 20.52018,0 l -1.537022,-9.15649 3.15552,
-8.06751 c 2.979241,2.08093 3.605942,
17.00168 3.605942,17.00168 l 6.61799,0.0548 c -0.0526,
-12.31399 1.21238,-21.24714 -8.266576,-29.71617 z");

It can now be drawn at any position, any angle and any scale using:

ctx.save();
ctx.translate(200,200); 
ctx.rotate(Math.PI/8); 
ctx.scale(2,2); 
ctx.lineWidth=1/2;
ctx.stroke(path1);
ctx.restore();

The only problem with this is defining the "center" of the shape. The center is the point of the shape that is located at 0,0 when then shape is drawn at 0,0. Usually you want this to be at a "natural" position - the center of the rocket or the tip of the nose. The reason is that the pixel at 0,0 when the shape is drawn is the one located at x,y when you do a translate to x,y and it is the point that any rotate is taken about. It is usually easy to fix the center when you hand-construct shapes - it is more difficult when you use an automatic method such as InkScape. In these cases the easiest solution is to find an initial offset that places the center where you want it when the shape is drawn at 0, 0. For the spaceship changing the initial m command to m 13.5,74.5 puts the center at the top of the nose.


rocketangle

Summary

 

  • Transformations include rotation, scaling and translation.

  • You can write all three as a simple matrix if you use homogeneous co-ordinates (x,y,1)

  • You can set the transform matrix directly or use one of the utility methods to set scaling, rotation and translation.

  • The order in which you apply transformations makes a difference.

  • You can think of transformations as actively moving something or just changing the co-ordinates. Canvas transforms change the co-ordinate system.

  • If you want to think “actively” then think of the transformations you want to perform on a shape and then apply them in the reverse order before drawing the shape.

  • One approach to organizing graphics is to draw everything centered on the origin and at unit scale and then use transformations to size, rotation and position where you really want to draw the shape.

  • You can change the co-ordinate system in use to anything that suits the current drawing task.

  • You can save the drawing state before changing the co-ordinate system so that it can be restored.

  • The drawing state includes the current transformation matrix, clipping region and all drawing attributes

 

To be informed about new articles on I Programmer, sign up for our weekly newsletter, subscribe to the RSS feed and follow us on Twitter, Facebook or Linkedin.

 

 

square

 



 

Comments




or email your comment to: comments@i-programmer.info

 

Last Updated ( Monday, 27 September 2021 )