Skip to content
Sample of Sand Stroke

particles_paths · sandstroke

Sand Stroke

The Sand Stroke is applied here in it's most basic form. Each image is composed of approximately 42 sand strokes. Each instance of the sand stroke lays down grains of colored sand (pixel exposures) growing horizontally from left to right. For every layer of growth, the sand stroke deposits precisely the same number of sand grains despite the magnitude of the wave. This produces a very interesting density modulation across an even mass of sand.

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
particles_paths
Source
core library
Evolution
particle
Render
trail_accumulation
Complexity
low
Agents
22
Year
2004
Author
j.tarbell

Math essence

k horizontal sweep agents walk rightward at vx pixels per frame; each step the head paints a leading column and a tapered fan of wd grain samples modulated by gage*sin(i*w). On crossing the right edge a sweep self-reinitialises with a fresh palette colour and y-position.

Parameters

Name Type Default Range Description
dim int 500 200–1000 Square canvas side length in pixels.
k int 22 4–64 Number of horizontal sweeps.
wd int 200 40–400 Number of grain samples laid down per sweep step (density of fan).
vx float 1 0.25–4 Horizontal sweep speed (pixels per step).
stroke_alpha float 0.07 0.01–0.3 Alpha of the leading edge column.
fan_alpha float 0.1 0.05–0.4 Base alpha of the tapered fan grains.
gauge_walk float 0.042 0–0.1 Per-step random walk amplitude on the fan angular frequency.
gauge_max float 0.3 0.05–1 Clamp on the fan wave amplitude.

Reference

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

from __future__ import annotations

from typing import ClassVar

import numpy as np


_PALETTE = np.array(
    [
        (0, 0, 0),
        (15, 15, 15),
        (32, 32, 32),
        (54, 54, 54),
        (76, 60, 38),
        (96, 75, 50),
        (118, 92, 64),
        (140, 110, 80),
        (162, 130, 96),
        (184, 150, 112),
        (200, 170, 128),
        (212, 188, 144),
        (224, 204, 160),
        (236, 220, 176),
        (244, 232, 192),
        (252, 244, 208),
        (255, 240, 200),
        (220, 100, 80),
        (180, 70, 60),
        (140, 50, 45),
        (100, 40, 35),
        (60, 80, 60),
        (80, 110, 90),
        (140, 170, 140),
        (80, 100, 140),
        (110, 140, 180),
        (160, 200, 220),
        (220, 220, 200),
        (245, 240, 230),
        (255, 250, 235),
    ],
    dtype=np.float32,
)


class SandStroke:
    """Sand Stroke — j.tarbell, January 2004.

    k horizontal sweeps grow left-to-right across a square canvas.  Each
    sweep carries a stochastic vertical wave whose amplitude ``gage`` is
    slowly modulated.  Every step the sweep paints a column of pixels at
    its head and a tapered fan above/below the head whose width is
    ``gage * sin(i*w)`` for ``wd`` samples.  When the sweep leaves the
    canvas on the right it ``selfinit``s with a fresh color and starts
    back at the left edge.

    This is the canonical sand-stroke painter: it lays down a coloured
    column at the leading edge and a fading fan of the same color up and
    down to produce the dense, granulated strokes Tarbell is known for.
    """

Try running it locally

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

python library/sandstroke/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 →