Tutorial 8: Drawing with Transformations

Affine Transformations

Processing provides some functions for transforming the drawing canvas through translation, rotation and scaling. These provide a very powerful way to draw complex and dynamic drawings without having to keep track of lots of variables or do lots of mathematics.

To illustrate the effects of using the transformation functions we'll draw a grid. Here is the un-transformed grid:

  • Show Code
/** @peep sketch */
void setup() {
  size(200, 200);
  background(204);
  grid();
}
 
void grid() {
  stroke(0, 32);
  fill(255, 64);
  rect(0, 0, width, height);
  for (int x = 0; x <= width; x += 10) { line(x, 0, x, height); }
  for (int y = 0; y <= height; y += 10) { line(0, y, width, y); }
  stroke(255, 0, 0, 128);
  line(10, 10, 30, 10);
  line(25, 15, 30, 10);
  line(25, 5, 30, 10);
  stroke(0, 0, 255, 128);
  line(10, 10, 10, 30);
  line(15, 25, 10, 30);
  line( 5, 25, 10, 30);
}

Note: The grid covers the whole display window, i.e., from (0, 0) to (width, height). The red arrow indicates the positive x direction and the blue arrow indicates the positive y direction.

Translation

The translate() function moves the origin by the amount specified. The first example moves the origin in the positive x and y direction to 1/3 of the way across the screen in both:

  • Show Code
/** @peep sketch */
void setup() {
  size(200, 200);
  background(204);
  translate(width/3, height/3);
  grid();
}
 
void grid() {
  stroke(0, 32);
  fill(255, 64);
  rect(0, 0, width, height);
  for (int x = 0; x <= width; x += 10) { line(x, 0, x, height); }
  for (int y = 0; y <= height; y += 10) { line(0, y, width, y); }
  stroke(255, 0, 0, 128);
  line(10, 10, 30, 10);
  line(25, 15, 30, 10);
  line(25, 5, 30, 10);
  stroke(0, 0, 255, 128);
  line(10, 10, 10, 30);
  line(15, 25, 10, 30);
  line( 5, 25, 10, 30);
}

The origin can also be moved in the opposite direction, i.e., off the screen. This can be helpful for positioning things like scrolling backgrounds that are larger than the display window.

  • Show Code
/** @peep sketch */
void setup() {
  size(200, 200);
  background(204);
  translate(-width/3, -height/3);
  grid();
}
 
void grid() {
  stroke(0, 32);
  fill(255, 64);
  rect(0, 0, width, height);
  for (int x = 0; x <= width; x += 10) { line(x, 0, x, height); }
  for (int y = 0; y <= height; y += 10) { line(0, y, width, y); }
  stroke(255, 0, 0, 128);
  line(10, 10, 30, 10);
  line(25, 15, 30, 10);
  line(25, 5, 30, 10);
  stroke(0, 0, 255, 128);
  line(10, 10, 10, 30);
  line(15, 25, 10, 30);
  line( 5, 25, 10, 30);
}

It is important to remember that the translate() function moves the origin by the amount specified, it is not an absolute movement to a place on the screen. This is evident when multiple calls to translate() are made:

  • Show Code
/** @peep sketch */
void setup() {
  size(200, 200);
  background(204);
  translate(width/6, height/6);
  grid();
  translate(width/6, height/4);
  grid();
  translate(width/4, height/6);
  grid();
}
 
void grid() {
  stroke(0, 32);
  fill(255, 64);
  rect(0, 0, width, height);
  for (int x = 0; x <= width; x += 10) { line(x, 0, x, height); }
  for (int y = 0; y <= height; y += 10) { line(0, y, width, y); }
  stroke(255, 0, 0, 128);
  line(10, 10, 30, 10);
  line(25, 15, 30, 10);
  line(25, 5, 30, 10);
  stroke(0, 0, 255, 128);
  line(10, 10, 10, 30);
  line(15, 25, 10, 30);
  line( 5, 25, 10, 30);
}

The translate() function is most often used to re-position the origin before drawing a complex shape because this allows the code for the complex shape to be free of any reference to "offset" variables whose only job is to position the drawing in the canvas. Drawings coded this way are often referred to as having been designed for a local coordinate frame.

Rotation

The rotate() function rotates the canvas around the origin by the angle specified in radians. Positive angles rotate the canvas clockwise:

  • Show Code
