Workbook 2, Page 2 - Drawing with Canvas

Now that we understand that Canvas is an immediate mode graphics API, we can look more at what commands it gives us for doing drawing.

Canvas provides a rich set of options for creating shapes and coloring and styling them. We won't talk about all of them here, but I'll give you the basic options and then encourage you to go out onto the web to read and to try things on your own.

I recommend reading this page first and then going on to other web resources (listed at the end of this page). I'll try to explain the concepts, and point you to things that give details.

For this page, you won't need to look at the HTML (you are of course welcome to, if you want to!). You will need to read the JavaScript (and some linked references).

You might wonder why I don't put "live code boxes" where the actual code that gets run appears on the page itself and you can read and tinker with it in place. Most of the better tutorials on the web do this. And its great since the code is right there in the text, and it is really easy to tinker with.

I have tried this in the past. However for CS559, we very quickly get to more complex programs where you will want to use "real tools", so I want you to get used to that with the simple examples. I wanted you to see from the very start how we structure programs. I wanted you to be able to use the debugger.

But this means that (1) you will actually have to go looking at the source code files, and (2) when I do put a snippet of code on a web page, it will be "marked up" as HTML, which makes it harder to look at the HTML.

Box 1: Review the Basics

On page 1 we saw a few simple examples. I will repeat the simplest one here.

Understanding this is quite important, so I'll put the actual drawing code here in the html:

    let canvas1 = document.getElementById("canvas1"));
    let context1 = canvas2.getContext('2d');
    context1.fillStyle = "#F00";
    context1.fillRect(30,30,30,30);

Note that I took out the comments. That includes the type declaration comments (see the Typed JavaScript page).

Line by line:

  1. Line 1 gets the canvas element from the DOM. The element is just like any other HTML element.
  2. Line 2 gets the context object out of the Canvas element. The context object stores all of the state that we need for drawing - things like where we will draw, what the current color to draw is, and partially finished objects, etc. At a practical level, most of the "drawing commands" are methods of this object.
  3. Line 3 sets the color for filling objects. Canvas is a stateful drawing model. For example, we pick a color and then we draw with that color. In contrast, a stateless drawing model would pass everything we need to know to draw to the actual drawing command.
  4. Line 4 actually draws the rectangle. It uses the "current state" of the context (such as the color we set on the previous line). Conceptually, the square is drawn immediately - changing the pixels on the screen. After the function completes, there is no memory that these pixels should be associated with a rectangle. The only representation of the rectangle is in the code. In practice, the drawing may happen asynchronously - we might not see the changes in the pixels until the system gets around to it.

Now, look at the official Mozilla Tutorial (which I recommend!), and get more details. You can see a few differences - these are worth pointing out:

  1. They are concerned with "fallback content" - if you're a real web developer, you need to be worried about what happens if someone uses an old browser. For class, we assume that everyone who looks at your program will have a modern, compatible web browser.
  2. They put the drawing code into an onload event handler for the Canvas, whereas we put the handler into the onload event for the entire page. Either way, we cannot draw into the canvas element until it has been created, so we need to draw after it has been. For us, we wait until the entire page (including the canvas element) has been created.

Box 2: Insides and Outsides

