How to convert PNG to SVG in Python
Python has no built-in way to turn pixels into vector shapes, but three good options are a pip install away. This guide shows working code for each (every sample here was run on real images), the settings worth changing, and how the results compare.
The short version
- VTracer (
pip install vtracer, MIT license): full-color tracing in one line, runs locally, free. Good on clean artwork; struggles with compressed JPEGs. - Potrace (
pip install potracer, GPL): the classic black-and-white tracer behind Inkscape’s Trace Bitmap. Very smooth curves, one color per trace, and about 30 lines of code to write an SVG. - The SVG Me API: one HTTP request, no native dependencies, few flat colors chosen automatically, and outputs for cutters, lasers and print. Costs 1 credit per image, from 10¢ in bulk.
Option 1: VTracer, for color images
VTracer is a Rust tracer with Python bindings. It ships prebuilt wheels, so installing it needs no compiler:
python3 -m pip install vtracerimport vtracer
vtracer.convert_image_to_svg_py("logo.png", "logo.svg")The defaults keep a lot of colors, because every slightly different shade becomes its own layer. Four settings do most of the work of simplifying the result:
import vtracer
vtracer.convert_image_to_svg_py(
"logo.png",
"logo.svg",
colormode="color", # or "binary" for black and white
hierarchical="stacked", # shapes stacked on top of each other, not cut out
mode="spline", # smooth curves; "polygon" gives straight segments
filter_speckle=8, # drop specks smaller than 8 px (default 4)
color_precision=4, # fewer significant bits per channel = fewer colors (default 6)
layer_difference=32, # merge shades closer than this (default 16)
)On our example logo, these settings took the result from 21 colors and 41 shapes down to 9 colors and 29 shapes, with no visible difference from the default trace. The right values depend on your images, so try a few on a handful of files before running a whole folder.
Use colormode="binary" for black-and-white art such as line drawings, stamps and laser designs. It is faster and gives one clean layer.
Option 2: Potrace, for black and white
Potrace fits remarkably smooth curves, which is why it is still the engine behind many tracing tools. potracer is a pure-Python port, so it installs anywhere, with Pillow and NumPy to read the image:
python3 -m pip install potracer pillow numpyIt returns curves rather than a file, so the script writes the SVG path itself:
import numpy as np
import potrace
from PIL import Image
# Flatten transparency onto white first: a transparent pixel would read as black.
image = Image.open("mandala.png").convert("RGBA")
page = Image.new("RGBA", image.size, "white")
page.alpha_composite(image)
gray = np.array(page.convert("L"))
# Black and white only. potracer traces the pixels that are False, so mark the
# light background True: everything darker than mid-grey becomes the shape.
bitmap = potrace.Bitmap(gray >= 128)
curves = bitmap.trace(turdsize=2, alphamax=1.0, opticurve=True, opttolerance=0.2)
d = []
for curve in curves:
d.append(f"M{curve.start_point.x:.2f},{curve.start_point.y:.2f}")
for seg in curve.segments:
if seg.is_corner:
d.append(f"L{seg.c.x:.2f},{seg.c.y:.2f}L{seg.end_point.x:.2f},{seg.end_point.y:.2f}")
else:
d.append(
f"C{seg.c1.x:.2f},{seg.c1.y:.2f} {seg.c2.x:.2f},{seg.c2.y:.2f} "
f"{seg.end_point.x:.2f},{seg.end_point.y:.2f}"
)
d.append("Z")
h, w = gray.shape
with open("mandala.svg", "w") as f:
f.write(
f'<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 {w} {h}">'
f'<path fill="#000" fill-rule="evenodd" d="{"".join(d)}"/></svg>'
)Two things trip people up, both handled above:
- Transparency. Converting a transparent PNG straight to greyscale turns transparent pixels black, and you get one big filled square. Flatten onto white first.
- Which pixels are traced.
potracertraces the pixels that are False, so pass True for the light background. Flip the comparison and you trace the background instead.
Potrace only does one color at a time. For color art you have to reduce the image to a palette, trace each color separately and stack the layers, which is what Inkscape’s Trace Bitmap does with Multiple scans. See how that compares.
Option 3: The SVG Me API
The API runs the same converter as the SVG Me web app. It is worth it when your inputs are messy (saved-from-the-web JPGs, screenshots, small logos), when you want a handful of flat colors without tuning, or when the output goes to a cutting machine, laser or printer. It needs only requests:
python3 -m pip install requestsimport os
import requests
with open("logo.png", "rb") as image:
response = requests.post(
"https://svgme.app/api/v1/convert",
headers={"Authorization": f"Bearer {os.environ['SVGME_API_KEY']}"},
files={"image": ("logo.png", image)},
data={"colors": "4"},
timeout=(10, 180),
)
response.raise_for_status()
with open("logo.svg", "wb") as f:
f.write(response.content)
print("Credits left:", response.headers.get("X-Credits-Remaining"))Add format=layers for Cricut and Silhouette, format=outline for lasers, remove_background=auto to drop a solid background, or file_type=pdf for print. The API guide has every option, async jobs, and a folder script with retries.
How the results compare
We traced 15 flat-color designs that had been shrunk to 200 px and saved as low-quality JPEGs, the kind of file people usually have, with each tool at its default settings. Medians per design:
- Colors: VTracer 133, Potrace (as an 8-color scan) 8, SVG Me 4.
- Anchor points: VTracer 1,450, Potrace 2,168, SVG Me 282. Fewer points means smoother cuts and smaller files.
- Stray specks: VTracer 0, Potrace 91, SVG Me 0.
- File size: VTracer 104 KB, Potrace 85 KB, SVG Me 13 KB.
SVG Me’s result was the closest to the original on all 15. On clean PNGs the gap is smaller, and tuned VTracer settings narrow it further. Full method and results: image-to-SVG converters, tested.
Converting a whole folder
With VTracer, loop over the folder and use the same settings for every file so the results are consistent:
from pathlib import Path
import vtracer
out = Path("svg")
out.mkdir(exist_ok=True)
for src in sorted(Path("images").iterdir()):
if src.suffix.lower() in {".png", ".jpg", ".jpeg"}:
vtracer.convert_image_to_svg_py(str(src), str(out / f"{src.stem}.svg"), filter_speckle=8)
print(f"{src.name} -> svg/{src.stem}.svg")For the API, use the folder script in the API guide: it retries rate limits and temporary errors, and reports failures without stopping. See also batch converting images to SVG.
Questions
Can OpenCV convert an image to SVG?
Not directly. cv2.findContours finds outlines as polygons, which you can write into an SVG yourself, but they follow the pixels, so edges stay jagged. VTracer and Potrace fit smooth curves instead.
Why does my SVG have hundreds of colors?
Compression noise and anti-aliased edges create many near-identical shades, and each becomes a layer. With VTracer, lower color_precision and raise layer_difference and filter_speckle. The SVG Me API groups them automatically.
Can I use these libraries in a commercial product?
VTracer is MIT licensed. Potrace and potracer are GPL, which has conditions if you distribute software that includes them. Check with whoever handles licensing for your project. The API is a service you call, so there is no library to license.
How do I convert SVG back to PNG in Python?
Use CairoSVG: cairosvg.svg2png(url="logo.svg", write_to="logo.png", output_width=1024). Rendering an SVG is easy; it is tracing a PNG into one that is hard.