Chris Tsang 9650808263 Snap watershed boundaries to the color-midpoint iso-line
Inside an antialiasing ramp (or JPEG halo) the per-pixel differences are
near-equal, so the minimum-spanning-forest cut meanders +-1-2 px with the
pixel noise and the fitted curves visibly wave. After the cut, boundary
pixels whose color is a mixture of the two adjacent region means are
re-assigned to the closer mean - the same rule color quantization applies,
which is why the color-cluster frontend never showed this. A mixture gate
keeps genuine third-color detail (e.g. dark outline strokes) with its
basin, and crisp synthetic edges are untouched (goldens unchanged).

The merge-tree replay now walks the full edge order: snapping can leave a
region's only adjacency running through a below-cut edge, and skipping
those left the tree unconnected. Costs ~6 ms on a 1400x775 cut (~30 ms
before, ~36 ms after); the first sweep scans the canvas, later sweeps
revisit only the moving front.

Absorb the fragments boundary snapping pinches off

A snap flip can strand a pixel (its supporting neighbour flips away in the
same sweep) or sever a thin strand of its source region. Watershed basins
are connected by construction and the mosaic gives every disjoint patch its
own face, so this debris surfaced in cutout mode as 1-px micro-faces wedged
between the real faces - visually gapless, but neighbours no longer shared
a fitted boundary (62 chips on the Cityscape sample, some with degenerate
zero-area outlines).

After the sweeps, flood every touched component with a small cap: one that
is disconnected from the rest of its region and fits under the floor moves
wholesale to the adjacent region with the closest mean. Substantial patches
severed at a thin antialiased neck stay - they make coherent faces of their
own, and recoloring them would be visible. Seeded by the sweep fronts, so
the cost is proportional to the flips, not the canvas (cut stays ~36 ms).

Cityscape cutout: 95 faces back down to 33 (32 pre-snap), zero coverage
gaps. Guarded by snap_leaves_no_debris on the real photo (smallest patch
was 1 px before, 147 patches; now every patch clears the speckle floor).
2026-07-27 21:26:49 +01:00
2026-07-27 19:56:03 +01:00
2021-07-23 18:29:12 +08:00
2024-04-20 16:21:36 +01:00

VTracer

Raster to Vector Graphics Converter

Article | Web App | Download

Rust library on crates.io Python package on PyPI Node package on npm

Packages

VTracer 1.0 is a vectorization framework (pluggable frontends, curve fitters, color fitting, and output optimization) shipped across four surfaces from this repository:

Package Registry Source Use
vtracer-cli crates.io crates/vtracer-cli Command-line tool (vtracer binary)
vtracer crates.io crates/vtracer Rust library / the framework core
vtracer PyPI crates/vtracer-py Python native extension (pyo3 + maturin)
@visioncortex/vtracer npm nodejs Node.js WebAssembly build, no native dependency

Introduction

visioncortex VTracer is an open source software to convert raster images (like jpg & png) into vector graphics (svg). It can vectorize graphics and photographs and trace the curves to output compact vector files.

Comparing to Potrace which only accept binarized inputs (Black & White pixmap), VTracer has an image processing pipeline which can handle colored high resolution scans. tl;dr: Potrace uses a O(n^2) fitting algorithm, whereas vtracer is entirely O(n).

Comparing to Adobe Illustrator's Image Trace, VTracer's output is much more compact (less shapes) as we adopt a stacking strategy and avoid producing shapes with holes.

VTracer is originally designed for processing high resolution scans of historic blueprints up to gigapixels. At the same time, VTracer can also handle low resolution pixel art, simulating image-rendering: pixelated for retro game artworks.

Technical descriptions of the tracing algorithm and clustering algorithm.

Desktop App (coming soon)

screenshot

VTracer App features:

  • Higher performance
  • Adaptive thresholding in B/W mode
  • Perfect cutout mode
  • Fixed color palette

Cmd App

Input and output can be given as positional arguments or as named flags:

vtracer input.jpg output.svg
# equivalent to:
vtracer --input input.jpg --output output.svg

Full options (flag names are kebab-case, e.g. --filter-speckle):

Usage: vtracer [OPTIONS] [INPUT] [OUTPUT]

Arguments:
  [INPUT]   Input raster image (positional; or use --input)
  [OUTPUT]  Output SVG (positional; or use --output)

Options:
  -i, --input <INPUT>                        Path to the input raster image
  -o, --output <OUTPUT>                      Path to the output SVG
      --preset <PRESET>                      Start from a preset: bw, poster, photo
      --clustering <CLUSTERING>              Region forming: `color-cluster` (default), `bw`, `watershed`
      --hierarchical <HIERARCHICAL>          Clustering: `stacked` (default) or `cutout` (seam-free mosaic)
  -m, --mode <MODE>                          Curve-fitting mode: `pixel`, `polygon`, `spline`
  -f, --filter-speckle <FILTER_SPECKLE>      Discard patches smaller than X px in size (0..=128)
  -p, --color-precision <COLOR_PRECISION>    Significant bits per RGB channel (1..=8)
  -g, --gradient-step <GRADIENT_STEP>        Color difference between gradient layers (0..=255)
  -c, --corner-threshold <CORNER_THRESHOLD>  Minimum momentary angle (degrees) to be a corner (0..=180)
  -l, --segment-length <SEGMENT_LENGTH>      Subdivide until all segments are shorter than this (3.5..=10)
  -s, --splice-threshold <SPLICE_THRESHOLD>  Minimum angle displacement (degrees) to splice a spline (0..=180)
      --path-precision <PATH_PRECISION>      Decimal places to use in path coordinates
      --palette <PALETTE>                    Fixed palette: comma-separated hex colors, e.g. '#112233,#445566'
      --palette-file <PALETTE_FILE>          Fixed palette from a file (hex colors, comma/newline separated)
      --max-colors <MAX_COLORS>              Auto-quantize to at most N colors
      --optimize <OPTIMIZE>                  Output optimization: 0 = off, 1 = quantize+simplify, 2 = + shorthands
      --threshold <THRESHOLD>                Binary mode: fixed threshold 0..=255 (foreground below it)
      --adaptive                             Binary mode: BradleyRoth adaptive threshold (uneven lighting)
      --adaptive-window <ADAPTIVE_WINDOW>    Adaptive window size in px (0 = auto); implies --adaptive
      --adaptive-t <ADAPTIVE_T>              Adaptive sensitivity: % below local mean (default 15)
      --watershed-detail <WATERSHED_DETAIL>  Watershed: hierarchy cut level 0..=255 (higher = more regions)
  -h, --help                                 Print help
  -V, --version                              Print version

