Help Needed! – Spotify Top 20 Visualization

Well, I’ve been trying to get different versions of this program to work for a while, but it’s not perfect yet. It’s far from it actually. I’m going to walk you through my thought process and show you my code, any suggestions would be much appreciated!

 

So, for this week, I noticed that Spotify allows users to access their own data as well as the charts from every day in CSV format (which is very cool). Unfortunately, personal data takes a long time to be prepared and sent to you, so even after asking for it on Thursday, I didn’t receive it so I couldn’t use it. Honestly, that was kind of a relief, I’m not ready to share my Spotify stats with you guys yet…a lot of embarrassing guilty pleasures on there.

Anyways, I decided to use the top 20 songs from the first week in September, and then create a data visualization of how well these songs were doing throughout the month. I downloaded all four CSV files that contain the Top 200 and recorded the values relevant to me (streams for week 1 top 20) in my own CSV file.

Process

Using a multi-row/column CSV file meant I needed different functions than the ones we used in class (loadStrings). I looked up loadTable() and watched a bunch of tutorials on it.

For my visualization, I wanted to achieve to main things. One, present the data using musical symbolism.  Two, use user text input to “search” one of the top 20 songs.

For one, I came up with a system where each note value represents a range of streams, and hence if the data in the CSV file corresponds to a certain range a certain note will be displayed on the staff I drew on screen.

So, through looking at a display of 4 notes on the screen one can evaluate how a certain song did throughout the month. which week had the most streams etc.

 

 

Example of a Visualization for a Song

 

 

 

 

 

 

 

 

Then, for 2, I looked in the processing reference for an input function and didn’t find anything relevant to what I needed. I ended up finding a very helpful code on learningprocessing.com, which allowed me to save the user input in a variable. However, my main struggle and the reason my code isn’t complete is that I am unable to compare the string in this variable with the song titles. I tried doing it through a for loop in the keypressed function and a for loop in the draw function, but it did not work. However, when I access a specific song and a specific week, it displays the corresponding note.

Song Class

For this program, I created a song class that takes the song title and the 4-week streaming values as arguments. The functions of this class consist mainly of display functions for different weeks and different kinds of notes.

Alternatives

I considered alternative options, such as simply clicking on the song title to display its data or pressing a number on the keyboard that corresponds to the song number on the chart, but I felt like I’ve already used these methods in previous assignments and was very stubborn about wanting user input to work.

Demonstration

