sw(a)g.converter
Locally convert raster images into real vector SVG — actual <path> data with
fitted <linearGradient> / <radialGradient> fills. No embedded bitmaps, no
cloud service, no API key. Everything runs on your machine.
swag logo.png
sw(a)g.converter raster → vector
────────────────────────────────────────────────────
╭──────────────────────────────────────────────────────────────╮
│ source logo.png 320×320 46.4 KB │
│ content icon (texture 0.00, flat 100%, 334 colours) │
│ shapes 9 regions · 9 gradients · 1,935 nodes │
│ similarity 98.2% │
│ output logo.svg 36.9 KB in 1.6s │
╰──────────────────────────────────────────────────────────────╯
Why another tracer
Most raster-to-vector tools flatten everything into flat colour patches. Smooth shading becomes a stack of hard-edged bands, and any semi-transparent pixel is painted as if it were opaque — which is what produces the coloured "shadow" you often see around a traced image.
This one does two things differently:
- Gradients are fitted, not faked. Neighbouring regions are merged for as long as a single linear or radial gradient still explains them, and the gradient stops are sampled from the real pixels. A shaded sphere becomes one path with one gradient instead of twenty concentric slivers.
- Alpha is respected. The silhouette comes from the alpha channel alone, and edge pixels take their colour from the nearest solid neighbour, so a faint halo stays faint instead of turning into solid paint.
Install
pip install swag-converter
Python 3.10 or newer is the only prerequisite; that command pulls in everything
else and puts a swag command on your PATH. Check it landed:
swag --version
Other ways to install
Isolated from your other packages, via pipx:
pipx install swag-converter
Straight from a release, no PyPI involved:
pip install https://github.com/CryptoNerf/swag-converter/releases/download/v0.1.0/swag_converter-0.1.0-py3-none-any.whl
From a clone, for hacking on it:
git clone https://github.com/CryptoNerf/swag-converter
cd swag-converter
pip install -e '.[dev]'
The similarity score is the one optional extra: it needs
CairoSVG, so pip install 'swag-converter[quality]'
(plus brew install cairo on macOS). Without it conversion works exactly the
same and the score reads not measured.
Your first conversion
Point it at any image. There is nothing to configure:
swag logo.png
That writes logo.svg next to the original and prints the report above. Reading
it top to bottom:
| line | what it tells you |
|---|---|
source |
the file it read, its pixel size and weight on disk |
content |
which preset auto picked, and the measurements behind the choice |
shapes |
how many paths, gradients and Bézier nodes the SVG contains |
similarity |
how closely the SVG re-renders to the original, 100% being pixel-identical — reads not measured until you install the optional CairoSVG above |
output |
where the SVG went, how big it is, how long it took |
A similarity in the nineties means the vector is a faithful stand-in for the
original. If it comes out low, the image is probably photographic — see
Photographs, honestly.
Nothing is ever overwritten silently except a .svg of the same name, and no
file leaves your machine.
Usage
swag logo.png # writes logo.svg next to it
swag icons/ --out svg/ # whole folder, in parallel
swag photo.jpg --preset photo # stylised vector from a photograph
swag art.png --quality max # slower, closer to the source
swag *.png --json # machine-readable output
| option | what it does |
|---|---|
-o, --out DIR |
Write SVGs into DIR instead of beside each input |
-p, --preset |
auto (default), icon, illustration, photo, poster |
-q, --quality |
fast, balanced (default), max |
--max-edge PX |
Resize the longer edge before tracing (default 1024, 0 disables) |
--background |
auto (default), always, keep — clear a flat backdrop |
-j, --workers N |
Parallel workers for batches |
--no-measure |
Skip the similarity score |
Input can be PNG, JPEG, WebP, GIF, BMP, TIFF, TGA, ICO, HEIC/AVIF — anything Pillow reads. EXIF rotation, grayscale, palette and CMYK all get normalised.
Presets
auto picks one by measuring how much fine detail survives a median filter —
grain does, clean edges do not — so drawings are never mistaken for photographs
however colourful they are.
| preset | for | result |
|---|---|---|
icon |
logos, UI icons, flat art | crisp edges, few shapes |
illustration |
shaded artwork, stickers, game assets | gradients preserved |
photo |
photographs | deliberate stylisation, bounded cost |
poster |
any image | a few big flat shapes, screen-print look |
Photographs, honestly
A photograph is not really vector material: it has texture in every pixel and no
region structure to find. --preset photo gives you a clean, deliberate
stylisation in seconds rather than a faithful reproduction — expect something
closer to a screen print than to the original. If you want fidelity from a
photo, keep the raster.
What it will not do is hang: a 3840×2160 photograph converts in about 25 seconds because the working size is capped and the merge stage runs on a priority queue.
Using it as a library
from swag_converter import convert
result = convert("logo.png", "logo.svg", preset="auto", quality="max")
print(result.regions, result.nodes, result.similarity)
How it works
image
└─ normalise: EXIF, colour mode, size cap, optional backdrop removal
└─ classify content → preset
└─ repair edge colour, derive the silhouette from alpha
└─ cluster colour in CIELAB, split into connected regions
└─ merge neighbours while one gradient still fits them
└─ marching squares → cubic Bézier fitting
└─ flat / linear / radial paint per region
└─ SVG, then render back to score it
Curve fitting is Schneider's least-squares cubic algorithm, so a square traces to exactly four segments with sharp corners and a circle to about ten smooth ones. Adjacent paths overlap by a fraction of a pixel, which is what stops anti-aliasing leaving hairline seams between them.
Limitations
- Photographs are stylised, not reproduced — see above.
- Very large images are downscaled to
--max-edgebefore tracing; raise it for more detail, at a real cost in time and file size. - Soft transitions become a boundary between two regions. Adjacent gradients are matched so the colour is continuous across it, but a truly soft edge (a blur, a glow) cannot be represented.
- Output is larger than the source PNG for detailed images. That is inherent: vector data at this fidelity costs more than a compressed bitmap.
Development
pip install -e '.[dev]'
pytest # full suite
pytest -m 'not slow' # skip the timing guards
python examples/make_samples.py
Releases are cut by pushing a tag; see RELEASING.md.
License
MIT — see LICENSE.
The SVGs you produce are yours; this tool claims nothing over them. Do check the licence of whatever you feed it — vectorising an image does not change who owns it.
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distribution
Built Distribution
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
File details
Details for the file swag_converter-0.1.0.tar.gz.
File metadata
- Download URL: swag_converter-0.1.0.tar.gz
- Upload date:
- Size: 43.8 kB
- Tags: Source
- Uploaded using Trusted Publishing? Yes
- Uploaded via:
twine/7.0.0 CPython/3.13.14
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
bbb91df6d66b1078d8d9724b8c68fb230702f772bc1c54b9ddafeafe744918b9
|
|
| MD5 |
f0729361f651b51e04e67554406a1821
|
|
| BLAKE2b-256 |
fa61c60a13370801e46e0ca27e5a5639cade6a4d3c6b7a1aa908870bebd97721
|
Provenance
The following attestation bundles were made for swag_converter-0.1.0.tar.gz:
Publisher:
release.yml on CryptoNerf/swag-converter
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
swag_converter-0.1.0.tar.gz -
Subject digest:
bbb91df6d66b1078d8d9724b8c68fb230702f772bc1c54b9ddafeafe744918b9 - Sigstore transparency entry: 2772176082
- Sigstore integration time:
-
Permalink:
CryptoNerf/swag-converter@49cf80823e0857d9eeef0491ba2c9de60be1615f -
Branch / Tag:
refs/tags/v0.1.0 - Owner: https://github.com/CryptoNerf
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
release.yml@49cf80823e0857d9eeef0491ba2c9de60be1615f -
Trigger Event:
push
-
Statement type:
File details
Details for the file swag_converter-0.1.0-py3-none-any.whl.
File metadata
- Download URL: swag_converter-0.1.0-py3-none-any.whl
- Upload date:
- Size: 40.8 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? Yes
- Uploaded via:
twine/7.0.0 CPython/3.13.14
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
9b286433752bc1548da9ffc3a8454b72b781f8503278fa9d38b5beb0697a56f2
|
|
| MD5 |
e97427112464c3f18016c2fc5c6b288e
|
|
| BLAKE2b-256 |
710da2f370e6209444fecab99345f85cec671e7c8e9abc2bec957b0dadb064d9
|
Provenance
The following attestation bundles were made for swag_converter-0.1.0-py3-none-any.whl:
Publisher:
release.yml on CryptoNerf/swag-converter
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
swag_converter-0.1.0-py3-none-any.whl -
Subject digest:
9b286433752bc1548da9ffc3a8454b72b781f8503278fa9d38b5beb0697a56f2 - Sigstore transparency entry: 2772176123
- Sigstore integration time:
-
Permalink:
CryptoNerf/swag-converter@49cf80823e0857d9eeef0491ba2c9de60be1615f -
Branch / Tag:
refs/tags/v0.1.0 - Owner: https://github.com/CryptoNerf
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
release.yml@49cf80823e0857d9eeef0491ba2c9de60be1615f -
Trigger Event:
push
-
Statement type: