Skip to content

feat(media-use): paint — compile images into brushstroke-code compositions - #3477

Open
miguel-heygen wants to merge 1 commit into
mainfrom
feat/media-use-paint
Open

feat(media-use): paint — compile images into brushstroke-code compositions#3477
miguel-heygen wants to merge 1 commit into
mainfrom
feat/media-use-paint

Conversation

@miguel-heygen

Copy link
Copy Markdown
Collaborator

What

A new media-use capability: paint compiles an image (or one frame of a video) into a standalone HyperFrames composition whose generated code repaints the subject in brushstrokes.

The artifact is the code, not the picture. The stroke list ships as an editable data module (strokes.jswindow.__PAINT_STROKES = [...]), and an inlined painter runtime reveals it over the timeline. Same input + seed produces byte-identical output.

Why media-use and not a CLI verb

The output is a composition fragment, which only matters to something building a HyperFrames project — media-use's exact audience. Resolve-style file placement and the capability-doc routing come free, and a CLI verb can later wrap the same module in ten lines.

How it works

node <SKILL_DIR>/scripts/paint.mjs --input photo.jpg --out paint-photo --detail medium
  1. Decode — ffmpeg rawvideo pipe (images and video frames share the path with the rest of media-use's operations).
  2. Compile (lib/paint-compiler.mjs, pure + seeded): Sobel gradient field → circular smoothing → seeded k-means palette with a saturation-restore pass → layered jittered-grid stroke placement, coarse underpainting to variance-gated fine detail.
  3. Emit (lib/paint-emit.mjs): index.html (contract-correct root, clip, paused timeline on window.__timelines, painter inlined so static analysis sees the registration) + strokes.js.

The emitted reveal is seek-safe by construction: strokes are split into chronological bands, each band canvas is a pure function of its band index (drawn once, cached), and the current band redraws from scratch per update. Per-frame cost is O(strokes/bands) plus bandCount drawImage calls, and any seek order lands on identical pixels.

Quality provenance

The low/medium/high presets were calibrated through an iterative critics-against-the-original loop on three public-domain paintings: The Great Wave off Kanagawa, Girl with a Pearl Earring, and The Scream. The Great Wave rendition passed a fresh-context critic against the original; the emitted composition also passes hyperframes check with zero findings.

Tests

6 node:test cases in scripts/paint.test.mjs: compile determinism (same seed → identical strokes), seed sensitivity, geometry bounds with painterly overhang, the emitted-composition contract (root attrs, clip, timeline registration, stroke count parity), emit determinism, and an end-to-end run against a real fixture image including a same-seed byte-equality check across two runs.

Out of scope (follow-ups)

  • Multi-frame video-to-code: the decode path already accepts video; the remaining work is per-frame stroke sets on a fixed sampling grid with interpolation.
  • p5.brush backend for the emitted painter (its brush.add() is currently broken in the p5 build; the emitted runtime is plain canvas and brush-agnostic).

…tions

Adds a media-use capability that compiles an image (or one frame of a
video) into a standalone HyperFrames composition whose generated code
repaints the subject in brushstrokes. The artifact is the code: the
stroke list ships as an editable data module (strokes.js), and the
inlined runtime reveals it over the timeline.

- scripts/paint.mjs: CLI entry. Decodes via ffmpeg rawvideo (images and
  video frames), compiles, emits, prints a JSON summary. Deterministic:
  same input + seed produces byte-identical stroke data.
- scripts/lib/paint-compiler.mjs: pure compiler. Sobel flow field,
  circular smoothing, seeded k-means palette with saturation restore,
  layered jittered-grid placement coarse to fine, variance-gated detail
  layers. low/medium/high presets calibrated on The Great Wave off
  Kanagawa, Girl with a Pearl Earring, and The Scream.
- scripts/lib/paint-emit.mjs: emits the composition. The painter runtime
  is inlined so static analysis sees the timeline registration; the
  reveal is seek-safe by construction (band canvases are pure functions
  of their band index; per-frame cost is O(strokes/bands)).
- references/paint.md: capability doc (when to reach for it, invocation,
  output layout, mounting, editing the art).
- SKILL.md: routing row.

The emitted composition passes hyperframes check with zero findings.
Tests: 6 node:test cases covering compile determinism, geometry bounds,
the emit contract, emit determinism, and an end-to-end run against a
real fixture image including a same-seed byte-equality check.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant