Node.js library to convert raster images to svg using VTracer, with time complexity of O(n).
Tip
Browser example: See twlite/vectorizer-playground for a complete browser example. Due to current WASI limitations in browsers, the async APIs may not work. The synchronous APIs work correctly when executed inside a Web Worker, which is covered by the example.
npx @neplex/vectorizer ./raster.png ./vector.svgOptimize while vectorizing:
npx @neplex/vectorizer ./raster.png ./vector.svg --optimize --multipass --multipass-iterations 5Optimize an existing SVG:
npx @neplex/vectorizer optimize ./vector.svg ./vector.optimized.svg --plugin preset-defaultUse --help to see all available options.
npm install @neplex/vectorizerimport { vectorize, ColorMode, Hierarchical, PathSimplifyMode } from '@neplex/vectorizer';
import { readFile, writeFile } from 'node:fs/promises';
const src = await readFile('./raster.png');
const svg = await vectorize(src, {
colorMode: ColorMode.Color,
colorPrecision: 6,
filterSpeckle: 4,
spliceThreshold: 45,
cornerThreshold: 60,
hierarchical: Hierarchical.Stacked,
mode: PathSimplifyMode.Spline,
layerDifference: 5,
lengthThreshold: 5,
maxIterations: 2,
pathPrecision: 5,
});
console.log(svg); // <svg>...</svg>
await writeFile('./vector.svg', svg);The generated SVG string is generally quite large, so it's recommended to optimize it before use. See the SVG Optimization section for more details. If you want to use a synchronous API, you can use vectorizeSync instead. See the API section for more details on available functions and options.
yarn bench
clk: ~5.32 GHz
cpu: Intel(R) Core(TM) i7-14700K
runtime: node 24.14.1 (x64-win32)
benchmark avg (min β¦ max) p75 / p99 (min β¦ top 1%)
-------------------------------------------------- -------------------------------
@neplex/vectorizer raw sync 529.82 Β΅s/iter 533.70 Β΅s β
ββ
(517.00 Β΅s β¦ 607.00 Β΅s) 552.90 Β΅s β
βββββββββ
( 1.65 kb β¦ 168.67 kb) 1.94 kb βββββββββββββββββββββ
@neplex/vectorizer encoded sync 549.19 Β΅s/iter 552.70 Β΅s ββββ
(534.80 Β΅s β¦ 597.10 Β΅s) 573.50 Β΅s βββββββββ
( 1.60 kb β¦ 74.57 kb) 1.70 kb βββββββββββββββββββββ
@neplex/vectorizer encoded async 585.94 Β΅s/iter 586.00 Β΅s ββ
(561.70 Β΅s β¦ 745.10 Β΅s) 691.10 Β΅s ββ
( 2.27 kb β¦ 76.16 kb) 2.34 kb ββ
ββββ
βββββββββββββββ
@neplex/vectorizer encoded callback 684.02 Β΅s/iter 698.80 Β΅s ββ
ββββ
(632.30 Β΅s β¦ 844.90 Β΅s) 770.60 Β΅s ββ
ββββββββ
( 4.27 kb β¦ 44.40 kb) 4.48 kb βββββββββββββββ
βββ
βββ
@neplex/vectorizer raw async 565.45 Β΅s/iter 565.20 Β΅s ββ
(548.70 Β΅s β¦ 706.00 Β΅s) 646.00 Β΅s βββ
( 2.33 kb β¦ 186.42 kb) 2.58 kb βββββ
ββββββββββββββββ
@neplex/vectorizer raw callback 625.91 Β΅s/iter 636.20 Β΅s βββ
(591.60 Β΅s β¦ 779.20 Β΅s) 704.80 Β΅s ββββ
( 3.98 kb β¦ 374.13 kb) 4.62 kb βββββββββββ
ββ
ββββββββ
imagetracerjs 2.41 ms/iter 2.42 ms βββ
(2.33 ms β¦ 2.93 ms) 2.73 ms βββββ
( 1.14 mb β¦ 6.92 mb) 4.52 mb βββββββββββββββββββββ
potrace trace 3.59 ms/iter 4.45 ms ββ
(1.70 ms β¦ 7.43 ms) 6.36 ms βββββββ ββ
β β
(736.00 b β¦ 11.91 mb) 2.27 mb βββββββββββββββββββββ
summary
@neplex/vectorizer raw sync
1.04x faster than @neplex/vectorizer encoded sync
1.07x faster than @neplex/vectorizer raw async
1.11x faster than @neplex/vectorizer encoded async
1.18x faster than @neplex/vectorizer raw callback
1.29x faster than @neplex/vectorizer encoded callback
4.54x faster than imagetracerjs
6.78x faster than potrace traceSee benchmark for more details.
Takes an image buffer and returns a promise that resolves to an SVG string.
Takes an image buffer and returns an SVG string synchronously.
vectorizeRaw(data: Buffer, args: RawDataConfig, config?: Config | Preset, signal?: AbortSignal): Promise<string>
Takes a raw pixel data buffer and returns a promise that resolves to an SVG string.
Takes a raw pixel data buffer and returns an SVG string synchronously.
vectorizeToCallback(data: Buffer, config: Config | Preset | null | undefined, callback: (chunk: string, progress: number) => void): void
Vectorizes an image buffer and emits SVG chunks to the callback. progress is a number from 0 to 100 indicating how much of the vectorization has been written.
vectorizeRawToCallback(data: Buffer, args: RawDataConfig, config: Config | Preset | null | undefined, callback: (chunk: string, progress: number) => void): void
Vectorizes raw pixel data and emits SVG chunks to the callback. progress is a number from 0 to 100.
Decodes an encoded image buffer, or a raw RGBA buffer when args is provided, and resolves image width, height, and pixels.
Decodes an encoded image buffer, or a raw RGBA buffer when args is provided, synchronously.
Checks asynchronously whether a decoded image contains the given RGB color.
Checks synchronously whether a decoded image contains the given RGB color.
findUnusedColorInImage(image: ImageData, options?: InternalOptions, signal?: AbortSignal): Promise<Color>
Finds asynchronously a color that does not exist in decoded image data.
Finds synchronously a color that does not exist in decoded image data.
Optimizes an SVG string asynchronously. Supports the same options as optimizeSync.
Optimizes an SVG string synchronously.
OptimizeOptions supports:
preset?: OptimizePreset:Default,Safe, orNone.plugins?: Array<string | { name: string, params?: unknown }>: SVGOConfig["plugins"]compatible job config. When provided, this defines the optimizer job set.omit?: string[]: optimizer job names to skip.multipass?: boolean: run optimization until output stops changing or the iteration limit is reached.multipassIterations?: number: maximum multipass iterations. Defaults to10.
Determines if the given chunk data indicates the end of the SVG output. Useful for callback-based vectorization to know when the final chunk has been received. Note that EOF here refers to the final chunk of svg, which is </svg>\n.
const { promise, resolve } = Promise.withResolvers<void>();
vectorizeToCallback(src, config, (chunk, progress) => {
if (isEOF(chunk, progress)) {
resolve();
}
});
await promise; // block until all the SVG chunks have been received
console.log('SVG output complete!');The raw output from vectorize can be significantly reduced in size using the built-in optimizer powered by oxvg_optimiser:
import { optimize, vectorize } from '@neplex/vectorizer';
import { readFile, writeFile } from 'node:fs/promises';
const src = await readFile('./input.png');
const svg = await vectorize(src);
const optimized = await optimize(
svg,
/* optional config */ {
plugins: ['preset-default', { name: 'removeTitle' }], // supports svgo plugin config
multipass: true, // enable multipass optimization
multipassIterations: 5, // limit multipass iterations to 5
},
);
await writeFile('./output.svg', optimized);Note that the optimizer included in this library is a thin wrapper around
oxvg_optimiserand does not intend to provide all the features ofoxvg. For advanced needs, consider using@oxvg/napidirectly.
Generated under the following configuration:
{
colorMode: ColorMode.Color,
colorPrecision: 8,
filterSpeckle: 4,
spliceThreshold: 45,
cornerThreshold: 60,
hierarchical: Hierarchical.Stacked,
mode: PathSimplifyMode.Spline,
layerDifference: 6,
lengthThreshold: 4,
maxIterations: 2
}| Raster Image (PNG Input) | Vector Image (Generated SVG) |
|---|---|
![]() CC-BY-SA 3.0 by Niabot |
CC-BY-SA 3.0 by Niabot |