/** @peep sketch */
void setup() {
  size(200, 200);
  background(204);
  rotate(radians(30));
  grid();
}
 
void grid() {
  stroke(0, 32);
  fill(255, 64);
  rect(0, 0, width, height);
  for (int x = 0; x <= width; x += 10) { line(x, 0, x, height); }
  for (int y = 0; y <= height; y += 10) { line(0, y, width, y); }
  stroke(255, 0, 0, 128);
  line(10, 10, 30, 10);
  line(25, 15, 30, 10);
  line(25, 5, 30, 10);
  stroke(0, 0, 255, 128);
  line(10, 10, 10, 30);
  line(15, 25, 10, 30);
  line( 5, 25, 10, 30);
}

Negative angles rotate the canvas anti-clockwise:

  • Show Code
/** @peep sketch */
void setup() {
  size(200, 200);
  background(204);
  rotate(radians(-30));
  grid();
}
 
void grid() {
  stroke(0, 32);
  fill(255, 64);
  rect(0, 0, width, height);
  for (int x = 0; x <= width; x += 10) { line(x, 0, x, height); }
  for (int y = 0; y <= height; y += 10) { line(0, y, width, y); }
  stroke(255, 0, 0, 128);
  line(10, 10, 30, 10);
  line(25, 15, 30, 10);
  line(25, 5, 30, 10);
  stroke(0, 0, 255, 128);
  line(10, 10, 10, 30);
  line(15, 25, 10, 30);
  line( 5, 25, 10, 30);
}

Like the translate() function, the rotate() function rotates a canvas relative to the angle provided, not absolutely to the angle, as demonstrated by the following sketch that rotates the canvas by the same amount 3 times:

  • Show Code
/** @peep sketch */
void setup() {
  size(200, 200);
  background(204);
  rotate(radians(-20));
  grid();
  rotate(radians(-20));
  grid();
  rotate(radians(-20));
  grid();
}
 
void grid() {
  stroke(0, 32);
  fill(255, 64);
  rect(0, 0, width, height);
  for (int x = 0; x <= width; x += 10) { line(x, 0, x, height); }
  for (int y = 0; y <= height; y += 10) { line(0, y, width, y); }
  stroke(255, 0, 0, 128);
  line(10, 10, 30, 10);
  line(25, 15, 30, 10);
  line(25, 5, 30, 10);
  stroke(0, 0, 255, 128);
  line(10, 10, 10, 30);
  line(15, 25, 10, 30);
  line( 5, 25, 10, 30);
}

Scaling

The scale() function rescales the display relative to the origin. The scale() function can take a single parameter that effectively zooms in or out the display by uniformly scaling the display. A parameter value greater than 1 zooms in the display:

  • Show Code
/** @peep sketch */
void setup() {
  size(200, 200);
  background(204);
  scale(2);
  grid();
}
 
void grid() {
  stroke(0, 32);
  fill(255, 64);
  rect(0, 0, width, height);
  for (int x = 0; x <= width; x += 10) { line(x, 0, x, height); }
  for (int y = 0; y <= height; y += 10) { line(0, y, width, y); }
  stroke(255, 0, 0, 128);
  line(10, 10, 30, 10);
  line(25, 15, 30, 10);
  line(25, 5, 30, 10);
  stroke(0, 0, 255, 128);
  line(10, 10, 10, 30);
  line(15, 25, 10, 30);
  line( 5, 25, 10, 30);
}

A parameter value less than 1 zooms out the display:

  • Show Code
/** @peep sketch */
void setup() {
  size(200, 200);
  background(204);
  scale(0.5);
  grid();
}
 
void grid() {
  stroke(0, 32);
  fill(255, 64);
  rect(0, 0, width, height);
  for (int x = 0; x <= width; x += 10) { line(x, 0, x, height); }
  for (int y = 0; y <= height; y += 10) { line(0, y, width, y); }
  stroke(255, 0, 0, 128);
  line(10, 10, 30, 10);
  line(25, 15, 30, 10);
  line(25, 5, 30, 10);
  stroke(0, 0, 255, 128);
  line(10, 10, 10, 30);
  line(15, 25, 10, 30);
  line( 5, 25, 10, 30);
}

Notice that the thickness of lines is also scaled. To maintain the same thickness for lines the strokeWeight() function must be called with the original line thickness divided by the scale factor:

  • Show Code
