[Week 1] Self-portrait with Processing

For the first assignment, I want to explore the various drawing functions in the Processing library for Java. Here’s a glimpse at my product:

 

Processes

I found out that the function createShape() can be used to define a new shapes on the basis of other primitive shape functions like ellipse(), rect(), arc(), etc. Each of the shape created this way can be stored in a PShape object and will later be drawn with the function shape(). I find this to be a particularly neat way of organizing different components of the sketch without having to construct new classes.

So, utilizing createShape(), I organize my drawing into different components: face, hair, mouth, and eyes. Each of these component is constructed from one or more PShape objects which are all included in one big object called portrait.

The face is a simple, classic ellipse. Nothing much to see here.

The hair is a bit more complex. I divide it into four objects: upperHair, lowerHair, leftFringe, and rightFringe.

The upper part of the hair is also a simple ellipse like the face, just slightly bigger. For the lower hair, I use a rectangle with a specified radius value to make the corners round. The top of the rectangle overlaps with the lower half of the upper hair ellipse to create a view of consistency.

The fringe, even though I have chosen to stick to primitive shapes, proves to be quite tricky. I first try to construct a curve using the edge of an ellipse.

 

The problem is that with this, I expected to be able to use the contour function to remove the unwanted part of the ellipse, keeping only the part that overlaps with the back hair. It turns out that beginContour() and endContour() cannot be used on an ellipse. There might be a way to get around this so I hope I can return to this problem later.

So instead of drawing a big ellipse hoping to cut out unwanted part, I try to draw a smaller ellipse with approximately the same fringe curve that fits right into the head. For this, I find rotate() really useful. One thing to note is that the coordinates are always rotated around their relative positive to the origin (from Processing reference page), so it will be best to create the shape at the origin (0, 0), move it to the desired position using translate(), then rotate().

arc() is very handy for drawing half an ellipse for the mouth.

For the eyes, I want something a little different from ellipses, so I turn to rect() instead with high radius value so that the eyes (and the eyeballs) will look rounded instead of sharp.

Development

First, as I mentioned above, I want to explore contour a bit more to see if there is a way to resolve the fringe problem without compromising the shapes.

Second, I would want to add some animations to the drawing. I was working on a blinkEyes() function (code at the end) but it is not finished. Right now the blinking motion looks very un-blinking-like, so I want to improve the piece of code too.

Code

PShape portrait, face, upperHair, lowerHair, leftFringe, rightFringe, mouth, leftEye, rightEye, leftEyeball, rightEyeball;

int faceWidth = 180;
int faceHeight = 230;
int leftXOfFace;
int rightXOfFace;
color skinColor = color(255, 181, 159);

int hairWidth = faceWidth + 20;
int hairHeight = faceHeight + 20;
color hairColor = color(102, 58, 37);
int roundFactor = 30;

color mouthColor = color(165, 69, 68);

float eyeWidth;
float eyeHeight;

PShape drawEye(int x, int y) {
  /*
  * @param x,y coordinates of the upper left corner of the eye
  * @return PShape object of the eye
  */
  PShape eye = createShape(RECT, x, y, eyeWidth, eyeHeight, roundFactor);
  return eye;
}

PShape drawEyeball(int x, int y) {
  /*
  * @param x,y coordinates of the upper left corner of the eyeball
  * @return PShape object of the eyeball
  */
  PShape eyeball = createShape(RECT, x, y, eyeWidth*.5, eyeHeight*.5, roundFactor);
  return eyeball;
}

