After watching Dan Shiffman’s YouTube video 1 on the paper2, I had to try it for myself.
Lloyd’s Relaxations
The algorithm builds on Lloyd’s Relaxations, which uses properties of Voronoi diagrams in a clever way. The goal of Lloyd’s Relaxations is to spread a point set so that all points even out their distance to their direct neighbours. Each iteration goes something like this:
- Generate N random points
- Update the Points in each iteration like so
- 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 Relaxations
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.
For each pixel (or subsample):
- Find its containing cell
- For each pixel of the cell
- Take its weight (brightness)
- Multiply pixel position by weight
- Add that to a running total
- That sum is the weighted centroid
- Move the point to the weighted centroid
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. It’s probably unavoidable to tweak those weights or add some brightness curves via the controls panel.
Hero: Based on Photo by Alexander Krivitskiy on Unsplash