generative_pioneers_2 · utterback_text_rain
Text Rain (Utterback & Achituv)
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. Visitors stand in front of a camera; their silhouette is thresholded into a binary mask and projected back as a dark figure on the screen. Coloured letters of Evan Zimroth's poem 'Talk, You' fall from the top of the screen, and they stop wherever they intersect the silhouette — letters 'land' on the body and pile up like snow on a roof. When the visitor moves, the letters that had collected resume falling. In this reference the silhouette is synthesised (an array of Gaussian blobs in the lower 70% of the canvas) so the piece is reproducible without a webcam; the optical-flow field is the gradient of that silhouette, and each character steps in a per-cell lattice under gravity + flow + a small noise term until it lands on a high-silhouette cell or a cell already occupied by another letter.
Original: https://camilleutterback.com/projects/text-rain/
▶ 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
Let S : [0,1]² → [0,1] be the silhouette image. The 2D optical flow field is f = (∇ₓ S, ∇ᵧ S). On a coarse cells×cells lattice each character c at (i, j) advances by (Δi, Δj) = (g + f_x(i,j)·strength + ε_x, v_flow_y(i,j)·strength + ε_y). When the local S exceeds a threshold, the character settles in that cell. The cells form a hard-occupancy grid, so no two characters can share a cell. The piece reads as a Poisson-disc-like fall: characters never stop in mid-air, and they pile up only on the silhouette.
Parameters
| Name | Type | Default | Range | Description |
|---|---|---|---|---|
dim | int | 512 | 200–1200 | Square canvas side length |
cell | int | 14 | 6–32 | Lattice cell size in pixels |
gravity | float | 0.6 | 0–2 | Per-step vertical fall speed (cells) |
obstacle_bias | float | 0.55 | 0–1 | Probability that a character lands on an obstacle cell when it crosses one |
flow_strength | float | 0.5 | 0–2 | Strength of the optical-flow push (per step) |
silhouette_threshold | float | 0.4 | 0–1 | Silhouette threshold above which a cell is 'dark' enough to catch a letter |
silhouette_seed | int | 99 | 0–999999 | Seed for the synthesized silhouette |
font_size | int | 13 | 6–32 | Glyph size (px) |
bg_color | tuple | 245,245,240 | null–null | Background RGB |
seed | int | null | 0–999999 | RNG seed for the character alphabet + initial jitter |
Reference
First 60 lines of reference.py. Full source:
library_external/generative_pioneers_2/utterback_text_rain/reference.py
from __future__ import annotations
from typing import ClassVar
import imageio.v2 as imageio
import numpy as np
class UtterbackTextRain:
family: ClassVar[str] = "generative_pioneers_2"
sketch: ClassVar[str] = "utterback_text_rain"
POEM_LINES = (
"TALK YOU AND I",
"EVERY WALK IS A TALK",
"EVERY BODY MAKES A SOUND",
"EVERY WALK A BODY TALKS",
"I HEAR YOUR BODY TALK",
"BODIES HEAR BODIES",
)
PALETTE = (
(220, 60, 60),
(240, 200, 60),
(60, 140, 230),
(90, 220, 130),
(220, 110, 220),
(240, 140, 80),
)
def __init__(
self,
dim: int = 512,
cell: int = 14,
gravity: float = 0.6,
obstacle_bias: float = 0.55,
flow_strength: float = 0.5,
silhouette_threshold: float = 0.4,
silhouette_seed: int = 99,
font_size: int = 13,
bg_color: tuple[int, int, int] = (245, 245, 240),
seed: int | None = None,
) -> None:
self.dim = dim
self.cell = cell
self.gravity = gravity
self.obstacle_bias = obstacle_bias
self.flow_strength = flow_strength
self.silhouette_threshold = silhouette_threshold
self._silhouette_seed = silhouette_seed
self.font_size = font_size
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._silhouette = self._build_silhouette()
self._flow = self._build_flow()
self._chars: list[dict] = []
self._init_chars()
self.time = 0 Try running it locally
The reference implementation is self-contained Python with numpy. Install the package and run:
python library_external/generative_pioneers_2/utterback_text_rain/reference.py Need help? See Learn for the standard environment setup, or Playground for an in-browser sketchbook.
More from generative_pioneers_2
Computer-Aided Painting (Schwartz)
generative_pioneers_2 · lillian_schwartz
Lillian Schwartz, who worked at Bell Labs from 1968 to 2001 with Kenneth C.
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.
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.