Skip to content

Instantly share code, notes, and snippets.

@ychalier
Last active July 22, 2022 19:40
Show Gist options
  • Save ychalier/c5a01cc7d5a4295c10aad688d677db14 to your computer and use it in GitHub Desktop.
Save ychalier/c5a01cc7d5a4295c10aad688d677db14 to your computer and use it in GitHub Desktop.
Photo Moshing
function gcd(a, b) {
if (b == 0) return a;
return gcd(b, a % b);
}
var context;
var width;
var height;
var image_data;
async function loadImageToCanvas(canvasSelector, imageUrl) {
const canvas = document.querySelector(canvasSelector);
canvas.style.imageRendering = "crisp-edges";
const image = new Image();
image.src = imageUrl;
await image.decode();
width = Math.min(256, window.innerWidth);
height = Math.floor(width * image.height / image.width);
canvas.width = width
canvas.height = height;
context = canvas.getContext("2d");
context.drawImage(image, 0, 0, image.width, image.height, 0, 0, width, height);
image_data = context.getImageData(0, 0, width, height);
return (context, width, height, image_data);
}
async function startDriftingLinesMosh() {
const colors = ["black", "#5bcffa", "#f5abb9", "white", "white", "white", "white"];
const drawSteps = 400;
const driftSteps = 50;
context.fillStyle = "white";
context.clearRect(0, 0, width, height);
const mosh_draw = async () => {
const pixel_count = image_data.data.length / 4;
let step = 2;
while (gcd(step, pixel_count) !== 1) {
step++;
}
for (let y = 0; y < pixel_count; y++) {
let pixel_index = y * step % pixel_count;
const brightness = (image_data.data[4 * pixel_index] + image_data.data[4 * pixel_index + 1] + image_data.data[4 * pixel_index + 2]) / (256 * 3);
const color_index = Math.floor(brightness * colors.length);
if (y > .8 * pixel_count && color_index <= 2) continue;
context.fillStyle = colors[color_index];
context.fillRect(pixel_index % width, Math.floor(pixel_index / width), 1, 1);
y % drawSteps === 0 && await new Promise(x => requestAnimationFrame(x));
}
};
const mosh_drift = async () => {
const seed = Math.random() * .5;
let direction = Math.random();
for (let h = 0; h < driftSteps; h++) {
const current_image_data = context.getImageData(0, 0, width, height);
const next_image_data = new ImageData(width, height);
for (let j = 0; j < width; j++)
for (let i = 0; i < height; i++) {
let data_index = 4 * (width * i + j);
let next_i, next_j;
if (direction < .25) {
next_j = Math.floor((j - seed + (2 * seed + 1) * current_image_data.data[data_index] / 256 + width) % width);
next_i = Math.floor((i - seed + (2 * seed + 1) * current_image_data.data[data_index + 1] / 256 + height) % height);
} else if (direction < .5) {
next_j = Math.floor((j - seed + (2 * seed + 1) * current_image_data.data[data_index] / 256 + width) % width);
next_i = Math.floor((i + seed - (2 * seed + 1) * current_image_data.data[data_index + 1] / 256 + height) % height);
} else if (direction < .75) {
next_j = Math.floor((j + seed - (2 * seed + 1) * current_image_data.data[data_index] / 256 + width) % width);
next_i = Math.floor((i - seed + (2 * seed + 1) * current_image_data.data[data_index + 1] / 256 + height) % height);
} else {
next_j = Math.floor((j + seed - (2 * seed + 1) * current_image_data.data[data_index] / 256 + width) % width);
next_i = Math.floor((i + seed - (2 * seed + 1) * current_image_data.data[data_index + 1] / 256 + height) % height);
}
const next_data_index = 4 * (width * next_i + next_j);
for (let channel = 0; channel < 4; channel++) {
next_image_data.data[data_index + channel] = current_image_data.data[next_data_index + channel];
}
}
context.putImageData(next_image_data, 0, 0);
await new Promise(x => requestAnimationFrame(x));
}
};
while (true) {
await mosh_draw();
await mosh_drift();
}
}
async function startSortedPixelsMosh() {
let custom_data = [];
for (let i = 0; i < height; i++) {
for (let j = 0; j < width; j++) {
let k = i * width + j;
let pixel = {
index: k,
i: i,
j: j,
r: image_data.data[k * 4],
g: image_data.data[k * 4 + 1],
b: image_data.data[k * 4 + 2]
};
pixel.luminosity = (pixel.r + pixel.g + pixel.b) / (255 * 3);
custom_data.push(pixel);
}
}
custom_data.sort((pixel_a, pixel_b) => pixel_a.luminosity - pixel_b.luminosity);
let custom_data_index = {};
for (let q = 0; q < custom_data.length; q++) {
custom_data_index[q] = custom_data[q].index;
}
context.fillStyle = `black`;
context.fillRect(0, 0, width, height);
while (true) {
var shift = Math.floor(Math.random() * width * height) + width;
for (let q = 0; q < custom_data.length; q++) {
let pixel_target = custom_data[(custom_data_index[q] + shift) % custom_data.length];
let pixel = custom_data[q];
context.fillStyle = `rgb(${ pixel.r }, ${ pixel.g }, ${ pixel.b })`;
context.fillRect(pixel_target.j, pixel_target.i, 1, 1);
q % 200 == 0 && await new Promise(x => requestAnimationFrame(x));
}
}
}
window.addEventListener("load", () => {
loadImageToCanvas("#profile", "profile.jpg").then(Math.random() < .5 ? startDriftingLinesMosh : startSortedPixelsMosh);
});
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment