luthenwald.tngl.sh/splined

2 min read Original article ↗

iterative image reconstruction using random cubic bézier strokes, accelerated on metal

showcase#

NOTE

images used here are all under open access by The Met

same input & different seeds → different reconstructions → simple animation:

build#

cargo build -r

usage#

splined: iterative image reconstruction with random cubic bézier strokes (metal-accelerated)

usage: splined <input> [args]

args:
   -n, --number                 <u32>         max splines to draw (default: (w*h)^0.7)
   -b, --batch                  <u32>         batch size per gpu step (default: 32)
   -s, --seed                   <u64>         rng seed (default: 0)
       --max-gpu                <f32>         max gpu usage in (0, 1] (default: 1.0)
   -l, --log                    <0..3>        logging level (default: 1)
   -o, --output                 <path>        output file or dir (default: output.png)
   -c, --current                <path>        current canvas image to resume from (single-file only)
       --nth                    <u32>         save every nth accepted stroke (uses -o as dir)
       --bg                     <avg|r,g,b>   initial canvas color (default: avg)
   -a, --alpha                  <f32>         stroke alpha in [0, 1] (default: 1)
       --min-accept-ratio       <f32>         stagnant if accepted < batch*ratio (default: 0.02)
       --max-stagnant-batches   <u32>         stop after this many stagnant batches (default: 10)

input:
   - file: writes one image to -o/--output (default: output.png)
   - dir:  -o/--output must be a dir; mirrors input tree under it
   - --nth: saves frames to output dir every nth accepted stroke; also writes final.png

examples:
   splined in.png -o out.png
   splined in.png -n 5000 -b 64 -s 42 -o out.png
   splined in.png --nth 50 -o frames/
   splined images/ -o results/ -n 5000 -b 64 -s 42 --nth 50

algorithm#

  • convert input to oklab color space
  • initialize canvas to image average (or --bg)
  • repeat until target stroke count or convergence:
    • sample batch of random cubic béziers (4 control points, uniform over image)
    • rasterize each curve to coverage mask
    • set stroke color to coverage-weighted mean of target pixels
    • accept curves that strictly reduce squared oklab error (Δε² < 0)
    • commit accepted strokes to canvas
  • export final canvas

reference#

  • Geometrize: a desktop app that geometrizes images into geometric primitives

todo#

  • (better) antialiasing algorithm for drawing cubic bézier strokes
  • support other gpu backends (e.g., wasm)