Published
Edited
Sep 13, 2022
1 fork
4 stars
Insert cell
# Circle Packing with Images

This notebook shows how a simple circle packing algorithm can produce some beautiful results when the circles are replaced by images
Insert cell
canvas = htl.html`<canvas width=500 height=500>`
Insert cell
Insert cell
Insert cell
viewof maxR = Inputs.range([20, 50], { step: 1, value: 30, label: "Max radius" });
Insert cell
viewof minR = Inputs.range([2, maxR - 1], { step: 1, value: 5, label: "Max radius" });
Insert cell
Insert cell
viewof imgFile = Inputs.file({ label: "Image maskfile" })
Insert cell
customImage = imgFile == null ? null : imgFile.image();
Insert cell
Insert cell
mcanvas = htl.html`<canvas width=500 height=500>`
Insert cell
imgData = {
function getImageData(mfile, canvasWidth, canvasHeight) {
var img = new Image();
img.src = mfile.src;
img.crossOrigin = "Anonymous";
// draw image
let ctx = mcanvas.getContext('2d');
ctx.clearRect(0, 0, canvas.width, canvas.height);
ctx.drawImage(img, 0, 0, 500, 500);
// get image data
let data = ctx.getImageData(0, 0, canvas.height, canvas.width).data;
let imgData = [];
for (let x = 0; x < canvas.width; ++x) {
let row = [];
for (let y = 0; y < canvas.height; ++y) {
let rgba = [];
for(let j=0;j<4;j++){
rgba.push(data[(y + x * canvas.height) * 4 + j]);
}
row.push(rgba);
}
imgData.push(row);
}
return imgData;
}

let wolf_head = customImage == null ? await FileAttachment("wolf_head.jpg").image() : customImage;
return getImageData(wolf_head, 500, 500);
}
Insert cell
Insert cell
// valid pixels are the pixels which we can start a circle on
validPixelsIdx = {
let validPixelsIdx = [];
for (let x = 0; x < imgData.length; ++x) {
let row = imgData[x];
for (let y = 0; y < row.length; ++y) {
let v = row[y];
if (v[0] == 0 && v[1] == 0 && v[2] == 0)
validPixelsIdx.push([x, y]);
}
}
return validPixelsIdx;
}
Insert cell
Insert cell
canvas2 = htl.html`<canvas width=500 height=500>`
Insert cell
circles = {
let ctx = canvas2.getContext('2d');
ctx.clearRect(0, 0, canvas.width, canvas.height);
function shuffleArray(array) {
for (let i = array.length - 1; i > 0; i--) {
const j = Math.floor(Math.random() * (i + 1));
[array[i], array[j]] = [array[j], array[i]];
}
}

function drawCircle(c) {
ctx.beginPath();
ctx.arc(c.x, c.y, c.r, 0, 2 * Math.PI);
ctx.stroke();
}

function circleDist(c1, c2) {
return Math.sqrt(Math.pow(c1.x - c2.x, 2) + Math.pow(c1.y - c2.y, 2)) - (c1.r + c2.r);
}
shuffleArray(validPixelsIdx);
let circles = []; // array to hold placed circles radius and position
for (let i=0; i<validPixelsIdx.length; i++) {
let centerX = validPixelsIdx[i][1];
let centerY = validPixelsIdx[i][0];
let radius = Math.random() * (maxR - minR) + minR;

let curr_c = {x: centerX, y: centerY, r: radius};
let dists = circles.map((c) => circleDist(c, curr_c))

let minDist = Math.min(...dists);
if (minDist < 0) continue;
circles.push(curr_c);
drawCircle(curr_c);
}

return circles;
}
Insert cell
Insert cell
canvas3 = htl.html`<canvas width=500 height=500>`
Insert cell
flowers = {
let flower_0 = await FileAttachment("masked_0.png").image();
let flower_1 = await FileAttachment("masked_1.png").image();
let flower_2 = await FileAttachment("masked_2.png").image();

return [flower_0, flower_1, flower_2];
}
Insert cell
{
[canvas.getContext('2d'), canvas3.getContext('2d')].forEach(ctx => {
ctx.clearRect(0, 0, canvas.width, canvas.height);
circles.forEach(c => {
let i = Math.floor(Math.random() * flowers.length);
let flower = flowers[i];
ctx.drawImage(flower, c.x - c.r, c.y - c.r, 3 * c.r, 3 * c.r);
})
})
}
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