New in 1.0

  • Positional argumentsvtracer in.png out.svg.
  • --hierarchical cutout is now a true seam-free mosaic (a gapless tessellation with shared boundaries), replacing the old re-clustered cutout.
  • --palette / --palette-file — snap colors to a fixed palette (nearest in OKLab); --max-colors auto-quantizes the palette.
  • Binary thresholding — a tunable fixed cutoff (--threshold) or BradleyRoth adaptive thresholding (--adaptive, with --adaptive-window / --adaptive-t) for scans with uneven lighting.
  • --clustering watershed — an alternative region-forming algorithm: a hierarchical watershed on the pixel graph (Cousty et al., TPAMI 2009; Najman, Cousty & Perret, ISMM 2013), cut at --watershed-detail. Content-adaptive regions that follow object shape — pairs beautifully with cutout.

Downloads

You can download pre-built binaries from Releases.

You can also install the program from source:

cargo install vtracer-cli

You are strongly advised to not download from any other third-party sources

Usage

# simplest form
./vtracer input.jpg output.svg

# black & white line art
./vtracer input.jpg output.svg --preset bw

# scanned/photographed line art with uneven lighting
./vtracer scan.jpg output.svg --clustering bw --adaptive

# seam-free mosaic (gapless tessellation)
./vtracer input.jpg output.svg --hierarchical cutout

# watershed region forming, cut to taste
./vtracer photo.jpg output.svg --clustering watershed --watershed-detail 192

# constrain to a fixed palette
./vtracer input.jpg output.svg --palette '#1b1b1b,#e0c088,#5a7d3c,#8fb0d0'

Rust Library

You can install vtracer as a Rust library.

cargo add vtracer@1.0.0-alpha.1
use vtracer::{ColorImage, Config, FitMode, Hierarchical, Preset, Session};

// Decode with whatever you like, then hand over pixels.
let raw = image::open("in.png")?.to_rgba8();
let (width, height) = (raw.width() as usize, raw.height() as usize);
let img = ColorImage { pixels: raw.into_raw(), width, height };

// one-liner
let svg = Config::default().build()?.to_svg(&img)?;

// presets + per-field config
let mut cfg = Config::from_preset(Preset::Poster);
cfg.mode = FitMode::Polygon;
cfg.hierarchical = Hierarchical::Cutout;  // seam-free mosaic
cfg.max_colors = Some(8);
let svg = cfg.build()?.to_svg(&img)?;

Split the pipeline when you want the stages separately — segment caches, finish re-runs:

let pipeline = cfg.build()?;
let seg = pipeline.segment(&img)?;        // the expensive part
let doc = pipeline.finish(&seg)?;         // VectorDoc, ready to serialize

See docs.rs/vtracer for the full API.

Python Library

vtracer is also packaged as a Python native extension.

pip install --pre vtracer
import vtracer

# one-liners
vtracer.convert_file("in.png", "out.svg")
svg = vtracer.convert_bytes(open("in.png", "rb").read())

# rich, reusable config + presets
cfg = vtracer.Config(mode="polygon", hierarchical="cutout")
cfg.palette = ["#1b1b1b", "#e0c088", "#5a7d3c"]
svg = cfg.convert_bytes(data)
vtracer.Config.poster().convert_file("photo.jpg", "poster.svg")

# watershed region forming
ws = vtracer.Config(clustering="watershed", watershed_detail=192)
svg = ws.convert_file("photo.jpg", "photo.svg")

# binary with adaptive (BradleyRoth) thresholding
bw = vtracer.Config(clustering="bw", adaptive=True)
svg = bw.convert_file("scan.jpg", "scan.svg")

See crates/vtracer-py for the full API.

Node.js Library

@visioncortex/vtracer is available for Node as a WebAssembly build (from the nodejs package) — image decoding and vectorization both run in wasm, so there is no native dependency. Decodes PNG, JPEG, GIF, BMP, and WebP; for other formats, decode yourself and pass raw RGBA to convertPixels.

npm install @visioncortex/vtracer@1.0.0-alpha.1
const vtracer = require('@visioncortex/vtracer');

await vtracer.convertFile('in.png', 'out.svg', { mode: 'polygon' });
const svg = vtracer.convertBuffer(buffer, { preset: 'poster' });
const svg2 = vtracer.convertPixels(rgba, width, height, { clustering: 'bw' });

// binary with adaptive thresholding
const bw = vtracer.convertBuffer(buffer, { clustering: 'bw', adaptive: true });

Citations

VTracer has since been cited by a few academic papers in computer graphics / vision research. Please kindly let us know if you have cited our work:

S
Description
Raster to Vector Graphics Converter
Readme MIT 39 MiB
Languages
Rust 89.6%
JavaScript 6.3%
HTML 2%
Shell 1.4%
Python 0.7%