Skip to content
Sample of Box Fitting Img

growth_aggregation · boxFittingImg

Box Fitting Img

The classic box fitting algorithm (see Box Fitting) modified with an image substrate, allowing new boxes to draw color from an invisible background. The following images use tornado photography as a color substrate and approximately 2200 individually fitted boxes. The structure of the fitted box region is determined randomly using the order in which boxes appear. Those boxes that appear earlier have a greater chance of a larger size.

Original: https://complexification.net

▶ 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
growth_aggregation
Source
core library
Evolution
growth
Render
grid_painting
Complexity
medium
Agents
2000
Year
2004
Author
j.tarbell

Math essence

Same non-overlapping expansion algorithm as boxFitting, but each box samples its color from a procedural 'sky' substrate image at its birth location. The result is a fitted-box mosaic where the substrate color is preserved only at fitted positions, surrounded by a white border.

Parameters

Name Type Default Range Description
dimx int 510 200–1500 Canvas width
dim int 330 200–1500 Canvas height
dimborder int 10 0–60 White border thickness
maxnum int 2000 100–8000 Maximum concurrent boxes
initial_boxes int 5 1–16 Boxes spawned on reset
substrate_seed int null 0–999999 Seed for procedural sky substrate

Reference

First 60 lines of reference.py. Full source: library/boxFittingImg/reference.py

from __future__ import annotations

from dataclasses import dataclass, field
from typing import ClassVar

import imageio.v2 as imageio
import numpy as np


def _sky_substrate(width: int, height: int, seed: int) -> np.ndarray:
    """Procedural 'sky' substrate mimicking the tornado/sky photos Tarbell used."""
    rng = np.random.default_rng(seed + 7919)
    yy = np.linspace(0.0, 1.0, height, dtype=np.float32)[:, None]
    xx = np.linspace(0.0, 1.0, width, dtype=np.float32)[None, :]
    sky_top = np.array([25.0, 35.0, 70.0], dtype=np.float32)
    sky_bot = np.array([180.0, 170.0, 160.0], dtype=np.float32)
    grad = yy
    base = sky_top[None, None, :] * (1.0 - grad[:, :, None]) + sky_bot[None, None, :] * grad[:, :, None]
    radial = np.sqrt((xx - 0.5) ** 2 + (yy - 0.55) ** 2)
    swirl = 0.5 + 0.5 * np.sin(50.0 * radial + 8.0 * np.arctan2(yy - 0.5, xx - 0.5))
    perturb = (swirl[..., None] - 0.5) * np.array([40.0, 25.0, -10.0], dtype=np.float32)[None, None, :]
    noise = rng.normal(0.0, 12.0, size=(height, width, 3)).astype(np.float32)
    img = np.clip(base + perturb + noise, 0, 255).astype(np.uint8)
    return img


@dataclass
class _Box:
    x: int
    y: int
    d: int
    color: np.ndarray
    ok_to_draw: bool = False
    chaste: bool = True


class BoxFittingImg:
    family: ClassVar[str] = "growth_aggregation"
    sketch: ClassVar[str] = "boxFittingImg"

    def __init__(
        self,
        dimx: int = 510,
        dim: int = 330,
        dimborder: int = 10,
        maxnum: int = 2000,
        initial_boxes: int = 5,
        bg_color: tuple[int, int, int] = (0, 0, 0),
        seed: int | None = None,
        substrate_seed: int | None = None,
    ) -> None:
        self.dimx = dimx
        self.dim = dim
        self.dimborder = dimborder
        self.maxnum = maxnum
        self.initial_boxes = initial_boxes
        self.bg_color = np.array(bg_color, dtype=np.uint8)
        self._seed = seed
        self.substrate_seed = substrate_seed
        self.rng = np.random.default_rng(seed)

Try running it locally

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

python library/boxFittingImg/reference.py

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

More from core library

See all library artworks →