This week I worked as a pair with Queenie on a color-based optical illusion where you lose something and gain something else. We both really loved the idea of doing some type of pixel swapping with our faces, so that was our starting point. We did not really have any projects we were directly inspired from for the core of our piece. I was inspired by the pixelated mirrors we had on our last worksheet, and felt good about how to make those. Initially we were exploring using face tracking code in our piece but it was too complicate for me to wrap my head around in one week, so we did not feel comfortable using that. Instead we worked with the idea of the pixel mirror applied to a still image and then using arrays to sample to the colors of the pixels in order to move them to their end location. We were able to design and lay out a lot of how this would work however we needed to turn to Claude for some help with some of the color sampling references as well as the animation and rules of the animation.
Some words to describe the piece: morphing, deceptive, maximalist, distraction, and imperfect. We created an illusion that makes it appears as though the pixels are traveling across the screen to switch Queenie and my locations in the picture, but really there is just an entirely new rectangle being drawn as that pixel with the color data that was sampled from the beginning location. The distraction in the piece comes from the rainbow hue image we placed under the main picture. This choice came out of the worksheet activity where we created a hue spectrum, except this is just an image off google and is meant to throw people off as to what is exactly happening as the rainbow is revealed.
What is revealed : copies of Queenie and I, slightly different than before but pretty much the same. Also that the rainbow is a big phony.
What is lost: the original pixel approximations of Queenie and I
Here is the fullscreen and the editor.
let us; //Queenie and Noah image
let rainbowBg; //rainbow background layer
let startTime; //start time of the swapping sequence
let transitionDuration = 60000; //60 seconds of pixel swapping
let pixelSize = 8; // Size of each pixel block in the grid
let pixels = []; //pixel array
let isSwappingRight = true; //direction of pixel swap (true = right, false = left)
let isAnimating = true; //is the swap happening?
//loading up all the images
function preload() {
us = loadImage("noahandi.jpg");
rainbowBg = loadImage("rainbow.avif");
}
function setup() {
createCanvas(windowWidth, windowHeight);//full screen mode fitting
pixelDensity(1);
// finds the aspect ratio of 'us' picture so we can later resize it
let aspectRatio = us.width / us.height;
let newWidth = windowWidth;
let newHeight = windowWidth / aspectRatio;
// if height is smaller than the window height than readjust
if (newHeight < windowHeight) {
newHeight = windowHeight;
newWidth = windowHeight * aspectRatio;
}
// Resize both the main photo and rainbow background to fit the canvas when fullscreen'd
us.resize(floor(newWidth), floor(newHeight));
rainbowBg.resize(width, height);
initializePixels(); // Initialize pixel array for animation
}
function initializePixels() {
pixels = [];
// calculating the cols + rows for the grid of pixels on the 'us' photo
let cols = floor(us.width / pixelSize);
let rows = floor(us.height / pixelSize);
// creates the grid of pixels in the nested for loop
for (let i = 0; i < cols; i++) {
for (let j = 0; j < rows; j++) {
let x = i * pixelSize;
let y = j * pixelSize;
// are the pixels on the left or right side of 'us' pic
let isLeftSide = x < us.width / 2;
// gets the color of a pixel in the center of the pixel, dividing by 2 centers the color sampling
let c = us.get(x + pixelSize/2, y + pixelSize/2);
//copies the pixel's color and stores it in the string
let col = "rgb(" + red(c) + ", " + green(c) + ", " + blue(c) + ")";
// set start and end X positions based on the swap direction
let startX = isSwappingRight ? x : (isLeftSide ? x + us.width / 2 : x - us.width / 2);
//sets the end point for the swapping direction
let endX = isSwappingRight ? (isLeftSide ? x + us.width / 2 : x - us.width / 2) : x;
// add pixel data to array with start and end positions, color, and random start time
pixels.push({
startX: startX,
endX: endX,
y: y,
startTime: random(0, transitionDuration),//randomizing the pixel swapping animation starts
color: col
});
}
}
shuffleArray(pixels); // random order for what pixels "swap" when
startTime = millis();
isAnimating = true; // Start animation
}
function draw() {
// bottom 'layer' rainbow image
image(rainbowBg, 0, 0);
let currentTime = millis() - startTime;//counter for the animation
let allComplete = true; // Flag to check if all pixels have completed animation
// Iterate over each pixel to animate it
pixels.forEach(pixel => {
let blockProgress = constrain((currentTime - pixel.startTime) / 2000, 0, 1);//constrains each pixel's movement to 2 seconds, and flags it as 1 once it is finished
let currentX = lerp(pixel.startX, pixel.endX, blockProgress);
// redraws the pixel as a rectangle with its color data from before... similar but not the same
noStroke();
fill(pixel.color);
rect(currentX, pixel.y, pixelSize, pixelSize);
// checks if any pixels are still moving to decide if the animation is over or not
if (blockProgress < 1) {
allComplete = false;
}
});
// checks if the conditions are met to make the animation as stopped, so it will allow mousePressed to start it over
if (allComplete && isAnimating) {
isAnimating = false; // End animation
}
}
function mousePressed() {
if (!isAnimating) {
isSwappingRight = !isSwappingRight; //switches animation direction
initializePixels();
}
}
function shuffleArray(array) {
// swaps the items in the array with each other to randomize it
for (let i = array.length - 1; i > 0; i--) {
const j = floor(random(i + 1));
[array[i], array[j]] = [array[j], array[i]]; // Swap elements
}
}
function windowResized() {
resizeCanvas(windowWidth, windowHeight);
rainbowBg.resize(width, height);
}