---
name: pixel2svg-html
description: Convert raster logos (PNG/JPG/WebP/screenshots) into clean, smooth, minimal-complexity SVG plus standalone JS-rendered HTML, using a lowest-complexity-first fitting workflow with overlay QA, edge smoothness as the primary hard gate, IoU optimized as high as reasonably possible without a fixed global threshold, Bezier audits, and a default 10-iteration budget. Use when asked to vectorize, trace, recreate, fit, or convert a logo image into SVG/HTML and no animation is requested. If the user asks for atomic motions, tuners/controllers, a main animation, or a logo animation showcase HTML, use pixel2motion instead. v2: also covers closed/self-intersecting variable-width ribbon fitting (centerline scaffold + edge snap) and a wordmark font-matching protocol (ink-weight ratio).
---
# Pixel2SVG-HTML (v2)
Turn a raster logo into:
- `logo.svg` — minimal, smooth, editable vector
- `logo.html` — standalone HTML that recreates the SVG via JavaScript DOM calls
- `final_render.png`, `html_render.png`, overlay iterations + `overlay_progress_strip.png`
- path-audit artifacts when smoothness was a concern; resumable fit state in `outputs/fit_work/`
This skill is static/vector-first. If the requested HTML needs atomic motions, playback tuners/controllers, or a main logo animation, switch to `pixel2motion`; do not force animation delivery through this static skill.
The governing rule is **lowest smooth complexity that passes visual QA**. Do not maximize pixel-fit by default: start with the simplest editable geometry that can explain the mark, escalate only when an overlay shows a structural mismatch. Edge smoothness is the primary hard gate for ordinary logos. IoU is a required diagnostic and should be pushed as high as practical, but there is no fixed global IoU threshold; a slightly lower IoU can pass when the residuals are visually minor and explained.
## Complexity Ladder
Use the first level that matches the source well enough:
1. **Primitives**: circles, ellipses, rects, lines, simple arcs, transforms.
2. **Primitive composites**: boolean-like combinations of a few primitives or masks.
3. **Few-curve analytic paths**: a small number of cubic segments for smooth ribbons, swooshes, C-marks, leaves, waves, shields. For closed and/or self-intersecting variable-width ribbons (∞ marks, scripts) — where two open boundaries don't exist — use the centerline-scaffold recipe: `references/ribbon-fitting.md` + `scripts/fit_ribbon_centerline.py` (playbook §8; the source-pixel edge snap is what reconciles it with playbook §3).
4. **Smoothed outline paths**: more knots only where the source has real shape changes; preserve G1 tangent continuity, no noisy handle flips.
5. **Trace-derived paths**: only for irregular silhouettes where simpler geometry fails (`scripts/raster_logo_trace.py` as measurement/starter). Trace output is a measurement aid, never automatically final art. Smooth or refit before delivery.
If a higher-complexity version improves only antialiasing or minor pixel coverage, keep the lower one. If a lower-complexity version has wrong endpoints, width profile, center, silhouette, negative space, or visibly stair-stepped edges, move up one level or refit with smooth curves. Prefer live SVG `` for wordmarks unless exact letterforms are required. Decide the provisional complexity from the source itself and record the reason.
## Smoothness And Fit Gate
IoU must not hide bad vector craft. A smooth logo source must ship as smooth vector geometry; the final SVG fails if intended smooth edges visibly stair-step, chatter, use pixel-grid orthogonal runs, contain noisy trace knots, or look like a bitmap mask at 200-400% zoom, even when IoU is numerically high.
- Pixel-grid contours from threshold masks are acceptable only as starter measurements or for genuinely pixel-art logos.
- Smooth parts should use primitives, arcs, ellipses, or cubic paths with the lowest complexity that preserves silhouette, width profile, extrema, and negative space.
- Compute and record IoU every iteration, but do not use a fixed global pass/fail value. Optimize IoU upward after smoothness and structural correctness are protected.
- A slightly lower-IoU result may pass when the overlay is visually faithful and residuals are explainable as deliberate smoothing, antialiasing, font substitution, or raster-source artifacts.
- If a lower-IoU smooth candidate ships over a higher-IoU jagged candidate, explicitly state that tradeoff and why the residuals are acceptable.
- Save smoothness evidence when the source has curves: a zoomed crop, path audit artifact, or render that makes edge quality inspectable.
- Binary-mask metrics over GRADIENT-filled parts (gold beads, glossy dots) inflate pixel diffs even when the part is visually faithful — the threshold cuts the gradient at a different isoline than the source's. Judge such parts structurally (center/radius/extent) and visually, and name the artifact when reporting.
**Structure the SVG semantically** even though no animation is planned: one element (or ``) per visual part with stable ids (`#mark`, `#dot`, `#wordmark`). It costs nothing, makes the SVG editable, and keeps it upgradeable to an animated version later.
## Wordmark Font Matching (live ``)
When shipping live `` instead of traced letterforms (the default unless exact letterforms are required):
1. **Enumerate fonts actually installed** (`ls /System/Library/Fonts*`, `fc-list`) before testing candidates. Never trust name-based stacks: two "different" families scoring *identical* wordmark IoU means both silently fell back to the same font.
2. Per candidate, **auto-tune `font-size` to the source cap height** (2–3 render→measure→rescale iterations against the wordmark ink bbox), baseline pinned to the measured source baseline.
3. Rank by wordmark-region IoU **and ink-weight ratio** (rendered ink px ÷ source ink px). A weight ratio ≈ 1.0 with slightly lower IoU usually looks more faithful than a higher-IoU family with 1.2× heavier strokes — weight mismatch reads instantly, letterform differences read slowly. (Field case: Baskerville ratio 1.04 / IoU 0.535 visibly beat Times 1.26 / 0.540.)
4. Lock geometry cross-platform: `textLength="" lengthAdjust="spacingAndGlyphs"` + `text-anchor` + measured baseline; order the family stack best-first with graceful fallbacks; document the substitution as a known residual.
## Workflow
1. **Measure first.** Locate the exact source file; record image size, mode, alpha, foreground colors, background. Extract per-part data: masks by color, centers/radii, edge samples. Read `references/fitting-playbook.md` BEFORE writing any fit code — it encodes the expensive lessons (windowed feature search, perpendicular width correction, boundary-from-source-pixels, G1 repair strategies, bridge segments, smooth correction models).
2. **Choose the first fitting strategy** from the ladder. Primitives for dots and geometric marks; stroke paths only for truly constant width; **filled outlines for ribbons or any variable-width stroke**; tracing as measurement, not final art.
3. **Generate a low-complexity first artifact.**
- SVG with a correct `viewBox`; per-part ids.
- Render + overlay + metrics in one command:
`python3 scripts/render_overlay.py logo.svg source.png --out outputs/fit_iterations/01_first_overlay.png --render-out outputs/final_render.png`
- Keep every fit script, sampled data, and parameter set in `outputs/fit_work/` from iteration 1 — never in `/tmp`.
4. **Inspect with multimodal vision.** Centers, radii, endpoints, width profile, extrema, negative space, silhouette, and edge smoothness. IoU is a supporting metric, not the whole judgment and not a fixed global gate. A high-IoU result still fails if it has structural mismatches, noisy handles, visible bumps, wrong negative space, or pixel-stair edges. Track pixel counts (`src_only_px`, `render_only_px`) and boundary RMS as supporting diagnostics. For smooth curves, run `scripts/svg_path_audit.py`; verify flagged joins by computing turn angles from the control points and classify intentional corners (tip caps, designed points) separately from kinks — only undesigned joins >8° are failures, and they are failures **even at high IoU**.
5. **Iterate — one change per iteration, 10 iterations max by default.**
- Prefer moving/retuning a few knots over adding knots; if a path looks bumpy, reduce knots and re-fit macro curves.
- When a local area is wrong, add local complexity only there (e.g. a short bridge segment across a curvature spike — see playbook §4).
- Apply at most one smooth feedback-correction round (playbook §5); further rounds chase noise.
- Save an overlay snapshot per iteration: `NN_name_overlay.png`, with metrics recorded.
- The two stop triggers are evaluated after every iteration, and whichever arrives first ends the fitting loop: **accepted fit** (smoothness, structural, and visual checks pass, with IoU pushed as high as practical) or **10 iterations attempted**.
6. **Budget exhausted or acceptance reached → evaluate and ship/report.**
- Acceptance reached early: finish immediately.
- Budget exhausted: stop refining and select the best candidate. Rank candidates with a combined scorecard: (1) smoothness gate pass, (2) no structural mismatch, (3) IoU and pixel deltas, (4) boundary RMS / local residuals, (5) fewest audit kinks and tangent issues, (6) visual overlay verdict, (7) lowest editable complexity. A clean-but-slightly-loose fit beats a tighter fit with a visible kink; an invisible kink may ship only as a disclosed known issue.
- Build the HTML deliverable: `python3 scripts/svg_to_js_html.py logo.svg --out logo.html --title "..."` — then render it in a real browser, save `html_render.png`, and visually compare against the SVG render (fail on blank, clipped, mis-scaled, or divergent output). The user-facing HTML preview should display the logo at **0.7x of the SVG's intrinsic width** (`width * 0.7`, still capped by the viewport) to leave breathing room.
- Build `overlay_progress_strip.png` (source + current-run overlays + final render only).
- In the final message, report the shipped iteration's quality honestly (metrics + known residuals, e.g. "IoU 0.97; sub-pixel boundary residue at the valley, antialiasing-scale"). If the IoU is lower than an alternative or visibly loose, explain the reason; if residuals are not acceptable, state that the current run is a preview, keep the fit state resumable, and offer continued refinement from `outputs/fit_work/`.
## Bundled Scripts
```bash
# measurement / starter trace (inspect & simplify; never final art by default)
python3 scripts/raster_logo_trace.py source.png --out outputs
# render + cyan overlay + IoU metrics in one step (headless Chrome; set CHROME_BIN if needed)
python3 scripts/render_overlay.py logo.svg source.png \
--out outputs/fit_iterations/02_refined_overlay.png \
--render-out outputs/final_render.png --report outputs/fit_metrics.json
# closed / self-intersecting variable-width ribbons: centerline scaffold +
# auto-recenter + source edge snap; emits outline + centerline path d + report
# (incl. each exclusion's arc fractions — the split-cut parameters pixel2motion needs)
python3 scripts/fit_ribbon_centerline.py source.png --seeds seeds.json --out-dir outputs/ribbon_fit
# Bezier smoothness audit before accepting complex smooth paths
python3 scripts/svg_path_audit.py logo.svg --out-svg outputs/bezier_segments.svg --report outputs/bezier_audit.json
# JS-rendered standalone HTML deliverable
python3 scripts/svg_to_js_html.py logo.svg --out logo.html --title "Vectorized Logo"
# end-of-run progress strip
python3 scripts/overlay_progress_strip.py --source source.png --dir outputs/fit_iterations \
--pattern "*overlay*.png" --final-image outputs/final_render.png --out outputs/overlay_progress_strip.png
```
Environment notes: Pillow/numpy via a venv when system Python is externally managed (`python3 -m venv .venv && .venv/bin/pip install pillow numpy`); rendering and HTML screenshots use headless Chrome — no rasterizer or Playwright required (`"$CHROME" --headless=new --screenshot=... --window-size=WxH file://...`).
## Practical Heuristics
- If a path looks bumpy, first reduce knots and re-fit macro curves; do not add more points.
- For smooth ribbons, compare both boundaries separately; a centerline stroke may be wrong even when it seems close. Never build outlines by offsetting a G1 centerline (playbook §3).
- Keep endpoints under colored circles or masks intentional; butt caps under the covering shape, no protruding round caps.
- For a circular C-mark: fit center, inner/outer radii, start/end angles; encode as one arc-based path.
- For a dot: `` with flat fill unless the source shows a gradient.
- For baked-in checkerboards: threshold foreground by darkness/saturation; exclude the checkerboard from final output.
- For solid backgrounds: infer the background color from image borders.
## Acceptance Criteria
Completion requires evidence, not claims:
- SVG exists and renders; standalone HTML exists, recreates the SVG via JavaScript DOM calls, and has been executed in a real browser with `html_render.png` inspected and matching the SVG render in scale, position, color, clipping.
- Final overlay viewed; `overlay_progress_strip.png` shows source → current-run overlays → final render.
- Report final IoU and pixel deltas, but do not apply a fixed global IoU pass/fail value. IoU should be as high as practical after preserving smoothness and structural correctness. If a lower-IoU result passes, explain exactly why the residuals are acceptable.
- Smooth marks pass the path audit (no undesigned joins >8°), or the kink is disclosed as a known issue under budgeted delivery.
- No structural mismatch: wrong center, scale, endpoint, width profile, spacing, negative space, or silhouette.
- Remaining differences are explainable as antialiasing, deliberate smoothing, or acceptable font substitution.
- **Budgeted delivery**: if the 10-iteration budget ran out, best-of-run may be packaged when smoothness, structure, and visual QA pass and residuals are disclosed. If residuals are not acceptable, label it as a preview, keep the fit state resumable from `outputs/fit_work/`, and offer continued refinement.
IoU is mandatory to report but not a fixed-threshold gate. A high-IoU result fails if the geometry is unnecessarily complex, visibly bumpy, stair-stepped, or built from noisy traced handles.
## References
- `references/fitting-playbook.md` — field-tested fitting strategies: measurement, feature windows, boundary-from-source fitting, G1 repair & bridge segments, feedback corrections, iteration discipline, environment pragmatics.
- `references/ribbon-fitting.md` — closed/self-intersecting variable-width ribbons (centerline scaffold + measured recenter + source edge snap), pitfall table, wordmark font-matching protocol.
## v2 changelog
Hardened from a production run (calligraphic ∞ ribbon, width 2→28px, one self-crossing, occluding gold bead, serif wordmark):
- New `scripts/fit_ribbon_centerline.py` + `references/ribbon-fitting.md`: the centerline-scaffold recipe for closed/self-intersecting ribbons (seeded Catmull-Rom → normal-direction midpoint/width measurement → arclength-spaced control rebuild, converges in 2–3 passes → sub-pixel edge snap to source pixels). Measured pitfalls encoded: stride-based rebuild inflates control counts; heavy width smoothing curve-cuts caps ~1.5px; hairline tapers must not be snapped; exclusion gaps interpolate circularly. The report emits each exclusion's arc fractions — exactly the cut parameters a downstream split draw-on needs.
- Wordmark font-matching protocol: enumerate installed fonts (identical IoU across names = silent fallback), tune size to cap height, rank by ink-weight ratio alongside IoU, lock width with `textLength`.
- Gradient-vs-threshold artifact rule: binary-mask diffs over gradient fills are not shape errors; judge structurally and name the artifact.