Turn PNG/JPG bitmaps into faithful, clean SVG vector art. Built for flat/vector-style art (mascots, logos, illustrations, icons) on Apple Silicon macOS.
When to Use
Use this skill when the user wants to:
- "vectorize" an image, or do "png to svg" / "raster to vector"
- "convert image to svg" or "trace" a bitmap
- Turn a mascot, logo, or illustration into scalable vectors
- Batch-convert a directory of PNG/JPG images to SVG
What It Does
scripts/vectorize.py runs a proven pipeline:
- Engine selection — COLOR art (fills, illustrations, logos) is traced with
vtracer; MONO/line art is traced with potrace (if installed) or vtracer
greyscale as a fallback. Default is
color. - Optional background removal — two strategies, neither universally correct
(see "Transparency: choosing a strategy" below).
--transparent(safe default) uses an edge-seeded flood fill;--global-keyuses a global color-key. Applied before tracing, so the SVG has no background shape. - Vectorize with tuned vtracer params for flat art (stacked, spline).
- Optimize (
svgo --multipassvia npx) — roughly 50% smaller. Skipped gracefully if npx/svgo is unavailable. - Verify (
cairosvg) — rasterizes the output SVG back to<name>.check.pngnext to the SVG so a human can eyeball fidelity. Skipped gracefully if unavailable.
Prerequisites
uv(required) —brew install uv. Used to build the pinned Python 3.12 venv.npx/svgo(optional) — for the optimize step. Provided by Node;--yes svgois fetched on demand. If missing, optimization is skipped with a warning.potrace(optional) —brew install potrace. Only used in--mode monofor the crispest line-art tracing. Without it, mono falls back to vtracer greyscale.cairo(optional, for verification) —brew install cairo. The script auto-adds Homebrew's lib dir to the loader path socairosvgfindslibcairoon Apple Silicon.
One-Time Setup (auto-bootstrapped)
There is nothing to install by hand beyond the prerequisites above. On first run the
script uses uv to create a cached Python 3.12 virtualenv at .venv/ inside this skill
directory and installs vtracer, pillow, and cairosvg into it. Subsequent runs reuse
that venv and are fast. If the venv ever gets corrupted, delete .venv/ and re-run.
Why a separate venv? vtracer's native wheel segfaults (exit 139) on Python 3.14 on Apple Silicon. It works reliably on Python 3.12. The script manages this itself — it never relies on the system Python having vtracer.
Usage
# Single file, output alongside the input
python3 scripts/vectorize.py mascot.png
# Single file, custom output dir, with SAFE background removal (edge flood fill)
python3 scripts/vectorize.py mascot.png -o out/ --transparent
# Aggressive background removal (global color-key) -- clears interior gaps
python3 scripts/vectorize.py mascot.png -o out/ --global-key
# Whole directory (globs *.png / *.jpg / *.jpeg)
python3 scripts/vectorize.py ./assets/ -o ./svg/
# Mono / line-art (uses potrace if present, else vtracer greyscale)
python3 scripts/vectorize.py logo_bw.png --mode mono
# Auto-detect color vs mono
python3 scripts/vectorize.py drawing.png --mode auto
# Tuning + skipping optional steps
python3 scripts/vectorize.py mascot.png --filter-speckle 10 --color-precision 8
python3 scripts/vectorize.py mascot.png --no-optimize --no-verify
Parameter Reference
| Flag | Default | Meaning |
|---|---|---|
input |
— | A single PNG/JPG file or a directory (globs images). |
-o, --outdir DIR |
alongside input | Where to write .svg (and .check.png). |
--transparent |
off | Remove background via edge flood fill (safe default). Preserves same-colored interior regions; leaves enclosed background opaque. |
--global-key (alias --transparent-aggressive) |
off | Remove background via global color-key. Clears enclosed gaps too, but erases interior regions matching the background. |
--mode color|mono|auto |
color |
Engine: color=vtracer, mono=potrace/greyscale, auto=heuristic. |
--filter-speckle N |
6 |
Drop specks smaller than N px. Higher = fewer specks, smaller file. |
--color-precision N |
7 |
Color bits of precision. Higher = more faithful, larger file. |
--no-optimize |
off | Skip the svgo pass. |
--no-verify |
off | Skip the cairosvg .check.png render. |
Fixed vtracer params (proven on flat art): colormode=color, hierarchical=stacked,
mode=spline, corner_threshold=60, path_precision=3.
Transparency: choosing a strategy
Background removal has two strategies and neither is universally correct. Pick based
on the artwork, and always eyeball the .check.png afterward.
| Strategy | Flag | Removes | Preserves | Fails when |
|---|---|---|---|---|
| Edge flood fill (safe default) | --transparent |
Only background connected to the image border | Enclosed same-colored regions (e.g. white/cream eye interiors) | Background is trapped inside the art (gaps between limbs, holes in a logo) — those stay opaque |
| Global color-key (aggressive) | --global-key |
All pixels near the background color, anywhere | Nothing color-matching is safe | An interior region matches the background color — it gets erased, leaving a hole (e.g. cream eye-whites become transparent voids, which look like creepy black holes on a dark page) |
Rule of thumb:
- Start with
--transparent. It never eats interior detail. - If enclosed gaps must be cleared and no interior region shares the background color,
use
--global-key. - If the art has cream/white eyes, teeth, or highlights on a light background, avoid
--global-key— it will punch them out. Use--transparentand accept the enclosed gaps, or hand-edit the SVG.
Troubleshooting
- vtracer segfault / exit 139 (the #1 gotcha). This happens when vtracer runs on
Python 3.14. The script avoids it by pinning a Python 3.12 venv via
uv— never run the tracing on the system Python directly. If you see a segfault, delete.venv/and re-run so the venv rebuilds on 3.12. Confirmuvis installed (brew install uv). - "uv not found." Install it:
brew install uv(expected at/opt/homebrew/bin/uv). The bootstrap cannot proceed without it. - SVG too large. Raise
--filter-speckle(e.g. 10–15) to drop small specks, and make sure--optimizeis on (svgo typically halves the size). Lowering--color-precisionalso shrinks output. - Jagged / blocky output. Raise
--color-precision(e.g. 8) and/or lower--filter-speckleso fine detail survives. - Background not removed. Use
--transparent. If a solid background still remains, the flood-fill threshold may be too low for a noisy/gradient background — raiseFLOODFILL_THRESHinscripts/vectorize.py(default 50). Note the fill is edge-connected, so same-colored interior regions are intentionally preserved. - Gaps inside the art stayed opaque.
--transparentonly removes background touching the border. Enclosed background (between limbs, holes in a logo) needs--global-key— but read the next entry first. - Interior regions turned into holes / creepy voids. You used
--global-keyon art whose interior (eye-whites, teeth, highlights) matches the background color, so the color-key erased them; on a dark page they read as black holes. Switch to--transparent(which preserves interiors), or hand-edit the SVG. See "Transparency: choosing a strategy". .check.pngskipped ("cairosvg unavailable"). Install cairo:brew install cairo. The script adds Homebrew's lib dir toDYLD_FALLBACK_LIBRARY_PATHautomatically; if it still can't load, confirmbrew --prefix/lib containslibcairo.2.dylib.- svgo skipped. Node/npx isn't on PATH. Install Node, or run with
--no-optimizeto silence the warning. Optimization is optional and never blocks output.