Here’s a clip of my code failing :-(( :

Here’s an example of my program displaying the streams for a song after I specifically called the display function in draw() (not user input):

 

 

 

 

 

Take a look at my main code:

// text input code from http://learningprocessing.com/examples/chp18/example-18-01-userinput
//Treble Clef png by Vasily Gedzun from the Noun Project

//loading image for treble clef
PImage treble;

PFont f;
// Variable to store text currently being typed
String typing = "";

// Variable to store saved text when enter is hit
String searchInput = ""; 
Table top20; //creating table
Song[] allSongs; //object list

void setup() {
  size(1024,768);
  f = createFont("Kayak Sans Bold (Italic).otf",20);
  textFont(f);
  top20 = loadTable("TOP 20.csv", "header"); //loading table excluding the first row, which are the headers
  treble = loadImage("Treble Clef.png");
  allSongs = new Song[20]; //creating the list
  for (int i=0; i<19; i++){
   allSongs[i] = new Song(top20.getString(i,0), top20.getFloat(i,1),top20.getFloat(i,2),top20.getFloat(i,3),top20.getFloat(i,4)); //creating the object list, the arguments get their vals from acessing CSV file data
  }
  
}
int indent = 25; // indentation for text

void draw() {
  background(255);
  
  
  
  fill(0);
  
  // Display initial text
  textSize(20);
  text("Type a Top 10 Song Title to View September Streams \n Hit enter ", indent, 40);
  text("Typing: " + typing,indent,190);
  text("Searching: " + searchInput,indent,230);
  displayMusicStaff(); // calling the function that draws the staff
  textSize(15);
  //displaying the search options
  text("WAP (feat. Megan Thee Stallion) \n Hawaii \n Mood (feat. Iann Dior) \n Dynamite \n Ice Cream (with Selena Gomez) \n Savage Love (Laxed - Siren Beat) \n Watermelon Sugar \n Blinding Lights \n ROCKSTAR (feat. Roddy Ricch) \n Laugh Now Cry Later (feat. Lil Durk)", width/2, 550);
  text("Roses - Imanbek Remix \n Mood Swings (feat. Lil Tjay) \n Breaking Me \n Ay, DiOs Mío! \n For The Night (feat. Lil Baby & DaBaby) \n UN DIA (One Day) (Feat. Tainy) \n Head & Heart (feat. MNEK) \n Heather \n La Curiosidad", width/2 + 250, 550); 
     // loop that compares user input to song list
   for (int i = 0; i<19; i++){
   if (searchInput == allSongs[i].songTitle){
     allSongs[i].week1Display(); 
     allSongs[i].week2Display();
     allSongs[i].week3Display();
     allSongs[i].week4Display();
   }
  }

  
  
     
   
   
 
 
}


void keyPressed() {
  //Code for user input
  // If the enter key is pressed, the user input is saved in a variable called searchInput
  if (key == '\n' ) {
    searchInput = typing;
    
    //the user is no longer typing, so we clear the typing variable
    typing = ""; 
  } else {
    // if enter isn't pressed keep adding characters to the typing variable
    typing = typing + key;
    
  }
  
}

void displayMusicStaff(){
  //drawing the staff
  strokeWeight(2);
  line(250,250,840,250);
  line(250,300,840,300);
  line(250,350,840,350);
  line(250,400,840,400);
  line(250,450,840,450);
  image(treble,250,250);
  
}

My Song Class:

class Song{
 //declaring variables
String songTitle;
float streams;
int locX, locY;
int ellipseW, ellipseH;
float wk1Streams, wk2Streams, wk3Streams, wk4Streams;



Song(String tempTitle, float tempwk1Streams, float tempwk2Streams, float tempwk3Streams, float tempwk4Streams){
 locX = 380;
 locY = 450;
 ellipseW = 35;
 ellipseH = 25;
 songTitle = tempTitle;
 wk1Streams = tempwk1Streams;
 wk2Streams = tempwk2Streams;
 wk3Streams = tempwk3Streams;
 wk4Streams = tempwk4Streams;
 
  
}

//function checks the value of week 1 streams and displays the note shape accordingly by calling that note shape's function
void week1Display(){
    if (wk1Streams > 10000000 && wk1Streams < 20000000){
     thrityTwoNote(locX,locY);
  }
  else if ((wk1Streams > 20000000 && wk1Streams < 25000000)){
    sixteenNote(locX,locY);
  }
  else if (wk1Streams> 25000000 && wk1Streams < 30000000){
    eightNote(locX,locY); 
  }
 else if (wk1Streams > 30000000 && wk1Streams < 35000000){
  quarterNote(locX,locY);
   
 }
 
 else if (wk1Streams > 35000000 && wk1Streams < 40000000){
   halfNote(locX,locY);
 }
 else if (wk1Streams > 40000000 && wk1Streams < 46000000){
   wholeNote(locX,locY);
   
 }
}
 //week2
 
 void week2Display(){
   //x and y values here are dependent on the week 1 x and y, to keep the code dynamic
 if (wk2Streams > 10000000 && wk2Streams < 20000000){
     thrityTwoNote(locX+100,locY-50);
  }
  else if ((wk2Streams > 20000000 && wk2Streams < 25000000)){
    sixteenNote(locX+100,locY-50);
  }
  else if (wk2Streams> 25000000 && wk2Streams < 30000000){
    eightNote(locX+100,locY-50); 
  }
 else if (wk2Streams > 30000000 && wk2Streams < 35000000){
  quarterNote(locX+100,locY-50);
   
 }
 
 else if (wk2Streams > 35000000 && wk2Streams < 40000000){
   halfNote(locX+100,locY-50);
 }
 else if (wk2Streams > 40000000 && wk2Streams < 46000000){
   wholeNote(locX+100,locY-50);
   
 }
 }
 
 //week3
 void week3Display(){
  if (wk3Streams > 10000000 && wk3Streams < 20000000){
     thrityTwoNote(locX+200,locY-100);
  }
  else if ((wk3Streams > 20000000 && wk3Streams < 25000000)){
    sixteenNote(locX+200,locY-100);
  }
  else if (wk3Streams> 25000000 && wk3Streams < 30000000){
    eightNote(locX+100,locY-100); 
  }
 else if (wk3Streams > 30000000 && wk3Streams < 35000000){
  quarterNote(locX+200,locY-100);
   
 }
 
 else if (wk3Streams > 35000000 && wk3Streams < 40000000){
   halfNote(locX+200,locY-100);
 }
 else if (wk3Streams > 40000000 && wk3Streams < 46000000){
   wholeNote(locX+200,locY-100);
   
 }
 }
 void week4Display(){
 //week4
 if (wk4Streams > 10000000 && wk4Streams < 20000000){
     thrityTwoNote(locX+300,locY-150);
  }
  else if ((wk4Streams > 20000000 && wk4Streams < 25000000)){
    sixteenNote(locX+300,locY-150);
  }
  else if (wk4Streams> 25000000 && wk4Streams < 30000000){
    eightNote(locX+300,locY-150); 
  }
 else if (wk4Streams > 30000000 && wk4Streams < 35000000){
  quarterNote(locX+300,locY-150);
   
 }
 
 else if (wk4Streams > 35000000 && wk4Streams < 40000000){
   halfNote(locX+300,locY-150);
 }
 else if (wk4Streams > 40000000 && wk4Streams < 46000000){
   wholeNote(locX+300,locY-150);
   
 }
 
}



//smallest stream range note shape
void thrityTwoNote(int x, int y){
  fill(0);
  ellipse(x,y,ellipseW,ellipseH);
  line(x+17.5, y, x+17.5, y -70);
  line(x+17.5, y-70, x+35, y-60);
  line(x+17.5, y-60, x+35, y-50);
  line(x+17.5, y-50, x+35, y-40);
}
  
void sixteenNote(int x, int y){
  fill(0);
  ellipse(x,y,ellipseW,ellipseH);
  line(x+17.5, y, x+17.5, y -70);
  line(x+17.5, y-70, x+35, y-60);
  line(x+17.5, y-60, x+35, y-50);
  
  
}

void eightNote(int x, int y){
 fill(0);
  ellipse(x,y,ellipseW,ellipseH);
  line(x+17.5, y, x+17.5, y -70);
  line(x+17.5, y-70, x+35, y-60);
}

void quarterNote(int x, int y){
 fill(0);
  ellipse(x,y,ellipseW,ellipseH);
  line(x+17.5, y, x+17.5, y -70);
}

void halfNote (int x, int y){
 noFill();
  ellipse(x,y,ellipseW,ellipseH);
  line(x+17.5, y, x+17.5, y -70);
  line(x+17.5, y-70, x+35, y-60);
  
  }
  
  void wholeNote(int x, int y){
   noFill();
  ellipse(380,450,35,25);
  
  }
  
  
  
  
}

 

Pixel Art Using a Square Class

This was a challenging assignment! Mainly because I had a game I wanted to do in mind, and after spending a lot of time on getting the basics to work I realized it might need more practice and time than what I have now, so I changed the direction of my assignment and decided to go with a more “artwork” focused approach.

Recently I’ve been thinking about the ‘paint by numbers notebooks’ I used to have as a child and how accessible they made creating artwork that is above my artistic skills. This made me want to buy a ‘paint by numbers’ set, but also I asked if I could use the same approach to create artwork on processing.

example is taken from https://tomyumtumweb.com/ 

I realized I can do a similar approach but with pixels! I made a class called “square” which represents each square in a grid, and then used a 2D array list that contains different combinations of numbers of squares to be filled.

My class has a display function, and a fill function that relies on a boolean variable.

For the drawing, I decided to go for a heart that is filling gradually with every number the user inputs from their keyboard. At the final level, when the heart is full, it goes from black to a mix of random shades of red and pink which is controlled by the mouseX movement to a certain extent (to avoid the colors being too random).

Inspiration Image

After finalizing the logic, which depends on creating an array of square objects and then displaying and referencing them as a grid. I went to figure out the square numbers needed in my 2D array for each heart stage. For this part, I used pixilart.com  and created a grid of the same size as mine then compared the square values.

 

Taking values from sketch to my code
Trying different grid sizes 

I settled on bigger pixels, but since the code contains, row, column, and square number variables, it is easy to change the grid appearance and square size.

I think the program has the potential to be more complex or contain more user interaction, I would love any suggestions!

Have a look at my main code:

Square[] allSquares; //declaring an array of objects that consists of the squares that will be in the grid

// 2D Array of the different heart level drawings, each inner list contains the number of the pixel that should be filled
int[][] heartLevels = { {2,3,4,11,15,21,26,32,37,43,48,53,58,62,67,71,76,81,85,92,93,94}, //empty heart
                   {2,3,4,11,15,21,26,32,37,43,47,48,53,57,58,62,67,71,76,81,85,92,93,94}, //initially filled heart (Level 1)
                   {2,3,4,11,15,21,25,26,32,35,36,37,43,45,46,47,48,53,55,56,57,58,62,65,66,67,71,75,76,81,85,92,93,94}, // medium full heart (Level 2) 
                   {2,3,4,11,13,14,15,21,23,24,25,26,32,33,34,35,36,37,43,44,45,46,47,48,53,54,55,56,57,58,62,63,64,65,66,67,71,73,74,75,76,81,83,84,85,92,93,94}, 
// almost full heart (Level 3)
                   {2,3,4,11,12,13,14,15,21,22,23,24,25,26,32,33,34,35,36,37,43,44,45,46,47,48,53,54,55,56,57,58,62,63,64,65,66,67,71,72,73,74,75,76,81,82,83,84,85,92,93,94}}; // full heart
                   
// a boolean to create the color effect for the full heart
boolean fullHeart; 
                   
 // declaring and initializing the column and row number for the grid                  
int cols = 10; 
int rows = 10;



void setup(){
 size(500,500);
 background(255);
 //creating the grid array, the array size corresponds to the dimension of the grid
 allSquares = new Square[cols*rows]; 
 //initializing and declaring an index variable to create different objects every time the  nested loop runs
 int index = 0; 
 //a nested loop that goes through each column and row to create a grid of objects
 for (int c = 0; c < cols; c++){
  for (int r = 0; r < rows; r++){
    // creating the objects, the arguments of the class are X location, Y location, Width, Height, and index

//we multiply c and r by 50 because it corresponds to the square dimension, so if my x is 2 and y is 2, the square will be at 100,100.
   allSquares[index] = new Square(c*50,r*50,50,50,index); 


   index+=1;
   
  }
 }
 fullHeart = false; //initializing the color effect variable with false
}



void draw(){
  //a loop that displays the grid
  for(int i = 0; i < cols*rows; i++){
   allSquares[i].display();
   
   //an if statement that checks which heart we are creating by checking if the fullHeart bool is true, it is changed to true below, when the key is pressed.
   if (fullHeart == true){
   for (int j = 0; j < heartLevels[4].length; j++){

     //accessing the heart color variable defined in the square class, and altering it for the color effect

//using random within a small range to generate the different shades of red, and applying interaction through mouseX
    allSquares[heartLevels[4][j]].heartColor = color(random(mouseX,255), random(0,90),random(90,100));  //interacting with user through changing the R random bottom range using mouseX. 
 
                                             
                                             
   //filling the squares, it keeps changing because it is in the draw function
    allSquares[heartLevels[4][j]].fillingSquare(true); 
   
  }
   }
  }
}
  
  
  
  void keyPressed(){
  //key pressed function that displays different heart shapes based on the number pressed  
    
  if (key == '0'){ //zero gives an empty screen
      for(int i = 0; i < cols*rows; i++){
        fullHeart = false; //this boolean is set to false for every key that does not correspond to a full heart

   allSquares[i].fillingSquare(false); //First loop goes through all the squares in the object list and unfills them to remove the effect of other shapes, this loop is repeated for all the other keys
   
      }
  }
  if (key == '1'){
      for(int i = 0; i < cols*rows; i++){ //same loop
   allSquares[i].fillingSquare(false); 
  }
      for (int i = 0; i < heartLevels[0].length; i++){ 
        fullHeart = false;
       allSquares[heartLevels[0][i]].heartColor = color(0); //sets the color variable from the class to black

       allSquares[heartLevels[0][i]].fillingSquare(true); ////fills every pixel that corresponds to values in the first heart shape list with black
       
  }
  }
  
  //the same repeats for the next if conditions
  else if (key == '2'){
    for(int i = 0; i < cols*rows; i++){
   allSquares[i].fillingSquare(false);
  }
    for (int i = 0; i < heartLevels[1].length; i++){
      fullHeart = false;
     allSquares[heartLevels[1][i]].heartColor = color(0);
     allSquares[heartLevels[1][i]].fillingSquare(true); 
  }
 

  
 
  }
  else if(key=='3'){
   for(int i = 0; i < cols*rows; i++){
   allSquares[i].fillingSquare(false); 
  }
    for (int i = 0; i < heartLevels[2].length; i++){
      fullHeart = false;
     allSquares[heartLevels[2][i]].heartColor = color(0);
     allSquares[heartLevels[2][i]].fillingSquare(true);
   
  }
  }
  else if(key=='4'){
     for(int i = 0; i < cols*rows; i++){
   allSquares[i].fillingSquare(false);
  }
     for (int i = 0; i < heartLevels[3].length; i++){
       fullHeart = false;
     allSquares[heartLevels[3][i]].heartColor = color(0); 
     allSquares[heartLevels[3][i]].fillingSquare(true);
   
  }
  }
  else if(key=='5'){
    for(int i = 0; i < cols*rows; i++){
  allSquares[i].fillingSquare(false);
  }
    for (int i = 0; i < heartLevels[4].length; i++){
     fullHeart = true; //this turns on the random color fill in the draw function
    allSquares[heartLevels[4][i]].fillingSquare(true); //and based on those colors fills the squares
    
    
    }
  }
  }

and my Square Class:

class Square{
//initializing variables that will be used throughout the class  
int squareX , squareY;  //square x and y location
int squareW , squareH; 
int squareNo; //the number of the pixel on the grid
boolean filled; //the boolean that determines which pixels on the screen are filled
color heartColor; //a color variable to control the color of the pixel
color gridStroke = color(203,197,197); // color of grid lines

Square(int tempX, int tempY, int tempW, int tempH, int tempSquareNoMinus1){ //constructor, takes X location, Y location, Width, Height, and the index as arguments
 //setting the constructor arguments to permanent variables
 squareX = tempX;
 squareY = tempY;
 squareW = tempW;
 squareH = tempH;
 squareNo = tempSquareNoMinus1 + 1; // 1 is added to the index as the counting of squares starts at 1, not zero. 
 
}



void display(){
 //function that displays the square on the screen
 stroke(gridStroke);
 strokeWeight(0.25);
 noFill();
 rect(squareX , squareY , squareW , squareH);
 
 
}


void fillingSquare(boolean filled){
  //a function that fills the pixels based on a filled? boolean
  if (filled == true){
    
   fill(heartColor); 
  }
  else{
  //if the boolean is false the square is filled with white and redrawn  
   fill(255); 
  }
  rect(squareX,squareY,squareW,squareH);
  }
}

 

 

Feels Like We Only Go Backwards – Artwork Using Loops

As I was brainstorming for this exercise, I came across a recreation of Joy Division’s Unknown Pleasures album cover that incorporates looping and the noise function to animate the same mountain-like structure.

Joy Division’s album cover

This led me to think of the album art of one of my favorite artists, Tame Impala. I looked some of the artwork up and it felt like something I could recreate using geometric shapes, loops, and the noise function.

Currents by Tame Impala Album Art

 

 

 

I recalled the lyric video for “Feels Like We Only Go Backwards”, and this particular frame inspired me. I wanted to attempt to recreate it and add an element of animation or flow.

Still from “Feels Like We Only Go Backwards” Lyric Video

I thought: “this shouldn’t be too hard, right?” Weeeelll, I did struggle quite a bit with recreating the exact image I had in my head, so I will take you through my various attempts and takeaway from this assignment.

 

Attempt 1: Using Equidistant Lines in a Triangle Form:

My approach here was going through a loop and drawing multiple vertical lines close to each other in the form of a triangle and then apply noise() to each of these lines to create a stream that’s narrow and then widens near the edge of the screen.

First Sketch

This didn’t work, as the noise function created a uniform movement where all the lines leaned together in one direction. Even when increasing and decreasing the input value to get more random movement, I didn’t get a smooth wave like I had imagined, the change in X was consistent for all the lines creating a beam-like movement rather than a wave.

Attempt 2: Using Points to Create a Diagonal Side

Then, inspired by this tutorial, I tried to take a different approach using Point(), where I draw the triangle sides as points and then apply the noise() function to the x value of each point. I used two variables for noise, the second one called xOffset which creates the animation.

Sketch 2

This way, I got closer to the movement I wanted, but it was still off, because I wanted the whole shape to flow as one, and this was too random to achieve that.

Finally, I decided that the most suitable approach would be similar to the one in the tutorial I linked earlier, to create a 4-sided shape (close to a rectangle or square) by drawing vertical equidistant lines through a loop and applying a noise() function to my X-value that takes a scaled Y for the first input, and an xOffset variable as the second input. This was the closest I got to my initial goal, but I think it could still be more accurate to the inspiration image.

This exercise really challenged my understanding of the thought process required for loops and the noise function, specifically how to choose values to input into the function. I went through our code in class and then followed any tutorials I could find on YouTube which helped make the concept more understandable. However, I still think that achieving my goal with this artwork could have been possible with the first two approaches I used, but it definitely requires more confidence with the concept of Noise(), so I might go back to these approaches after more practice.

Ironically, at some points, it really did feel like the more I tried the less progress I made (very in line with the song title), so it was a bit frustrating.  Nonetheless, this exercise was a great way to work on my skills in using sketches and trial to translate an image I have in my head to code and to be patient and kind to myself when things don’t work out as well as I thought.

Have a look at my code, and make sure you go listen to Feels Like We Only Go Backwards!

color bgColor = color(240,245,230);
color upperLeft = color(221, 28,26);
color upperRight = color(111,156,235 );  
color lowerLeft = color(7,42,200  );
color lowerRight = color( 249,240,0);
float xOff = 0;
float yOff =0;
PImage img;
void setup(){
 size(400,400);
 //image from noun project, artist: Maxim Kulikov
 img = loadImage("noun_Head_2053552TWO.png");
 
}

void draw(){
 background(bgColor);
 
 
 // loop 1, upper left corner
 for(float y = 0; y<height/2; y++){
   strokeWeight(4);
   stroke(upperLeft);
   //pushMatrix();
   //translate(150,0);
   //rotate(radians(45)); // and attempt to make it look as if it is diagonally flowing out of the head. current shape looked better in my opinion.
   line(width/2*noise(y/100,xOff),y,width/2,y);
   //shifting the "wave" horizontally and decreasing opacity
   stroke(upperLeft,80);
   line(width/2*noise(y/100,xOff) - 50,y,width/2,y);
   stroke(upperLeft,60);
   line(width/2*noise(y/100,xOff) - 100,y,width/2,y);

  y +=1 ;
 
}
//loop2, upper right corner
for(float y2 = 0; y2<height/2; y2++){
 stroke(upperRight);
 

 line(width/2, y2, 200 + width/2*noise(y2/100,xOff),y2); 
 stroke(upperRight,80);
 line(width/2, y2, 250 + width/2*noise(y2/100,xOff),y2); 
 stroke(upperRight,60);
 line(width/2, y2, 300 + width/2*noise(y2/100,xOff),y2);


 y2 += 1; 
}

//loop3, lower left corner
for(float y3 = height/2; y3<height; y3++){
 stroke(lowerLeft);
 line(width/2*noise(y3/100,xOff),y3,width/2,y3);
 stroke(lowerLeft,80);
 line(width/2*noise(y3/100,xOff) -50,y3,width/2,y3);
 stroke(lowerLeft,60);
 line(width/2*noise(y3/100,xOff)- 100,y3,width/2,y3);
  y3 +=1 ;
 
}

// loop 4, lower right corner
for(float y4 = height/2; y4<height; y4++){
  stroke(lowerRight);
  line(width/2, y4, 200 + width/2*noise(y4/100,xOff),y4); 
  stroke(lowerRight,80);
  line(width/2, y4, 250 + width/2*noise(y4/100,xOff),y4); 
  stroke(lowerRight,60);
  line(width/2, y4, 300 + width/2*noise(y4/100,xOff),y4); 



 y4 += 1; 

}
 
//incrementing the Y val of the noise function to create movement.
xOff += 0.01;


imageMode(CENTER);
image(img, width/2, height/2);
}

 

Getting Started – Self-Portrait!

 

There she is, Processing Sarah and her two moods!

slightly unsettling, I know

I’m not sure what the second mood is, but maybe a video will make it clearer.

https://youtu.be/QN89B1quGJk

I started working by looking for inspiration, I looked through the old blog posts here, openprocessing.org  (which is a great source to look at the works of people from all over the world and different age and skill levels), and youtube.

A snapshot of the results you get when searching “self-portrait” on OpenProcessing

 

This research helped me find new tools I could use for the more complex shapes such as the beginShape(), endShape() function. Additionally, thanks to Jana from last(?) year, I found a cursor tracker code that helped a lot in finding the coordinates of certain points on the screen. You can find that code at the end of my work.

Next, I sketched a really rough draft to brainstorm and figure out the geometric shapes and functions needed to create my portrait. I then realized that some elements of the portrait will use the arc function, so I spent some time playing around with it and inserting different values and angles.

I got to coding the static elements of my portrait. I tried my best to make it resemble me somehow and I found that giving it a half-bun would be perfect to mark that resemblance.

Finally, I wanted to add a factor of animation and thought that going for a moving background could be cool. I settled for making something that makes my character look like she’s in a state of hypnosis…? I drew multiple circles in the code and created an “expand” variable that was added with every iteration of the draw function until the circle width and height reached a specific value which corresponds to the screen size (or the previous circle). Then, it restarts from the default width and height. When you hover over the eyes, you get a similar effect which was done using the same logic.

 

here’s the code!

// color palette:

color bgColor = color(198,197,185);
color skinColor = color(239,211,187);
color noseColor = color(220,190,200);
color blushColor = color(193,112,122,30);
color lipColor = color(193,112,112);
color frecklesColor = color(184,150,133);
color hairColor = color(80,71,70); 
color firstBlue = color(84,106,123);
color secondBlue = color(98,146,158);
color thirdBlue = color(224,234,236);
color irisColor = color(52,42,25);

//declaring variables
int x;
int y;
int ellipse1WH;
int ellipse2WH;
int ellipse3WH;
int ellipse4WH;
int ellipse5WH;
int iris1WH;
int expand = 3;
int irisExpand = 1;



void setup(){
 size(500,500);
 x = width/ 2;
 y = height / 2;
 ellipse1WH = 100;
 ellipse2WH = 80;
 ellipse3WH = 60;
 ellipse4WH = 40;
 ellipse5WH = 20;
 iris1WH = 6;
 
}
 
 void draw(){
   //moving bg
  background(bgColor);
  stroke(firstBlue);
  strokeWeight(60);
  noFill();
  //drawing the circles
  ellipse(x,y,ellipse1WH,ellipse1WH);
  stroke(secondBlue);
  ellipse(x,y,ellipse2WH,ellipse2WH);
  stroke(145,179,187);
  ellipse(x,y,ellipse3WH, ellipse3WH);
  stroke(192,212,216);
  ellipse(x,y,ellipse4WH,ellipse4WH);
  stroke(thirdBlue);
  strokeWeight(40);
  ellipse(x,y,ellipse5WH,ellipse5WH);

  //circles expanding
  ellipse1WH = ellipse1WH + expand;
  ellipse2WH = ellipse2WH + expand;
  ellipse3WH = ellipse3WH + expand;
  ellipse4WH = ellipse4WH + expand;
  ellipse5WH = ellipse5WH + expand;
  //upper limit
   if (ellipse1WH > 500 ){
    ellipse1WH = 100;
      
 }
 
 if (ellipse2WH > 480 ){
    ellipse2WH = 80;
      
 }
 if (ellipse3WH > 460 ){
    ellipse3WH = 60;
      
 }
 if (ellipse4WH > 440 ){
    ellipse4WH = 40;    
 }
 if (ellipse5WH > 420 ){
    ellipse5WH = 20;    
 }
 
 //hair1, behind the face
 noStroke();
 fill(hairColor);
 //right side
 beginShape();
 vertex(286,82);
 vertex(395,166);
 vertex(395,450);
 vertex(360,450);
 vertex(269,96);
 endShape();
 
 rect(108,356,287,100,10);
 
 beginShape();
 vertex(217,82);
 vertex(108,166);
 vertex(108,450);
 vertex(143,450);
 vertex(269,96);
 endShape();
 
 
  //face
  fill(skinColor);
  noStroke();
  ellipse(x,y,250, 300);
  
  //neck
  
  rect(208,380,90,100,7);
  
  //torso
  triangle(0,500,271,424,271,500);
  triangle(500,500,271,424,271,500);
  
  //eyebrows
  strokeWeight(5);
  stroke(0);
  line(180,190,215,185);
  line(282,185,317,190);
  
  //eyes
  
  arc(197,213,35,13,0, PI); //left
  arc(295,211,35,13,0,PI); // right
  
 
 
  
  //eyelashes
  strokeWeight(1);
  
  //left eye
  line(179,215,174,219);
  line(186,219,183,224);
  line(195,218,195,225);
  line(203,218,207,225);
  line(213,216,219,220);
  
  //right eye
  line(279,215,274,219);
  line(286,219,283,222);
  line(295,218,295,223);
  line(303,218,307,223);
  line(310,212,318,219);
  
 //eye animation
 if (mouseX >= 176 && mouseX <= 215 && mouseY >= 208 && mouseY <= 222  || mouseX >= 276 && mouseX <= 313 && mouseY >= 208 && mouseY <= 222 ){
   
   //covers closed eyes
   stroke(skinColor);
   fill(skinColor);
   ellipse(195,213,55,30);
   ellipse(295,211,55,30);
    
   //open eyes
   fill(255);
   ellipse(195,217,50,50);
   ellipse(295,215,50,50);
   stroke(irisColor);
   strokeWeight(15);
   ellipse(195,217,iris1WH,iris1WH);
   ellipse(295,215,iris1WH,iris1WH);
   noStroke();
   fill(0);
   ellipse(195,217,15, 15);
   ellipse(295,215,15,15);
    
   //eye expanding
   iris1WH = iris1WH + irisExpand;
   if (iris1WH > 45) {
     iris1WH = 6;
   }
   
   
 }
  ////nose
  noFill();
  stroke(0);
  strokeWeight(2.5);
  beginShape();
  vertex(255,256);
  vertex(255,276);
  vertex(240,283);
  endShape();
  
  //cheeks/blush
  //left
  noStroke();
  fill(blushColor);
  ellipse(178,270,40,40);
  
  //right
  ellipse(323,270,40,40);
  
  //freckles
  fill(frecklesColor);
  
  //left
  ellipse(178,260,3,3);
  ellipse(169,273,3,3);
  ellipse(187,273,3,3);
  
  
  //right
  ellipse(323,260,3,3);
  ellipse(314,273,3,3);
  ellipse(332,273,3,3);
  
  //mouth
  stroke(lipColor);
  strokeWeight(5);
  noFill();
  arc(250,330,80,30,0,PI);
  
  
  
  //hair2, bangs
  
  noStroke();
  fill(hairColor);
  //left
  beginShape();
  vertex(253,101);
  vertex(243,169);
  vertex(129,224);
  vertex(132,202);
  vertex(228,100);
  endShape();
  
  beginShape();
  vertex(224,102);
  vertex(161,127);
  vertex(116,200);
  vertex(113,221);
  vertex(230,113);
  endShape();
  
  //right
  beginShape();
  vertex(253,101);
  vertex(267,169);
  vertex(368,223);
  vertex(371,202);
  vertex(280,100);
  endShape();
  
  beginShape();
  vertex(284,104);
  vertex(352,137);
  vertex(387,183);
  vertex(387,222);
  vertex(364,222);
  vertex(277,128);
  vertex(286,94);
  endShape(CLOSE);
  
  //bun
  ellipse(253,68,75,75);
    
  
  //cursor tracker from Jana
  print(mouseX);
  print(" ");
  println(mouseY);
 }