|
| 1 | +## Context |
| 2 | + |
| 3 | +Imported artwork is flattened to "master" polylines once at import (`flattenSvg` at a fixed 0.2 mm tolerance; `flattenImageFile` at the import-time threshold/levels). `applyDetail` then thins the master live for both the canvas preview and the plot — this is the only live control today. PNG threshold/levels live in `Calibration` (global, persisted) and apply only to the *next* import; the SVG tolerance is a module constant. |
| 4 | + |
| 5 | +## Goals / Non-goals |
| 6 | + |
| 7 | +**Goals** |
| 8 | +- Re-shape an artwork already on the page and see it instantly, for PNG and SVG. |
| 9 | +- Drive preview and plot from the same geometry (no "looks different when plotted"). |
| 10 | +- Sliders for look; each value typeable and within a sensible, configurable range. |
| 11 | +- Per-artwork settings so multiple artworks tune independently. |
| 12 | + |
| 13 | +**Non-goals** |
| 14 | +- True SVG fill/hatch tracing (still via PNG import). |
| 15 | +- Offloading tracing to a worker/GPU (debounced main thread first). |
| 16 | +- Colour/multi-pen separation. |
| 17 | + |
| 18 | +## Decisions |
| 19 | + |
| 20 | +### Two-stage pipeline: source → master → display |
| 21 | +``` |
| 22 | + SOURCE (retained per artwork) SOURCE controls (debounced, expensive) |
| 23 | + SVG: raw text PNG: threshold, levels, invert, contrast |
| 24 | + PNG: grayscale field (Float32) SVG: sampling tolerance |
| 25 | + │ re-derive master |
| 26 | + ▼ |
| 27 | + MASTER polylines (full detail) GEOMETRY controls (instant, pure) |
| 28 | + │ applyDetail + simplify detail, smoothing/simplify, min stroke len |
| 29 | + ▼ |
| 30 | + DISPLAY polylines ──▶ canvas preview + plot |
| 31 | +``` |
| 32 | +Today only the bottom arrow is live. The change is to **retain the source** and make the top arrow live (debounced), keeping the bottom arrow per-frame. |
| 33 | + |
| 34 | +- **PNG:** separate decode+grayscale (produces a reusable `Float32Array` field at working resolution) from the iso-contour trace. Invert and contrast are cheap point transforms on the field. Re-tracing on a threshold/levels/invert/contrast change reuses the cached field — no re-decode. (`raster.ts` already computes the field; this exposes it.) |
| 35 | +- **SVG:** retain the SVG text; re-run `flattenSvg` at the chosen tolerance. Flattening uses the DOM `getPointAtLength` path and is heavier than PNG re-trace per call but only re-runs on a tolerance change, debounced. |
| 36 | + |
| 37 | +### Per-artwork source + control values |
| 38 | +Controls now retune an artwork *in place*, so the values must travel with the artwork, not be a single global applied at import. Each `PlacedArt` gains its source (text or field) and its control values. The session persists the control values (and ideally the source, subject to storage limits — large images may exceed quota, in which case the artwork persists its derived master only and source-controls are disabled until re-import, matching today's graceful-skip behaviour). |
| 39 | + |
| 40 | +### Sliders with typeable, configurable values |
| 41 | +A small reusable control: a range slider plus a numeric box bound to the same value, with `min`/`max`/`step` props. Drag or type; typing accepts exact values. "Customisable values" = the ranges are defined centrally (in settings/defaults) and the numeric box lets the operator exceed the slider's comfortable range when needed. Look controls are sliders; speed/feeds stay number fields (mobile change). |
| 42 | + |
| 43 | +### Debounce the expensive stage only |
| 44 | +Source-control changes schedule a debounced re-derivation (e.g. ~150–250 ms after the last change) so dragging a slider doesn't fire a re-trace per pixel; the cheap geometry stage and the canvas redraw stay immediate. The preview shows the latest derived master; a subtle "updating…" affordance covers the debounce gap. |
| 45 | + |
| 46 | +## Risks / Trade-offs |
| 47 | + |
| 48 | +- **Re-trace cost on large images / complex SVGs:** mitigated by the existing `MASTER_MAXDIM` working-resolution cap, reusing the decoded field for PNG, and debouncing. If still janky, a later change can move tracing to a Web Worker (explicit non-goal here). |
| 49 | +- **Session storage bloat:** retaining sources per artwork can exceed localStorage/daemon-session limits. Mitigation: persist control values always; persist source best-effort and degrade gracefully (disable source controls, keep the master) when too large — consistent with today's quota handling. |
| 50 | +- **Preview/plot divergence risk:** avoided by construction — plot consumes the exact display polylines, as it does today. |
| 51 | + |
| 52 | +## Migration |
| 53 | + |
| 54 | +Additive and backward-compatible. Sessions without per-artwork control values load with current defaults; the global PNG threshold/levels become per-artwork defaults seeded from the existing calibration values. No protocol change. |
| 55 | + |
| 56 | +## Open Questions |
| 57 | + |
| 58 | +- Contrast model: simple linear contrast/brightness on the grayscale field vs. a gamma curve — pick the one that gives the most intuitive slider during implementation. |
| 59 | +- Whether to persist the raw source in the daemon session (cross-device retune) or only locally; depends on observed session sizes. |
| 60 | +- Default ranges for each slider (threshold 0–1; levels 1–N; tolerance lo–hi mm; min stroke length mm) — set sensible defaults, refine against real artwork. |
0 commit comments