Assignment 1: Self Portrait | Hasibur

For this assignment, we were asked to create a self-portrait using p5.js. I started by looking into the listed self-portraits and previous students work. I was heavily inspired by a few of them and later incorporated a few styles from their portraits. [1. https://editor.p5js.org/Sarthak-Malla/full/3NRqaQVKQ, 2. Koala portrait, 3. Assignment 1: Self-portrait]

I wanted to have a full-body portrait. However, I could not settle on one. Later, I came across this portrait online and decided to follow a similar structure. I initiated the process by sketching a rudimentary outline, focusing initially on the facial features. I used the bezier for the eye brows and the curveVertex for the hair. For the bezier shape, I used this tutorial. In the rest of the portrait, I just used the primitive shapes.

Each cell is a dynamic entity, its behavior governed by its position and the cursor’s proximity. The canvas isn’t just a static backdrop but a living part of the portrait, changing its hues and vibrancy in real-time. Each cell on the canvas pulsates with an oscillating brightness, creating a mesmerizing wave effect. This is not just a pre-programmed animation; the cells also react to the position of your mouse, creating an interactive dance of light and shadow.

Plain text
Copy to clipboard
Open code in new window
EnlighterJS 3 Syntax Highlighter
function drawBackground() {
for (let r = 0; r < rows; r++) {
for (let c = 0; c < columns; c++) {
let distance = dist(mouseX, mouseY, c * cellWidth, r * cellHeight);
let offset = map(distance, 0, sqrt(sq(width) + sq(height)), 1, 0);
let wave = (sin(time - c * r * 0.1) + 1) / 2;
let brightness = map(wave, 0, 1, 100, 255) * offset;
cells[r][c] = brightness;
fill(brightness * 0.9, brightness * 0.7, brightness);
noStroke();
rect(c * cellWidth, r * cellHeight, cellWidth, cellHeight);
}
}
}
function drawBackground() { for (let r = 0; r < rows; r++) { for (let c = 0; c < columns; c++) { let distance = dist(mouseX, mouseY, c * cellWidth, r * cellHeight); let offset = map(distance, 0, sqrt(sq(width) + sq(height)), 1, 0); let wave = (sin(time - c * r * 0.1) + 1) / 2; let brightness = map(wave, 0, 1, 100, 255) * offset; cells[r][c] = brightness; fill(brightness * 0.9, brightness * 0.7, brightness); noStroke(); rect(c * cellWidth, r * cellHeight, cellWidth, cellHeight); } } }
function drawBackground() {
  for (let r = 0; r < rows; r++) {
    for (let c = 0; c < columns; c++) {
      let distance = dist(mouseX, mouseY, c * cellWidth, r * cellHeight);
      let offset = map(distance, 0, sqrt(sq(width) + sq(height)), 1, 0);

      let wave = (sin(time - c * r * 0.1) + 1) / 2;
      let brightness = map(wave, 0, 1, 100, 255) * offset;

      cells[r][c] = brightness;

      fill(brightness * 0.9, brightness * 0.7, brightness); 
      noStroke();
      rect(c * cellWidth, r * cellHeight, cellWidth, cellHeight);
    }
  }
}

The eyes follow your cursor, adding a layer of depth and engagement. The mouth opens and closes, reacting to the cursor’s vertical position, making the portrait not just seen but also felt.

Plain text
Copy to clipboard
Open code in new window
EnlighterJS 3 Syntax Highlighter
function drawMouth() {
noStroke();
// calculate the openness of the mouth based on mouseY position
let deltay = ((400 - mouseY) / 400) * 13 + 6;
if(deltay <= 9.5) deltay=9.5;
// outer mouth (lips)
fill(255, 150, 122); // color for lips
ellipse(200, 173, 27, deltay);
// inner mouth (white part to represent teeth or inside of mouth)
let innerMouthHeight = deltay - 2;
fill(255); // white color for the inner mouth
if (innerMouthHeight > 8) {
// show inner mouth only if it's significantly open
ellipse(200, 173, 20, innerMouthHeight);
// black line to split the white part (teeth or mouth separation)
if (innerMouthHeight > 10) {
fill(0); // black color for the separation
let separationHeight = 4; // height of the separation line
rect(200 - 10, 173 - separationHeight / 2, 20, separationHeight, 10); // centered black line
}
else {
fill(0)
let separationHeight = 1; // height of the separation line
rect(200 - 10, 173 - separationHeight / 2, 20, separationHeight, 10);
}
}
}
function drawMouth() { noStroke(); // calculate the openness of the mouth based on mouseY position let deltay = ((400 - mouseY) / 400) * 13 + 6; if(deltay <= 9.5) deltay=9.5; // outer mouth (lips) fill(255, 150, 122); // color for lips ellipse(200, 173, 27, deltay); // inner mouth (white part to represent teeth or inside of mouth) let innerMouthHeight = deltay - 2; fill(255); // white color for the inner mouth if (innerMouthHeight > 8) { // show inner mouth only if it's significantly open ellipse(200, 173, 20, innerMouthHeight); // black line to split the white part (teeth or mouth separation) if (innerMouthHeight > 10) { fill(0); // black color for the separation let separationHeight = 4; // height of the separation line rect(200 - 10, 173 - separationHeight / 2, 20, separationHeight, 10); // centered black line } else { fill(0) let separationHeight = 1; // height of the separation line rect(200 - 10, 173 - separationHeight / 2, 20, separationHeight, 10); } } }
function drawMouth() {
  noStroke();

  // calculate the openness of the mouth based on mouseY position
  let deltay = ((400 - mouseY) / 400) * 13 + 6;
 
  if(deltay <= 9.5) deltay=9.5;

  // outer mouth (lips)
  fill(255, 150, 122); // color for lips
  ellipse(200, 173, 27, deltay);

  // inner mouth (white part to represent teeth or inside of mouth)
  let innerMouthHeight = deltay - 2; 
  fill(255); // white color for the inner mouth
  if (innerMouthHeight > 8) {
    // show inner mouth only if it's significantly open
    ellipse(200, 173, 20, innerMouthHeight);

    // black line to split the white part (teeth or mouth separation)
    if (innerMouthHeight > 10) {
      fill(0); // black color for the separation
      let separationHeight = 4; // height of the separation line
      rect(200 - 10, 173 - separationHeight / 2, 20, separationHeight, 10); // centered black line
    }
    else {
      fill(0)
      let separationHeight = 1; // height of the separation line
      rect(200 - 10, 173 - separationHeight / 2, 20, separationHeight, 10); 
    }
  }
}

While working, I couldn’t figure out the nose style. Most portraits that I have seen use triangles or similar shapes. I found this portrait to have a very distinctive style, which I followed. Creating the hair shape took a lot of time for me. I am still not particularly happy with the hair. I wanted more realism for the hair shape.

In the future, I want to create a Lego character resembling my character and try to create a self-portrait based on the Lego character, which I believe would be easily achievable using the primitive shapes.

Leave a Reply