Skip to content

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

12 Commits
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

Kurviq

Kurviq is a fast, deterministic raster-to-SVG tracer for Node.js. It ships its engine as WebAssembly, has zero runtime dependencies, and supports Node.js 18 or newer.

Install

npm i kurviq

Use it immediately from the command line:

npx kurviq --input in.png --output out.svg

CLI reference

A preset is applied first; explicitly supplied tracing options override it.

Option Values or range Default
-i, --input <path> Input PNG, JPEG, or other supported raster path required
-o, --output <path> Output SVG path required
--preset bw, poster, photo none
--colormode color, binary, bw color
--hierarchical stacked, cutout stacked
-m, --mode pixel, polygon, spline polygon
-f, --filter_speckle integer 016 2
-p, --color_precision integer 18 6
-g, --gradient_step integer 0255 16
-c, --corner_threshold integer 0180 60
-l, --segment_length number 3.510 4
-s, --splice_threshold integer 0180 45
--path_precision unsigned integer 2
--upscale number 14 1
--max-size integer 644096 off
--sharpen [colors] bare flag or integer 164 off; bare flag uses 12
--alpha-mode auto, keep, threshold, threshold:N (N is 0255) auto; threshold cutoff 128
--remove-background [auto|#RRGGBB] bare flag, auto, or a six-digit RGB color off
--background-fuzz integer 064 10
--background-channel integer radius 08 2
--background-shadows remove, keep remove
--shadow-chroma integer 0255 8
--shadow-luma-min integer 0255 205
--shadow-luma-max integer 0255 249
--auto Classify the source and pick a recipe off
--explain Print the classification and chosen flags to stderr off
--consolidate [colors] bare flag, or integer 2256 off; bare uses perceptual ΔE 2.3
--flatten bare flag off
--snap [tolerance] bare flag, or number (0,0.2] off; bare uses 0.004
--structure off, groups, layers off
--theme off, css-vars, classes off
--animate none, reveal, draw none
--animate-duration milliseconds 1200
--animate-stagger milliseconds 40
--animate-easing linear, ease, ease-in-out ease-in-out
-h, --help Show usage
-V, --version Print the installed version

For command-line compatibility, --colormode binary resolves to color. Use --colormode bw or --preset bw for binary tracing. The Node API accepts the semantic value colormode: 'binary'.

Letting Kurviq pick

npx kurviq --input in.png --output out.svg --auto --explain

--auto traces once at defaults, classifies the result, and re-traces with a matching recipe. --explain prints the decision and the exact flags to stderr; those flags reproduce the run byte-identically, so pin them once you are happy and drop --auto from your build.

Measured over 47 test images, --auto improved 33 and left 7 unchanged, reducing mean structural distance by 37%. Seven regressed slightly — it is a heuristic, not an oracle, which is why --explain exists. --auto never enables a document pass below: those change the shape of the document, which is your intent, not a property of the image.

Document passes

These change the emitted document rather than the trace. All are opt-in; with none of them the output is byte-identical to previous versions.

Flag What it does
--consolidate [colors] Merges perceptually equal fills. A default trace of a photographic source can carry tens of thousands of near-identical colours; this is what makes the passes below meaningful.
--flatten Merges elements that share a paint. Render-identical by construction.
--snap [tolerance] Emits real <circle>, <rect> and <ellipse> where the fit is provable.
--structure groups Adds stable ids and palette classes so the output is addressable.
--structure layers Nests the engine cluster tree as <g> where paint order allows; otherwise flat ids.
--theme css-vars Emits the palette as CSS custom properties.
--animate reveal CSS-only entrance animation. Requires --structure groups.
npx kurviq -i icon.png -o icon.svg --consolidate 12 --snap --structure groups

Notes worth knowing before you use them:

  • Bare --consolidate is safe on every image tested. A hard cap such as --consolidate 12 is a deliberate posterisation: good on flat artwork, destructive on photographs.
  • --theme scopes its custom-property rules under a kurviq-theme class that Kurviq never sets. Add it to the <svg> or an ancestor to activate theming. Without it the literal fill attributes paint, so rasterisers that do not support CSS custom properties are unaffected.
  • --animate refuses above 120 animated elements, where the CSS would dwarf the geometry. Run --consolidate first, or pass --animate-force.
  • --snap leaves rings and annuli alone: splitting a shape whose hole is encoded by opposite winding would fill the hole.

Picking settings

Input Recommended settings Why
Logos, icons, and large flat shapes Defaults Fast and compact for ordinary artwork
Oversized sources and AI renders --max-size 720, then add tracing options as needed Caps tracing complexity without changing the SVG's displayed dimensions
Small text and fine detail --upscale 2 Gives the tracer more pixels while preserving the original SVG display size
Rounded flat icons --mode spline --filter_speckle 4 --path_precision 1 --segment_length 8, optionally with --upscale 2 Fits smooth contours instead of polygon facets
Soft-shaded icons Add --gradient_step 8 --color_precision 8 to the spline recipe Retains smoother shading and gradients
Blurry flat-color art Add --sharpen to the spline recipe Recovers a compact palette and snaps antialiased ramps
Icon cutouts on a uniform background --remove-background auto --background-fuzz 3 --background-channel 3 Removes border-connected background and attached neutral shadows while guarding narrow channels
Images with meaningful soft transparency Choose --alpha-mode keep, --alpha-mode threshold:N, or the default auto Controls whether partial alpha is retained or made binary

Avoid combining --sharpen with the gradient flags: sharpening deliberately flattens color ramps, while the gradient flags preserve them.

Downscaling oversized sources

npx kurviq --input render.png --output render.svg --max-size 720

--max-size caps the largest working-canvas dimension while preserving aspect ratio and rounding the other dimension to the nearest pixel. It runs immediately after decode using premultiplied-alpha Lanczos3, before alpha handling, upscaling, and background removal. Sources already within the cap take a byte-identical no-op path.

Like --upscale, downscaling changes tracing coordinates but not display size: SVG width and height remain the decoded input's original dimensions, and the viewBox describes the final working canvas. The options compose in order, so a 1024×1024 input with --max-size 720 --upscale 2 is traced at 1440×1440 but still displays at 1024×1024.

Upscaling small text and fine detail

npx kurviq --input diagram.png --output diagram.svg --upscale 2

Built-in upscaling leaves the SVG's displayed width and height at the input dimensions and enlarges its coordinate system through the viewBox. Start with 2×; higher values cost more time and usually produce larger SVGs.

Sharpening blurry flat-color art

npx kurviq --input soft-icon.png --output crisp-icon.svg \
  --upscale 2 --mode spline --filter_speckle 4 \
  --path_precision 1 --segment_length 8 --sharpen

The bare --sharpen flag uses at most 12 recovered colors. Pass another ceiling from 1 to 64 as --sharpen 6 or --sharpen=6. Sharpening is intended for flat-color artwork; do not use it for photos, continuous gradients, or intentionally soft shading.

Background removal

The bare --remove-background flag is equivalent to --remove-background auto. Auto selection uses the image border. An explicit color is useful when the border is not representative:

npx kurviq --input icon.png --output icon.svg \
  --remove-background '#f2f4f8' --background-fuzz 10

Removal is deterministic and limited to border-connected pixels. The default --background-channel 2 protects enclosed areas reached only through narrow gaps; use --background-channel 0 for an unrestricted four-connected flood. The default fuzz of 10 is suitable for distinctly colored uniform backgrounds, while the conservative icon recipe uses a fuzz of 3.

Attached neutral shadows are removed by default. Use --background-shadows keep when the shadow belongs in the artwork. The sweep can be tuned with --shadow-chroma, --shadow-luma-min, and --shadow-luma-max; the luma minimum must not exceed the maximum.

Alpha handling

The default --alpha-mode auto measures partial-alpha pixels against the decoded original's visible silhouette, before any --max-size downscale. At the 1% gate, resizing becomes alpha-aware and the result is thresholded at 128. This prevents hidden RGB values in transparent pixels from bleeding into the trace.

Use --alpha-mode threshold to force cutoff 128, --alpha-mode threshold:N to choose a cutoff from 0 to 255, or --alpha-mode keep to retain the straight-RGBA path. On auto-gated soft-alpha inputs, Kurviq intentionally improves on and diverges from, the native binary.

Node API

The CommonJS API has exactly two functions:

const fs = require('node:fs');
const { convert, convertFile } = require('kurviq');

const svg = convert(fs.readFileSync('in.png'), {
  mode: 'spline',
  maxSize: 720,
  upscale: 2,
  sharpen: true,
  alphaMode: 'auto',
  removeBackground: 'auto',
  backgroundFuzz: 3,
  backgroundChannel: 3,
});

convertFile('in.png', 'out.svg', { preset: 'poster' });

convert(imageBytes, options?) accepts a Buffer or Uint8Array and returns an SVG string. convertFile(inputPath, outputPath, options?) reads and writes files. Both functions are synchronous and CPU-bound; use a worker thread when conversion must not block the event loop.

Options use the CLI names, except camel-cased additions such as maxSize, alphaMode, removeBackground, backgroundFuzz, backgroundChannel, backgroundShadows, shadowChroma, shadowLumaMin, and shadowLumaMax. Both colormode and color_mode are accepted.

Authoring SVG (v1.3, kurviq/author)

Kurviq also builds SVG, not just traces it. Zero dependencies, deterministic, and exposed on subpaths so the root API stays two functions.

const { createScene, shapes } = require('kurviq/author');
const { linearGradient } = require('kurviq/author-paint');

const scene = createScene({ width: 200, height: 100 });
const ramp = linearGradient(scene, {
  units: 'objectBoundingBox',
  stops: [{ offset: 0, color: '#3366ff' }, { offset: 1, color: '#10b981' }],
});
scene.add(shapes.rect(0, 0, 200, 100, { fill: ramp.ref }));
scene.serialize();
Subpath Provides
kurviq/author scenes, shapes, transforms, path data, deterministic ids
kurviq/author-paint gradients, patterns, hatch, symbol/use, expandUses
kurviq/author-stroke stroke attributes, markers, arrowheads
kurviq/author-mask clipPath, mask, fade masks
kurviq/author-filter blur, drop shadow, colour matrix, morphology, creative noise
kurviq/author-interactive hover/target states, links, focus, reduced motion, safety checks
kurviq/author-animate AnimationPlan → CSS or SMIL, frame sampling

Three rules worth knowing before you start, each of which exists because guessing produced a bug:

  1. Paint-server and mask units are mandatory. The SVG default differs per element and silently changes the result.
  2. Filter regions are mandatory. The default clips blurs and shadows, and the symptom never points at the cause.
  3. Interactivity depends on the embedding. An <img>-embedded SVG receives no pointer events, so :hover there is dead markup. assertEmbedding() turns that into an error.

Full reference: docs/MANUAL.md.

Verifying output (@tecsteps/kurviq-toolbelt)

Rendering, comparison and evaluation live in a separate package, so kurviq itself stays dependency-free:

npm i @tecsteps/kurviq-toolbelt
const { evaluate } = require('@tecsteps/kurviq-toolbelt');
evaluate({ svg, referencePng });                 // optional reference
evaluate({ svg, width: 512, states: ['hover'] }); // verify interaction states

Determinism

For identical input bytes, options, Kurviq version, and platform, conversion is deterministic: repeated runs produce byte-identical SVG output. Default settings retain byte parity with the corresponding native engine pipeline.

Limitations

  • Conversion is synchronous, single-threaded, and CPU-bound.
  • Kurviq traces pixels; it does not perform OCR or preserve a selectable text layer.
  • This release targets Node.js and does not yet include a browser build.

License

Kurviq is available under the MIT License. See LICENSE for the license text and required third-party notices.

Acknowledgements

Kurviq is a port of vtracer: its WebAssembly engine is built on the vtracer and visioncortex vector-tracing libraries (MIT), compiled to wasm with additional capabilities on top (input downscaling, upscaling, background removal, soft-alpha handling, sharpening).

Author

Fabian Wesner · fabian.wesner@tecsteps.com · LinkedIn

About

No description, website, or topics provided.

Resources

Stars

Watchers

Forks

Releases

Packages

Contributors

Languages