Week 2 – Maneuvring Around Difficulties

Intro

Sometimes, unexpected pop-ups may not be serendipities but discoveries of limits that propel us to set off on a new adventure.

My expenditure this time didn’t turn out to be very smooth in terms of my realizing my initial vision and how that vision deviates from my expectations. On the other hand, I believe these sorts of experiences would be quite beneficial as a lesson learned to guide future planning.

As my initial vision (or product concept) changed many times in the course, it’s rather difficult to start this documentary with a definitive concept aforesaid. Therefore, I will briefly touch on the concept of each of the prototypes alongside my process description.

process

A Definitive Heart in 3D Space

In the first stage of my development, my goal was to construct a framework of basic interactions that could satisfy my needs later on – which was attempted later on but eventually did not stand for my final product. Hence, I stuck to the idea of generating a visible coordinate system in 3D space at the beginning and wrote a basic ‘arrow generating-shooting’ mechanism.

The loops in this prototype are mostly for displaying every ‘arrow’ I stored in an array and the axis plane in each frame.

let arrowArray = [];

function draw() {
...
// Display all arrows in arrowArray for 1 frame
  if (arrowArray[0]) {
    for (let i = 1; j = arrowArray[i] ;i += 1) {
      if (j._3Dz < -50) {
        arrowArray.splice(i, 1); // Remove the arrow that is behind the plane
      } else {
        j.arrowDisplay(); // A method in Arrow class
      }
    }
  }
}

// Draw axis plane on a Graphics object
function drawAxis() {
  // translate to 2D coordinate system
  axis.translate(-axis.width / 2, -axis.height / 2);
  
  axis.background(10);
  
  // draw the axises
  axis.stroke('white');
  axis.line(0, axis.height / 2, axis.width, axis.height / 2);
  axis.line(axis.width / 2, 0, axis.width / 2, axis.height);
  
  // draw the marks on the y axis
  for (let i = 0; i < width; i += axis.height / 20) {
    axis.line(axis.width / 2, 
         0 + i, 
         axis.width / 2 + 5, 
         0 + i);
  }
  
  // draw the marks on the x axis
  for (let i = 0; i < width; i += axis.width / 20) {
    axis.line(0 + i, 
         axis.height / 2,
         0 + i,
         axis.height / 2 - 5);
  }
}

Here, I adopted a math function that has the graphic appearance of a heart to test out the prototype. As the heart function could have multiple y values for one x value, I used the unit circle to generate the coordinates of the points on the function.

// Heart shape function
function functionHeartX(t) {
  return 160 * pow(sin(t), 3);
}

function functionHeartY(t) {
  return 150 * cos(t) - 55 * cos(2 * t) - 30 * cos(3 * t) - 10 * cos(4 * t);
}

Besides, I also tried some basic lighting to hue the ‘arrows’ with redish-pinky color. It was quite interesting to see how the gamma factor affects the actual light color.

lights();
ambientLight(50); // as a gamma factor
pointLight(
  255, 0, 0, // color
  0, 0, 0 // position
);

A Probability Heart in 2D Space

Then, I started a new prototype to specifically realize generating random positions based on the probability distribution in a 2D space (in order to later adopt into the 3D space). Multiple probability functions were tested in this stage, including:

// Gaussian probability function
function gaussianProbability(distance) {
  if (distance > maxDistance) {
    return 0; // No points beyond maxDistance
  }
  return exp(-pow(distance, 2) / (2 * pow(sigma, 2))); // Gaussian decay
}

// Quadratic probability function
function quadraticProbability(distance) {
  if (distance > maxDistance) {
    return 0; // No points beyond maxDistance
  }
  return max(0, 1 - pow(distance / maxDistance, 2)); // Quadratic decay
}

It is also noticeable that the default random() function could be used as such to mimic the probability realization:

// Add point based on probability
 if (random() < probability) {
   points.push(createVector(x + width / 2, -y + height / 2)); // Store the point
}

A Probability Heart in 3D Space

After that, it was much easier to adopt the probability parts into the 3D version (and to be presented as a cliche gift in a long distance relationship):

A Grayscale Webcam Downgrader

# Please access the p5js page directly from the instance to allow webcam/mic usage for the following demonstration.

On top of that, I started to experiment with the webcam as an input:

