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.
Files changed (36) hide show
  1. svgsmith-0.1.0/LICENSE +21 -0
  2. svgsmith-0.1.0/PKG-INFO +214 -0
  3. svgsmith-0.1.0/README.md +187 -0
  4. svgsmith-0.1.0/pyproject.toml +56 -0
  5. svgsmith-0.1.0/setup.cfg +4 -0
  6. svgsmith-0.1.0/src/svgsmith/__init__.py +3 -0
  7. svgsmith-0.1.0/src/svgsmith/__main__.py +6 -0
  8. svgsmith-0.1.0/src/svgsmith/classify.py +90 -0
  9. svgsmith-0.1.0/src/svgsmith/cli.py +266 -0
  10. svgsmith-0.1.0/src/svgsmith/engines/__init__.py +28 -0
  11. svgsmith-0.1.0/src/svgsmith/engines/base.py +118 -0
  12. svgsmith-0.1.0/src/svgsmith/engines/binary.py +71 -0
  13. svgsmith-0.1.0/src/svgsmith/engines/color.py +36 -0
  14. svgsmith-0.1.0/src/svgsmith/pipeline.py +165 -0
  15. svgsmith-0.1.0/src/svgsmith/postprocess.py +583 -0
  16. svgsmith-0.1.0/src/svgsmith/preprocess.py +253 -0
  17. svgsmith-0.1.0/src/svgsmith/render.py +79 -0
  18. svgsmith-0.1.0/src/svgsmith/report.py +69 -0
  19. svgsmith-0.1.0/src/svgsmith/smooth.py +389 -0
  20. svgsmith-0.1.0/src/svgsmith/verify.py +204 -0
  21. svgsmith-0.1.0/src/svgsmith.egg-info/PKG-INFO +214 -0
  22. svgsmith-0.1.0/src/svgsmith.egg-info/SOURCES.txt +34 -0
  23. svgsmith-0.1.0/src/svgsmith.egg-info/dependency_links.txt +1 -0
  24. svgsmith-0.1.0/src/svgsmith.egg-info/entry_points.txt +2 -0
  25. svgsmith-0.1.0/src/svgsmith.egg-info/requires.txt +10 -0
  26. svgsmith-0.1.0/src/svgsmith.egg-info/top_level.txt +1 -0
  27. svgsmith-0.1.0/tests/test_classify.py +45 -0
  28. svgsmith-0.1.0/tests/test_cli.py +106 -0
  29. svgsmith-0.1.0/tests/test_corpus.py +73 -0
  30. svgsmith-0.1.0/tests/test_engines.py +79 -0
  31. svgsmith-0.1.0/tests/test_pipeline.py +141 -0
  32. svgsmith-0.1.0/tests/test_postprocess.py +139 -0
  33. svgsmith-0.1.0/tests/test_preprocess.py +104 -0
  34. svgsmith-0.1.0/tests/test_render.py +39 -0
  35. svgsmith-0.1.0/tests/test_report.py +66 -0
  36. 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.
@@ -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).
@@ -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"
@@ -0,0 +1,4 @@
1
+ [egg_info]
2
+ tag_build =
3
+ tag_date = 0
4
+
@@ -0,0 +1,3 @@
1
+ """svgsmith — convert raster images into clean, editable SVG."""
2
+
3
+ __version__ = "0.1.0"
@@ -0,0 +1,6 @@
1
+ """Allow ``python -m svgsmith`` to invoke the CLI."""
2
+
3
+ from svgsmith.cli import main
4
+
5
+ if __name__ == "__main__":
6
+ raise SystemExit(main())
@@ -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", ())