generative_pioneers_2 · lillian_schwartz
Computer-Aided Painting (Schwartz)
Lillian Schwartz, who worked at Bell Labs from 1968 to 2001 with Kenneth C. Knowlton on the BEFLIX and EXPLOR graphics languages, produced a series of computer-aided films and paintings through the early 1970s — *Enigma* (1972), *Googolplex* (1972), *Apotheosis* (1972), and *Affinities* (1972) — all of which took a synthesised noise field, thresholded it to a binary mask, applied a symmetry operation (usually 4-fold rotational, sometimes 8-fold or 2-fold with translation), and recoloured the result on a CRT phosphor palette before sending it to a 16 mm film recorder. The pipeline is the algorithm: noise → threshold → symmetry → palette. Schwartz's signature is the *chromatic shift* (a different palette index per rotated copy) that gives the layered, moiré look of *Enigma* and *Apotheosis*. This reference implements that exact pipeline using value-noise fBm, a hard threshold, an N-fold rotation, and a six-colour palette with optional chromatic shift.
Original: https://en.wikipedia.org/wiki/Lillian_Schwartz
▶ 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.
Math essence
Build fBm noise F : [0,1]² → [0,1] (octaves=3, persistence=0.55), normalise to zero-mean / unit-std, threshold at τ to obtain a binary mask M. For each rotation r ∈ {0, 1/K, 2/K, …, (K−1)/K × 2π}, place M_r = rot90(M, r) on a black canvas using palette index p(r) = (r + t·chromatic_shift)·K mod 6. The final image is the union (max) of the K layers.
Parameters
| Name | Type | Default | Range | Description |
|---|---|---|---|---|
dim | int | 512 | 200–1200 | Square canvas side length |
octaves | int | 3 | 1–8 | fBm octaves (1 = pure value noise) |
persistence | float | 0.55 | 0.1–0.95 | fBm amplitude falloff per octave |
threshold | float | 0.62 | 0–1 | Noise threshold (higher = sparser mask) |
symmetry | int | 4 | 1–16 | Rotational symmetry order K (4 = Schwartz's *Enigma* default) |
rotate_quarters | int | 1 | 0–3 | Extra quarter-turn applied to the whole image |
chromatic_shift | float | 0.06 | 0–1 | Palette index shift per rotation layer (the *Enigma* moiré) |
palette_warmth | float | 0.6 | 0–1 | Brightness/saturation of the palette |
bg_color | tuple | 12,8,18 | null–null | Background RGB |
seed | int | null | 0–999999 | RNG seed |
Reference
First 60 lines of reference.py. Full source:
library_external/generative_pioneers_2/lillian_schwartz/reference.py
from __future__ import annotations
from typing import ClassVar
import imageio.v2 as imageio
import numpy as np
class LillianSchwartz:
family: ClassVar[str] = "generative_pioneers_2"
sketch: ClassVar[str] = "lillian_schwartz"
def __init__(
self,
dim: int = 512,
octaves: int = 3,
persistence: float = 0.55,
threshold: float = 0.62,
symmetry: int = 4,
rotate_quarters: int = 1,
chromatic_shift: float = 0.06,
palette_warmth: float = 0.6,
bg_color: tuple[int, int, int] = (12, 8, 18),
seed: int | None = None,
) -> None:
self.dim = dim
self.octaves = octaves
self.persistence = persistence
self.threshold = threshold
self.symmetry = symmetry
self.rotate_quarters = rotate_quarters % 4
self.chromatic_shift = chromatic_shift
self.palette_warmth = palette_warmth
self.bg_color = np.array(bg_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.time = 0
self._dirty = True
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.canvas = np.full((self.dim, self.dim, 3), self.bg_color, dtype=np.uint8)
self.time = 0
self._dirty = True
def _value_noise(self, w: int, h: int, period: int) -> np.ndarray:
rng = self.rng
gx = max(2, w // period)
gy = max(2, h // period)
grid = rng.random((gy + 1, gx + 1)).astype(np.float32)
ys = np.linspace(0, gy, h, endpoint=False)
xs = np.linspace(0, gx, w, endpoint=False)
y0 = np.floor(ys).astype(int)
x0 = np.floor(xs).astype(int)
y1 = np.clip(y0 + 1, 0, gy)
x1 = np.clip(x0 + 1, 0, gx)
fy = (ys - y0).astype(np.float32) Try running it locally
The reference implementation is self-contained Python with numpy. Install the package and run:
python library_external/generative_pioneers_2/lillian_schwartz/reference.py Need help? See Learn for the standard environment setup, or Playground for an in-browser sketchbook.
More from generative_pioneers_2
Hypertransformations (Molnár)
generative_pioneers_2 · molnar_hypertransformations
Vera Molnár's 'Hypertransformations' (1974-1976), executed on the IBM 360 with a CRT 2250 display and output on a Benson plotter.
P-159 D (Mohr)
generative_pioneers_2 · mohr_p159d
Manfred Mohr's 'P-159 D' (1969, Vincennes) sits in the P-159 family that the artist developed from his earlier P-021 / P-036 line-arrangement programs.
Raster Sequence (Nake)
generative_pioneers_2 · nake_raster_sequence
Frieder Nake's 'Walk-through-Raster' / 'Raster Sequence' (1966) is the first non-stationary Markov chain in computer art history.
Structure Computer (Cohen / AARON)
generative_pioneers_2 · cohen_structure_computer
Harold Cohen's AARON, begun at UCSD in 1967-1973 and continuously developed until Cohen's death in 2016, is the longest-running art-making AI in history.
Text Rain (Utterback & Achituv)
generative_pioneers_2 · utterback_text_rain
Camille Utterback and Romy Achituv's 'Text Rain' (1999) is one of the canonical interactive installations of the late-1990s computer-vision era, and the piece that won them a US patent (US 6,928,313) in 2005.
Wall Drawings (LeWitt)
generative_pioneers_2 · sol_lewitt_wall_drawings
Sol LeWitt's wall drawings (1968-2007) are the canonical example of conceptual-art-as-algorithm: the work is the set of written instructions, and any number of draftspeople can execute them with different results that all satisfy the same rule.