Skip to content
Sample of Boundaries

complexification_net · boundaries

Boundaries

Boundaries traces gradient-magnitude contours on a fractal Brownian motion noise substrate. An 'edge_field' holds per-pixel ink weight; on each step the field decays everywhere, then re-ignites at every pixel where the gradient of the noise exceeds a threshold. This produces line-art that maps the contours of the noise terrain — soft meandering boundaries like topographic contour lines, or like ink seeping through the high-curvature folds of paper. The piece evokes the same family as Tarbell's 'substrate' (where recursive vein growth lives on a noisy substrate) and 'stitches' (where stitches are pinned along a substrate's contours).

Original: https://web.archive.org/web/20041223174832/http://www.levitated.net/daily/levMainVein.html

▶ 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
complexification_net
Source
complexification.net
Evolution
iterated_map
Render
grid_painting
Complexity
low
Agents
1
Year
2004
Author
j.tarbell (style)

Math essence

Build a 2D fBm value-noise field F(x,y) of dim×dim pixels at `noise_octaves` octaves with persistence p. Compute gradient (gx, gy) = ∇F. Maintain edge field E(x,y). Each step: E *= edge_decay; E[i,j] = 1 if |∇F[i,j]| > edge_threshold and E[i,j] < 0.05; render E onto canvas as semi-transparent ink.

Parameters

Name Type Default Range Description
dim int 500 150–1000 Square canvas side length
noise_scale float 0.018 0.002–0.1 Base spatial frequency of noise
noise_octaves int 5 1–8 Number of fBm octaves
noise_persistence float 0.55 0.1–0.9 Amplitude multiplier per octave
edge_threshold float 0.012 0.001–0.1 Gradient magnitude above which a pixel becomes an edge
edge_decay float 0.9 0.5–0.999 Per-step multiplicative decay of edge ink
edge_init_density float 0.001 0–0.05 Initial fraction of pixels with edge ink
bg_color tuple 240,235,225 null–null Background RGB
line_color tuple 35,25,45 null–null Edge ink RGB
seed int null 0–999999 RNG seed

Reference

First 60 lines of reference.py. Full source: library_external/complexification_net/boundaries/reference.py

from __future__ import annotations

from typing import ClassVar

import imageio.v2 as imageio
import numpy as np


class Boundaries:
    family: ClassVar[str] = "complexification_net"
    sketch: ClassVar[str] = "boundaries"

    def __init__(
        self,
        dim: int = 500,
        noise_scale: float = 0.018,
        noise_octaves: int = 5,
        noise_persistence: float = 0.55,
        edge_threshold: float = 0.012,
        edge_decay: float = 0.90,
        edge_init_density: float = 0.001,
        bg_color: tuple[int, int, int] = (240, 235, 225),
        line_color: tuple[int, int, int] = (35, 25, 45),
        seed: int | None = None,
    ) -> None:
        self.dim = dim
        self.noise_scale = float(noise_scale)
        self.noise_octaves = int(noise_octaves)
        self.noise_persistence = float(noise_persistence)
        self.edge_threshold = float(edge_threshold)
        self.edge_decay = float(edge_decay)
        self.edge_init_density = float(edge_init_density)
        self.bg_color = np.array(bg_color, dtype=np.uint8)
        self.line_color = np.array(line_color, dtype=np.uint8)
        self._seed = seed
        self.rng = np.random.default_rng(seed)
        self.canvas = np.full((dim, dim, 3), self.bg_color, dtype=np.uint8)
        self.field: np.ndarray = np.zeros((dim, dim), dtype=np.float32)
        self.edge_field: np.ndarray = np.zeros((dim, dim), dtype=np.float32)
        self._grad_x: np.ndarray = np.zeros((dim, dim), dtype=np.float32)
        self._grad_y: np.ndarray = np.zeros((dim, dim), dtype=np.float32)
        self.time = 0
        self._build_noise()
        self.reset()

    def _build_noise(self) -> None:
        rng = np.random.default_rng(self._seed if self._seed is not None else 0)
        d = self.dim
        big = d * 4
        self._perm = rng.integers(0, 256, size=big * big, dtype=np.int32)
        self._noise_period = big

    @staticmethod
    def _fade(t: np.ndarray) -> np.ndarray:
        return t * t * t * (t * (t * 6 - 15) + 10)

    @staticmethod
    def _lerp(a: np.ndarray, b: np.ndarray, t: np.ndarray) -> np.ndarray:
        return a + t * (b - a)

Try running it locally

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

python library_external/complexification_net/boundaries/reference.py

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

More from complexification.net

See all complexification_net artworks →