svgsmith 0.1.0__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- svgsmith-0.1.0/LICENSE +21 -0
- svgsmith-0.1.0/PKG-INFO +214 -0
- svgsmith-0.1.0/README.md +187 -0
- svgsmith-0.1.0/pyproject.toml +56 -0
- svgsmith-0.1.0/setup.cfg +4 -0
- svgsmith-0.1.0/src/svgsmith/__init__.py +3 -0
- svgsmith-0.1.0/src/svgsmith/__main__.py +6 -0
- svgsmith-0.1.0/src/svgsmith/classify.py +90 -0
- svgsmith-0.1.0/src/svgsmith/cli.py +266 -0
- svgsmith-0.1.0/src/svgsmith/engines/__init__.py +28 -0
- svgsmith-0.1.0/src/svgsmith/engines/base.py +118 -0
- svgsmith-0.1.0/src/svgsmith/engines/binary.py +71 -0
- svgsmith-0.1.0/src/svgsmith/engines/color.py +36 -0
- svgsmith-0.1.0/src/svgsmith/pipeline.py +165 -0
- svgsmith-0.1.0/src/svgsmith/postprocess.py +583 -0
- svgsmith-0.1.0/src/svgsmith/preprocess.py +253 -0
- svgsmith-0.1.0/src/svgsmith/render.py +79 -0
- svgsmith-0.1.0/src/svgsmith/report.py +69 -0
- svgsmith-0.1.0/src/svgsmith/smooth.py +389 -0
- svgsmith-0.1.0/src/svgsmith/verify.py +204 -0
- svgsmith-0.1.0/src/svgsmith.egg-info/PKG-INFO +214 -0
- svgsmith-0.1.0/src/svgsmith.egg-info/SOURCES.txt +34 -0
- svgsmith-0.1.0/src/svgsmith.egg-info/dependency_links.txt +1 -0
- svgsmith-0.1.0/src/svgsmith.egg-info/entry_points.txt +2 -0
- svgsmith-0.1.0/src/svgsmith.egg-info/requires.txt +10 -0
- svgsmith-0.1.0/src/svgsmith.egg-info/top_level.txt +1 -0
- svgsmith-0.1.0/tests/test_classify.py +45 -0
- svgsmith-0.1.0/tests/test_cli.py +106 -0
- svgsmith-0.1.0/tests/test_corpus.py +73 -0
- svgsmith-0.1.0/tests/test_engines.py +79 -0
- svgsmith-0.1.0/tests/test_pipeline.py +141 -0
- svgsmith-0.1.0/tests/test_postprocess.py +139 -0
- svgsmith-0.1.0/tests/test_preprocess.py +104 -0
- svgsmith-0.1.0/tests/test_render.py +39 -0
- svgsmith-0.1.0/tests/test_report.py +66 -0
- svgsmith-0.1.0/tests/test_verify.py +103 -0
svgsmith-0.1.0/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 realproject7
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
svgsmith-0.1.0/PKG-INFO
ADDED
|
@@ -0,0 +1,214 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: svgsmith
|
|
3
|
+
Version: 0.1.0
|
|
4
|
+
Summary: Convert raster images into clean, editable SVG.
|
|
5
|
+
Author: svgsmith contributors
|
|
6
|
+
License-Expression: MIT
|
|
7
|
+
Project-URL: Homepage, https://github.com/realproject7/svgsmith
|
|
8
|
+
Project-URL: Repository, https://github.com/realproject7/svgsmith
|
|
9
|
+
Project-URL: Issues, https://github.com/realproject7/svgsmith/issues
|
|
10
|
+
Keywords: svg,vectorization,raster,potrace
|
|
11
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
12
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
13
|
+
Classifier: Topic :: Multimedia :: Graphics :: Graphics Conversion
|
|
14
|
+
Requires-Python: >=3.11
|
|
15
|
+
Description-Content-Type: text/markdown
|
|
16
|
+
License-File: LICENSE
|
|
17
|
+
Requires-Dist: vtracer==0.6.15
|
|
18
|
+
Requires-Dist: Pillow<13,>=10.4
|
|
19
|
+
Requires-Dist: numpy<3,>=1.26
|
|
20
|
+
Requires-Dist: cairosvg<3,>=2.7
|
|
21
|
+
Requires-Dist: scikit-image<0.27,>=0.22
|
|
22
|
+
Requires-Dist: scipy<2,>=1.11
|
|
23
|
+
Provides-Extra: dev
|
|
24
|
+
Requires-Dist: ruff>=0.5; extra == "dev"
|
|
25
|
+
Requires-Dist: pytest>=8; extra == "dev"
|
|
26
|
+
Dynamic: license-file
|
|
27
|
+
|
|
28
|
+
# svgsmith
|
|
29
|
+
|
|
30
|
+
> Agent-native, self-verifying raster→SVG vectorizer.
|
|
31
|
+
|
|
32
|
+
`svgsmith` turns PNG/JPG images into **editable** SVG. It is built to be driven by an
|
|
33
|
+
AI agent without a human in the loop: it picks the right tracing engine for the input,
|
|
34
|
+
post-processes the result into clean editable layers, and **verifies its own output** by
|
|
35
|
+
re-rasterizing the SVG and comparing it to the original — re-tuning until a quality
|
|
36
|
+
threshold is met. Every run returns a structured JSON report so a calling agent can
|
|
37
|
+
decide whether to accept, retry, or escalate.
|
|
38
|
+
|
|
39
|
+
It does **not** reinvent tracing. It wraps proven engines
|
|
40
|
+
([VTracer](https://github.com/visioncortex/vtracer) for color,
|
|
41
|
+
[Potrace](https://potrace.sourceforge.net/) for line art) and adds the layer that is
|
|
42
|
+
missing for agent use: routing, editable output, and a self-verification loop.
|
|
43
|
+
|
|
44
|
+
> **Status:** MVP complete — engine routing, editable post-processing, the self-verify loop,
|
|
45
|
+
> the CLI/JSON report, and the [`vectorize` skill](skills/vectorize/SKILL.md) are all in.
|
|
46
|
+
> See the [EPIC](../../issues/1) for scope.
|
|
47
|
+
|
|
48
|
+
## System dependencies
|
|
49
|
+
|
|
50
|
+
The line-art engine shells out to the [**Potrace**](https://potrace.sourceforge.net/)
|
|
51
|
+
binary (svgsmith does not bundle a Potrace Python binding). Install it from your
|
|
52
|
+
package manager before use:
|
|
53
|
+
|
|
54
|
+
The self-verify loop rasterizes SVGs with [CairoSVG](https://cairosvg.org/), which needs
|
|
55
|
+
the **Cairo** system library. Install both:
|
|
56
|
+
|
|
57
|
+
```bash
|
|
58
|
+
# Debian / Ubuntu
|
|
59
|
+
sudo apt-get install -y potrace libcairo2 libcairo2-dev
|
|
60
|
+
# macOS (Homebrew)
|
|
61
|
+
brew install potrace cairo
|
|
62
|
+
```
|
|
63
|
+
|
|
64
|
+
The color engine ([VTracer](https://github.com/visioncortex/vtracer)) ships as a
|
|
65
|
+
pinned PyPI wheel and needs no system package.
|
|
66
|
+
|
|
67
|
+
## Installation
|
|
68
|
+
|
|
69
|
+
Requires **Python 3.11+**. Install the [system dependencies](#system-dependencies)
|
|
70
|
+
above first, then svgsmith:
|
|
71
|
+
|
|
72
|
+
```bash
|
|
73
|
+
pip install svgsmith
|
|
74
|
+
```
|
|
75
|
+
|
|
76
|
+
### From source
|
|
77
|
+
|
|
78
|
+
```bash
|
|
79
|
+
git clone https://github.com/realproject7/svgsmith
|
|
80
|
+
cd svgsmith
|
|
81
|
+
pip install . # add ".[dev]" for the test/lint extras
|
|
82
|
+
```
|
|
83
|
+
|
|
84
|
+
Verify the install:
|
|
85
|
+
|
|
86
|
+
```bash
|
|
87
|
+
svgsmith --version
|
|
88
|
+
svgsmith convert path/to/image.png --out out.svg --report json
|
|
89
|
+
```
|
|
90
|
+
|
|
91
|
+
## What makes it different
|
|
92
|
+
|
|
93
|
+
- **Auto-routing** — classifies the input (logo/icon vs illustration vs pixel art) and
|
|
94
|
+
selects the engine + preset automatically. No tracer-flag expertise required.
|
|
95
|
+
- **Editable output** — instead of one monolithic `<path>`, output is grouped into
|
|
96
|
+
`<g>` layers with simplified paths and a consolidated color palette.
|
|
97
|
+
- **Self-verifying** — converts, re-rasterizes, diffs against the original (SSIM), and
|
|
98
|
+
re-tunes parameters until it converges on a quality target.
|
|
99
|
+
- **Structured report** — emits JSON (mode, engine, iterations, similarity score,
|
|
100
|
+
warnings) so agents can branch programmatically.
|
|
101
|
+
- **Local & private** — runs fully offline; images never leave the machine.
|
|
102
|
+
|
|
103
|
+
## Gallery
|
|
104
|
+
|
|
105
|
+
Each pair is the original raster (left) and the svgsmith SVG output (right), rendered at the
|
|
106
|
+
same size. Fixtures are self-generated (see `tests/corpus/`).
|
|
107
|
+
|
|
108
|
+
| | Original | svgsmith SVG |
|
|
109
|
+
|---|---|---|
|
|
110
|
+
| **Logo** (`--mode binary`) | <img src="docs/gallery/logo_in.png" width="160"> | <img src="docs/gallery/logo_out.png" width="160"> |
|
|
111
|
+
| **Icon** (`--mode binary`) | <img src="docs/gallery/icon_in.png" width="160"> | <img src="docs/gallery/icon_out.png" width="160"> |
|
|
112
|
+
| **Illustration** (`--mode color`) | <img src="docs/gallery/illustration_in.png" width="160"> | <img src="docs/gallery/illustration_out.png" width="160"> |
|
|
113
|
+
| **Pixel art** (`--mode pixel`) | <img src="docs/gallery/pixel_in.png" width="160"> | <img src="docs/gallery/pixel_out.png" width="160"> |
|
|
114
|
+
|
|
115
|
+
## Usage
|
|
116
|
+
|
|
117
|
+
```bash
|
|
118
|
+
svgsmith convert input.png \
|
|
119
|
+
--mode auto \ # auto | binary | color | pixel
|
|
120
|
+
--quality 0.9 \ # target similarity (0–1), drives the verify loop
|
|
121
|
+
--max-iters 4 \
|
|
122
|
+
--editable \ # editable layered output (default on; --no-editable for raw)
|
|
123
|
+
--out output.svg \
|
|
124
|
+
--report json
|
|
125
|
+
```
|
|
126
|
+
|
|
127
|
+
### Flags
|
|
128
|
+
|
|
129
|
+
| Flag | Default | Meaning |
|
|
130
|
+
|---|---|---|
|
|
131
|
+
| `--mode {auto,binary,color,pixel}` | `auto` | `auto` classifies the image and routes it: `binary`→Potrace (logos/line art), `color`→VTracer (illustrations), `pixel`→VTracer pixel preset. |
|
|
132
|
+
| `--quality FLOAT` | `0.9` | Target fidelity in `[0,1]` (SSIM vs the original). Drives the verify loop. |
|
|
133
|
+
| `--max-iters INT` | `4` | Max verify/refine iterations before returning the best result so far. |
|
|
134
|
+
| `--editable` / `--no-editable` | on | Editable grouped/simplified SVG, or the raw traced output. |
|
|
135
|
+
| `--smooth` / `--no-smooth` | on | Curve-refit color contours into smooth, sparse Béziers (Schneider least-squares). |
|
|
136
|
+
| `--detail {high,normal,clean,poster}` | `normal` | Color detail dial. `high` = maximum detail; `clean` = edge-preserving cleanup (less noise/grain); `poster` = bold flat graphic with few colors. |
|
|
137
|
+
| `--solid-background` | off | Isolate the subject and repaint the background as one clean solid color — removes texture/grain/specks while keeping subject detail. |
|
|
138
|
+
| `--uniform-outline` | off | Force an even-width outline band (outlined illustrations only; would add a wrong border on line art). |
|
|
139
|
+
| `--out PATH` | `<input>.svg` | Output SVG path. |
|
|
140
|
+
| `--report {off,json}` | `off` | Print a JSON report to stdout (the only thing on stdout). |
|
|
141
|
+
|
|
142
|
+
> **Composable for agents.** svgsmith is meant to be driven by an AI agent that maps a
|
|
143
|
+
> user's intent to flags. *"Detailed character on a clean flat background"* →
|
|
144
|
+
> `--detail high --solid-background`; *"poster-style, minimal"* → `--detail poster`;
|
|
145
|
+
> *"crisp logo"* → `--mode binary`; *"keep the raw look"* → `--no-smooth`. The
|
|
146
|
+
> [`vectorize` skill](skills/vectorize/SKILL.md) encodes this mapping.
|
|
147
|
+
|
|
148
|
+
### Rasterize (SVG → PNG)
|
|
149
|
+
|
|
150
|
+
The inverse command renders an SVG back to a PNG (preview, thumbnail, round-trip):
|
|
151
|
+
|
|
152
|
+
```bash
|
|
153
|
+
svgsmith rasterize input.svg --out out.png # intrinsic (viewBox) size
|
|
154
|
+
svgsmith rasterize input.svg --width 512 # fixed width
|
|
155
|
+
svgsmith rasterize input.svg --scale 2 --background white
|
|
156
|
+
```
|
|
157
|
+
|
|
158
|
+
### Output
|
|
159
|
+
|
|
160
|
+
The SVG is **responsive and scalable**: it carries a `viewBox` and no fixed pixel
|
|
161
|
+
dimensions (`style="width:100%;height:100%"`, `preserveAspectRatio="xMidYMid meet"`),
|
|
162
|
+
so it fits any container or browser window with its aspect ratio preserved — no
|
|
163
|
+
overflow or scrollbars.
|
|
164
|
+
|
|
165
|
+
### Exit codes
|
|
166
|
+
|
|
167
|
+
| Code | Meaning |
|
|
168
|
+
|---|---|
|
|
169
|
+
| `0` | Success — `similarity >= --quality`. |
|
|
170
|
+
| `2` | SVG was produced but stayed below the quality target (still written to `--out`). |
|
|
171
|
+
| `1` | Hard error (e.g. unreadable input, missing `potrace` binary). |
|
|
172
|
+
|
|
173
|
+
### JSON report
|
|
174
|
+
|
|
175
|
+
```json
|
|
176
|
+
{
|
|
177
|
+
"output": "output.svg",
|
|
178
|
+
"mode_used": "color",
|
|
179
|
+
"engine": "vtracer",
|
|
180
|
+
"preset": "illustration",
|
|
181
|
+
"iterations": 2,
|
|
182
|
+
"similarity": 0.93,
|
|
183
|
+
"passed_threshold": true,
|
|
184
|
+
"svg": { "paths": 84, "groups": 6, "colors": 12, "bytes": 14820 },
|
|
185
|
+
"warnings": []
|
|
186
|
+
}
|
|
187
|
+
```
|
|
188
|
+
|
|
189
|
+
| Field | Meaning |
|
|
190
|
+
|---|---|
|
|
191
|
+
| `mode_used` / `engine` / `preset` | What the router actually chose. |
|
|
192
|
+
| `iterations` | How many verify/refine passes ran. |
|
|
193
|
+
| `similarity` | Best SSIM achieved vs the original. |
|
|
194
|
+
| `passed_threshold` | `similarity >= --quality`. |
|
|
195
|
+
| `svg` | Output stats: path count, `<g>` groups, distinct colors, byte size. |
|
|
196
|
+
| `warnings` | Human-readable caveats (e.g. photographic gradients that vectorize poorly). |
|
|
197
|
+
|
|
198
|
+
## How the self-verify loop works
|
|
199
|
+
|
|
200
|
+
svgsmith doesn't trust a single trace. After producing an SVG it:
|
|
201
|
+
|
|
202
|
+
1. **re-rasterizes** the SVG back to a bitmap (via `cairosvg`) at the original resolution,
|
|
203
|
+
2. **scores** it against the source with SSIM — that score is `similarity`,
|
|
204
|
+
3. if it's below `--quality`, **re-tunes** the trace/post-process parameters and retries,
|
|
205
|
+
up to `--max-iters`, and
|
|
206
|
+
4. returns the **best-scoring** result with the score in the report.
|
|
207
|
+
|
|
208
|
+
That closed loop is what lets an agent run svgsmith unsupervised: it gets a converged
|
|
209
|
+
result and a confidence number, not a guess. For an end-to-end agent wrapper, see the
|
|
210
|
+
[`vectorize` skill](skills/vectorize/SKILL.md).
|
|
211
|
+
|
|
212
|
+
## License
|
|
213
|
+
|
|
214
|
+
MIT — see [LICENSE](LICENSE).
|
svgsmith-0.1.0/README.md
ADDED
|
@@ -0,0 +1,187 @@
|
|
|
1
|
+
# svgsmith
|
|
2
|
+
|
|
3
|
+
> Agent-native, self-verifying raster→SVG vectorizer.
|
|
4
|
+
|
|
5
|
+
`svgsmith` turns PNG/JPG images into **editable** SVG. It is built to be driven by an
|
|
6
|
+
AI agent without a human in the loop: it picks the right tracing engine for the input,
|
|
7
|
+
post-processes the result into clean editable layers, and **verifies its own output** by
|
|
8
|
+
re-rasterizing the SVG and comparing it to the original — re-tuning until a quality
|
|
9
|
+
threshold is met. Every run returns a structured JSON report so a calling agent can
|
|
10
|
+
decide whether to accept, retry, or escalate.
|
|
11
|
+
|
|
12
|
+
It does **not** reinvent tracing. It wraps proven engines
|
|
13
|
+
([VTracer](https://github.com/visioncortex/vtracer) for color,
|
|
14
|
+
[Potrace](https://potrace.sourceforge.net/) for line art) and adds the layer that is
|
|
15
|
+
missing for agent use: routing, editable output, and a self-verification loop.
|
|
16
|
+
|
|
17
|
+
> **Status:** MVP complete — engine routing, editable post-processing, the self-verify loop,
|
|
18
|
+
> the CLI/JSON report, and the [`vectorize` skill](skills/vectorize/SKILL.md) are all in.
|
|
19
|
+
> See the [EPIC](../../issues/1) for scope.
|
|
20
|
+
|
|
21
|
+
## System dependencies
|
|
22
|
+
|
|
23
|
+
The line-art engine shells out to the [**Potrace**](https://potrace.sourceforge.net/)
|
|
24
|
+
binary (svgsmith does not bundle a Potrace Python binding). Install it from your
|
|
25
|
+
package manager before use:
|
|
26
|
+
|
|
27
|
+
The self-verify loop rasterizes SVGs with [CairoSVG](https://cairosvg.org/), which needs
|
|
28
|
+
the **Cairo** system library. Install both:
|
|
29
|
+
|
|
30
|
+
```bash
|
|
31
|
+
# Debian / Ubuntu
|
|
32
|
+
sudo apt-get install -y potrace libcairo2 libcairo2-dev
|
|
33
|
+
# macOS (Homebrew)
|
|
34
|
+
brew install potrace cairo
|
|
35
|
+
```
|
|
36
|
+
|
|
37
|
+
The color engine ([VTracer](https://github.com/visioncortex/vtracer)) ships as a
|
|
38
|
+
pinned PyPI wheel and needs no system package.
|
|
39
|
+
|
|
40
|
+
## Installation
|
|
41
|
+
|
|
42
|
+
Requires **Python 3.11+**. Install the [system dependencies](#system-dependencies)
|
|
43
|
+
above first, then svgsmith:
|
|
44
|
+
|
|
45
|
+
```bash
|
|
46
|
+
pip install svgsmith
|
|
47
|
+
```
|
|
48
|
+
|
|
49
|
+
### From source
|
|
50
|
+
|
|
51
|
+
```bash
|
|
52
|
+
git clone https://github.com/realproject7/svgsmith
|
|
53
|
+
cd svgsmith
|
|
54
|
+
pip install . # add ".[dev]" for the test/lint extras
|
|
55
|
+
```
|
|
56
|
+
|
|
57
|
+
Verify the install:
|
|
58
|
+
|
|
59
|
+
```bash
|
|
60
|
+
svgsmith --version
|
|
61
|
+
svgsmith convert path/to/image.png --out out.svg --report json
|
|
62
|
+
```
|
|
63
|
+
|
|
64
|
+
## What makes it different
|
|
65
|
+
|
|
66
|
+
- **Auto-routing** — classifies the input (logo/icon vs illustration vs pixel art) and
|
|
67
|
+
selects the engine + preset automatically. No tracer-flag expertise required.
|
|
68
|
+
- **Editable output** — instead of one monolithic `<path>`, output is grouped into
|
|
69
|
+
`<g>` layers with simplified paths and a consolidated color palette.
|
|
70
|
+
- **Self-verifying** — converts, re-rasterizes, diffs against the original (SSIM), and
|
|
71
|
+
re-tunes parameters until it converges on a quality target.
|
|
72
|
+
- **Structured report** — emits JSON (mode, engine, iterations, similarity score,
|
|
73
|
+
warnings) so agents can branch programmatically.
|
|
74
|
+
- **Local & private** — runs fully offline; images never leave the machine.
|
|
75
|
+
|
|
76
|
+
## Gallery
|
|
77
|
+
|
|
78
|
+
Each pair is the original raster (left) and the svgsmith SVG output (right), rendered at the
|
|
79
|
+
same size. Fixtures are self-generated (see `tests/corpus/`).
|
|
80
|
+
|
|
81
|
+
| | Original | svgsmith SVG |
|
|
82
|
+
|---|---|---|
|
|
83
|
+
| **Logo** (`--mode binary`) | <img src="docs/gallery/logo_in.png" width="160"> | <img src="docs/gallery/logo_out.png" width="160"> |
|
|
84
|
+
| **Icon** (`--mode binary`) | <img src="docs/gallery/icon_in.png" width="160"> | <img src="docs/gallery/icon_out.png" width="160"> |
|
|
85
|
+
| **Illustration** (`--mode color`) | <img src="docs/gallery/illustration_in.png" width="160"> | <img src="docs/gallery/illustration_out.png" width="160"> |
|
|
86
|
+
| **Pixel art** (`--mode pixel`) | <img src="docs/gallery/pixel_in.png" width="160"> | <img src="docs/gallery/pixel_out.png" width="160"> |
|
|
87
|
+
|
|
88
|
+
## Usage
|
|
89
|
+
|
|
90
|
+
```bash
|
|
91
|
+
svgsmith convert input.png \
|
|
92
|
+
--mode auto \ # auto | binary | color | pixel
|
|
93
|
+
--quality 0.9 \ # target similarity (0–1), drives the verify loop
|
|
94
|
+
--max-iters 4 \
|
|
95
|
+
--editable \ # editable layered output (default on; --no-editable for raw)
|
|
96
|
+
--out output.svg \
|
|
97
|
+
--report json
|
|
98
|
+
```
|
|
99
|
+
|
|
100
|
+
### Flags
|
|
101
|
+
|
|
102
|
+
| Flag | Default | Meaning |
|
|
103
|
+
|---|---|---|
|
|
104
|
+
| `--mode {auto,binary,color,pixel}` | `auto` | `auto` classifies the image and routes it: `binary`→Potrace (logos/line art), `color`→VTracer (illustrations), `pixel`→VTracer pixel preset. |
|
|
105
|
+
| `--quality FLOAT` | `0.9` | Target fidelity in `[0,1]` (SSIM vs the original). Drives the verify loop. |
|
|
106
|
+
| `--max-iters INT` | `4` | Max verify/refine iterations before returning the best result so far. |
|
|
107
|
+
| `--editable` / `--no-editable` | on | Editable grouped/simplified SVG, or the raw traced output. |
|
|
108
|
+
| `--smooth` / `--no-smooth` | on | Curve-refit color contours into smooth, sparse Béziers (Schneider least-squares). |
|
|
109
|
+
| `--detail {high,normal,clean,poster}` | `normal` | Color detail dial. `high` = maximum detail; `clean` = edge-preserving cleanup (less noise/grain); `poster` = bold flat graphic with few colors. |
|
|
110
|
+
| `--solid-background` | off | Isolate the subject and repaint the background as one clean solid color — removes texture/grain/specks while keeping subject detail. |
|
|
111
|
+
| `--uniform-outline` | off | Force an even-width outline band (outlined illustrations only; would add a wrong border on line art). |
|
|
112
|
+
| `--out PATH` | `<input>.svg` | Output SVG path. |
|
|
113
|
+
| `--report {off,json}` | `off` | Print a JSON report to stdout (the only thing on stdout). |
|
|
114
|
+
|
|
115
|
+
> **Composable for agents.** svgsmith is meant to be driven by an AI agent that maps a
|
|
116
|
+
> user's intent to flags. *"Detailed character on a clean flat background"* →
|
|
117
|
+
> `--detail high --solid-background`; *"poster-style, minimal"* → `--detail poster`;
|
|
118
|
+
> *"crisp logo"* → `--mode binary`; *"keep the raw look"* → `--no-smooth`. The
|
|
119
|
+
> [`vectorize` skill](skills/vectorize/SKILL.md) encodes this mapping.
|
|
120
|
+
|
|
121
|
+
### Rasterize (SVG → PNG)
|
|
122
|
+
|
|
123
|
+
The inverse command renders an SVG back to a PNG (preview, thumbnail, round-trip):
|
|
124
|
+
|
|
125
|
+
```bash
|
|
126
|
+
svgsmith rasterize input.svg --out out.png # intrinsic (viewBox) size
|
|
127
|
+
svgsmith rasterize input.svg --width 512 # fixed width
|
|
128
|
+
svgsmith rasterize input.svg --scale 2 --background white
|
|
129
|
+
```
|
|
130
|
+
|
|
131
|
+
### Output
|
|
132
|
+
|
|
133
|
+
The SVG is **responsive and scalable**: it carries a `viewBox` and no fixed pixel
|
|
134
|
+
dimensions (`style="width:100%;height:100%"`, `preserveAspectRatio="xMidYMid meet"`),
|
|
135
|
+
so it fits any container or browser window with its aspect ratio preserved — no
|
|
136
|
+
overflow or scrollbars.
|
|
137
|
+
|
|
138
|
+
### Exit codes
|
|
139
|
+
|
|
140
|
+
| Code | Meaning |
|
|
141
|
+
|---|---|
|
|
142
|
+
| `0` | Success — `similarity >= --quality`. |
|
|
143
|
+
| `2` | SVG was produced but stayed below the quality target (still written to `--out`). |
|
|
144
|
+
| `1` | Hard error (e.g. unreadable input, missing `potrace` binary). |
|
|
145
|
+
|
|
146
|
+
### JSON report
|
|
147
|
+
|
|
148
|
+
```json
|
|
149
|
+
{
|
|
150
|
+
"output": "output.svg",
|
|
151
|
+
"mode_used": "color",
|
|
152
|
+
"engine": "vtracer",
|
|
153
|
+
"preset": "illustration",
|
|
154
|
+
"iterations": 2,
|
|
155
|
+
"similarity": 0.93,
|
|
156
|
+
"passed_threshold": true,
|
|
157
|
+
"svg": { "paths": 84, "groups": 6, "colors": 12, "bytes": 14820 },
|
|
158
|
+
"warnings": []
|
|
159
|
+
}
|
|
160
|
+
```
|
|
161
|
+
|
|
162
|
+
| Field | Meaning |
|
|
163
|
+
|---|---|
|
|
164
|
+
| `mode_used` / `engine` / `preset` | What the router actually chose. |
|
|
165
|
+
| `iterations` | How many verify/refine passes ran. |
|
|
166
|
+
| `similarity` | Best SSIM achieved vs the original. |
|
|
167
|
+
| `passed_threshold` | `similarity >= --quality`. |
|
|
168
|
+
| `svg` | Output stats: path count, `<g>` groups, distinct colors, byte size. |
|
|
169
|
+
| `warnings` | Human-readable caveats (e.g. photographic gradients that vectorize poorly). |
|
|
170
|
+
|
|
171
|
+
## How the self-verify loop works
|
|
172
|
+
|
|
173
|
+
svgsmith doesn't trust a single trace. After producing an SVG it:
|
|
174
|
+
|
|
175
|
+
1. **re-rasterizes** the SVG back to a bitmap (via `cairosvg`) at the original resolution,
|
|
176
|
+
2. **scores** it against the source with SSIM — that score is `similarity`,
|
|
177
|
+
3. if it's below `--quality`, **re-tunes** the trace/post-process parameters and retries,
|
|
178
|
+
up to `--max-iters`, and
|
|
179
|
+
4. returns the **best-scoring** result with the score in the report.
|
|
180
|
+
|
|
181
|
+
That closed loop is what lets an agent run svgsmith unsupervised: it gets a converged
|
|
182
|
+
result and a confidence number, not a guess. For an end-to-end agent wrapper, see the
|
|
183
|
+
[`vectorize` skill](skills/vectorize/SKILL.md).
|
|
184
|
+
|
|
185
|
+
## License
|
|
186
|
+
|
|
187
|
+
MIT — see [LICENSE](LICENSE).
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
[build-system]
|
|
2
|
+
requires = ["setuptools>=77", "wheel"]
|
|
3
|
+
build-backend = "setuptools.build_meta"
|
|
4
|
+
|
|
5
|
+
[project]
|
|
6
|
+
name = "svgsmith"
|
|
7
|
+
dynamic = ["version"]
|
|
8
|
+
description = "Convert raster images into clean, editable SVG."
|
|
9
|
+
readme = "README.md"
|
|
10
|
+
requires-python = ">=3.11"
|
|
11
|
+
license = "MIT"
|
|
12
|
+
license-files = ["LICENSE"]
|
|
13
|
+
authors = [{ name = "svgsmith contributors" }]
|
|
14
|
+
keywords = ["svg", "vectorization", "raster", "potrace"]
|
|
15
|
+
classifiers = [
|
|
16
|
+
"Programming Language :: Python :: 3.11",
|
|
17
|
+
"Programming Language :: Python :: 3.12",
|
|
18
|
+
"Topic :: Multimedia :: Graphics :: Graphics Conversion",
|
|
19
|
+
]
|
|
20
|
+
dependencies = [
|
|
21
|
+
"vtracer==0.6.15",
|
|
22
|
+
"Pillow>=10.4,<13",
|
|
23
|
+
"numpy>=1.26,<3",
|
|
24
|
+
"cairosvg>=2.7,<3",
|
|
25
|
+
"scikit-image>=0.22,<0.27",
|
|
26
|
+
"scipy>=1.11,<2",
|
|
27
|
+
]
|
|
28
|
+
|
|
29
|
+
[project.optional-dependencies]
|
|
30
|
+
dev = ["ruff>=0.5", "pytest>=8"]
|
|
31
|
+
|
|
32
|
+
[project.urls]
|
|
33
|
+
Homepage = "https://github.com/realproject7/svgsmith"
|
|
34
|
+
Repository = "https://github.com/realproject7/svgsmith"
|
|
35
|
+
Issues = "https://github.com/realproject7/svgsmith/issues"
|
|
36
|
+
|
|
37
|
+
[project.scripts]
|
|
38
|
+
svgsmith = "svgsmith.cli:main"
|
|
39
|
+
|
|
40
|
+
[tool.setuptools.packages.find]
|
|
41
|
+
where = ["src"]
|
|
42
|
+
|
|
43
|
+
# Single source of truth for the version: svgsmith.__version__.
|
|
44
|
+
[tool.setuptools.dynamic]
|
|
45
|
+
version = { attr = "svgsmith.__version__" }
|
|
46
|
+
|
|
47
|
+
[tool.ruff]
|
|
48
|
+
line-length = 100
|
|
49
|
+
src = ["src", "tests"]
|
|
50
|
+
|
|
51
|
+
[tool.ruff.lint]
|
|
52
|
+
select = ["E", "F", "I", "W", "UP", "B"]
|
|
53
|
+
|
|
54
|
+
[tool.pytest.ini_options]
|
|
55
|
+
testpaths = ["tests"]
|
|
56
|
+
addopts = "-q"
|
svgsmith-0.1.0/setup.cfg
ADDED
|
@@ -0,0 +1,90 @@
|
|
|
1
|
+
"""Input classification for ``--mode auto``.
|
|
2
|
+
|
|
3
|
+
Deterministic, heuristic-only (no ML) selection of an engine *mode* and a
|
|
4
|
+
canonical *preset* for an input image. Downstream the pipeline maps:
|
|
5
|
+
|
|
6
|
+
- ``binary`` → Potrace (``logo`` / ``icon`` preset)
|
|
7
|
+
- ``color`` → VTracer (``illustration`` preset)
|
|
8
|
+
- ``pixel`` → VTracer (``pixel`` preset)
|
|
9
|
+
|
|
10
|
+
Photographic inputs classify as ``color`` and carry a raw warning string; this
|
|
11
|
+
module emits the string only — #8 assembles the canonical report, so the report
|
|
12
|
+
schema is intentionally not imported here.
|
|
13
|
+
"""
|
|
14
|
+
|
|
15
|
+
from __future__ import annotations
|
|
16
|
+
|
|
17
|
+
from typing import NamedTuple
|
|
18
|
+
|
|
19
|
+
from PIL import Image, ImageFilter, ImageOps
|
|
20
|
+
|
|
21
|
+
from svgsmith.engines.base import ImageInput, load_image
|
|
22
|
+
|
|
23
|
+
# Heuristic thresholds. The fixture feature values sit well clear of every
|
|
24
|
+
# boundary, so classification stays stable across Pillow versions.
|
|
25
|
+
PIXEL_MAX_DIM = 64 # pixel art lives on a tiny canvas
|
|
26
|
+
PIXEL_MAX_PALETTE = 32 # ...with a small palette
|
|
27
|
+
PHOTO_MIN_PALETTE = 64 # rich palette / gradients read as photographic
|
|
28
|
+
BINARY_MAX_PALETTE = 3 # monochrome-ish ink-on-paper
|
|
29
|
+
BINARY_MIN_EDGE_DENSITY = 0.02 # ...with sharp edges
|
|
30
|
+
EDGE_MAGNITUDE_CUTOFF = 40 # grayscale edge strength counted as a "strong" edge
|
|
31
|
+
|
|
32
|
+
PHOTO_WARNING = "photographic gradients; vectorization may bloat"
|
|
33
|
+
|
|
34
|
+
|
|
35
|
+
class Classification(NamedTuple):
|
|
36
|
+
"""Result of :func:`classify`.
|
|
37
|
+
|
|
38
|
+
Unpacks as ``(mode, preset, warnings)``; callers that only need the engine
|
|
39
|
+
selection can read ``.mode`` / ``.preset``.
|
|
40
|
+
"""
|
|
41
|
+
|
|
42
|
+
mode: str # "binary" | "color" | "pixel"
|
|
43
|
+
preset: str # canonical preset name from engines.PRESETS
|
|
44
|
+
warnings: tuple[str, ...]
|
|
45
|
+
|
|
46
|
+
|
|
47
|
+
def _palette_size(img: Image.Image) -> int:
|
|
48
|
+
"""Distinct colors after a coarse posterize that suppresses codec noise."""
|
|
49
|
+
posterized = ImageOps.posterize(img, 4)
|
|
50
|
+
colors = posterized.getcolors(maxcolors=1 << 16)
|
|
51
|
+
return len(colors) if colors is not None else (1 << 16)
|
|
52
|
+
|
|
53
|
+
|
|
54
|
+
def _edge_density(img: Image.Image) -> float:
|
|
55
|
+
"""Fraction of pixels whose grayscale edge magnitude exceeds the cutoff."""
|
|
56
|
+
edges = img.convert("L").filter(ImageFilter.FIND_EDGES)
|
|
57
|
+
histogram = edges.histogram()
|
|
58
|
+
strong = sum(histogram[EDGE_MAGNITUDE_CUTOFF:])
|
|
59
|
+
total = img.width * img.height
|
|
60
|
+
return strong / total if total else 0.0
|
|
61
|
+
|
|
62
|
+
|
|
63
|
+
def classify(image: ImageInput) -> Classification:
|
|
64
|
+
"""Classify ``image`` into an engine mode + canonical preset.
|
|
65
|
+
|
|
66
|
+
Returns a :class:`Classification`. Photographic inputs classify as ``color``
|
|
67
|
+
with :data:`PHOTO_WARNING` attached.
|
|
68
|
+
"""
|
|
69
|
+
img = load_image(image, "RGB")
|
|
70
|
+
max_dim = max(img.size)
|
|
71
|
+
palette = _palette_size(img)
|
|
72
|
+
|
|
73
|
+
# Monochrome-ish + sharp edges → line art. Checked before the pixel-art
|
|
74
|
+
# branch so a tiny 2-3 color icon reaches `binary`/`icon` instead of being
|
|
75
|
+
# captured as pixel art (pixel art carries more than a couple of hues).
|
|
76
|
+
if palette <= BINARY_MAX_PALETTE and _edge_density(img) >= BINARY_MIN_EDGE_DENSITY:
|
|
77
|
+
preset = "icon" if max_dim <= PIXEL_MAX_DIM else "logo"
|
|
78
|
+
return Classification("binary", preset, ())
|
|
79
|
+
|
|
80
|
+
# Tiny canvas + small (but non-monochrome) palette → pixel art.
|
|
81
|
+
if max_dim <= PIXEL_MAX_DIM and palette <= PIXEL_MAX_PALETTE:
|
|
82
|
+
return Classification("pixel", "pixel", ())
|
|
83
|
+
|
|
84
|
+
# Rich palette / gradients → photographic. Still vectorizable as color, but
|
|
85
|
+
# flag the likely bloat for the report to surface.
|
|
86
|
+
if palette >= PHOTO_MIN_PALETTE:
|
|
87
|
+
return Classification("color", "illustration", (PHOTO_WARNING,))
|
|
88
|
+
|
|
89
|
+
# Everything else: flat multi-color illustration.
|
|
90
|
+
return Classification("color", "illustration", ())
|