void setup() {
  size(640, 480);
  frameRate(30);
  
  leftXOfFace = width/2 - faceWidth/2;
  rightXOfFace = width/2 + faceWidth/2;
  eyeWidth = faceWidth*.2;
  eyeHeight = faceHeight*.2;
  
  portrait = createShape(GROUP);

  face = createShape(ELLIPSE, width/2, height/2, faceWidth, faceHeight);
  face.setFill(skinColor);
  face.setStroke(false);

  upperHair = createShape(ELLIPSE, width/2, height/2, hairWidth, hairHeight);
  upperHair.setFill(hairColor);
  upperHair.setStroke(false);

  lowerHair = createShape(RECT, leftXOfFace-10, height/2, hairWidth, faceHeight, 0, 0, roundFactor, roundFactor);
  lowerHair.setFill(hairColor);
  lowerHair.setStroke(false);
  
  leftFringe =  createShape(ELLIPSE, 0, 0, hairWidth*.75, hairHeight*.3);
  leftFringe.translate(width/2-faceWidth*.2, height/2-faceHeight*.28);
  leftFringe.rotate(-PI*.23);
  leftFringe.setFill(hairColor);
  leftFringe.setStroke(false);
  
  rightFringe = createShape(ELLIPSE, 0, 0, hairWidth*.6, hairHeight*.2);
  rightFringe.translate(width/2+faceWidth*.27, height/2-faceHeight*.28);
  rightFringe.rotate(PI*.28);
  rightFringe.setFill(hairColor);
  rightFringe.setStroke(false);

  mouth = createShape(ARC, width/2, height/2+faceWidth*.25, faceWidth*.5, faceHeight*.3, 0, PI, CHORD);
  mouth.setFill(mouthColor);
  mouth.setStroke(false);
  
  leftEye = drawEye(0, 0);
  leftEye.translate(width/2-faceWidth*.25-eyeWidth/2, height/2-faceHeight*.15);
  leftEye.setStroke(false);
  
  leftEyeball = drawEyeball(0, 0);
  leftEyeball.setFill(color(0, 0, 0));
  leftEyeball.translate(width/2-faceWidth*.25, height/2-faceHeight*.15+eyeHeight*.3);
  leftEyeball.setStroke(false);
  
  rightEye = drawEye(0, 0);
  rightEye.translate(width/2+faceWidth*.25-eyeWidth/2, height/2-faceHeight*.15);
  rightEye.setStroke(false);
  
  rightEyeball = drawEyeball(0, 0);
  rightEyeball.setFill(color(0, 0, 0));
  rightEyeball.translate(width/2+faceWidth*.25-eyeWidth/2, height/2-faceHeight*.15+eyeHeight*.3);
  rightEyeball.setStroke(false);
  
  // .addChild(): object added later will be placed on top
  portrait.addChild(upperHair);
  portrait.addChild(lowerHair);
  portrait.addChild(face);
  portrait.addChild(leftEye);
  portrait.addChild(rightEye);
  portrait.addChild(leftEyeball);
  portrait.addChild(rightEyeball);
  portrait.addChild(leftFringe);
  portrait.addChild(rightFringe);
  portrait.addChild(mouth);
}

void draw() {
  background(255);
  shape(portrait);
}
void blinkEyes() {
  /* Create animation of eyes blinking
  * @param None
  * @return None
  */
  if (eyeHeight > 1) {
    eyeHeight -= 1;
  } else {
    eyeHeight = faceHeight*.2;
  }
  eyes = createShape(GROUP);
  leftEye = drawEye(0, 0);
  leftEye.translate(width/2-faceWidth*.25-eyeWidth/2, height/2-faceHeight*.15);
  leftEye.setStroke(false);
  
  leftEyeball = drawEyeball(0, 0);
  leftEyeball.setFill(color(0, 0, 0));
  leftEyeball.translate(width/2-faceWidth*.25, height/2-faceHeight*.15+eyeHeight*.3);
  leftEyeball.setStroke(false);
  
  rightEye = drawEye(0, 0);
  rightEye.translate(width/2+faceWidth*.25-eyeWidth/2, height/2-faceHeight*.15);
  rightEye.setStroke(false);
  
  rightEyeball = drawEyeball(0, 0);
  rightEyeball.setFill(color(0, 0, 0));
  rightEyeball.translate(width/2+faceWidth*.25-eyeWidth/2, height/2-faceHeight*.15+eyeHeight*.3);
  rightEyeball.setStroke(false);
  
  eyes.addChild(leftEye);
  eyes.addChild(rightEye);
  eyes.addChild(leftEyeball);
  eyes.addChild(rightEyeball);
  shape(eyes);
}