What is Processing?
The "Processing" language (also referred to as "P5", think "Proce55ing") is a programming language (emphatically not "a program") with a focus on data visualisation. Of course, "data" is a loose concept, and Processing can be used for anything from drawing a few lines and circles on a screen, to full blown interactive animations. In fact, a basic Processing program is two lines of code, and will already play an animation:
void setup() { size(..., ...); } void draw() {}
Of course this program doesn't show you anything, because all it does is set up the visual context to have a certain width and height (indicated in the setup method as the size(...,...) instruction) and then calls draw() every few milliseconds. Of course, draw() is empty, so it won't actually show you anything. A more useful minimal program would be a "hello world" program, but I hate those because they only show a programming language can write text, and that's pretty much the least interesting feature of any programming language. Instead let's look at a minimal program that makes sense for a data visualisation language:
play controls:
■
▶
|
float frame = 0; // we start at frame 0 float framerate = 24; // our "sketch" will have a framerate of 24 frames per second. int ball_x; // ball administration: x coordinate int ball_y; // ball administration: y coordinate int ball_radius = 20; // ball administration: ball radius void setup() { size(200,200); // set draw area size frameRate(framerate); // set animation framerate ball_x = width/2; // set the initial ball coordinates ball_y = ball_radius; // set the initial ball coordinates stroke(#003300); // set the default shape outline colour fill(#0000FF); // set the default shape fill colour } void draw() { frame++; // note that we're one frame further than last time float bounce_height = height/2 * abs(sin(PI*frame/framerate)); // compute the ball height for this frame float ball_height = height - (bounce_height+ball_radius); // because the top of the screen is 0, and the bottom is "height", background(#FFFFEE); // clear the drawing area ball_y = (int) (ball_height); // set the new ball y position ellipse(ball_x,ball_y,ball_radius,ball_radius); // draw the ball }
This looks a bit long for a minimal program, but then again, this actually does something: it shows us a ball that bounces up and down, taking one second for each bounce. It shows a few aspects of Processing too: every variable is strongly typed. So you have to indicate what you'll be using a variable for, and you can choose from:
- boolean, a binary value that can be either true or false
- byte, an 8-bit value
- char, a byte representing an ascii character
- color, a type specific to Processing, representing an on-screen color
- int, a 32 bit signed integer number
- long, a 64 bit signed integer number
- float, a 32 bit signed decimal number
- double, a 64 bit signed decimal number
And of course there are also the typical complex data types:
- Object, a catch-all data type for things that are complex data types.
- String, a text string (stored as UTF-16).
- ArrayList, a list structure for arbitrary values with add/remove functionality.
- HashMap, a structure that can store {<unique key>, <arbitrary value>} pairs.
- XMLElement, a convenient XML-mirroring object.
You'll see why this last one turns out to be really useful later on.
Coming back to the minimally functional example of a Processing program, or "sketch", there are also some examples of Processing' own API at work. The following methods are native Processing calls:
- size(int, int), sets the drawing area dimensions, and the global "width" and "height" values.
- frameRate(int), sets the refresh rate for the drawing area, and the frameRate value.
- stroke(color), sets the shape outline color.
- fill(color), sets the shape fill color.
- abs(number), computes the absolute value of any number.
- sin(number), computes the sinoid value based on any number (treated as radians).
- background(color), sets every pixel of the drawing area to the specified color.
- ellipse(int, int, int, int), draws an ellipse on the drawing area.
The Processing API is in fact quite expansive (See http://processing.org/reference for the full list), but it can't cover everything. Luckily it supports object oriented programming, so that our previous example can also be written as an object oriented sketch:
play controls:
■
▶
|
Bouncer bouncer; void setup() { size(200,200); frameRate(24); stroke(#003300); fill(#0000FF); bouncer = new Ball(width/2,20,20); } void draw() { bouncer.computeNextStep(width, height, frameRate); background(#FFFFEE); bouncer.draw(); } interface Bouncer { void computeNextStep(int width, int height, float framerate); void draw(); } class Ball implements Bouncer { int x,y,radius; int step=0; Ball(int x, int y, int r) { this.x = x; this.y = y; this.radius = r; } void computeNextStep(int sketch_width, int sketch_height, float frame_rate) { step++; float sin_value = abs(sin(PI*step/(float)frame_rate)); float bounce_height = sketch_height/2 * sin_value; float ball_height = sketch_height - (bounce_height + radius); y = (int) (ball_height); } void draw() { ellipse(x,y,radius,radius); } }
Instead of doing everything in the draw() function, the object oriented approach tucks all the code that relates to computing the ball's position in the definition for what we consider a "Ball". To be good object oriented programmers, we've also said that things that are a Ball are also a Bouncer, and this lets us extend our sketch very easily to instead of a bouncing ball, have a bouncing box by keeping almost everything the same, and adding a new class Box that's a Bouncer:
play controls:
■
▶
|
void setup() { ... bouncer = new Box(width/2,20,20,20); } class Box implements Bouncer { int x,y,w,h; int step=0; Box(int x, int y, int w, int h) { this.x = x; this.y = y; this.w = w; this.h = h; } void computeNextStep(int sketch_width, int sketch_height, float frame_rate) { step++; float sin_value = abs(sin(PI/2.0 + (PI*step/(float)frame_rate))); float bounce_height = sketch_height/2 * sin_value; float ball_height = sketch_height - (bounce_height + h); y = (int) (ball_height); } void draw() { rect(x,y,w,h); } }
All of a sudden we have a bouncing box, that starts from a falling position instead of from the ground, and we didn't have to modify the master draw() function for it! In fact, let's just use a group of bouncing things:
play controls:
■
▶
|
Bouncer[] bouncer = new Bouncer[3]; void setup() { ... bouncer[0] = new Ball(width/3-20,20,20); bouncer[1] = new Box(width/2-10,20,20,20); bouncer[2] = new Ball((2*width/3)+20,20,20); } void draw() { for(int b=0, end=bouncer.length; b<end;b++) { bouncer[b].computeNextStep(width, height, frameRate); } background(#FFFFEE); for(int b=0, end=bouncer.length; b<end;b++) { bouncer[b].draw(); } } ...
Fantastic, two bouncing balls and a bouncing box, bouncing counter-point to each other. But it's not very interactive yet. Let's change it so that we can "hang on" to bouncing things until we let go of them again. Processing allows interaction with the keyboard and mouse, using what are known as "event handlers", methods that Processing automatically calls for you when you use the keyboard or mouse. In this case we care about mouse interaction, so we'll look at mousePressed and mouseReleased events:
play controls:
■
▶
|
... void mousePressed() { for(int b=0, end=bouncer.length; b<end;b++) { if(bouncer[b].mouseOver(mouseX, mouseY)) { bouncer[b].mousePressed(); }} } void mouseReleased() { for(int b=0, end=bouncer.length; b<end;b++) { bouncer[b].mouseReleased(); } } abstract class Bouncer { int x, y; boolean canmove = true; int step = 0; abstract void computeNextStep(int width, int height, float framerate); abstract void draw(); abstract boolean mouseOver(int mx, int my); void mousePressed() { canmove = false; } void mouseReleased() { canmove = true; } } class Ball extends Bouncer { int radius; Ball(int x, int y, int r) { this.x = x; this.y = y; this.radius = r; } void computeNextStep(int sketch_width, int sketch_height, float frame_rate) { if(canmove) { step = (int)((step+1) % frame_rate); float sin_value = abs(sin(PI*step/(float)frame_rate)); float bounce_height = sketch_height/2 * sin_value; float ball_height = sketch_height - (bounce_height + radius); y = (int) (ball_height); }} void draw() { ellipse(x,y,radius,radius); } boolean mouseOver(int mx, int my) { return sqrt((x-mx)*(x-mx) + (y-my)*(y-my)) <= radius; } } class Box extends Bouncer { int w,h; int step=0; Box(int x, int y, int w, int h) { this.x = x; this.y = y; this.w = w; this.h = h; } void computeNextStep(int sketch_width, int sketch_height, float frame_rate) { if(canmove) { step = (int)((step+1) % frame_rate); float sin_value = abs(sin(PI/2.0 + (PI*step/(float)frame_rate))); float bounce_height = sketch_height/2 * sin_value; float ball_height = sketch_height - (bounce_height + h); y = (int) (ball_height); }} void draw() { rect(x,y-h/2,w,h); } boolean mouseOver(int mx, int my) { return x<=mx && mx<=x+w && (y-h/2)<=my && my<=(y+h/2); } }
Because the Ball and Box classes will do the same thing on mouse interaction, the interface Bouncer has been changed to an actual class too, to take care of some of the shared functionality. Now if you click on anything that's a Bouncer it'll stop moving until you let it go. Let's go one step further an just allow us to move the bouncing things around, too.
play controls:
■
▶
|
Bouncer[] bouncer = new Bouncer[3]; void setup() { size(200,200); frameRate(24); stroke(#003300); fill(#0000FF); bouncer[0] = new Ball(width/3-20,20,20); bouncer[1] = new Box(width/2-10,20,20,20); bouncer[2] = new Ball((2*width/3)+20,20,20); } void draw() { for(int b=0, end=bouncer.length; b<end;b++) { bouncer[b].computeNextStep(width, height, frameRate); } background(#FFFFEE); for(int b=0, end=bouncer.length; b<end;b++) { bouncer[b].draw(); } } void mousePressed() { for(int b=0, end=bouncer.length; b<end;b++) { if(bouncer[b].mouseOver(mouseX, mouseY)) { bouncer[b].mousePressed(); }} } void mouseReleased() { for(int b=0, end=bouncer.length; b<end;b++) { bouncer[b].mouseReleased(); } } void mouseDragged() { for(int b=0, end=bouncer.length; b<end;b++) { bouncer[b].mouseDragged(mouseX, mouseY); } } abstract class Bouncer { int x, y; boolean canmove = true; int step = 0; int xoffset = 0; int yoffset = 0; void computeNextStep(int width, int height, float framerate) { if(canmove) { reallyComputeNextStep(width, height, framerate); }} abstract void reallyComputeNextStep(int width, int height, float framerate); abstract void draw(); abstract boolean mouseOver(int mx, int my); void mousePressed() { canmove = false; } void mouseReleased() { canmove = true; x += xoffset; y += yoffset; xoffset = 0; yoffset = 0; } void mouseDragged(int mx, int my) { if(!canmove) { xoffset = mx-x; yoffset = my-y; }} } class Ball extends Bouncer { int radius; Ball(int x, int y, int r) { this.x = x; this.y = y; this.radius = r; } void reallyComputeNextStep(int sketch_width, int sketch_height, float frame_rate) { step = (int)((step+1) % frame_rate); float sin_value = abs(sin(PI*step/(float)frame_rate)); float bounce_height = sketch_height/2 * sin_value; float ball_height = sketch_height - (bounce_height + radius); y = (int) (ball_height); } void draw() { ellipse(x+xoffset,y+yoffset,radius,radius); } boolean mouseOver(int mx, int my) { return sqrt((x-mx)*(x-mx) + (y-my)*(y-my)) <= radius; } } class Box extends Bouncer { int w,h; int step=0; Box(int x, int y, int w, int h) { this.x = x; this.y = y; this.w = w; this.h = h; } void reallyComputeNextStep(int sketch_width, int sketch_height, float frame_rate) { step = (int)((step+1) % frame_rate); float sin_value = abs(sin(PI/2.0 + (PI*step/(float)frame_rate))); float bounce_height = sketch_height/2 * sin_value; float ball_height = sketch_height - (bounce_height + h); y = (int) (ball_height); } void draw() { rect(x+xoffset,(y-h/2)+yoffset,w,h); } boolean mouseOver(int mx, int my) { return x<=mx && mx<=x+w && (y-h/2)<=my && my<=(y+h/2); } }
And with that, on to the original topic of this article: using Processing on web pages