Assignment 1: Self Portrait

Here is my final self-portrait:

Concept:

For this project, I created a self-portrait using only 2D primitives. My goal was to make the portrait have many traits of me while still keeping the forms simple, using what we learned in class about 2D primitives and the p5 reference website. I used the same hijab color I wear most often, gold jewelry that is similar to what I wear every day, and burgundy for the shirt since it is my favorite color. 

How this was made:

The portrait was created using basic 2D primitives such as the ellipse, rect, arc, line, and quad. It was really hard positioning the shapes, especially when creating the lashes and the bent right arm. It was a constant trial-and-error process, which helped me better understand how each value in the shape controls the position and form.

For the face, I was not sure what exactly to do, especially because my knowledge of the platform and scope was limited. But I kept the features minimal by just using simple shapes, like an ellipse for the eyes, a small half-triangle nose made with lines, and an arc for the mouth. I was initially unsure how to create the hijab, but by experimenting with a large black ellipse and putting it behind and around my face, I was able to create a framing effect that resembles a hijab without making it too difficult. 

After making the portrait of myself, I decided to experiment with some motion and interactivity, even though we have not covered it yet in class. I started looking at some examples, and I saw many people make the pupils of the eye follow the mouse. So I did this by slightly adding mouseX and mouseY values to their original positions and then limiting the movement by multiplying by 0.008 so the pupils stayed inside the eye. I learned this by reviewing the slides to get an understanding of the variables and watching some tutorials from “The Coding Train” to get a better idea of it. 

Code highlight:

One part of the code that I am particularly proud of is the thought bubble interaction. When you click on the portrait, two thought bubbles appear with random words generated inside them to represent my thoughts. 

let showThoughts = false;//to make the thoughts hidden at first

let thoughts = [
  "THINKING",
  "IDK",
  "SCHOOL",
  "FUTURE",
  "TIME",
  "WHY",
  "WHAT IF",
  "PRESSURE",
  "OK",
  "TIRED"
]; //words I want to have in the background

function setup() {
  createCanvas(400,400);
  }

function mousePressed() {
  showThoughts = !showThoughts;
}//so if you click the thoughts the thoughts appear and if you click again the thoughts disappear.

