Jan 9: Drawing
I can't reiterate enough that the best way to learn this stuff is by doing. Don't just passively skim over these notes. Try each new construct yourself, and see what happens when you change things. Likewise, load the examples in, modify them, see what you get, and come up with new stuff. (And send cool things my way.) We'll do some of that in class, too -- just yell if you want to try a variation on the theme.
Graphics
To draw something, we have to tell the computer what we want to draw and where we want to draw it (among other things). In a drawing program, one typically specifies these things by selecting from menus and pointing-and-clicking. However, in order to be able to do some of that work computationally, we'll have to look under the hood. A recurring theme in this course will be how can we specify and represent, within a computer, things that are visual.
Where is specified with respect to a coordinate system, like in math classes, but oriented somewhat differently:
Thus we specify where in terms of an x coordinate and a y coordinate. The x axis runs left-to-right (0 at the left) and the y axis top-to-bottom (0 at the top). In Processing, by default the maximum possible x and y coordinates are both 100. However, the very first line of a sketch can use the size() function to specify a different size for the window, e.g., 300 (x) by 200 (y):
size(300,200);This is an example of a function call; the function is named size, and its parameters are the width (here, 300) and height (here, 200). See the Programming notes section below for a discussion of how to read and write function calls.
What to draw is specified by calling an appropriate function. We saw last time the ellipse() function, e.g.,
ellipse(130,90,70,70);The function takes four parameters, in order: the x (here 130) and y (here 90) coordinates of the center, along with the width (here 70), and the height (here also 70). An ellipse with width equal to height is a circle, but we can get something that's oblong by making them unequal (try it).
The rect() function is similar; its parameters are the x and y coordinates of the upper left corner (rather than the center, like for ellipse), along with the width and height. E.g., a 40x5 rectangle at (110,20):
rect(110,20,40,5);If we'd rather specify an ellipse like a rectangle (upper-left corner) or a rectangle like an ellipse (center), use the ellipseMode() or rectMode() function. E.g., try putting ellipseMode(CORNER); and rectMode(CENTER); in front of the above examples.
Plotting a single point is done with the point() function, specifying the x and y coordinates. E.g., at (125, 35):
point(125,35);A single point is pretty much invisible, unless we change the strokeWeight(), giving a parameter for the thickness (default 1). E.g., try the above with strokeWeight(5);.
We can draw a line() by giving the coordinates for its two endpoints, in the order x1, y1, x2, y2:
line(100,90,60,50);I'll let you look over on your own the documentation for triangle() and quad(), which work much the same way; let me know if anything's confusing.
We saw above that the weight of a stroke can be controlled. Likewise, the strokeCap() function modifies the cap on a line, and the strokeJoin() function modifies how lines are joined together in rects, triangles, and quads. One more function that modifies appearances of things being drawn is smooth() (and its opposite, noSmooth()), which does what its name suggests. Smoothing (anti-aliasing, technically) makes shapes look better, but can slow things down a bit (this is only potentially a problem for intensive sketches).
Finally (for now), we can "draw" text. The function is text(), and its parameters are the text (in double quotes) and the coordinates of its lower-left corner. However, before we can draw any text, we have to specify the font. This is a two-step process. The first step is done in the Processing environment, using the menu option "Tools" / "Create Font..." to create the images for the letters. (If you forget that step, you'll get an error when you try to run the sketch.) The next step is to call the function loadFont() to load that font and the function textFont() to set it as the font to use for subsequent text. These two functions can be "chained" together, taking a parameter that is the name of the created font (in double quotes, including the .vlw extension). E.g. (after creating a 24-point TrebuchetMS),
textFont(loadFont("TrebuchetMS-24.vlw")); text("Let it snow!",155,25);
Here's an example combining these various drawing functions. Pick it apart, modify it, and see what you get.
size(300,200); smooth(); ellipse(130,40,40,40); // head ellipse(130,90,70,70); // torso ellipse(130,160,90,90); // legs? strokeWeight(5); point(125,35); // left eye point(135,35); // right eye strokeWeight(1); triangle(130,40,140,45,130,45); // nose rect(110,20,40,5); // top hat rect(120,0,20,20); line(100,90,60,50); // right arm line(60,50,65,40); // right hand line(60,50,57,35); line(60,50,50,48); line(158,76,200,53); // left arm line(200,53,200,42); // left hand line(200,53,210,65); textFont(loadFont("TrebuchetMS-24.vlw")); text("Let it snow!",155,25);
[applet]
The little comments after double slashes (e.g., // head) are called comments, and are just notes to the reader. See the Programming notes section below for a discussion.
Color
Notice that by default the Processing background is kind of grayish, the strokes are black, and the things we draw (rectangles, ellipses) are filled in white. Processing of course has a much richer palette than that, but it requires us to again think how we can specify color in a program, without resorting to a menu.
Let's start with a touch of gray. Processing has a grayscale model in which we specify the shade by a number between 0 (black) and 255 (white). We can pass such a number as the parameter to the background(), stroke(), and fill() functions. Here's an example, following Reas and Fry (21-02):
noStroke(); smooth(); background(128); // Right shape fill(255); ellipse(65, 44, 60, 60); fill(0); ellipse(75, 44, 30, 30); fill(255); ellipse(81, 39, 6, 6); // Left shape fill(255); ellipse(20, 50, 60, 60); fill(0); ellipse(30, 50, 30, 30); fill(255); ellipse(36, 45, 6, 6);
[applet]
The "stroke" is the outline of the object, and the "fill" its interior. We control the colors for these separately. If we don't want an outline, we call noStroke(), while if we don't want it to be filled, we call noFill().
Now let's move on to color. In the RGB (red-green-blue) model, color is specified as a set of three values: red, green, and blue, each in the range from 0 (none of that color) to 255 (full on). Thus:
| red | green | blue | result |
|---|---|---|---|
| 255 | 255 | 255 | white |
| 0 | 0 | 0 | black |
| 255 | 0 | 0 | bright red |
| 0 | 255 | 0 | bright green |
| 0 | 0 | 255 | bright blue |
| 128 | 0 | 0 | not-as-bright red |
| 0 | 128 | 0 | not-as-bright green |
| 0 | 0 | 128 | not-as-bright blue |
Other colors are given as combinations of red, green, and blue, as illustrated in this simple color table. Processing has a built in tool, under the Tools menu, for helping determine the values for a color. A web search will return many such tools (e.g., one with r,g,b sliders). Note that RGB is an additive color model (e.g., red+green=yellow), suitable for mixing light (as a computer display does). Processing also supports the HSB (hue-saturation-brightness) color scheme; see the colorMode() function.
After selecting the proper r, g, and b values, we pass them as the three parameters to the background(), stroke(), and fill() functions. Processing is smart enough to know that if we only give one number, it's grayscale, whereas if we give three, it's RGB.
Anybody up for a psychadelic snowperson?
size(300,200); smooth(); background(0,51,153); noStroke(); fill(51,255,255); ellipse(130,40,40,40); // head fill(255,255,51); ellipse(130,90,70,70); // torso fill(153,255,51); ellipse(130,160,90,90); // legs? stroke(0); strokeWeight(5); point(125,35); // left eye point(135,35); // right eye strokeWeight(1); fill(255,153,102); stroke(255,153,102); triangle(130,40,140,45,130,45); // nose fill(255,51,204); noStroke(); rect(110,20,40,5); // top hat rect(120,0,20,20); stroke(204,153,255); line(100,90,60,50); // right arm line(60,50,65,40); // right hand line(60,50,57,35); line(60,50,50,48); line(158,76,200,53); // left arm line(200,53,200,42); // left hand line(200,53,210,65); fill(255); textFont(loadFont("TrebuchetMS-24.vlw")); text("Let it snow!",155,25);
[applet]
You might have noticed that various parts of the snowperson overlap each other (eyes on the head, snowballs on each other). In Processing, whatever is drawn last appears on top. However, by varying the transparency/opacity, underlying parts can "show through". The transparency is specified as an additional parameter (second for grayscale, fourth for RGB) to the fill() and stroke() functions. Its value ranges from 0 (totally transparent -- can't see it) to 255 (totally opaque -- can't see through it). Give it a try (e.g., for the top hat, use fill(255,51,204,150);).
Programming notes
- Sketch
- So far, a sketch is just a sequence of function calls and comments. The sketch is "performed" (executed) top to bottom, so the results of later ones can obscure the results of earlier ones.
- Function call, parameters
- Processing provides many built-in functions (e.g., we've seen some for drawing things and setting colors), and we can also specify our own (we'll cover that later). A function is essentially just a command to perform some action. The name of the function (e.g., ellipse) says what action to perform; it is case sensitive (so Ellipse is not the same thing as ellipse). A function may need some more information in order to perform the action; we use parameters to give such information. For the ellipse function, we need to give its position and size. If a function name is analogous to a menu option, then parameters are analogous to various parts of a dialog that have to be filled in (e.g., "save as" needs the filename). The order of the parameters is fixed, and specified in the documentation for the function (I hyperlink to the Processing reference when I introduce a function). The parameters are separated by commas, and enclosed in parentheses after the function name.
- Comment
- A comment is just a note for the reader, and is ignored by Processing. A single-line comment starts with a double slash (//); we can write anything we want after that, up to the end of the line. A multi-line comment starts with slash-star (/*) and runs until star-slash (*/).
- Syntax
- Processing (along with all other programming languages) is like a very picky English teacher -- you have to put commas, parentheses, and semicolons in the right places, give the right number of parameters, etc. If (rather, when) you make a mistake, Processing will do its best to help you figure out where and what, but its advice can sometimes be a bit cryptic. In addition to the syntax for function calls and comments, the other piece of syntax we've seen is the semicolon. Generally speaking, each command (so far, each function call) is followed by a semicolon. That is, semicolons aren't separators, like in English, but terminators.
- Style
- One thing that Processing doesn't care about is spacing. However, the reader does. We can add as many spaces and blank lines as we want, between things, but not in the middle of a name or a value. (Give it a try.) Use spacing carefully, to make the code easy to read. This will become more of an issue as we expand our set of Processing constructs.
Practice problems
- Modify the snowperson -- make the eyes bigger or smaller, choose more appropriate colors, place the arms differently, put a snowball in one of the hands, add a corncob pipe, etc.
- Draw a stoplight, first just the objects and then with the appropriate colors. Comment each of the parts. [hints]