Skip to content
Sample of Voronoi Tessellation with Lloyd Relaxation

toxi_openframeworks · VoronoiRelaxation

Voronoi Tessellation with Lloyd Relaxation

Lloyd's algorithm applied to a jittered point cloud: each seed is replaced with the centroid of its Voronoi cell, repeatedly, producing ever-more-uniform polygonal cells. To stay interactive on pure numpy, the nearest-seed partition for centroid estimation is computed on a `centroid_sub` × `centroid_sub` subsample; the final image is rendered at full resolution. Each step also adds a small Gaussian jitter to every seed so the cells drift apart and reform.

Original: https://codeberg.org/thi.ng/umbrella/src/branch/develop/packages/geom-voronoi

▶ Try the live demo (JS port) ▶ Run reference.py via Pyodide 10 formulas have instant JS ports. The Pyodide path runs the actual Python in your browser — first load is ~6 MB, then instant.

Family
toxi_openframeworks
Source
toxi · openFrameworks
Evolution
geometric
Render
nearest_seed_partition
Complexity
medium
Agents
64
Year
2010
Author
Karsten Schmidt (toxiclibs / thi.ng) / Stuart Lloyd (algorithm)

Math essence

Lloyd step: for each pixel (x, y) in a coarse grid, compute argmin_i |(x, y) - p_i|^2; for each seed, average the positions of all pixels assigned to it; blend old and new position by `relaxation_blend`. Step adds Gaussian noise to seeds before each Lloyd pass, so cells slowly drift. Render is the nearest-seed partition at full resolution, colored by the seed's palette entry.

Parameters

Name Type Default Range Description
dim int 320 120–1000 Square canvas side length in pixels.
num_seeds int 64 4–400 Number of Voronoi seeds.
lloyd_steps int 6 0–60 Lloyd relaxation rounds run in reset() before the first render.
step_jitter float 4 0–60 Per-step Gaussian jitter (σ px) added to each seed.
relaxation_blend float 0.65 0.05–1 Lloyd step blend (1.0 = full snap to centroid; 0 = no movement).
centroid_sub int 48 16–96 Subsample resolution for centroid estimation (lower = faster but coarser).
palette_mode str earth — Palette of cell colors.
show_seeds bool true — Overlay small dots on each seed after rendering.
seed_radius int 2 0–6 Radius (px) of the seed overlay dots.
seed int null 0–999999 RNG seed.

Reference

First 60 lines of reference.py. Full source: library_external/toxi_openframeworks/VoronoiRelaxation/reference.py

from __future__ import annotations

from typing import ClassVar

import numpy as np


class VoronoiRelaxation:
    """Voronoi tessellation with Lloyd relaxation (toxi / thi.ng style).

    Lloyd's algorithm iteratively replaces each seed with the centroid
    of its Voronoi cell, producing ever-more-uniform polygons. The
    same algorithm is the basis of Karsten Schmidt's oF Voronoi
    tessellation patches (early-2010s demos) and the
    `@thi.ng/geom-voronoi` relaxation examples. To stay interactive on
    pure numpy, the nearest-seed partition is computed on a
    ``sub`` × ``sub`` subsample for centroid estimation and on a
    full-resolution grid for the final image.
    """

    family: ClassVar[str] = "toxi_openframeworks"
    sketch: ClassVar[str] = "voronoirelaxation"

    def __init__(
        self,
        dim: int = 320,
        num_seeds: int = 64,
        lloyd_steps: int = 6,
        step_jitter: float = 4.0,
        relaxation_blend: float = 0.65,
        centroid_sub: int = 48,
        palette_mode: str = "earth",
        show_seeds: bool = True,
        seed_radius: int = 2,
        bg_color: tuple[int, int, int] = (245, 240, 230),
        seed_color: tuple[int, int, int] = (35, 35, 50),
        seed: int | None = None,
    ) -> None:
        self.dim = dim
        self.num_seeds = num_seeds
        self.lloyd_steps = lloyd_steps
        self.step_jitter = step_jitter
        self.relaxation_blend = relaxation_blend
        self.centroid_sub = centroid_sub
        self._palette_mode = palette_mode
        self.show_seeds = show_seeds
        self.seed_radius = seed_radius
        self.bg_color = np.array(bg_color, dtype=np.float32)
        self.seed_color = np.array(seed_color, dtype=np.float32)
        self._seed = seed
        self.reset()

    def reset(self, seed: int | None = None) -> None:
        if seed is not None:
            self._seed = seed
        self.rng = np.random.default_rng(self._seed)
        self.pts = self.rng.uniform(0, self.dim, size=(self.num_seeds, 2))
        self.colors = self._palette(self.num_seeds)
        for _ in range(self.lloyd_steps):
            self.pts = self._lloyd(self.pts, sub=self.centroid_sub)

Try running it locally

The reference implementation is self-contained Python with numpy. Install the package and run:

python library_external/toxi_openframeworks/VoronoiRelaxation/reference.py

Need help? See Learn for the standard environment setup, or Playground for an in-browser sketchbook.

More from toxi · openFrameworks

Sample of Generative Type Letterforms

Generative Type Letterforms

toxi_openframeworks · GenerativeTypeLetterforms

Letterforms assembled from a 9×9 stroke grid alphabet (A–Z subset), then modulated by a parametric function (bar widths and stroke positions follow `a * sin(b*x + c)` style curves) and optionally recursively subdivided into smaller marks.

View →
Sample of Gray-Scott Reaction-Diffusion

Gray-Scott Reaction-Diffusion

toxi_openframeworks · ReactionDiffusionGS

The canonical two-species Gray-Scott reaction-diffusion model on a periodic square grid: dU/dt = Du*lap(U) - U*V^2 + F*(1-U), dV/dt = Dv*lap(V) + U*V^2 - (F+k)*V.

View →
Sample of Median-Cut Color Quantizer

Median-Cut Color Quantizer

toxi_openframeworks · ColorQuantizer

Heckbert's 1982 median-cut color quantization applied to an evolving procedural gradient + low-resolution noise wash.

View →
Sample of Pixel Sort (Glitch Art)

Pixel Sort (Glitch Art)

toxi_openframeworks · PixelSort

Per-row pixel sorting à la Kim Asendorf's `ASDFPixelSort` and Karsten Schmidt's own glitch-art Processing sketches.

View →
Sample of Sound-Reactive Particle Field (Synthetic Audio)

Sound-Reactive Particle Field (Synthetic Audio)

toxi_openframeworks · SoundReactiveField

Particles advected by a force field whose horizontal component is driven by a synthetic audio signal: a chain of N sinusoids at different frequencies/amplitudes (the 'audio'), with the audio's instantaneous value plus its time derivative ('beat onset') driving the field.

View →
Sample of Verlet Particle-Spring Network

Verlet Particle-Spring Network

toxi_openframeworks · ParticleSpring2D

A Verlet-integrated particle network connected to its k-nearest neighbours by Hooke's-law springs.

View →

See all toxi_openframeworks artworks →