function draw() {
background('#D2D1C7');//to set the background
print(mouseX, mouseY)
 
//Thought words (the background layer)
//fill(0,80); //making the words black and low opacity
//stroke(0,80);//so the stroke is the same color as the fill
//strokeWeight (3); //to make the text bold
//textSize(20);//to make the text bigger
//text(random(thoughts), random(width), random(height));//to draw a random word at a random position.

  
  
//Only draw thoughts if clicked
if (showThoughts) {
//Thought bubbles
noStroke();
fill(255, 230);//soft white and slightly transparent
ellipse(110, 90, 200, 140);//left bubble shape
ellipse(297, 85, 200, 140);//right bubble shape

//Thought words
fill(0);//black
stroke(0);
strokeWeight(2);
textSize(20);

//Left bubble text
text(random(thoughts),random(110 - 60, 110 + 50),random(90 - 40, 90 + 40))//picks one word randomly, then I put padding so it does not go outside the circle 
//Right bubble text
text(random(thoughts),random(277 - 60, 277 + 50),random(85 - 40, 85 + 40));//same thing here just with different placements (I put a smaller number, 277 instead of 297 because some of the text still went outside the bubble for some reason)
  }

This section was quite challenging because I had to control where the text appeared so it stayed inside the bubbles. 

Reflection/future work:

I did not want the background to be left empty. At first, I wanted random words to fill the entire background, but I figured it was too visually messy, and I did not like the result of the background just being black. So I decided to scratch that and just make a thought bubble using white ellipses and have my thoughts scatter in them, where they only appear when you click on the portrait. 

I liked this idea because it allows the viewer to first see my appearance, then reveal what is happening internally, making my self-portrait a bit more personal. At first, you only see me holding up a peace sign, but when you click on the portrait, you get a deeper look into my mind. So I’m particularly proud of this part of the code because it took some time learning how to do it by searching questions and the keyword functions like the function mousepressed and random, and what I needed to equal them with for the function to work, on Google, and watching multiple tutorials, especially from “The Coding Train” like these:

https://youtu.be/POn4cZ0jL-o?si=yuoXE6cM294Seczd

 https://www.youtube.com/watch?v=Bn_B3T_Vbxs 

At one point, I was confused about why words were still appearing in the background. I later realized this was because I left the background in the setup function instead of the draw function, which caused text from previous frames to accumulate on the canvas rather than being cleared each frame.

For future work, I would like to be more accurate with the positioning of things, add smoother animations, and create more interactive elements to further express more emotion and movement.

Week 1 – Self Portrait

Concept:

I dove into the assignment blind, no initial sketches or anything as such, instead of conveying an idea through the portrait beyond simply portraying myself, I thought it would be fun to try to implement other features of p5js into it, which lead to us having a color changing shirt, moving clouds and automatic blinking animation!

Implementation:

Honestly I did not sure on how to make my curly hair, at first I thought I could use the curve() function of p5.js to maybe simulate “curl” layers over a circle layer on the head. However it did not turn out realistic whatsoever, so my next step was to first add a rectangular layer behind the head, (besides the forehead part where I added a small rectangle on top of the forehead so that it looks like the hair is covering). I then added a bunch of circles centered inside the hair layer so that it looks like curls on top of my head, there was an emoji I used as reference actually and it was this: 👨‍🦱, not exactly the same however I took the top part of the hair as reference.

// hair
fill(0);
noStroke();
rect(210, 50, 180, 80);
circle(220, 60, 30);
circle(220, 80, 30);
circle(220, 100, 30);
circle(220, 120, 30);
circle(240, 50, 30);
circle(260, 50, 30);
circle(280, 50, 30);
circle(300, 50, 30);
circle(320, 50, 30);
circle(340, 50, 30);
circle(360, 50, 30);
circle(380, 60, 30);
circle(380, 80, 30);
circle(380, 100, 30);
circle(380, 120, 30);

// face
noStroke();
fill(223, 170, 139);
ellipse(300, 150, 175, 200);

// hair over face
fill(0);
rect(236, 50, 125, 20);

Next is the automatic blinking, there is 3 variables that are used for this process, lastblink, blinkInterval and the boolean blinking. The way the logic works is that I use the in built function millis(), and what this does is keep track of how much time has passed since the sketch started running, using that we can subtract our last blink and check if it is greater than our blink interval, so here I use that so the blink interval is 3 seconds, so when the sketch start running lastblink is going to be 0, so when millis reach 3001 milliseconds we get 3001-0 which is greater than 3000, meaning it is time for the character to blink, this sets blinking to true which “disables” the eye and pupil code giving the illusion of blinking. However we want the character eyes to open up after a bit so we use setTimeout() which waits a certain time we set before executing a command, so here we wait  300 ms / 0.3 seconds before setting blinking to false and opening up the character’s eyes.

let lastBlink = 0;
let blinkInterval = 3000; 
let blinking = false;

  // Check if time to blink
  if (millis() - lastBlink > blinkInterval) {
    blinking = true;
    setTimeout(() => blinking = false, 300); 
    lastBlink = millis();
  }

  // eyes and pupils
  stroke(223, 170, 139);
  strokeWeight(2);
  if (!blinking) {
    fill(255);
    ellipse(260, 125, 50, 40);
    ellipse(335, 125, 50, 40);
    noStroke();
    fill(0);
    circle(260, 125, 15);
    circle(335, 125, 15);
  }

The color changing shirt logic is pretty simple, for smooth transition I use sin on our shirtcolor variable, sin goes between -1 and 1, however rgb takes from 0 to 255, so we multiply by 127 to get -127 and 127 and then add 128 to this to get a range from 0 to 255, for r we just use sin of colorshirt, for g and b we delay the sin by two_pi  / 3 and 2*two_pi / 3, and finally we add 0.03 every time draw runs to shirtcolor.

let shirtcolor = 0;
  // shirt color changing
  let r = 128 + 127 * sin(shirtcolor);
  let g = 128 + 127 * sin(shirtcolor + TWO_PI / 3);
  let b = 128 + 127 * sin(shirtcolor + 2 * TWO_PI / 3);
  shirtcolor += 0.03;

  // shirt
  noStroke();
  fill(r, g, b);
  rect(200, 290, 200, 250);

The final thing that is worth mentioning is the cloud movement in the background, I use a function that takes in the x, y and s (where s is set to 1 unless specified otherwise), and draw our cloud using an ellipse with the parameters we put. I initialize 14 different “clouds” with different x,y and s values in an array, now to draw and move the clouds, I use a loop to move through the cloud array and call the drawCloud function that draws each cloud, then to move it along the background, I add 1 to every cloud’s x value in the loop and to make the clouds move up and down add the value of sin(shirtcolor) to the y value of each cloud, since shirtcolor constantly changes it’s a good variable to use in this case. Finally to make sure the clouds wrap back, we check if the x value of each cloud has went past the width of canvas by 25 pixels or not, if it has we set the x value to -50! This gives the illusion of clouds wrapping around without them “disappearing” from one side and “appearing” at the other.

let clouds = [];
clouds = [
    {x: 80, y: 80, s: 1},
    {x: 200, y: 60, s: 1.3},
    {x: 350, y: 90, s: 0.9},
    {x: 500, y: 70, s: 1.2},
    {x: 120, y: 140, s: 0.8},
    {x: 300, y: 160, s: 1.1},
    {x: 450, y: 140, s: 0.7},
    {x: 80, y: 200, s: 1},
    {x: 200, y: 260, s: 1.3},
    {x: 350, y: 290, s: 0.9},
    {x: 500, y: 270, s: 1.2},
    {x: 120, y: 340, s: 0.8},
    {x: 300, y: 460, s: 1.1},
    {x: 450, y: 340, s: 0.7},
  ];

  // draw and move clouds
  for (let c of clouds) {
    drawCloud(c.x, c.y, c.s);
    c.x += 1;
    c.y += sin(shirtcolor)
    if (c.x > width+25) c.x = -50;
  }


function drawCloud(x, y, s = 1) {
  stroke(255);
  strokeWeight(1);
  fill(255);
  ellipse(x, y, 50 * s, 24 * s);
}

Overall code:

let shirtcolor = 0;
let clouds = [];
let lastBlink = 0;
let blinkInterval = 3000; 
let blinking = false;

function setup() {
  createCanvas(600, 550);

  // initialize clouds
  clouds = [
    {x: 80, y: 80, s: 1},
    {x: 200, y: 60, s: 1.3},
    {x: 350, y: 90, s: 0.9},
    {x: 500, y: 70, s: 1.2},
    {x: 120, y: 140, s: 0.8},
    {x: 300, y: 160, s: 1.1},
    {x: 450, y: 140, s: 0.7},
    {x: 80, y: 200, s: 1},
    {x: 200, y: 260, s: 1.3},
    {x: 350, y: 290, s: 0.9},
    {x: 500, y: 270, s: 1.2},
    {x: 120, y: 340, s: 0.8},
    {x: 300, y: 460, s: 1.1},
    {x: 450, y: 340, s: 0.7},
  ];
}

function draw() {

  // Check if time to blink
  if (millis() - lastBlink > blinkInterval) {
    blinking = true;
    setTimeout(() => blinking = false, 300); 
    lastBlink = millis();
  }
  // shirt color changing
  let r = 128 + 127 * sin(shirtcolor);
  let g = 128 + 127 * sin(shirtcolor + TWO_PI / 3);
  let b = 128 + 127 * sin(shirtcolor + 2 * TWO_PI / 3);
  shirtcolor += 0.03;

  // background
  background(178, 237, 232);

  fill(0);
  strokeWeight(1);
  text(`${mouseX}, ${mouseY}`, 20, 20);

  // draw and move clouds
  for (let c of clouds) {
    drawCloud(c.x, c.y, c.s);
    c.x += 1;
    c.y += sin(shirtcolor)
    if (c.x > width+25) c.x = -50;
  }

  // hair
  fill(0);
  noStroke();
  rect(210, 50, 180, 80);
  circle(220, 60, 30);
  circle(220, 80, 30);
  circle(220, 100, 30);
  circle(220, 120, 30);
  circle(240, 50, 30);
  circle(260, 50, 30);
  circle(280, 50, 30);
  circle(300, 50, 30);
  circle(320, 50, 30);
  circle(340, 50, 30);
  circle(360, 50, 30);
  circle(380, 60, 30);
  circle(380, 80, 30);
  circle(380, 100, 30);
  circle(380, 120, 30);

  // face
  noStroke();
  fill(223, 170, 139);
  ellipse(300, 150, 175, 200);

  // hair over face
  fill(0);
  rect(236, 50, 125, 20);

  // jaw and neck
  noStroke();
  fill(223, 170, 139);
  quad(224, 200, 377, 200, 340, 250, 260, 250);
  rect(260, 250, 80, 40);

  // shirt
  noStroke();
  fill(r, g, b);
  rect(200, 290, 200, 250);

  // eyebrows
  fill(0);
  stroke(0);
  noFill();
  strokeWeight(10);
  arc(265, 100, 25, 7, PI, -0.2);
  arc(332, 100, 25, 7, PI, -0.2);

  // eyes and pupils
  stroke(223, 170, 139);
  strokeWeight(2);
  if (!blinking) {
    fill(255);
    ellipse(260, 125, 50, 40);
    ellipse(335, 125, 50, 40);
    noStroke();
    fill(0);
    circle(260, 125, 15);
    circle(335, 125, 15);
  }

  // nose
  stroke(194, 132, 103);
  noFill();
  line(297.5, 150, 295, 170);
  line(302.5, 150, 305, 170);
  arc(305, 170, 10, 10, -0.5, PI / 2);
  arc(295, 170, 10, 10, PI / 2, PI + 0.5);

  // mouth
  fill(180, 13, 61);
  noStroke();
  arc(300, 200, 50, 50, 0, PI);

  // glasses
  stroke(0);
  noFill();
  ellipse(260, 125, 40, 30);
  ellipse(335, 125, 40, 30);
  line(275, 115, 320, 115);
  line(355, 125, 385, 130);
  line(240, 125, 210, 130);

  // ears
  stroke(194, 132, 103);
  fill(194, 132, 103);
  arc(385, 145, 30, 30, -PI / 2, PI / 2);
  arc(214, 145, 30, 30, PI / 2, -PI / 2);
}

function drawCloud(x, y, s = 1) {
  stroke(255);
  strokeWeight(1);
  fill(255);
  ellipse(x, y, 50 * s, 24 * s);
}

 

 

Reflection:

I am not a very artistic person so honestly I did not know how this would go, I did not know how I’d draw myself using just 2D shapes, and to be honest I did struggle, especially with the hair, however I am pretty happy with how it turned out, in the future I am hoping to be able to make more realistic animations, for example for the blinking animation maybe the animation of the eye slowly closing up and opening up would be a cool thing to experiment with and add. Maybe also a scene where my character is doing some sort of activity! I love wall climbing so something where my character is climbing would be cool to do in the future.