Week 1: Self-Portrait

Week 1: Self Portrait

Concept

I wanted to create a self-portrait that reflected my at times chaotic attention span, along with my curly hair. I created a relatively simple portrait, but spent a lot of time tweaking the curls and the eyes (that represent my constantly shifting attention).

Process

Constructing my self-portrait with P5.js was a relatively iterative process, that required bouncing between the provided reference manual and the IDE itself.

Highlight: One of my favorite parts of code is how the curls are drawn. While I was considering what approaches may work, I realized that my multi-variable calculus class could come in handy, representing spirals as parametric equations.

function drawSpiral(centerX, centerY, scale, strokeThickness) {
  let numPoints = 1000;
  let maxT = 6 * PI;
  let step = maxT / numPoints;

  stroke(0);
  strokeWeight(strokeThickness);

  for (let t = 0; t <= maxT; t += step) {
    let x = scale * sin(t) * t;
    let y = scale * cos(t) * t;
    point(centerX + x, centerY + y);
  }
}

Improvements: A potential improvement I would like to implement in the future is to randomize the curls in a more organic way, rather than generating uniformly-random points. For instance, they could be generated using my hair semi-circle, with semi-randomly deviations from a perfect line.

Another potential improvement may be to produce an algorithm that can more efficiently draw the rotating spirals. In it’s current implementation, it appears to be quite computationally expensive, and slows down if too many curls are present.

Code

let size = 600;
let midX;
let midY;
let curlNums = 30;
let curlSpeeds;
let curlSizes;
let curlThiccs;

function setup() {
  createCanvas(size, size);
  midX = width / 2;
  midY = height / 2;

  curlSpeeds = generateRandomArray(curlNums, -5, 5);
  curlSizes = generateRandomArray(curlNums, 1, 2);
  curlThiccs = generateRandomArray(curlNums, 2, 5);
  curlXs = generateRandomArray(curlNums, 150, 450);
  curlYs = generateRandomArray(curlNums, 75, 200);
}

function generateRandomArray(length, min, max) {
  return Array.from({ length }, () => getRandomInRange(min, max));
}

function getRandomInRange(min, max) {
  return Math.random() * (max - min + 1) + min;
}

function drawSpinningSpiral(
  centerX,
  centerY,
  scale,
  strokeThickness,
  rotationSpeed
) {
  push();
  translate(centerX, centerY);
  rotate(frameCount * rotationSpeed * 0.01);
  drawSpiral(0, 0, scale, strokeThickness);
  pop();
}

function drawSpiral(centerX, centerY, scale, strokeThickness) {
  let numPoints = 1000;
  let maxT = 6 * PI;
  let step = maxT / numPoints;

  stroke(0);
  strokeWeight(strokeThickness);

  for (let t = 0; t <= maxT; t += step) {
    let x = scale * sin(t) * t;
    let y = scale * cos(t) * t;
    point(centerX + x, centerY + y);
  }
}

function draw() {
  background(255);

  // Head
  noStroke();
  fill("#FFD58C");
  let headSize = size * 0.6;
  let headSizeW = headSize * 0.95;
  let headSizeH = headSize * 1.1;
  ellipse(midX, midY, headSizeW, headSizeH);

  // Eyes
  fill(0);
  let eyeSize = headSize * 0.1;

  // Movement
  let xOffset = 5 * cos(frameCount / 10);
  let yOffset = 5 * sin(frameCount / 10);

  // Left Eye -- todo - ovals
  let leftEyeX = midX / 1.2 + xOffset;
  let leftEyeY = midY / 1.2 + yOffset;
  circle(leftEyeX, leftEyeY, eyeSize);

  // Right eye
  let rightEyeX = size - leftEyeX;
  let rightEyeY = leftEyeY;
  circle(rightEyeX, rightEyeY, eyeSize);

  // Eyebrows
  let eyeBrowWidth = eyeSize;
  let eyeBrowHeight = eyeSize * 0.2;
  let eyeBrowThicc = eyeSize * 0.2;
  noFill();
  stroke(0);
  strokeWeight(eyeBrowThicc);

  // Left eyebrow
  let leftEyeBrowX = leftEyeX;
  let leftEyeBrowY = leftEyeY - eyeBrowWidth;
  arc(leftEyeBrowX, leftEyeBrowY, eyeBrowWidth, eyeBrowHeight, PI, 2 * PI);

  // Right eyebrow
  let rightEyeBrowX = rightEyeX;
  let rightEyeBrowY = rightEyeY - eyeBrowWidth;
  arc(rightEyeBrowX, rightEyeBrowY, eyeBrowWidth, eyeBrowHeight, PI, 2 * PI);

  // Nose bar
  let noseHeight = headSize * 0.08;
  let noseTopOffset = headSize * 0.04;
  let noseTopX = midX;
  let noseTopY = midY + noseTopOffset;
  let noseBottomX = midX;
  let noseBottomY = noseTopY + noseHeight;
  let noseThicc = headSize * 0.02;
  strokeWeight(noseThicc);
  line(noseTopX, noseTopY, noseBottomX, noseBottomY);

  // Nose arc
  let noseArcSize = headSize * 0.06;
  let noseArcLeftX = noseBottomX - noseArcSize * 0.5;
  let noseArcLeftY = noseBottomY;
  let noseArcWidth = noseArcSize;
  let noseArcHeight = noseArcSize;
  arc(noseArcLeftX, noseArcLeftY, noseArcWidth, noseArcHeight, 0, PI);

  // Smile arc
  let smileArcSize = headSize * 0.4;
  let smileYOffset = headSize * 0.25;
  let smileArcLeftX = midX;
  let smileArcLeftY = midY + smileYOffset;
  let smileArcWidth = smileArcSize;
  let smileArcHeight = smileArcSize / 2;
  arc(smileArcLeftX, smileArcLeftY, smileArcWidth, smileArcHeight, 0, PI);

  // Hair
  let hairTopX = midX;
  let hairTopY = midY;
  let hairWidth = headSize;
  let hairHeight = headSize / 2;
  let hairThicc = headSize / 10;
  stroke(20);
  strokeWeight(hairThicc);
  arc(hairTopX, hairTopY, headSizeW, headSizeH, 1.1 * PI, 1.9 * PI);

  // Curls
  let curlScale = 2;
  let curlThicc = 7;
  let curlSpeed = 3;
  for (let i = 0; i < curlNums; i++) {
    drawSpinningSpiral(
      curlXs[i],
      curlYs[i],
      curlSizes[i],
      curlThiccs[i],
      curlSpeeds[i]
    );
  }
}

 

Leave a Reply