art_blocks · HobbsWatercolorEdge
Hobbs Watercolor Edge (Tyler Hobbs — Soft Textures / Watercolor)
An implementation of Tyler Hobbs' 2017 'watercolor' technique: a recursive polygon-deformation algorithm whose midpoints are jittered by Gaussian noise across many passes, then stacked with low alpha (4% per layer, 30–100 layers total) to produce the soft, ink-bleed look of wet pigment on paper. Hobbs describes the technique explicitly in his essay: 'The core of the watercolor technique is a recursive polygon deformation algorithm ... stack lots of nearly transparent layers on top of each other.' Here we condense the pipeline to one numpy pass per layer: a deformed substrate is blurred, Sobel-edge-detected to extract the ink-bleed lines, tinted with a per-layer colour from a curated palette, and alpha-stamped onto the canvas. The cumulative effect is Hobbs' signature watercolor-edge aesthetic — visible in his Isohedral, Linear II and subsequent series, and the foundation for the soft textures in *Fidenza* and *QQL*.
Original: https://www.tylerxhobbs.com/words/a-guide-to-simulating-watercolor-paint-with-generative-art
▶ 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
For each layer L: (1) build a deformed copy of the base polygon P via D iterations of midpoint Gaussian jitter (sigma = jitter_frac * segment_length); (2) rasterise the deformed polygon to a mask; (3) blur the mask with a Gaussian of sigma blur_sigma px; (4) compute the Sobel magnitude of the luminance and threshold by edge_threshold; (5) tint the thresholded edge by the layer's palette colour; (6) alpha-blend onto the canvas with edge_alpha. Stack num_layers such layers. The Sobel magnitude effectively extracts the polygon boundary as a 1-px ink line.
Parameters
| Name | Type | Default | Range | Description |
|---|---|---|---|---|
dim | int | 512 | 200–1500 | Square canvas side length (px). |
num_layers | int | 24 | 4–120 | Number of translucent watercolor layers to stack (Hobbs: 30–100). |
base_shape | str | polygon | null–null | polygon|noise — substrate generator. |
base_sides | int | 5 | 3–12 | Sides on the base polygon (3 = triangle, 6 = hexagon). |
deformation_depth | int | 5 | 2–8 | Polygon deformation recursion depth (Hobbs: 4–7). |
jitter_sigma | float | 0.3 | 0.05–0.6 | Gaussian jitter sigma as a fraction of segment length. |
blur_sigma | float | 4 | 0.5–16 | Per-pass Gaussian blur sigma in pixels. |
edge_threshold | float | 0.1 | 0.01–0.6 | Sobel-magnitude threshold for ink edges. |
edge_alpha | float | 0.35 | 0.05–0.95 | Per-edge alpha when stamping. |
blur_alpha | float | 0.04 | 0.005–0.3 | Per-layer soft-fill alpha (Hobbs: ~0.04). |
palette | list | 60,95,145,185,70,60,210,155,70,80,130,90,115,60,130 | null–null | Per-layer colour tints sampled from this palette. |
background | tuple | 252,250,244 | null–null | Paper-white background RGB. |
Reference
First 60 lines of reference.py. Full source:
library_external/art_blocks/HobbsWatercolorEdge/reference.py
"""HobbsWatercolorEdge — Tyler Hobbs' watercolor "soft-edge" stack.
Inspired by Tyler Hobbs' *A Guide to Simulating Watercolor Paint with
Generative Art* (2017) and his subsequent watercolor series. Hobbs'
recipe, quoted directly from his essay:
"The technique to simulate watercolor in generative art consists of
stacking lots of nearly transparent layers on top of each other.
To create the watercolor-like appearance of a gradual fade out,
the layers need to have a lot of variation in their shape.
The core of the watercolor technique is a recursive polygon
deformation algorithm:
1. For each line A -> C in the polygon, find the midpoint B.
From a Gaussian distribution centred on B, pick a new point B'.
2. Update the polygon, replacing A -> C with two lines A -> B'
and B' -> C.
3. If we haven't hit our max recursion depth, repeat from
step 1, splitting the child lines."
Hobbs runs the input through ~7 deformation rounds to make the "base
polygon", then for every layer (30–100 layers total) he starts from
that base and runs another 4–5 rounds, drawing each with ~4% alpha.
The result is the soft, ink-bleeding look of wet pigment on paper.
This implementation condenses Hobbs' "many translucent layers" recipe
into a single, fast numpy pipeline that captures the essential
visual signature:
1. Build a base noise-driven substrate (Hobbs' underlying form —
in his essay a polygon, here a Perlin-noise tinted field).
2. Apply a sequence of small "translucent layer" passes: each pass
is a deformed copy of the substrate plus a Gaussian blur and
a Sobel edge detector to extract the ink-bleed lines, then
alpha-blended onto the canvas.
3. The cumulative effect after `num_layers` passes is the
characteristic watercolor edge — soft, broken, organic.
Reference:
- https://www.tylerxhobbs.com/words/a-guide-to-simulating-watercolor-paint-with-generative-art
- https://www.tylerxhobbs.com/words/creating-soft-textures-generatively
"""
from __future__ import annotations
from typing import ClassVar
import numpy as np
# ---------------------------------------------------------------------------
# Local helpers: 2D convolution, Gaussian kernel, Sobel edge detector
# ---------------------------------------------------------------------------
def _gaussian_kernel(size: int, sigma: float) -> np.ndarray:
"""Return a normalised (size x size) Gaussian kernel."""
ax = np.arange(size) - (size - 1) / 2.0
xx, yy = np.meshgrid(ax, ax)
g = np.exp(-(xx * xx + yy * yy) / (2.0 * sigma * sigma))
return (g / g.sum()).astype(np.float32)
Try running it locally
The reference implementation is self-contained Python with numpy. Install the package and run:
python library_external/art_blocks/HobbsWatercolorEdge/reference.py Need help? See Learn for the standard environment setup, or Playground for an in-browser sketchbook.
More from art blocks
Cherniak Ringers (Dmitri Cherniak — Ringers)
art_blocks · CherniakRingers
An implementation of Dmitri Cherniak's *Ringers* algorithm (Art Blocks Curated, January 2021, 1,000 NFTs) — one of the most-cited generative-art projects of the NFT era, and the source of *Ringers #879* ('The Goose') and *Ringers #962* (acquired by LACMA).
Chromie Squiggle (Snowfro / Cherniak — Squiggles)
art_blocks · CherniakSquiggles
An implementation of the squiggle-generation kernel used in Snowfro's *Chromie Squiggle* (Art Blocks #0, November 2020) — the on-chain generative-art project that launched the Art Blocks platform itself and was the work Dmitri Cherniak cited (alongside *Genesis*) as his inspiration for Ringers.
Genesis (DCA — Day-Zero Art Blocks)
art_blocks · DcaGenesis
An interpretation of DCA's *Genesis* (Art Blocks #1, November 2020) — one of three Day-Zero collections (alongside Snowfro's Chromie Squiggle and Jeff Davis's Construction Token) that minted in the first 24 hours of the Art Blocks platform.
Hobbs Flow Field (Tyler Hobbs — Incomplete Control / Fidenza)
art_blocks · HobbsFlowField
The canonical perlin-noise flow-field algorithm described by Tyler Hobbs in his 2020 essay 'Flow Fields', and the algorithmic backbone of his Art Blocks Curated works *Incomplete Control* (Bright Moments, 2021) and *Fidenza* (Art Blocks, 2021).
Hobbs Watercolor Blob
art_blocks · HobbsWatercolorBlob
Layered translucent Gaussian blobs with watercolor-style alpha falloff.
Penne 80s Vector (Aaron Penne — outrun perspective wireframe)
art_blocks · Penne80sVector
The 80s-synthwave outrun grid, the visual aesthetic Aaron Penne references in his generative-art practice (notably in *Apparitions* and his broader body of work).