/** @peep sketch */
void setup() {
  size(200, 200);
  background(204);
  float scaleFactor = 2.0;
  scale(scaleFactor);
  strokeWeight(1.0/scaleFactor);
  grid();
}
 
void grid() {
  stroke(0, 32);
  fill(255, 64);
  rect(0, 0, width, height);
  for (int x = 0; x <= width; x += 10) { line(x, 0, x, height); }
  for (int y = 0; y <= height; y += 10) { line(0, y, width, y); }
  stroke(255, 0, 0, 128);
  line(10, 10, 30, 10);
  line(25, 15, 30, 10);
  line(25, 5, 30, 10);
  stroke(0, 0, 255, 128);
  line(10, 10, 10, 30);
  line(15, 25, 10, 30);
  line( 5, 25, 10, 30);
}
  • Show Code
/** @peep sketch */
void setup() {
  size(200, 200);
  background(204);
  float scaleFactor = 0.5;
  scale(scaleFactor);
  strokeWeight(1.0/scaleFactor);
  grid();
}
 
void grid() {
  stroke(0, 32);
  fill(255, 64);
  rect(0, 0, width, height);
  for (int x = 0; x <= width; x += 10) { line(x, 0, x, height); }
  for (int y = 0; y <= height; y += 10) { line(0, y, width, y); }
  stroke(255, 0, 0, 128);
  line(10, 10, 30, 10);
  line(25, 15, 30, 10);
  line(25, 5, 30, 10);
  stroke(0, 0, 255, 128);
  line(10, 10, 10, 30);
  line(15, 25, 10, 30);
  line( 5, 25, 10, 30);
}

Just like the translate() and rotate() functions, the scale() function is applied relative to the existing scale:

  • Show Code
/** @peep sketch */
void setup() {
  size(200, 200);
  background(204);
  scale(0.8);
  grid();
  scale(0.8);
  grid();
  scale(0.8);
  grid();
}
 
void grid() {
  stroke(0, 32);
  fill(255, 64);
  rect(0, 0, width, height);
  for (int x = 0; x <= width; x += 10) { line(x, 0, x, height); }
  for (int y = 0; y <= height; y += 10) { line(0, y, width, y); }
  stroke(255, 0, 0, 128);
  line(10, 10, 30, 10);
  line(25, 15, 30, 10);
  line(25, 5, 30, 10);
  stroke(0, 0, 255, 128);
  line(10, 10, 10, 30);
  line(15, 25, 10, 30);
  line( 5, 25, 10, 30);
}

Combining Transformations

Affine transformations can be combined together in a Processing sketch by simply calling the functions in order. The order in which the transformations are applied is important. Compare the following two sketches, the first translates the origin and then rotates:

  • Show Code
/** @peep sketch */
void setup() {
  size(200, 200);
  background(204);
  translate(width/2, height/2);
  rotate(radians(30));
  grid();
}
 
void grid() {
  stroke(0, 32);
  fill(255, 64);
  rect(0, 0, width, height);
  for (int x = 0; x <= width; x += 10) { line(x, 0, x, height); }
  for (int y = 0; y <= height; y += 10) { line(0, y, width, y); }
  stroke(255, 0, 0, 128);
  line(10, 10, 30, 10);
  line(25, 15, 30, 10);
  line(25, 5, 30, 10);
  stroke(0, 0, 255, 128);
  line(10, 10, 10, 30);
  line(15, 25, 10, 30);
  line( 5, 25, 10, 30);
}

The second sketch rotates first and then translates the origin in the rotated space:

  • Show Code
/** @peep sketch */
void setup() {
  size(200, 200);
  background(204);
  rotate(radians(30));
  translate(width/2, height/2);
  grid();
}
 
void grid() {
  stroke(0, 32);
  fill(255, 64);
  rect(0, 0, width, height);
  for (int x = 0; x <= width; x += 10) { line(x, 0, x, height); }
  for (int y = 0; y <= height; y += 10) { line(0, y, width, y); }
  stroke(255, 0, 0, 128);
  line(10, 10, 30, 10);
  line(25, 15, 30, 10);
  line(25, 5, 30, 10);
  stroke(0, 0, 255, 128);
  line(10, 10, 10, 30);
  line(15, 25, 10, 30);
  line( 5, 25, 10, 30);
}

As you can see the results are quite different! To get a better idea for the effect of the transformation functions we can create "debug" versions of the functions that draw some useful information to the screen:

  • Show Code
/** @peep sketch */
void setup() {
  size(200, 200);
  background(204);
  debugTranslate(width/4, height/4);
  debugRotate(radians(30));
  debugScale(0.8);
  grid();
}
 
void grid() {
  stroke(0, 32);
  fill(255, 64);
  rect(0, 0, width, height);
  for (int x = 0; x <= width; x += 10) { line(x, 0, x, height); }
  for (int y = 0; y <= height; y += 10) { line(0, y, width, y); }
  stroke(255, 0, 0, 128);
  line(10, 10, 30, 10);
  line(25, 15, 30, 10);
  line(25, 5, 30, 10);
  stroke(0, 0, 255, 128);
  line(10, 10, 10, 30);
  line(15, 25, 10, 30);
  line( 5, 25, 10, 30);
}
 
void debugTranslate(float x, float y) {
  pushStyle();
  noFill();
  stroke(0, 0, 128, 64);
  line(0, 0, x, 0);
  line(x, 0, x, y);
  line(x, y, x-2.5, y-2.5);
  line(x, y, x+2.5, y-2.5);
  popStyle();
  translate(x, y);
}
 
void debugRotate(float a) {
  pushStyle();
  noFill();
  stroke(0, 0, 128, 64);
  line(0, 0, 25, 0);
  arc(0, 0, 40, 40, 0, a);
  pushMatrix();
  rotate(a);
  translate(20, 0);
  line(0, 0, -2.5, -2.5);
  line(0, 0, 2.5, -2.5);
  popMatrix();
  popStyle();
  rotate(a);
}
 
void debugScale(float s) {
  pushStyle();
  noFill();
  stroke(0, 0, 128, 64);
  rect(0, 0, width, height);
  rect(0, 0, s*width, s*height);
  line(s*width, 0, s*width + 2.5, -2.5);
  line(s*width, 0, s*width + 2.5,  2.5);
  line(0, s*height, -2.5, s*height + 2.5);
  line(0, s*height,  2.5, s*height + 2.5);
  line(width, height, s*width, s*height);
  pushMatrix();
  translate(s*width, s*height);
  rotate(atan2(height, width));
  line(0, 0, 2.5, -2.5);
  line(0, 0, 2.5,  2.5);
  popMatrix();
  popStyle();
  scale(s);
}
  • Show Code
/** @peep sketch */
void setup() {
  size(200, 200);
  background(204);
  debugScale(0.8);
  debugRotate(radians(30));
  debugTranslate(width/4, height/4);
  grid();
}
 
void grid() {
  stroke(0, 32);
  fill(255, 64);
  rect(0, 0, width, height);
  for (int x = 0; x <= width; x += 10) { line(x, 0, x, height); }
  for (int y = 0; y <= height; y += 10) { line(0, y, width, y); }
  stroke(255, 0, 0, 128);
  line(10, 10, 30, 10);
  line(25, 15, 30, 10);
  line(25, 5, 30, 10);
  stroke(0, 0, 255, 128);
  line(10, 10, 10, 30);
  line(15, 25, 10, 30);
  line( 5, 25, 10, 30);
}
 
void debugTranslate(float x, float y) {
  pushStyle();
  noFill();
  stroke(0, 0, 128, 64);
  line(0, 0, x, 0);
  line(x, 0, x, y);
  line(x, y, x-2.5, y-2.5);
  line(x, y, x+2.5, y-2.5);
  popStyle();
  translate(x, y);
}
 
void debugRotate(float a) {
  pushStyle();
  noFill();
  stroke(0, 0, 128, 64);
  line(0, 0, 25, 0);
  arc(0, 0, 40, 40, 0, a);
  pushMatrix();
  rotate(a);
  translate(20, 0);
  line(0, 0, -2.5, -2.5);
  line(0, 0, 2.5, -2.5);
  popMatrix();
  popStyle();
  rotate(a);
}
 
