hatchsvg
Turn raster images into pen-plotted hatched SVGs. Optimized for Cricut, Axidraw, and any pen plotter that reads SVG.
What's new in 2.0.0
- Package renamed to
hatchsvg(waspng2svg). Matches the repo name. Install:pip install hatchsvg. Use:hatchsvg photo.jpg out.svg. - All v1.2.0 features still ship:
--preview,--split-layers,--optimize-travel,--hatch-angles, plus 5 inline examples in--help. - Pure Python — no C compiler required. Works on every platform pip supports.
What it does
hatchsvg converts raster images (PNG, JPG, WebP, BMP, GIF, TIFF) into
hatched SVG vector files. Each color in the image becomes a separate SVG
layer, and each layer is filled with parallel hatch lines sized to match a
physical marker set. The output is optimized for Cricut pen plotters and
similar pen-plotting machines — it includes continuous serpentine paths, arc
smoothing at row ends, and connected-component chaining to minimize pen
lifts and ringing artifacts.
Quickstart
pip install hatchsvg
hatchsvg photo.jpg drawing.svg --preset portrait
That's it. Open drawing.svg in Inkscape, Cricut Design Space, or any
vector editor. The output uses Inkscape-compatible layer names so each color
is selectable individually.
No SVG file? Try it on the included sample:
hatchsvg examples/scene.png /tmp/scene.svg --preset fast
Why use hatchsvg?
| Problem with naive "image → SVG" converters | How hatchsvg solves it |
|---|---|
| Tracer output is dense, jittery, and pen plotter hates it. | Uses scanline hatching at a configurable line step — predictable pen motion. |
| Pen lifts between every line segment cause visible artifacts on Cricut. | Serpentine continuous paths chain rows together (zigzag motion). |
| Sharp 180° U-turns vibrate the pen carriage. | Arc smoothing at row-end reversals (configurable radius). |
| Color matching to physical markers is hard. | Built-in Crayola and Jot marker palettes; bring your own JSON. |
| Re-rendering with slightly different settings breaks reproducibility. | --save-session writes a JSON snapshot; --use-session replays it bit-for-bit. |
Comparison with similar tools
| Tool | Input | Output | Pen-plotter aware | Marker palettes |
|---|---|---|---|---|
| hatchsvg | Raster | Hatched SVG (per-color layers) | Yes — serpentine + arc smoothing + chain components | Yes — Crayola, Jot, custom JSON |
potrace |
Bitmap | Smooth traced paths | No — fills entire region | No |
vtracer |
Bitmap | Smooth traced paths | Partial | No |
inkscape --trace-bitmap |
Bitmap | Smooth traced paths | No | No |
stipplegen, penkit, etc. |
Raster | Various pen-plotter art | Yes (each is its own niche) | No |
hatchsvg is the right choice when you want stylized, low-pen-lift,
marker-accurate output rather than a photo-faithful trace.
Presets
Presets are named combinations of parameters tuned for common use cases.
Apply one with --preset NAME. You can still override any individual flag
on top of the preset.
| Preset | Use case |
|---|---|
portrait |
Photographic portraits, faces, soft gradients. Tight hatch + arc smoothing. |
logo |
Brand marks, flat colors, clean edges. Separate outline for crisp boundaries. |
line-art |
Pencil sketches, ink drawings, fine line illustrations. Low min-pixels preserves thin strokes. |
photo |
Detailed photos, landscapes, complex gradients. High palette + fine hatch. |
sketch |
Hand-drawn rough aesthetic. Wider hatch + visible U-turns. |
fast |
Quick preview. Few colors, wide hatch, no optimization. Renders in seconds. |
hatchsvg drawing.png out.svg --preset portrait
hatchsvg drawing.png out.svg --preset logo --max-palette 4 # override one flag
See hatchsvg --help for full preset descriptions.
Installation
pip install hatchsvg
Pure Python: hatchsvg has zero C extensions. No compiler needed, no
potracer/pypotracebuild failures. Works on Windows, macOS, Linux, ARM, anywhere Python 3.11+ runs.
Optional extras:
pip install hatchsvg[plot] # progress bars (rich) + scipy for component chaining
pip install hatchsvg[palette] # Streamlit app to build custom palettes from photos
Building a custom palette
If you have a set of physical markers (Crayola, Jot, Copic, Sharpie, whatever),
you can take a photo and turn it into a hatchsvg palette JSON in ~30 seconds:
pip install hatchsvg[palette]
streamlit run scripts/palette_extractor.py
Upload a photo of your markers, edit the names, click Download palette JSON,
and pass it to hatchsvg via --palette-file. You can also download a
standalone HTML preview to keep alongside the JSON for visual reference:
# Generate the JSON via the Streamlit app, then:
python scripts/palette_preview.py my_palette.json
# Writes my_palette.html — open in any browser.
For development:
git clone https://github.com/Veedubin/hatchsvg
cd hatchsvg
python -m venv .venv
.venv/bin/pip install -e ".[dev,plot]"
CLI Reference
hatchsvg [OPTIONS] INPUT OUTPUT_SVG
| Flag | Default | Description |
|---|---|---|
--preset NAME |
(none) | Apply a named preset (see table above). |
--max-palette N |
12 | Maximum number of colors in the quantized output. |
--line-step N |
4 | Spacing between hatch lines (pixels). Lower = denser. |
--alpha-threshold N |
10 | Pixels with alpha below this are treated as transparent. |
--min-pixels N |
200 | Minimum pixel count for a layer to be rendered. |
--stroke-width N |
auto | Stroke width for hatch lines (uses marker tip_width_mm if a palette is set). |
--outline-width N |
auto | Stroke width for outline paths. |
--no-skip-background |
(off) | Include the background color as a layer. |
--white-medium |
(off) | Use white as a medium color instead of skipping near-white. |
--paper-white-soft N |
20 | Soft threshold for "paper white" detection. |
--scale N |
1.0 | Scale factor for the output SVG. |
--separate-outline |
(off) | Generate outline paths as a separate SVG group. |
--palette-file FILE |
(none) | Path to a JSON marker palette. |
--naming-mode {inkscape,flat} |
inkscape | Layer naming convention. |
--continuous-paths |
(off) | Generate serpentine paths (huge pen-lift reduction). |
--arc-radius N |
0.0 | Arc radius at U-turns (0 = disabled; try 3-5). |
--save-session |
(off) | Write a *.session.json for reproducibility. |
--use-session FILE |
(none) | Replay a previous session (overrides all flags). |
--preview |
(off) | Open output SVG in default viewer after render. |
--split-layers |
(off) | Write one SVG file per color layer. |
--optimize-travel |
(off) | Reorder layers to minimize pen travel distance. |
--hatch-angles ANGLES |
(none) | Comma-separated hatch angles per layer (degrees). |
--progress |
(off) | Show Rich progress bars (requires pip install hatchsvg[plot]). |
--stats |
(off) | Show processing statistics on completion. |
--version |
Print the version and exit. |
Cookbook
Basic conversion
hatchsvg photo.jpg drawing.svg
Recommended for Cricut pen plotters
The two flags that matter most:
hatchsvg photo.jpg drawing.svg --continuous-paths --arc-radius 5
--continuous-pathschains rows into a single zigzag, eliminating the thousands of micro-pen-lifts that cause "ringing" artifacts.--arc-radius 5smooths the 180° U-turns with arc commands so the pen carriage doesn't slam to a stop.
Use a bundled marker palette
hatchsvg photo.jpg drawing.svg --palette-file color_palettes/crayola_10ct_fine_line_classic.json
Bundled palettes: crayola_10ct_fine_line_classic.json and
jot_20ct_washable_fineline.json.
Reproducible output
# First run: save the exact config used
hatchsvg photo.jpg drawing.svg --preset portrait --save-session
# Later: replay the exact same config
hatchsvg photo.jpg drawing2.svg --use-session drawing.svg.session.json
Multi-format input
hatchsvg scan.webp out.svg # WebP
hatchsvg photo.bmp out.svg # BMP
hatchsvg photo.tiff out.svg # TIFF
hatchsvg anim.gif out.svg # GIF (uses first frame)
Show processing stats
hatchsvg photo.jpg out.svg --progress --stats
Outputs a Rich table with image dimensions, color count, layer count, pen-lift reduction percentage, and estimated plot time savings.
Project Structure
hatchsvg/
├── src/
│ └── hatchsvg/
│ ├── __init__.py # __version__
│ ├── __main__.py # python -m hatchsvg
│ ├── core.py # Algorithm (hatching, color matching, SVG emission)
│ ├── cli.py # CLI + friendly error handling
│ └── presets.py # Named presets (portrait, logo, line-art, photo, sketch, fast)
├── tests/
│ ├── unit/ # 51 unit tests (segments, color, palette, session, hatch, presets)
│ ├── integration/ # 9 integration tests (golden file + CLI subprocess)
│ ├── fixtures/ # Golden SVG for regression
│ └── conftest.py
├── examples/ # Sample input/output pairs (4 presets × 1 image)
├── color_palettes/ # Bundled Crayola + Jot palettes
├── .github/workflows/ci.yml # CI: ruff + pytest on Linux+macOS × 3.11/3.12/3.13
├── pyproject.toml # Build + project config
├── LICENSE # MIT
├── CHANGELOG.md # Version history
├── REVIEW.md # Audit + release plan
├── PLAN.md # v1.0.0 implementation plan
└── README.md # This file
Tech Stack
- Python 3.11+ (uses PEP 604 unions, tomllib, modern type hints)
- NumPy — image array processing and color quantization
- Pillow — image decoding (PNG, JPG, WebP, BMP, GIF, TIFF)
- dataclasses —
RenderParams,LayerResult,StrokeStyle - Rich (optional) — progress bars and statistics table
- scipy (optional) — connected component detection for pen-lift reduction
Development
# Run all tests with coverage
.venv/bin/pytest
# Lint
.venv/bin/ruff check src tests
.venv/bin/ruff format --check src tests
# Build a wheel for local testing
.venv/bin/python -m build
See PLAN.md for the v1.0.0 implementation plan and REVIEW.md for the comprehensive audit, feature roadmap, and 30-day release timeline.
Compatibility
- Python: 3.11, 3.12, 3.13 (CI matrix covers all three on Linux + macOS)
- Input formats: PNG, JPG, JPEG, WebP, BMP, GIF, TIFF, TIF
- Output: SVG 1.1 with Inkscape-compatible layer names
- Consumers: Inkscape, Cricut Design Space, Illustrator, browser preview
License
MIT — see LICENSE.
Credits
Algorithm extracted from the original hatchsvg.py script. Bundled marker
palettes (Crayola, Jot) are trademarks of their respective owners and are
included only as practical examples.
Metadata
Release files for hatchsvg 2.3.2
For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.
Source distribution (sdist)
| File | Size | Uploaded | |
|---|---|---|---|
| hatchsvg-2.3.2.tar.gz | 172.5 kB | Details |
Built distribution (wheel)
| File | Interpreter | ABI | Platform | Reset |
|---|---|---|---|---|
| hatchsvg-2.3.2-py3-none-any.whl | Python 3 | none | any | Details |
Total release size: 206.4 kB
Release files / hatchsvg-2.3.2.tar.gz
| Download URL | hatchsvg-2.3.2.tar.gz |
|---|---|
| Size | 172.5 kB |
| Tags | Source |
|
SHA-256 checksum How to use checksums |
269d4bd6215d6b93909d39ab8cf59b08699ad95a3ad9b5ff7443d58ff4312c3c
|
|
BLAKE2b-256 checksum How to use checksums |
31725e285e2f56f7afa93849105dc517e10659b77e2d611aa6fb7520398f42bd
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
twine/6.1.0 CPython/3.13.12
|
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
PyPI Publish Attestation
PyPI verified that this artifact, at this checksum, originated from the publisher listed below.
Signed by GitHub Actions, verified by PyPI on Jun 24, 2026.
Transparency logRelease files / hatchsvg-2.3.2-py3-none-any.whl
| Download URL | hatchsvg-2.3.2-py3-none-any.whl |
|---|---|
| Size | 33.9 kB |
| Tags | Python 3 |
|
SHA-256 checksum How to use checksums |
71b02e1a8f0cf91abc233c84b593619724b1c0135e5fd7286916c03626f11f22
|
|
BLAKE2b-256 checksum How to use checksums |
f3d51da79403ba75391eea5cdf062a48684f4964fadbc45ac4ef20f4821285d9
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
twine/6.1.0 CPython/3.13.12
|
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
PyPI Publish Attestation
PyPI verified that this artifact, at this checksum, originated from the publisher listed below.
Signed by GitHub Actions, verified by PyPI on Jun 24, 2026.
Transparency log