// Create a video capture from the webcam
video = createCapture(VIDEO, { flipped:true });
video.hide(); // Hide the default video element that appears under the canvas

My intentions in this stage is: 1. convert the video into grayscale values instead of RGBs (as the grayscale values can be later on mapped into probabilities); 2. downgrade the video resolution into a mosaic (as, for probability generation purposes, it would save a lot of time and be even more beneficial for the depiction of the overall shape that I planned to deliver with a collective of ‘arrows’).

To achieve the first goal, we have to operate directly on the pixel array that holds the RGB values for each pixel of each frame in the video with a time complexity of O(n) (instead of calling a filter() method for the displayed effect on the video stream when showing the video).

function videoToGray() {
  // Load the pixels from the video
  video.loadPixels();

  // Check if the video has pixels loaded
  if (video.pixels.length > 0) {
    // Convert to grayscale
    for (let i = 0; i < video.pixels.length; i += 4) {
      let r = video.pixels[i];     // Red
      let g = video.pixels[i + 1]; // Green
      let b = video.pixels[i + 2]; // Blue

      // Calculate grayscale value
      let gray = (r + g + b) / 3;

      // Set the pixel color to the grayscale value
      video.pixels[i] = gray;       // Red
      video.pixels[i + 1] = gray;   // Green
      video.pixels[i + 2] = gray;   // Blue
      // pixels[i + 3] stays the same (Alpha)
    }

    // Update the video pixels
    video.updatePixels();
  }
}

As for the second goal, however, it was also at this stage that I did a lot of technical idle work. I started to write my method for downgrading the video resolution right away without checking the features of the video.size() method, and ended up with a function that loops through the pixel arrays with a time complexity of O(n^2) (which became very time-consuming when the original video resolution is relatively high):