void debugScale(float s) {
  pushStyle();
  noFill();
  stroke(0, 0, 128, 64);
  rect(0, 0, width, height);
  rect(0, 0, s*width, s*height);
  line(s*width, 0, s*width + 2.5, -2.5);
  line(s*width, 0, s*width + 2.5,  2.5);
  line(0, s*height, -2.5, s*height + 2.5);
  line(0, s*height,  2.5, s*height + 2.5);
  line(width, height, s*width, s*height);
  pushMatrix();
  translate(s*width, s*height);
  rotate(atan2(height - s*height, width - s*width));
  line(0, 0, 2.5, -2.5);
  line(0, 0, 2.5,  2.5);
  popMatrix();
  popStyle();
  scale(s);
}

NOTE: You don't have to worry too much about the implementation of the "debug" versions of the transformation functions, these are provided here for convenience.

Experiment with the above sketches using the "debug" versions of the transformation functions to see the effect of using different values for the parameter values and try to get a good feel for how the transformation functions affect each other.

Repeating transformations

As you may have discovered from the above experiment, when the translate() function is applied before the scale() or rotate() it is much easier to control the position of a drawing on the screen.

Using this knowledge it is a simple step to working with loops and transformations to create complex patterns in a controlled way. For example, the following sketch translates the origin to the centre of the display window, scales down the coordinate system to one third it's original size, and draws the grid shape rotated around the origin 10 times:

  • Show Code
/** @peep sketch */
void setup() {
  size(200, 200);
  background(204);
  translate(width/2, height/2);
  scale(1.0/3.0);
  for (int i = 0; i < 10; i++) {
    grid();
    rotate(radians(360.0/10.0));
  }
}
 
void grid() {
  stroke(0, 32);
  fill(255, 64);
  rect(0, 0, width, height);
  for (int x = 0; x <= width; x += 10) { line(x, 0, x, height); }
  for (int y = 0; y <= height; y += 10) { line(0, y, width, y); }
  stroke(255, 0, 0, 128);
  line(10, 10, 30, 10);
  line(25, 15, 30, 10);
  line(25, 5, 30, 10);
  stroke(0, 0, 255, 128);
  line(10, 10, 10, 30);
  line(15, 25, 10, 30);
  line( 5, 25, 10, 30);
}

Experiment with the above sketch. Try to reproduce the following drawings:

0

Hint: How would you increase the number of times the loop is executed?

1

Hint: How can you change the size of the grid each time the loop is executed?

2

Hint: How can you chance the location of the grid each time the loop is executed?

Saving and restoring transformations

Processing allows the transformed state of the canvas to be saved and restored. The functions that save and restore the current state are called pushMatrix() and popMatrix() because they save the current "transformation matrix", which stores all of the information about the translations, rotations and scalings that have been applied.

These functions can be very useful when we want to arrange multiple transformed drawings on screen, because we don't have to work out how to transform from one state to another. For example, imagine that we wanted to arrange a small grid of transformed shapes with different locations:

  • Show Code
/** @peep sketch */
 
void setup() {
  size(200, 200);
  background(204);
  for (int x = 0; x <= width; x += 40) {
    for (int y = 0; y <= height; y += 40) {
      pushMatrix();
      translate(x, y);
      float angle = map(x+y, 0, width+height, -60, 60);
      rotate(radians(angle));
      scale(0.25);
      translate(-width/2, -height/2);
      grid();
      popMatrix();
    }
  }
}
 
void grid() {
  stroke(0, 32);
  fill(255, 64);
  rect(0, 0, width, height);
  for (int x = 0; x <= width; x += 10) { line(x, 0, x, height); }
  for (int y = 0; y <= height; y += 10) { line(0, y, width, y); }
  stroke(255, 0, 0, 128);
  line(10, 10, 30, 10);
  line(25, 15, 30, 10);
  line(25, 5, 30, 10);
  stroke(0, 0, 255, 128);
  line(10, 10, 10, 30);
  line(15, 25, 10, 30);
  line( 5, 25, 10, 30);
}

We can easily substitute the drawing of the grid above with the star() function that we developed in the previous tutorial:

  • Show Code
/** @peep sketch */
 
void setup() {
  size(200, 200);
  background(204);
  fill(255, 191);
  stroke(0, 191);
  strokeWeight(3);
  for (int x = 0; x <= width; x += 40) {
    for (int y = 0; y <= height; y += 40) {
      pushMatrix();
      translate(x, y);
      float angle = map(x+y, 0, width+height, -60, 60);
      rotate(radians(angle));
      star(0, 0, 10, 25);
      popMatrix();
    }
  }
}
 