You may have noticed that when we drew the rectangle, we "filled" its inside with the red color (#F00).

In Canvas, we can apply styles (like colors) to both the insides and outsides of shapes (like rectangles). The inside is the "fill" and the outside is the "stroke" (as in, you make a stroke with a pen to draw the outline or boundary of a shape).

With Canvas, we need to do the stroke and fill separately. They are different commands, and get their styles from different properties. Here are some examples (look at the JavaScript code!).

You can look at the Official Documentation to see more about what styles are available (including non-constant fills).

But... I want to point out another key concept hidden in that example. When you set a drawing state, it stays for the next object. So, for example, when square 3 in the previous example set dashed lines, the next square also got it (whether it wanted it or not). In the simple example, we can see what is going on - but in a more complex example, the state might get set by some code somewhere very different.

Since the green square didn't know what happened before it, if it wants something specific, it has to reset all of the different pieces of the state!

Instead, a better idea is to have drawing an object "clean up" after itself. If we set some state to draw an object, we set it back before going on to the next one (unless we're sure it wants our changes). To simplify this convention, stated APIs (like Canvas) allow us to "save" and "restore" the state. Observe (and look at the source code!):

Saving and restoring works like a stack: if you save twice, it makes a stack of the two saves. Each restore takes something else off of the stack.

Notice how this leads to nesting, or "hierarchy". We will use this concept (save/restore with a stack) for many things in graphics.

Box 3: Drawing order and transparency

If rectangles (or any shape, for that matter) overlap, the shape that is drawn last covers over anything that was drawn before it. This is like painting with thick paint - we see the last thing drawn. Here is an example:

Of course, if we don't fill, then we don't cover over things inside the rectangle.

Drawing order effects commands that draw the same rectangle (the stroke vs. the fill). The stroke and fill share some of the same area (since the stroke is centered on the line around the filled area, the fill covers half the stroke). Here is another example of order change...

All of our colors so far have been opaque - they cover over what is behind them. We can also make semi-transparent colors. That is, the color lets through some of what's behind it. To do this, we extend our colors with an extra number: the opacity (or alpha). In addition to an amount of red, green, and blue, we add a forth number that is the amount that it covers what is behind it. By default, this is 100% (or 255/255), so things are opaque. But most places where we specify 3 numbers, we can specify a forth - so to make red that only blocks 50% of what it covers, we can say #FF00007F (where 7F is 127, or about half of 255).

Remember that drawing order matters - the transparent thing covers what was there before it. Things drawn afterwards will cover the transparent thing. Also, the transparent things will let the background (white) through if that is what they cover.

Here is a simple example:

We are using the simple math for transparency (it's called alpha-blending). We discuss it in class. There are more ways to combine the colors. Canvas supports many of them, but we might not get to learn about them in class.

Note that in the code, I specified colors as rgba(255,0,0,.5) rather than #FF00007F. Also, notice that the stroke can be transparent, and we can have transparent dark and light colors.

Box 4: Shapes besides rectangles (paths)

Canvas has 2 kinds of shapes: rectangles, and everything else. There are also images and text (which I guess are shapes too).

To make a path based shape, you define a "path" - which is basically the outline of the shape, and then you can stroke and fill that. Just as the rectangle had fillRect and strokeRect paths have stroke and fill. The biggest difference is that rather than telling stroke and fill what the shape is, it uses the current path. That is, the current path is part of the context, the same way things like fill color and line width are.

The official documentation is the best place to look Mozilla Canvas Shape Tutorial. You can skip over curves for now - we'll learn about them in a few weeks.

Make sure you understand how to make paths, and use them to draw with Canvas. And note that to make a circle, you need to draw an arc that has 2*Math.PI radians of arc to it.

Summary:

Now we've seen the basics of drawing with Canvas. On the next page, you'll get to try it out. (A02_p3.html).

From this page, make sure you understand the concepts (like drawing state).

You will want to learn more about Canvas drawing (so you can make more interesting pictures). Some of the things (like curves and transformations) we'll introduce in the coming weeks as we introduce the graphics concepts in class. For now, focus on making shapes and giving them styles.

Here are some resources to look at (you don't have to read them all, but do read some beyond just this tutorial):

  1. Mozilla Canvas Docs top level: This is the "official" documentation. Everything is in here, somewhere. It is actually quite well written and well organized.
  2. Official Mozilla Canvas Tutorial: This is part of #1. Again, I think it's very good. It very quickly gets beyond the basics. We mainly need the basics.
  3. Canvas Cheat Sheet: A concise page that reminds you of the different things you can do with Canvas.
  4. HTML Canvas Deep Dive: This is a "book length" tutorial on web graphics program (it even gets to 3D stuff). The first chapter covers a lot of the basic stuff.

Ok, now move on to the next page to try this out!