function videoToMosaic(mosaicSize) {
  // Load the pixels from the video
  video.loadPixels();
  
  // Check if the video has pixels loaded
  if (video.pixels.length > 0) {
    // Clear the mosaicPixels array
    mosaicPixels = new Uint8ClampedArray(video.pixels.length);
    
    // Loop through the canvas in blocks
    for (let y = 0; y < height; y += mosaicSize) {
      for (let x = 0; x < width; x += mosaicSize) {
        // Calculate the average color for the block
        let r = 0, g = 0, b = 0;
        let count = 0;

        // Loop through the pixels in the block
        for (let j = 0; j < mosaicSize; j++) {
          for (let i = 0; i < mosaicSize; i++) {
            let pixelX = x + i;
            let pixelY = y + j;

            // Check if within bounds
            if (pixelX < width && pixelY < height) {
              let index = (pixelX + pixelY * video.width) * 4;
              r += video.pixels[index];       // Red
              g += video.pixels[index + 1];   // Green
              b += video.pixels[index + 2];   // Blue
              count++;
            }
          }
        }

        // Calculate average color
        if (count > 0) {
          r = r / count;
          g = g / count;
          b = b / count;
        }

        // Set the color for the entire block in the mosaicPixels array
        for (let j = 0; j < mosaicSize; j++) {
          for (let i = 0; i < mosaicSize; i++) {
            let pixelX = x + i;
            let pixelY = y + j;

            // Check if within bounds
            if (pixelX < width && pixelY < height) {
              let index = (pixelX + pixelY * video.width) * 4;
              mosaicPixels[index] = r;        // Set Red
              mosaicPixels[index + 1] = g;    // Set Green
              mosaicPixels[index + 2] = b;    // Set Blue
              mosaicPixels[index + 3] = 255;   // Set Alpha to fully opaque
            }
          }
        }
      }
    }

At the end of the day, it turned out that I could simply lower the hardware resolution with video.size() method:

video.size(50, 50); // Set the size of the video

Random Points Generated Based on Probability

Next, I wrote a prototype to see how the mosaic of probabilities could guide the random generation. The core code at this stage is to map the x-y coordinates on the canvas to the mosaic pixel array and map the grayscale value to probability (the greater the grayscale, the lower the probability, as the brighter the mosaic, the fewer the points):

function mapToPixelIndex(x, y) {
  // Map the y-coordinate to the pixel array
  let pixelX = Math.floor((x + windowWidth / 2) * j / windowWidth);
  let pixelY = Math.floor((y + windowHeight / 2) * k / windowHeight);
  
  // Ensure the pixel indices are within bounds
  pixelX = constrain(pixelX, 0, j - 1);
  pixelY = constrain(pixelY, 0, k - 1);
  
  // Convert 2D indices to 1D index
  return pixelX + pixelY * j;
}

function shouldDrawPoint(x, y) {
  let index = mapToPixelIndex(x, y);
  let grayscaleValue = pixelArray[index];
  
  // Convert grayscale value to probability
  let probability = 1 - (grayscaleValue / 255);
  
  // Decide to draw the point based on probability
  return random() < probability; // Return true or false based on random chance
}

A Probability Webcam in 3D space

‘Eventually’, I incorporated all the components together to try out my initial vision of creating a fluid, responsive, and vague but solid representation of images from the webcam with arrows generated based on probabilities flying towards an axis plane.

Unfortunately, although the product with many tuning of variables like the amount of arrows, the spread and speed of the arrows, and the lapse of arrows on the axis, etc., there could be a vague representation captured (aided by re-mapping & stretching out the probabilities with more contrasting grayscale values), the huge amount of 3D objects required to shape a figure significantly undermines the experience of running the product.

function applyHighContrast(array) {
  // Stretch the grayscale values to increase contrast
  let minVal = Math.min(...array);
  let maxVal = Math.max(...array);

  // Prevent division by zero if all values are the same
  if (minVal === maxVal) {
    return array.map(() => 255);
  }

  // Apply contrast stretching with a scaling factor
  const contrastFactor = 6; // Increase this value for more contrast
  return array.map(value => {
    // Apply contrast stretching
    let stretchedValue = ((value - minVal) * (255 / (maxVal - minVal))) * contrastFactor;
    
    // Clip the value to ensure it stays within bounds
    return constrain(Math.round(stretchedValue), 0, 255);
  });
}

Besides, the 3D space did not benefit the demonstration of this idea but hindered it as the perspectives of the arrow farther away from the focus would make them occupy more visual space and disturb the probability distribution. 

A Probability Webcam in 2D space

At the end of the day, after trying the ortho() method in the 3D version (which makes the objects appear without the affecting perspectives), I realized that reconstructing a 2D version was the right choice to better achieve my goals.

In this latest 2D version, I gave up the idea of drawing the axis plane and introduced the concept of probability distribution affected by ‘mic level’.

function setup() {
  ...

  // Create an audio from mic
  audio = new p5.AudioIn();
  audio.start(); // start mic
}

function draw() {
  ...

  // Map the audio level to contrast factor
  let level = map(audio.getLevel(), 0, 1, 1.05, 30);
  // Apply high contrast transformation
  pixelArray = applyHighContrast(pixelArray, level);
  ...
}

function applyHighContrast(array, contrastFactor) {
  // Stretch the grayscale values to increase contrast
  let minVal = Math.min(...array);
  let maxVal = Math.max(...array);

  // Prevent division by zero if all values are the same
  if (minVal === maxVal) {
    return array.map(() => 255);
  }

  // Apply contrast stretching with a scaling factor
  return array.map(value => {
    // Apply contrast stretching
    let stretchedValue = ((value - minVal) * (255 / (maxVal - minVal))) * contrastFactor;
    
    // Clip the value to ensure it stays within bounds
    return constrain(Math.round(stretchedValue), 0, 255);
  });
}

reflection

TBH, the technical explorations did consume much of my time this week, and it came to me later to realize that I could have thought of more about the theme ‘loop’ before getting started – as it appears now to be a very promising topic to delve deeper into. Nevertheless, I believe our workflow of production could be like this from time to time, and it is crucial to maintain this balance thoughout.

 

 

 

Week 2 – Bubbles.

CONCEPT:

While walking across campus, I saw children blowing bubbles with their families. This simple, joyful scene transported me back to my own childhood, when my brother and I would spend hours blowing bubbles in the park. While I was in front of the screen, I thought about the patterns I recently saw during the week — and then I was reminded of the bubbles. I was then propelled this wave of nostalgia, inspiring me to incorporate the playful essence of bubbles into my piece. 

 

CODE:

In terms of coding, I was particularly challenged by the randomness factor. I was unsure of how to produce bubbles at random places, sizes, and even colours. Therefore, I looked for assistance on the p5.js website.: https://p5js.org/reference/p5/random/ to understand the syntax of the ‘random’ feature. After relentless experimenting, I was able to randomise the selection of colours, which I had little idea on how to approach. I was particularly proud of it. In terms of design, I removed the stroke to add some sort aesthetic to the bubbles, and I changed the background to a light blue, to resemble the sky.

 

for (let i = 0; i < 20; i++ ){
  
  let x = random(width);
  let y = random(height);
  let r = random (10, 50);
  
  fill(random(250), random(250), random(250))
  
  noStroke()
  
  ellipse(x,y,r)
  

REFLECTION:

This assignment really challenged what my understanding of art is, and also made me realise how often we are surrounded by ‘art’ – no matter how subtle, quotidian, or ordinary it may be. Perhaps, if I were given the opportunity to further it, I may add some user interactivity such as popping the bubble and slowing down the speed.

WORK:

 

Reading Reflection – Week 2

I found Casey Reas’ Eyeo talk to be really interesting because it gave me a new perspective on how technology can create art. Although I would consider myself a fairly creative person, recently I’ve felt surprisingly restricted by how I can use code to make art. Reas’ work, in particular the one that represented the movement of cancer cells inspired me because it showed how interpretive art can be and also the unique ways in which we can reference aspects about life to influence what we create.

I also found it interesting how Reas distinguished between pieces he deemed complete and others he did not. He labeled a few of them as rather simple or mundane but as a viewer they seemed complete to me. Although this seems rather insignificant I believe it highlights how personal the artistic experience is to an artist. This acted as a reminder for me to work with intention because once I have a goal in mind, like Reas, I believe it will be easier for me to deliberately work on my projects with creative freedom while still having goals in mind.

All in all, I enjoyed the refreshing prospective Reas provided through displaying his own work and hope to apply it to my own methods. Seeing the wide array of examples and applications has not only given me ideas for how I might approach this week’s project but also gives me more confidence to freely explore future developments.

Week 2: Loop Flowers

For inspiration, I found some online references with patterns created by combining similar shapes. Most of these combinations resulted in forms that resembled flowers.

Flower Pattern

I drew a connection between using a shape multiple times to draw a pattern and using loop in p5js. My idea is using the same shape, with elements of randomness to create a different flower every time the user interacts with the canvas.

Challenge

When creating this piece, what I found particularly challenging was using the rotate() function to position the petals around the center (the mouse cursor). However, the rotate() function in p5.js only allows rotation around the origin (0,0), so I had to use translate() to change the origin to the mouse cursor’s position. This quick fix created a problem later when I wanted the flowers to remain permanently on the canvas instead of disappearing every time the mouse was released.

For drawing the flowers, I created two functions: one to draw flowers with squares and one with ellipses. They are similar in essence except for the change in shapes used to draw.

function squareFlower(x,y,size,fcolor){
  //repeat drawing the square with the rotation of angle until the 360 degree is full
  for(let i =0; i<(360/angle); i++){
    rotate(angle);
    stroke(fcolor[0], fcolor[1],fcolor[2]);
    fill(fcolor[0], fcolor[1],fcolor[2],fcolor[3]);
    rect(x,y,size);
  }
}

Finally, I included random attributes like shape, petal size, and angle of rotation to create different flowers. I also adjusted the opacity of the shapes to create a more interesting composition. To make it look more like flowers, I implemented an increase in shape size over time when the mouse is pressed, creating a blooming effect.

Final Sketch

Reflection

After finishing the sketch, I tried to make the flower appear permanently on the screen instead of disappearing, but I couldn’t because the flowers are drawn based on their rotation around the origin (which changes for each flower). When I revisit this in the future, I want to change the drawing of the flowers from using the built-in rotate() function to using math to calculate the new coordinates. This will allow the flowers to be drawn independently of the origin’s location. Another issue is that some shapes in the flowers are overlapping, which I think stems from the calculation of the rotation angle. I will also try to improve this calculation in future sketches.

Assignment 1 Self Portrait

Concept: 

For my self portrait I wanted to do something slightly unconventional. Joan Miro is an artist that I really like and I thus decided to do my self portrait in his unorthodox surrealist style. I wanted to have fun background elements commonly seen in his artwork such as the crescent moon and a star.

Something I’m proud of:

Something I’m proud of for this assignment are the random curves across the whole portrait, and especially drawing the crescent moon. These aspects took me the longest time to figure out and are thus the ones I am most proud of.

function details() {
  
  fill(255,255,0);//drawing a crescent moon shape by drawing two intersecting circles and having one of them be the color of the background
  //this effect makes it so that the second circle cuts the first one to make the moon shape
  noStroke();
  ellipse(150,630,100,100);
  fill(240, 248, 255);
  ellipse(170,630,100,100);

  //drawing the diagonal lines for the eyebrows
  stroke(0);
  strokeWeight(4);
  line(300, 300, 450, 400);
  line(500, 400, 650, 300);

  //drawing the yellow curve around the mouth
  noFill();
  stroke(255, 215, 0);
  strokeWeight(8);
  beginShape();
  vertex(250, 550);
  bezierVertex(350, 500, 450, 700, 550, 550);
  endShape();
}

Final Product:

Reflections:

I enjoyed working on this assignment quite a lot, it was a fun experience juggling between taking inspiration from Miro and then also trying to add my own personality into the portrait so I could make something I could be truly proud of. An aspect that I would like to work on in the future is adding some sort of interactivity maybe with eye movement or some background features morphing into others to further portray the dreamlike essence of Joan Miro.

Weekly Reflection(Creative Reading): Week 2

Reflection:

Casey Reas’ video shows how structure and randomness blend together in digital art. He explains that with the use of software, artists can combine strict rules with random elements to create pieces that are both unique and unpredictable. This approach challenges the traditional view of art, where the artist had full control over every detail. Now, algorithms can introduce unexpected outcomes, making the process less about direct control and more about exploring the balance between order and chance. This new way of creating art mirrors life itself, where both structure and unpredictability constantly interact.

Watching this video made me reflect on how creativity is evolving. Reas shows that creativity is no longer just about human control or intention; it can also come from the interaction between the artist and the machine. This got me thinking about whether using randomness and algorithms might make the art less personal or if it actually enhances the creative process by introducing new possibilities that an artist might not think of alone. I’m curious to discuss these ideas with others and hear different perspectives on whether this shift in the role of the artist adds value to art or takes away from it. The questions Reas raises make me wonder how much of creativity comes from us and how much can come from machines, and I believe exploring this could change the way we understand the art of the future.

Self Portrait: Assignment_01

Concept: The concept comes from my favourite childhood cartoon Masha and Bear. I always loved how the bear’s room looked so cozy and peaceful in the cartoon. As I grew older, I noticed my younger brothers acting mischievously, much like Masha. In response, I found myself playing a protective role similar to the Bear’s in the series. So, I choose the bear as my concept for this assignment and tried to create a stylized portrait of the bear using basic 2D shapes in P5.js. While the result may not be an exact replica of the Bear, I tried to capture the essence of his character as seen in cartoon.

Highlight Code: One part of the code that I’m particularly proud of is the drawing of the bookshelf. It’s my first time working with Java and doing a full sketch on P5.js, so I had to go through quite a few tutorials from this channel to understand basics of the drawings. I tried to code the bookself using loops technique from this video. I calculated the positions of the shelves and books using variables and loops. This method allowed me to place multiple shelves and books at regular interval but I had to go through a lot of trials and errors so took me quite some time to figure out how to work with the perspective drawing in P5.js. On my first try, I tried to make a 2D bookshelf, but that didn’t look satisfactory so I added some depth in the drawing using beginShape(), vertex(), and endShape() functions to add some thickness on the sides and changed the fill() to create a contrast on the shadow and lighting. To express what I meant to portray I added one line of quote about books, I achieved this by using textStyle()and textAlign() functions.

Code:

function setup() {
  createCanvas(600, 500);
  setGradient(0, 0, width, height, color(255, 182, 193), color(255, 228, 225)); // Gradient background
  drawHelloKitty(); //function that I am using to draw the main character 
  drawBookshelf();
}

function drawHelloKitty() {
  noLoop();
  noStroke();

  // Draw face
  stroke(0);
  strokeWeight(0.2);
  fill("rgb(252,245,225)");   
  ellipse(200, 200, 180, 180); 

  // Draw ears
  fill('rgb(244,230,215)');
  ellipse(120, 130, 70, 70); // Left ear
  ellipse(280, 130, 70, 70); // Right ear

  //inner ear color

  fill(160, 82, 45); // Medium brown
  ellipse(120, 130, 50, 50); // Left inner ear
  ellipse(280, 130, 50, 50); // Right inner ear

  // eyes
  fill(0); 
  ellipse(170, 200, 22, 22); // Left eye
  ellipse(230, 200, 22, 22); // Right eye

  // Draw nose
  fill(160, 82, 45); // Medium brown
  ellipse(200, 230, 22, 18); // Nose

  // Draw mouth
  
  stroke(0);
  strokeWeight(2);
  noFill();
  arc(200, 245, 50, 25, 0, PI); // Mouth

  // Draw whiskers
  
  stroke(0);
  strokeWeight(2);
  line(100, 200, 50, 190); // Left whisker 1
  line(100, 210, 50, 210); //2
  line(100, 220, 50, 230);  //3

  line(300, 200, 350, 190); // Right whisker 1
  line(300, 210, 350, 210); //2
  line(300, 220, 350, 230); //3

  
  // Drawing body
  fill(255, 0, 0);
  stroke(0);
  strokeWeight(2);
 
  fill(160, 82, 45);
  ellipse(200, 480, 260, 400);
  stroke(0);
  strokeWeight(2);
  fill("rgb(250,204,109)")
  rect(155, 270, 90, 100);


  // hand details
  fill('rgb(252,245,225)'); 
  ellipse(140, 300, 70, 40); // Left hand
  ellipse(260, 300, 70, 40); // Right hand 
  
stroke(0); 
  
strokeWeight(2); 

  // lines for left hand
line(140, 295, 170, 290); // First finger
line(140, 305, 173, 305); 
line(140, 315, 165, 315); 
  
// lines for right hand
line(228, 295, 265, 297); // First finger
line(225, 305, 265, 305); 
line(240, 315, 266, 315); 
}

function drawBookshelf() {
  // Adjusted position of the bookshelf
  const shelfX = 400;
  const shelfY = 150; 
  const shelfWidth = 160;
  const shelfHeight = 360;
  const shelfThickness = 19;
  
  // Draw the bookshelf
  fill(150, 75, 0); // Wood color
  rect(shelfX, shelfY, shelfWidth, shelfHeight); // Main body of the bookshelf
  
  // Draw the shelves
  fill(120, 60, 0); // Slightly darker wood color
  for (let i = 1; i <= 5; i++) { // Adjusted to have more shelves
    rect(shelfX, shelfY + i * 60, shelfWidth, shelfThickness); // Horizontal shelves
  }
  
  // Draw the sides for depth
  fill(100, 50, 0); // Darker wood color for sides
  beginShape();
  vertex(shelfX, shelfY); // Top left corner
  vertex(shelfX + 10, shelfY - 10); // Slightly to the right and up
  vertex(shelfX + 10, shelfY + shelfHeight - 10); // Down to the bottom
  vertex(shelfX, shelfY + shelfHeight); // Back to the bottom left corner
  endShape(CLOSE);
  
  beginShape();
  vertex(shelfX + shelfWidth, shelfY); // Top right corner
  vertex(shelfX + shelfWidth + 10, shelfY - 10); // Slightly to the right and up
  vertex(shelfX + shelfWidth + 10, shelfY + shelfHeight - 10); // Down to the bottom
  vertex(shelfX + shelfWidth, shelfY + shelfHeight); // Back to the bottom right corner
  endShape(CLOSE);

  // Draw the books
  fill(200, 0, 0); // first row
  drawBook(shelfX + 10, shelfY + 60, 40, 80);
  drawBook(shelfX + 60, shelfY + 60, 40, 80);
  drawBook(shelfX + 110, shelfY + 60, 40, 80);
  
  fill(0, 200, 0); // second row
  drawBook(shelfX + 10, shelfY + 130, 40, 80);
  drawBook(shelfX + 60, shelfY + 130, 40, 80);
  drawBook(shelfX + 110, shelfY + 130, 40, 80);

  
  
  // a quote below the bookshelf
  fill(0); 
  textSize(18);
  stroke(0);
  strokeWeight(0);
  textFont('Arial');
  textStyle(NORMAL);
  textAlign(CENTER);
  
 // Text lines
let line1 = "Read,";
let line2 = "Masha!";

// position of the quote
text(line1, width / 3, 320); 
text(line2, width / 3, 340); 
  
}

function drawBook(x, y, w, h) {
  fill(255);
  rect(x, y, w, h); 
  fill(0); 
  rect(x + 10, y, 20, h); //the book spine

}

function setGradient(x, y, w, h, c1, c2) {
  noFill();
  for (let i = y; i <= y + h; i++) {
    let inter = map(i, y, y + h, 0, 1);
    let c = lerpColor(c1, c2, inter);
    stroke(c);
    line(x, i, x + w, i);
  }
}

P5.js Sketch:

Reflection and ideas for future work or improvements:

I felt as a beginner I learnt a lot while doing this basic shape based portrait. From brainstorming to implementing, I changed some ideas, colors and techniques but at the end, I am pretty happy with this little drawing. However, there are areas for improvement and future exploration.

The bear doesn’t look or match with the bear color from the cartoon, so it can be enhanced by using different color pallets from rgb colors. The face shape of the bear can be refined a bit more with some curves to give a bear like feature.  Interactivity can be added into this portrait to make it more appealing and fun. The color combination makes the brown color heavy on eyesight, it can be changed as well.

Week 1- Self Portrait: MANG-F2024

Concept

For this self-portrait, I wanted to create something that shows who I am but also adds some fun, interactive elements. I focused on making my hair, face, and upper body look as close to real as I could. I also added cool features like changing the background color when you click the mouse and making the eyes follow the cursor. Plus, there is a custom wand that moves with your mouse, adding a playful touch to the whole thing.

Code

The code begins by setting up a canvas that is 400×400 pixels in size and gives it a light blue background. The default mouse cursor is hidden to make the custom wand cursor more prominent.

The draw() function runs continuously, which allows the portrait to update in real-time. The background color changes to a random color when the mouse is pressed and resets to the original blue when released.

The main features of the face—head, eyes, nose, mouth, ears, and hair—are drawn using basic shapes like ellipses, rectangles, and triangles. The pupils are interactive and follow the mouse cursor, giving the eyes a dynamic effect. The shoulders and chest area are represented with curves and rectangles to add more detail to the portrait.

Finally, a custom wand cursor is drawn at the mouse position, with a black handle and a yellow tip, adding a playful touch to the sketch.

Highlight

One part of my code that I’m really happy with is the way the pupils of the eyes move. I made it so that the pupils follow the mouse as you move it around. This makes the eyes look like they’re actually looking at the cursor, which brings the portrait to life. Here’s the code that does this:

 

// Interactive Pupils Movement
fill(0);  // Sets the fill color to black for the pupils
let leftPupilX = map(mouseX, 0, width, 165, 175);  // Maps mouseX to move the left pupil within the eye
let rightPupilX = map(mouseX, 0, width, 225, 235);  // Maps mouseX to move the right pupil within the eye
ellipse(leftPupilX, 180, 10, 10);  // Left pupil
ellipse(rightPupilX, 180, 10, 10);  // Right pupil

This part of the code lets the pupils move depending on where the mouse is. It’s a small detail, but it makes the portrait feel more interactive and real.

 

Embedded Sketch

.

 

Reflection and Ideas for Future Work

Looking back, I’m happy with how the interactive features turned out, especially the moving pupils and the changing background color. These parts make the portrait more fun to play with. But, I think I could improve the details of the hair and the face to make them look better. In the future, I would like to work on shading and maybe add more interactive features, like changing facial expressions. I also want to get the proportions of the head and face just right to make the portrait even more realistic.

 

Week 1: A Self Portrait

Concept

For my self-portrait, I wanted to create something that represents who I am on the surface and who I sometimes become inside. Growing up, I was a huge fan of Dragon Ball Z, so for this project, I decided to make a portrait of myself that can transform into a ‘Super Saiyan.’ For this assignment, we were required to use p5.js to create the self-portrait using the shapes and lines we learned in our first week. Before getting started, I drew the following images to guide my code; let’s call this the R&D part of the process:

This is the normal portrait concept of myself before the transformation.
This image represents the transformation during the R&D process.

 

The Process

The process for this project was quite enjoyable. I had a lot of fun experimenting with different colors, shapes, and lines while creating the portrait. I started from scratch, using an ellipse to resemble the face, and other shapes as the building blocks for the body’s anatomy. Once the basic human figure was drawn in JavaScript, the hair surprisingly became the most time-consuming aspect. I had to use triangles to accurately represent how my hair looks to closely match my image. I used online resources like ‘Google Color Picker’ to find the unique color codes that created my desired effect. A simple if-else statement was also used to enable the switch from normal to Super Saiyan. Be sure to stick around to find out about my favorite part of the code that sells the entire effect.

if(keyIsPressed === true) // he turns 'Super Sayain' { background(174, 224, 235);

 

Code that I am Proud of:

The code that I’m particularly proud of is the one where I used a ‘for loop’. I started coding last fall, and during that time, I learned the basics, all the way up to classes and object-oriented programming. With this knowledge in mind, I wanted to experiment with how loops would function (no pun intended) in this self-portrait. The best way to integrate this was by using a for loop. It’s one thing to see your code run in a terminal, but it’s another glorious moment to see the visuals your code can produce. That’s exactly how I felt when I saw my code turn into something visually intriguing. The following code creates the energy around my transformed character to maximize the intended effect:

// The following loop is for the energy around our 'Sayain'
    for (let i = 0; i < 20; i++) 
    {
      let x1 = random(width);
      let y1 = random(height);
      let x2 = x1 + random(-30, 30); // Randomizing the length and direction of the lines
      let y2 = y1 + random(-30, 30);
      line(x1, y1, x2, y2);
    }

The final Outcome:

The moment you’ve all been waiting for! To turn Super Saiyan, press any key on your keyboard (for simplicity, try pressing the spacebar 😉)

Reflection:

I thoroughly enjoyed every part of creating this self-portrait, and it taught me how to visualize my code and think creatively while coding. Although I faced some challenges in aligning each shape with others, overall, this assignment helped me strengthen both my creative and coding skills. For future improvements, I’d like to implement more conditionals, variables, loops, and classes in this portrait and in my future projects. I’d also like to explore how these elements can be integrated with 3D models in p5.js to create something so interactive that people would become truly captivated by my work.

Week 1 – Self Portrait

Concept
My concept centres around the idea that a cat represents an important aspect of my identity, being my favourite animal and symbolising certain personal traits like independence, curiosity, and playfulness. In this project, I wanted to create an interactive experience where a simple click could reveal the transition between a cat and my self-portrait. The idea was to make this transformation not just about visuals but also about the connection between the two images—how the cat symbolises me in a more abstract way and how that symbolism morphs into a more literal self-portrait. The goal was to capture a sense of play and surprise, inviting the viewer to engage with the piece and reflect on how we often associate certain animals with our personalities or characteristics.

function draw() {
  if (showCat) {
    drawCat();
  } else {
    drawPerson();
  }
}

 

This function is simple yet crucial for the interactive element of my project. It efficiently toggles between two distinct visual representations based on user interaction. The conditional logic is straightforward but powerful, allowing for a seamless transition between the cat and the self-portrait. What I find particularly satisfying about this piece of code is how it encapsulates the entire concept in just a few lines. It’s a perfect example of how simplicity in code can lead to a more engaging and fluid user experience. This function effectively bridges the gap between concept and execution, making the interaction feel natural and intuitive.

Reflection and Future Improvements
Reflecting on the project, I’m pleased with how the interactive element turned out, but I see a lot of potential for further refinement. One area I would like to focus on in the future is adding more realism to the visuals, particularly through the use of shadows and shading techniques. Currently, the transition between the cat and the self-portrait is clean and simple, but adding shadows could give the images more depth and make the transformation feel more lifelike. I also want to explore more advanced rendering techniques to enhance the texture and detail of both the cat and the self-portrait. Additionally, experimenting with subtle animations during the transition could add another layer of sophistication, making the experience even more engaging. This project has been a great learning experience, and I’m excited to see how I can push it further to create a more polished and immersive piece.