Published
Edited
Dec 21, 2021
2 forks
9 stars
Insert cell
Insert cell
Insert cell
Insert cell
{
const { width, height } = imgdata;
const d = ((width * height) / 10000) ** 0.5;
const ctx = DOM.context2d(width, height, 1);
const radius = (x, y) => ((1 - luminance[~~x + ~~y * width]) * d) / 2;
const draw = primitives(ctx, d, prim);
const f = animation.length ? d3.easeBounceInOut : (t) => 1;
let displacements = {};
for (let x = 0; x < width; x += d)
displacements[x] = Math.random() * height * 2 - height;
const nframes = animation.length ? 100 : 1;
let newimg = loadImage(await randomImage(1024, 768, "butterfly"));
for (let frame = 0; frame <= nframes; frame++) {
const t = frame / nframes;
ctx.clearRect(0, 0, width, height);
let delta = 1 - f(t);
for (let x = 0; x < width; x += d) {
let disp = displacements[x];
for (let y = 0; y < height; y += d) {
let r = radius(x, y);
draw(x, y + delta * disp, r * (1 - delta));
}
}
yield ctx.canvas;
}
await Promises.delay(animation.length ? 2000 : 10000);
for (let frame = 0; frame <= nframes; frame++) {
const t = frame / nframes;
ctx.clearRect(0, 0, width, height);
let delta = f(t);
for (let x = 0; x < width; x += d) {
let disp = displacements[x];
for (let y = 0; y < height; y += d) {
let r = radius(x, y);
draw(x, y + delta * disp, r * (1 - delta));
}
}
yield ctx.canvas;
}
await Promises.delay(500);
mutable image = await newimg;
}
Insert cell
mutable image = FileAttachment("photo-1560263816-d704d83cce0f.jpeg").image()
Insert cell
imgdata = imageData(image)
Insert cell
luminance = {
const { width, height, data } = imgdata;
const n = width * height;
const lumArray = new Float64Array(n);
for (let i = 0; i < n; i++) {
const offset = i * 4;
lumArray[i] =
(data[offset] * 0.2126 +
data[offset + 1] * 0.7152 +
data[offset + 2] * 0.0722) /
255;
}
return lumArray;
}
Insert cell
primitives = function (ctx, d, prim) {
let tog = false;
if (prim == "square")
return (x, y, r) =>
ctx.fillRect(x + d / 2 - r, y + d / 2 - r, r * 2, r * 2);
if (prim == "circle")
return (x, y, r) => {
ctx.beginPath();
ctx.arc(x + d / 2 - r, y + d / 2 - r, r, 0, Math.PI * 2);
ctx.fill();
};
if (prim == "zigzag")
return (x, y, r) => {
ctx.beginPath();
ctx.moveTo(x, y);
ctx.lineTo(x + r * 2, y + d / 2);
ctx.lineTo(x, y + d);
ctx.stroke();
};
if (prim == "triangle")
return (x, y, r) => {
tog = !tog;
if (tog) {
ctx.beginPath();
ctx.moveTo(x, y + d / 2 - r);
ctx.lineTo(x + r * 2, y + d / 2);
ctx.lineTo(x, y + d / 2 + r);
ctx.fill();
} else {
ctx.beginPath();
ctx.moveTo(x + d, y + d / 2 - r);
ctx.lineTo(x + d - r * 2, y + d / 2);
ctx.lineTo(x + d, y + d / 2 + r);
ctx.fill();
}
};
if (prim == "line")
return (x, y, r) => {
ctx.lineWidth = r;
ctx.beginPath();
ctx.moveTo(x, y);
ctx.lineTo(x + d, y + d);
ctx.stroke();
};
}
Insert cell
import { randomImage, loadImage, imageData } from "@esperanc/voronoi-mosaics"
Insert cell

Purpose-built for displays of data

Observable is your go-to platform for exploring data and creating expressive data visualizations. Use reactive JavaScript notebooks for prototyping and a collaborative canvas for visual data exploration and dashboard creation.
Learn more