void star(float x, float y, float inner, float outer) {
  int points = 5;
  beginShape();
  float delta = radians(360/points);
  for (int i = 0; i < points; i++) {
    float ox = x + outer * cos(i*delta);
    float oy = y + outer * sin(i*delta);
    vertex(ox, oy);
    float ix = x + inner * cos(i*delta + delta/2);
    float iy = y + inner * sin(i*delta + delta/2);
    vertex(ix, iy);
  }
  endShape(CLOSE);
}

Try to adapt the code that you wrote to draw many rotations of the grid in the previous chapter, to draw the following:

3

Hint: You need to isolate the translation of the grid from the origin using the pushMatrix() and popMatrix() functions, just before you draw it.

Combine the translation of the grid with the other sketches that you developed for the last chapter to produce drawings similar to the following:

4

5

Use the star() function from the above sketch and combine it with one of the rotated grid drawings to produce something like the following:

6

Composing drawings using transformations

While the basic drawing functions provided by Processing take location information to allow shapes to be drawn away from the origin of the canvas, it can be useful to draw using these functions relative to a "local coordinate system". For example, we can take the face() function that we started with in the previous tutorial and adapt it slightly to draw a background rectangle over the whole display window:

  • Show Code
/** @peep sketch */
 
void setup() {
  size(200, 200);
  background(204);
  face(80, 50, 40);
}
 
void face(int x, int y, int gap) {
  pushStyle();
  rect(0, 0, width, height);
  line(x, 0, x, y);               // Nose Bridge 
  line(x, y, x+gap, y);           // Nose 
  line(x+gap, y, x+gap, height); 
  int mouthY = (height+y)/2;
  line(x, mouthY, x+gap, mouthY); // Mouth 
  fill(0);
  ellipse(x-gap/2, y/2, 5, 5);    // Left eye 
  ellipse(x+gap, y/2, 5, 5);      // Right eye 
  popStyle();
}

Using the transformation functions we can translate and scale the face to different positions and sizes without having to add parameters to the function:

  • Show Code
/** @peep sketch */
 
void setup() {
  size(200, 200);
  background(204);
  translate(width/3, height/3);
  scale(0.2);
  strokeWeight(1.0/0.2);
  face(80, 50, 40);
}
 
void face(int x, int y, int gap) {
  pushStyle();
  rect(0, 0, width, height);
  line(x, 0, x, y);               // Nose Bridge 
  line(x, y, x+gap, y);           // Nose 
  line(x+gap, y, x+gap, height); 
  int mouthY = (height+y)/2;
  line(x, mouthY, x+gap, mouthY); // Mouth 
  fill(0);
  ellipse(x-gap/2, y/2, 5, 5);    // Left eye 
  ellipse(x+gap, y/2, 5, 5);      // Right eye 
  popStyle();
}

The above sketch compensates for the use of scale() to ensure that the thickness of the stroke remains.

Notice that this makes writing functions like face() much simpler because we don't have to deal with the extra parameters to control the position and size of the drawing, as we did in the previous tutorial. We can also rotate the drawing of the face, which is something that would have been much more difficult to code within the face() function itself.

  • Show Code
/** @peep sketch */
 
void setup() {
  size(200, 200);
  background(204);
  translate(width/3, height/3);
  scale(0.2);
  strokeWeight(1.0/0.2);
  rotate(radians(30));
  face(80, 50, 40);
}
 
void face(int x, int y, int gap) {
  pushStyle();
  rect(0, 0, width, height);
  line(x, 0, x, y);               // Nose Bridge 
  line(x, y, x+gap, y);           // Nose 
  line(x+gap, y, x+gap, height); 
  int mouthY = (height+y)/2;
  line(x, mouthY, x+gap, mouthY); // Mouth 
  fill(0);
  ellipse(x-gap/2, y/2, 5, 5);    // Left eye 
  ellipse(x+gap, y/2, 5, 5);      // Right eye 
  popStyle();
}

Notice, however, that like the grid() function that we've been using to illustrate the effects of applying transformations in this tutorial, the face() function doesn't draw the face centred on the origin, and so when rotations are applied they "swing" the face around it's top-left hand corner. We can easily adapt the face() function to draw centred around the origin by applying a local translation to the drawing:

    void face(int x, int y, int gap) {
      pushStyle();
      pushMatrix();
      translate(-width/2, -height/2);
      rect(0, 0, width, height);
      line(x, 0, x, y);               // Nose Bridge 
      line(x, y, x+gap, y);           // Nose 
      line(x+gap, y, x+gap, height); 
      int mouthY = (height+y)/2;
      line(x, mouthY, x+gap, mouthY); // Mouth 
      fill(0);
      ellipse(x-gap/2, y/2, 5, 5);    // Left eye 
      ellipse(x+gap, y/2, 5, 5);      // Right eye 
      popMatrix();
      popStyle();
    }

    At this point, we might also want to add the ability to set the relative width and height of the face, so that the shape can be varied. We could do this by calling the scale() with two parameters to scale in the x and y directions independently, but the problem with this approach is that the line thickness will be different in x and y, which will always cause some problems. Instead, we can think of the width and height of the face as controlling the shape, and leave the controlling of the size to the code calling the face() function.

      void face(int w, int h, int x, int y, int gap) {
        pushStyle();
        pushMatrix();
        translate(-w/2, -h/2);
        rect(0, 0, w, h);
        line(x, 0, x, y);               // Nose Bridge 
        line(x, y, x+gap, y);           // Nose 
        line(x+gap, y, x+gap, h); 
        int mouthY = (h+y)/2;
        line(x, mouthY, x+gap, mouthY); // Mouth 
        fill(0);
        ellipse(x-gap/2, y/2, 5, 5);    // Left eye 
        ellipse(x+gap, y/2, 5, 5);      // Right eye 
        popMatrix();
        popStyle();
      }

      We can see the difference of these changes in the following sketch that rotates the face around it's centre and has a different shape to the square one we had before:

      • Show Code
      /** @peep sketch */
       
      void setup() {
        size(200, 200);
        background(204);
        translate(width/3, height/3);
        scale(0.2);
        strokeWeight(1.0/0.2);
        rotate(radians(30));
        face(160, 200, 80, 50, 40);
      }
       
      void face(int w, int h, int x, int y, int gap) {
        pushStyle();
        pushMatrix();
        translate(-w/2, -h/2);
        rect(0, 0, w, h);
        line(x, 0, x, y);               // Nose Bridge 
        line(x, y, x+gap, y);           // Nose 
        line(x+gap, y, x+gap, h); 
        int mouthY = (h+y)/2;
        line(x, mouthY, x+gap, mouthY); // Mouth 
        fill(0);
        ellipse(x-gap/2, y/2, 5, 5);    // Left eye 
        ellipse(x+gap, y/2, 5, 5);      // Right eye 
        popMatrix();
        popStyle();
      }

      We can use this to produce something like the crowd drawing that we created in the last tutorial, except that now the faces can be rotated by a small amount, giving the drawing a more dynamic composition:

      • Show Code
      /** @peep sketch */
       
      void setup() {
        size(200, 200);
        background(204);
        for (int i = 0; i < 50; i++) {
          pushMatrix();
          translate(random(width), random(height));
          float scaleFactor = random(0.15, 0.25);
          scale(scaleFactor);
          strokeWeight(1.0/scaleFactor);
          rotate(radians(random(-10, 10)));
          int fw = random(140, 200);
          int fh = random(180, 200);
          int fx = random(fw/6, 5*fw/6);
          int fy = random(fh/6, 4*fh/6);
          int fgap = random((fw-fx)/5, (fw-fx)/3);
          face(fw, fh, fx, fy, fgap);
          popMatrix();
        }
      }
       
      void face(int w, int h, int x, int y, int gap) {
        pushStyle();
        pushMatrix();
        translate(-w/2, -h/2);
        rect(0, 0, w, h);
        line(x, 0, x, y);               // Nose Bridge 
        line(x, y, x+gap, y);           // Nose 
        line(x+gap, y, x+gap, h); 
        int mouthY = (h+y)/2;
        line(x, mouthY, x+gap, mouthY); // Mouth 
        fill(0);
        ellipse(x-gap/2, y/2, 5, 5);    // Left eye 
        ellipse(x+gap, y/2, 5, 5);      // Right eye 
        popMatrix();
        popStyle();
      }

      Use what you've learned in this tutorial about transformation functions to compose a drawing using the updated face() function. For example, you might want to try to create another drawing of a multi-cultural crowd of faces, using the scale() function with a value linked to the y coordinate of each face, to give the illusion of perspective:

      7

      Comments