PFS! Computer Science

16. Computer Graphics: Processing and JavaScript

There are lots of ways to learn about programming, and one of those is by drawing diagrams on the computer. Now that we know a little bit about data, variables, if-else statements, and loops, let's take a look at how we can use computing to draw pictures, create art, and write/play graphics-based games.

16.1. The Processing platform for JavaScript

Processing is a library created to allow artists and programmers to create visual "sketches" by using code.

You can learn more about processing at processing.org. You can learn about using JavaScript with Processing at p5js.org.

But what you really want to do is just get started by using the online p5js editor.

Let's go!

16.2. The editor interface

Using your browser, go to editor.p5js.org. You'll see an online interface that looks like this:

Similar to our replit interface, the left half of the screen is dedicated to writing code, and the right half of the screen will display a "Preview" of the visual results of that code whenever we click on the gray "Play" button at the upper left corner of the window. There is also a small "Console" window below our code that we can use to help debug our code.

  1. Click on the gray "Play" button now to see what happens.

You should see a gray square area appear in the Preview window on the right. The code is running! (Not that there's much to see at this point.)

Click the "Stop" square, and click on "Sign Up" to create an account with your pfspasadena.org Google account.

Now we're ready to move on.

16.3. setup() and draw() functions

We've discussed functions in JavaScript, and there are two important functions for Processing sketches.

The setup() function is executed once when the code begins running. This is usually where the Canvas is created (see below), initial background colors are set up, and where variables are declared and initialized.

The draw() function is executed next, and this is where most of the interesting code will go. Note: The draw() function is executed repeatedly in a type of infinite loop. As soon as all the code is executed once, the draw() function runs again, over and over until the program is stopped.

This will allow us to write some really interesting programs!

Add three lines to your sketch as shown in the screenshot here. Then click the "Play" button. You should see the value of test repeatedly printed out in the Console, confirming that the draw() function is being repeatedly run, over and over again.

Stop the program and delete those three lines before continuing with our exploration.

16.4. The canvas, background, and colors

The canvas represents the area that is available for us to draw on. Currently, it is specified as 400 pixels wide and 400 pixels tall. Let's change the canvas to 600 pixes by 400 pixels:

createCanvas(600, 400);

Run the sketch again and you should see the gray-colored canvas as a landscape-oriented rectangle.

The term pixel refers to a "picture element" on the screen, one "dot" of light on your screen. A single point of light on a computer screen is not very large, but the p5.js environment allows us to keep track of them all and use them in creating our sketches.

The background() instruction specifies the color of the canvas's background. The parameter is currently set to 220, but it can be specified as something else, in one of two ways.

If you're just working with different black-gray-white shades, the value can be anything from 0 (black) to 255 (white). Here, 220 is pretty close to 255, so it's a light gray color. Try changing it to a lower number—maybe 80?—and run the program again to see what the background looks like now.

16.4.1. Colors

Of course what we really want to be able to use is colors, and these can be specified in a few ways. The easiest perhaps, is using Red-Green-Blue (RGB) values.

The primary light colors are red, green, and blue, and by combining these colors in varying degrees, all the colors of the rainbow can be produced.

The diagram to the right indicates that red light and green light, when combined, will produce a yellow color. (If you were to combine red and green pigments, on the other hand, you'd have a brown/gray mess.)

To specify different colors for use in your artwork you can indicate how much of each primary color—red, green, and blue—you wish to use, with a value from 0-255.

 

So, if you want "red" to be the background of your window, you'd use the instruction

background(255, 0, 0);    // red, green, and blue values

To specify a magenta color, you'd use the instruction

background(255, 0, 255);    // all red, no green, all blue = magenta

Crazy background colors are interesting, but most sketches use a simple black, white, or gray background, however, and save the use of color for the other elements on the screen.

16.5. Orienting yourself in the window

Each "picture element," or pixel on the screen, can be individually specified by its column and row. Columns are numbered from left to right, from 0 to width - 1 across the screen, and rows are numbered top to bottom, from 0 to height - 1, down the screen.

For a 640 × 480 window, the coordinates would look like this.

16.6. Setting the Fill; Drawing Shapes

In order to be able to draw on the screen, we can use a number of built-in functions. To use these you'll typically need to specify a number of parameters.

The ellipse(x, y, width, height) function, for example, draws an ellipse on the screen.

Copy this script into the sketch window and try running it:

function setup() {
  createCanvas(600, 400);
}

function draw() {
  background(220);
  fill(255, 0, 0);  // sets the color for the next element
  ellipse(100, 200, 50, 30);
}

Notice that we filled the ellipse, but there is also a black outline around the outside of it. That line is called the stroke, and its color and thickness can be specified as well.

Each of these fill and stroke commands needs to be given before a line or shape is drawn. Once the fill/stroke have been changed, those new values will continue until they are reset again.

stroke(0-255);         // defines the grayscale color of a line
stroke(r, g, b);       // describes an RGB line color
strokeWeight(3);       // describes the width of the stroke
noStroke();            // removes the stroke color completely
fill(0-255);           // describes a grayscale fill color
fill(r, g, b);         // describes an RGB fill color
noFill();              // removes the fill color completely

Here are a few shapes to experiment with:

ellipse(centerX, centerY, width, height)
point(X, Y);                          // highlights a point in canvas
triangle(x1, y1, x2, y2, x3, y3);     // creates triangle with these vertices
quad(x1, y1, x2, y2, x3, y3, x4, y4); // creates a quadrilateral
rect(x, y, width, height);            // creates a rectangle
line(x1, y1, x2, y2);                 // creates a line

16.7. Planning a drawing

At this point, we can issue commands to draw a simple picture. Consider drawing a simple house with the sun shining down on it. To save myself a lot of trouble, I'd plan out the sketch by drawing it on graph paper first...

I've identified important pixels in that diagram, and some shapes and colors. Then I could write the code to make that happen.

function setup() {
  createCanvas(600, 400);
}

function draw() {
  background(220);
  line(50,300,550,300);
  rect(100,200,200,100);
  triangle(200,100,100,200,300,200);
  circle(500,80,80);
}

When I entered that code and ran it, I realized the sun was too small, so I made the radius a little bigger:

It needs some color, though. Yes? Set the different fills before each shape gets drawn:

function setup() {
  createCanvas(600, 400);
}

function draw() {
  background(220);
  line(50,300,550,300);
  fill(255, 0, 100);     // not sure what color this is -- lol
  rect(100,200,200,100); // house
  fill(0);              // black
  triangle(200,100,100,200,300,200); // roof
  fill(255, 255, 0);    // yellow for the sun
  circle(500,80,80);
  // Adding a door
  fill(140, 140, 0);
  rect(120, 240, 30, 60);
}

Notice I added a door there to make it look a little more like a house.

16.7.1. Reminder: draw() runs continuously

Although you can't really tell in this sketch, the draw() function is redrawing this picture approximately 30 times a second. We can see that by making one small change in our sketch.

Instead of creating a yellow fill for the sun, let's make a random color for the sun:

fill(random(256), random(256), random(256));

This chooses a random number 0-255 for the red value, 0-255 for the green value, and 0-255 for the blue value, to create a random color that is used for the fill. A new random color is chosen every time we run this fill instruction, which happens ~30 times a second!

Try it!

function setup() {
  createCanvas(600, 400);
}

function draw() {
  background(220);
  line(50,300,550,300);
  fill(255, 0, 100);     // not sure what color this is -- lol
  rect(100,200,200,100); // house
  fill(0);              // black
  triangle(200,100,100,200,300,200); // roof
  // random color for the sun
  fill(random(256), random(256), random(256));
  circle(500,80,80);
  // Adding a door
  fill(140, 140, 0);
  rect(120, 240, 30, 60);
}

One more example of how we can use the repeating draw() function.

16.8. How animation works

When we redraw the screen some 30 times every second, we can produce an animation: draw an image, then draw a slightly different image, then draw another slightly different image...

In this version of the program, the sun's horizontal position x starts at 0 and increases by 2 every time the draw() function runs.

let x = 0                  // x-coordinate for the sun
                                
function setup() {
  createCanvas(600, 400);
}

function draw() {
  background(220);
  line(50,300,550,300);
  fill(255, 0, 100);     // not sure what color this is -- lol
  rect(100,200,200,100); // house
  fill(0);              // black
  triangle(200,100,100,200,300,200); // roof
  // random color for the sun
  fill(random(256), random(256), random(256));    
  circle(x,80,80);   // drawing the sun at the x-coordinate
  x = x + 2          // move the sun over 2 pixels for next time
  // Adding a door
  fill(140, 140, 0);
  rect(120, 240, 30, 60);
}

Note that the sun moves across the sky and passes in front of the house. Can you figure out how to fix that issue?

The command background(220); in our program clears the screen every time it is called, just before we draw the house in its same position and the sun in a slightly different position.

To see what happens when we don't clear the screen, remove that background(220) command (or comment it out) and see how it effects the running of your program.

function draw() {
  // background(220);
  line(50,300,550,300);
  ...

16.9. Using "event listeners"

The program above demonstrates how we can create simple animations, but we also want to have the user interact with our program.

16.9.1. mouseX and mouseY events

Clear out the code you've written and try this program:

function setup() {
  createCanvas(600, 400);
}

function draw() {
  background(220);              // clear background
  fill(255, 0, 0);              // set red fill for circle
  circle(mouseX, mouseY, 30);   // draw circle wherever the mouse is
  console.log(mouseX, mouseY);  // display mouse coordinates in console
}

In this script, mouseX and mouseY are two values that indicate the x,y location on the screen. In this version of the draw() function we're both drawing the circle at whatever location the mouse is located, and printing out those coordinates in the Console window (if you want to see their numeric value).

We say that the draw() function is "listening" for mouse events. If the mouse moves, if the mouse button is pressed, or if the mouse button is released, that information can be relayed to the script and our program can act on that information.

16.9.2. Interacting with mouse clicks

There are two common ways of interacting with a mouseclick.

One is the mousePressed boolean variable, which is true if the mouse is down. Change your draw() function so that it looks like this:

function setup() {
  createCanvas(600, 400);
}

function draw() {
  background(220);        // clear the screen
  if (mouseIsPressed) {
    fill(255,0,0);
    circle(mouseX, mouseY, 40);
  }
}

This version of the draw() function only puts a circle on the screen while the mouse button is pressed. If you move the mouse while the mouse is pressed, Processing will continue to put ellipses on the screen.

Because we're clearing the screen every time through the draw() function, we don't see the old circles on the screen. If you comment out the background(220); line then we won't clear the screen. Try that out and see what happens.

A second way of interacting with the mouse captures individual clicks. Just as Processing knows about the setup() and draw() functions, it looks for a mouseClicked() function. If a mouseClicked() function is defined for a program, then Processing will automatically call that function anytime the mouse is clicked.

Add this function to your program to see an example of using mouseClicked().

function setup() 
{
  createCanvas(600, 400);
  noStroke();
}

function mouseClicked()
{
  fill(random(256), random(256), random(256));
}

function draw() 
{
  // background(220);        // clear the screen
  if (mouseIsPressed) 
  {
    circle(mouseX, mouseY, 10);
  } 
}

16.10. Generative art

The programs that we've written so far allow us to create images on the screen based on using the mouse and mouse-clicks. Another type of computer art is generative art, in which the program (written by us), creates the art.

Take a look at this program to see if you can figure out what it does.

Then enter the program into Processing and run it.

Is this art?

function setup() 
{
  createCanvas(600, 400);
  // Create random x,y locations on the screen for
  // each end of a line
  x1 = random(width);
  y1 = random(height);
  x2 = random(width);
  y2 = random(height);
}

function draw() 
{
  line(x1, y1, x2, y2);             // Draw the line...
  // Then randomly change the x & y coordinates for next time
  x1 = x1 + 2 * (random(20) - 10); 
  x2 = x2 + 2 * (random(20) - 10);
  y1 = y1 + 2 * (random(20) - 10);
  y2 = y2 + 2 * (random(20) - 10);
}