After watching Dan Shiffman’s YouTube video 1 on the paper2, I had to try it for myself.
Lloyd’s algorithm
The algorithm builds on Lloyd’s algorithm (also called Lloyd relaxation), which uses properties of Voronoi diagrams in a clever way. Its goal is to spread a point set so that all points even out their distance to their direct neighbours. It goes something like this:
- Generate N random points
- Then, in each iteration:
- Calculate the Voronoi diagram of those points
- Find the centroid of each cell (d3’s
polygonCentroidworks well here) - Move each point to its cell’s centroid
Maybe just me, but I find it highly satisfying when it converges.
Weighted relaxation
Just one change to the loop above: instead of moving each point to the geometric centroid of its cell, move it to the weighted centroid — pulled toward the darker pixels so dots cluster where the image is darkest.
In each iteration, go over every pixel (or a subsample):
- Find the cell it belongs to, which is the cell of its nearest point
- Take its weight (its darkness)
- Add its position times its weight, and the weight itself, to that cell’s running totals
Then for each cell, its position total divided by its weight total is the weighted centroid. Move the cell’s point there.
Think of it as every pixel pulling the point toward itself with a force proportional to its weight — the weighted centroid is just where those forces reach equilibrium.
My implementation is not without its flaws. The effect works best on images with high contrast, large areas of low light and on edges. Areas of uniform value often lack uniform distribution with clusters forming randomly. Tweaking those weights should help, so the controls have a weight curve: it reshapes how much ink each tone gets, and moving its first point to the right cuts off everything below a tone. I have not yet tested whether it cures the clustering.
Hero: Based on Photo by Dawn Casey on Unsplash