This self-portrait was rendered using the p5.js library. The image is composed of basic geometric primitives, specifically ellipses, arcs, and rectangles. The procedure involved analyzing a reference photograph to abstract key facial features into these shapes. Each shape was then implemented programmatically using p5.js drawing functions to generate the final output.
This is the initial hand-drawn sketch that served as the blueprint for the final p5.js portrait. In analyzing my reference selfie, the focus was placed primarily on three key features: the hairstyle, eyebrows, and eyes. These elements were deliberately exaggerated in the sketch to create a distinct, stylized representation.
The keywords that I came up with the sketches are: a slender face, a close-cropped buzz cut, and ears that are quite noticeable in size and shape.
This is the image that I used gemini 2.5 pro to generate. Below is the prompt that I used:
Generate a p5js script for an East Asian man with a slender face, a close-cropped buzz cut, and ears that are quite noticeable in size and shape.
For the differences between my sketch and the visual outcome from AI, I can summarize with the following several points:
The AI cannot meticulously position the facial features; the proportions aren't quite right.
The AI's design for the facial features and hair is rather crude, tending to use lines instead of more complex shapes like rectangles or ellipses.
The AI used a very limited, flat color palette.
During the coding process, the greatest challenge came from calculate the coordinates of different facial features, especially drawing the eyebrows. My initial approach was to draw the eyebrows using rectangles. However, I discovered that the existing code or library lacked the functionality to rotate these shapes. To achieve the necessary tilted angle, I had to switch to drawing them by explicitly defining the vertices of each eyebrow.
Here is the code that I used for this project:
function setup() {
createCanvas(400, 400);
rectMode(CENTER);
noStroke();
}
function draw() {
background(96, 125, 139);
// Shirt
fill(0);
ellipse(width / 2, height, 400, 150);
// Hair
fill(0, 0, 0);
arc(width / 2, height / 2 - 70, 260, 250, PI, TWO_PI, CHORD);
rectMode(CORNER);
rect(70, height / 2 - 70, 20, 55);
rect(330 - 20, height / 2 - 70, 20, 55);
rectMode(CENTER);
// Ears
fill(253, 229, 202);
ellipse(width / 2 - 120, height / 2, 40, 75);
ellipse(width / 2 + 120, height / 2, 40, 75);
// Face
fill(253, 229, 202);
ellipse(width / 2, height / 2, 250, 300);
// Eyes
fill(255, 255, 255);
ellipse(120, height / 2, 30);
ellipse(270, height / 2, 30);
fill(0, 0, 0);
ellipse(115, height / 2, 20);
ellipse(265, height / 2, 20);
// Nose
stroke(255, 200, 188);
strokeWeight(10);
line(width / 2, height / 2 + 30, width / 2 + 15, height / 2 + 30);
noStroke();
// Mouth
stroke(0);
strokeWeight(3);
noFill();
arc(width / 2, height / 2 + 80, 50, 20, 0, PI/2);
noStroke();
// Eye brow
fill(0, 0, 0);
beginShape();
vertex(105, 131);
vertex(160, 154);
vertex(154, 168);
vertex(99, 145);
endShape(CLOSE);
beginShape();
vertex(229, 154);
vertex(284, 131);
vertex(290, 145);
vertex(235, 168);
endShape(CLOSE);
}