@@ -8,6 +8,18 @@ Node.js library to convert raster images to svg using [VTracer](https://github.c
88npx @neplex/vectorizer ./raster.png ./vector.svg
99```
1010
11+ Optimize while vectorizing:
12+
13+ ``` bash
14+ npx @neplex/vectorizer ./raster.png ./vector.svg --optimize --multipass --multipass-iterations 5
15+ ```
16+
17+ Optimize an existing SVG:
18+
19+ ``` bash
20+ npx @neplex/vectorizer optimize ./vector.svg ./vector.optimized.svg --plugin preset-default
21+ ```
22+
1123Use ` --help ` to see all available options.
1224
1325## Installation
@@ -42,112 +54,170 @@ console.log(svg); // <svg>...</svg>
4254await writeFile (' ./vector.svg' , svg);
4355```
4456
45- If you want to use a synchronous API, you can use ` vectorizeSync ` instead.
57+ The generated SVG string is generally quite large, so it's recommended to optimize it before use. See the [ SVG Optimization section ] ( #svg-optimization ) for more details. If you want to use a synchronous API, you can use ` vectorizeSync ` instead. See the [ API section ] ( #api ) for more details on available functions and options .
4658
4759## Benchmark
4860
4961``` js
5062yarn bench
51- clk: ~ 5.30 GHz
63+ clk: ~ 5.32 GHz
5264cpu: Intel (R ) Core (TM ) i7- 14700K
5365runtime: node 24.14 .1 (x64- win32)
5466
55- benchmark avg (min … max) p75 / p99 (min … top 1 % )
56- ---------------------------------------------- - ------------------------------ -
57- @neplex/ vectorizer raw sync 532.14 µs/ iter 533.70 µs ▂▇█▃▃
58- ( 524.20 µs … 590.90 µs) 550 .90 µs ▂██ ███
59- ( 1.65 kb … 168.67 kb) 1.94 kb ▂▄ ███████▇▅▃▄▂▁▁▂▁▁▁ ▁
67+ benchmark avg (min … max) p75 / p99 (min … top 1 % )
68+ -------------------------------------------------- ------------------------------ -
69+ @neplex/ vectorizer raw sync 529.82 µs/ iter 533.70 µs ▅█▅
70+ ( 517.00 µs … 607.00 µs) 552 .90 µs ▅██▆▇ ████▄
71+ ( 1.65 kb … 168.67 kb) 1.94 kb ▃ ███████████▆▄▃▂▁▂▂▂ ▁
6072
61- @neplex/ vectorizer encoded sync 552.92 µs/ iter 554 .70 µs █▄▂
62- ( 544.90 µs … 613.00 µs) 569.60 µs ▅ ████▆▃
63- ( 1.60 kb … 74.57 kb) 1.70 kb ▂▄ ████████▇▅▃▃▂▂▁▁▁ ▁▁
73+ @neplex/ vectorizer encoded sync 549.19 µs/ iter 552 .70 µs ▃█▆▇
74+ ( 534.80 µs … 597.10 µs) 573.50 µs ▃▆▆ ████▆▄
75+ ( 1.60 kb … 74.57 kb) 1.70 kb ▂▆ ██████████▆▄▂▂▂▂▂ ▁▁
6476
65- @neplex/ vectorizer encoded async 617.69 µs/ iter 650.90 µs █▆
66- ( 572.30 µs … 785 .10 µs) 728.20 µs ██▄▂
67- ( 2.27 kb … 76.16 kb) 2.35 kb ▅████▆▅▄▄▅▄▅██▅▃▂▁▂▁▂
77+ @neplex/ vectorizer encoded async 585.94 µs/ iter 586.00 µs ▇█
78+ ( 561.70 µs … 745 .10 µs) 691.10 µs ██
79+ ( 2.27 kb … 76.16 kb) 2.34 kb ▂ ▅███▅▃▂▂▁▁▁▁▁▁▂▁▁▁▁▁
6880
69- @neplex/ vectorizer raw async 583.75 µs/ iter 593.00 µs █▃
70- ( 551.50 µs … 721.00 µs) 677.90 µs ████▄
71- ( 2.33 kb … 76.23 kb) 2.40 kb ▅ █████▇ ▆▆▅▃▂▂▂▅▄▃▂▁▁▁
81+ @neplex/ vectorizer encoded callback 684.02 µs/ iter 698.80 µs ▃▅▃▃█▂
82+ ( 632.30 µs … 844.90 µs) 770.60 µs ▂▅ ██████▇▃
83+ ( 4.27 kb … 44.40 kb) 4.48 kb ▂ ████████████▇▅ ▆▆▅▃▃▂
7284
73- @neplex/ vectorizer raw callback 601.52 µs/ iter 604.30 µs █
74- ( 561.30 µs … 1.18 ms) 1.11 ms ▇█
75- ( 5.02 kb … 217.06 kb) 5.48 kb ███▄▂▁▁▁▁▁▁▁▁▁▁▁ ▁▁▁▁▁
85+ @neplex/ vectorizer raw async 565.45 µs/ iter 565.20 µs ▂ █
86+ ( 548.70 µs … 706.00 µs) 646.00 µs ██▃
87+ ( 2.33 kb … 186.42 kb) 2.58 kb ▂ ███▅▃▃▂▂▁▂▁▂▁▁▂ ▁▁▁▁▁
7688
77- imagetracerjs 2.37 ms / iter 2.36 ms █
78- ( 2.32 ms … 2.92 ms) 2.65 ms █▇
79- ( 1.17 mb … 6.89 mb ) 4.52 mb ▆ ███▄▂▂▁▁▁▂▂▂▂▂▁▁▁▁▁▁
89+ @neplex / vectorizer raw callback 625.91 µs / iter 636.20 µs ▃█▇
90+ ( 591.60 µs … 779.20 µs) 704.80 µs ██ █▇
91+ ( 3.98 kb … 374.13 kb ) 4.62 kb ▂ ████████▄▅▆▅▆▄▄▃▁▂▁▂
8092
81- potrace trace 3.83 ms/ iter 4.72 ms ▃█ ▅▇
82- (1.82 ms … 7.31 ms) 6.73 ms ▆ ██ ██▂ ▇█
83- (175.78 kb … 12.23 mb) 2.34 mb ▄▇████▇▄███▄██▇▅▄▇▂▁▂
93+ imagetracerjs 2.41 ms/ iter 2.42 ms █▇▂
94+ (2.33 ms … 2.93 ms) 2.73 ms ████▂
95+ ( 1.14 mb … 6.92 mb) 4.52 mb ▂█████▃▃▃▃▂▂▂▂▁▂▁▁▁▁▁
96+
97+ potrace trace 3.59 ms/ iter 4.45 ms █▅
98+ (1.70 ms … 7.43 ms) 6.36 ms ▆▃█▂██▃ ▃▅▂ ▄
99+ (736.00 b … 11.91 mb) 2.27 mb ▃██████████████▇▃█▁▆▃
84100
85101summary
86102 @neplex/ vectorizer raw sync
87103 1 .04x faster than @neplex/ vectorizer encoded sync
88- 1 .1x faster than @neplex/ vectorizer raw async
89- 1 .13x faster than @neplex/ vectorizer raw callback
90- 1 .16x faster than @neplex/ vectorizer encoded async
91- 4 .45x faster than imagetracerjs
92- 7 .19x faster than potrace trace
104+ 1 .07x faster than @neplex/ vectorizer raw async
105+ 1 .11x faster than @neplex/ vectorizer encoded async
106+ 1 .18x faster than @neplex/ vectorizer raw callback
107+ 1 .29x faster than @neplex/ vectorizer encoded callback
108+ 4 .54x faster than imagetracerjs
109+ 6 .78x faster than potrace trace
93110```
94111
95112See [ benchmark] ( https://github.com/neplextech/vectorizer/blob/main/benchmark/bench.ts ) for more details.
96113
97114## API
98115
99- ### ` vectorize(data: Buffer, config?: Config | Preset): Promise<string> `
116+ ### ` vectorize(data: Buffer, config?: Config | Preset, signal?: AbortSignal ): Promise<string> `
100117
101118Takes an image buffer and returns a promise that resolves to an SVG string.
102119
103120### ` vectorizeSync(data: Buffer, config?: Config | Preset): string `
104121
105122Takes an image buffer and returns an SVG string synchronously.
106123
107- ### ` vectorizeRaw(data: Buffer, args: RawDataConfig, config?: Config | Preset): Promise<string> `
124+ ### ` vectorizeRaw(data: Buffer, args: RawDataConfig, config?: Config | Preset, signal?: AbortSignal ): Promise<string> `
108125
109126Takes a raw pixel data buffer and returns a promise that resolves to an SVG string.
110127
111128### ` vectorizeRawSync(data: Buffer, args: RawDataConfig, config?: Config | Preset): string `
112129
113130Takes a raw pixel data buffer and returns an SVG string synchronously.
114131
115- ### ` vectorizeToCallback(data: Buffer, config: Config | Preset | null | undefined, callback: ([chunk, progress]) => void): Promise<void> `
132+ ### ` vectorizeToCallback(data: Buffer, config: Config | Preset | null | undefined, callback: (chunk: string, progress: number) => void): void `
133+
134+ 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.
135+
136+ ### ` vectorizeRawToCallback(data: Buffer, args: RawDataConfig, config: Config | Preset | null | undefined, callback: (chunk: string, progress: number) => void): void `
137+
138+ Vectorizes raw pixel data and emits SVG chunks to the callback. ` progress ` is a number from 0 to 100.
139+
140+ ### ` readImage(data: Buffer, args?: RawDataConfig, signal?: AbortSignal): Promise<ImageData> `
141+
142+ Decodes an encoded image buffer, or a raw RGBA buffer when ` args ` is provided, and resolves image width, height, and pixels.
143+
144+ ### ` readImageSync(data: Buffer, args?: RawDataConfig): ImageData `
116145
117- Vectorizes an image buffer and emits SVG chunks to the callback. ` progress ` is a number from 0–100 indicating how much of the vectorization has been written .
146+ Decodes an encoded image buffer, or a raw RGBA buffer when ` args ` is provided, synchronously .
118147
119- ### ` vectorizeRawToCallback(data: Buffer, args: RawDataConfig, config: Config | Preset | null | undefined, callback: ([chunk, progress]) => void) : Promise<void >`
148+ ### ` colorExistsInImage(image: ImageData, color: Color, signal?: AbortSignal) : Promise<boolean >`
120149
121- Vectorizes raw pixel data and emits SVG chunks to the callback. ` progress ` is a number from 0–100 .
150+ Checks asynchronously whether a decoded image contains the given RGB color .
122151
123- ### ` isEOF(data: [string, number]): boolean `
152+ ### ` colorExistsInImageSync(image: ImageData, color: Color): boolean `
153+
154+ Checks synchronously whether a decoded image contains the given RGB color.
155+
156+ ### ` findUnusedColorInImage(image: ImageData, options?: InternalOptions, signal?: AbortSignal): Promise<Color> `
157+
158+ Finds asynchronously a color that does not exist in decoded image data.
159+
160+ ### ` findUnusedColorInImageSync(image: ImageData, options?: InternalOptions): Color `
161+
162+ Finds synchronously a color that does not exist in decoded image data.
163+
164+ ### ` optimize(svg: string, options?: OptimizeOptions, signal?: AbortSignal): Promise<string> `
165+
166+ Optimizes an SVG string asynchronously. Supports the same options as ` optimizeSync ` .
167+
168+ ### ` optimizeSync(svg: string, options?: OptimizeOptions): string `
169+
170+ Optimizes an SVG string synchronously.
171+
172+ ` OptimizeOptions ` supports:
173+
174+ - ` preset?: OptimizePreset ` : ` Default ` , ` Safe ` , or ` None ` .
175+ - ` plugins?: Array<string | { name: string, params?: unknown }> ` : SVGO ` Config["plugins"] ` compatible job config. When provided, this defines the optimizer job set.
176+ - ` omit?: string[] ` : optimizer job names to skip.
177+ - ` multipass?: boolean ` : run optimization until output stops changing or the iteration limit is reached.
178+ - ` multipassIterations?: number ` : maximum multipass iterations. Defaults to ` 10 ` .
179+
180+ ### ` isEOF(chunk: string, progress: number): boolean `
124181
125182Determines 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 ` .
126183
127184``` js
128- await vectorizeToCallback (src, config, (chunk ) => {
129- if (isEOF (chunk)) {
130- console .log (' SVG output complete!' );
185+ const { promise , resolve } = Promise .withResolvers < void > ();
186+
187+ vectorizeToCallback (src, config, (chunk , progress ) => {
188+ if (isEOF (chunk, progress)) {
189+ resolve ();
131190 }
132191});
192+
193+ await promise; // block until all the SVG chunks have been received
194+ console .log (' SVG output complete!' );
133195```
134196
135197## SVG Optimization
136198
137- The raw output from ` vectorize ` can be significantly reduced in size using optimizers like [ ` @oxvg/napi ` ] ( https://www.npmjs. com/package/@ oxvg/napi ) :
199+ The raw output from ` vectorize ` can be significantly reduced in size using the built-in optimizer powered by [ oxvg_optimiser ] ( https://github. com/noahbald/ oxvg ) :
138200
139201``` js
140- import { vectorize } from ' @neplex/vectorizer' ;
141- import { optimise } from ' @oxvg/napi' ;
202+ import { optimize , vectorize } from ' @neplex/vectorizer' ;
142203import { readFile , writeFile } from ' node:fs/promises' ;
143204
144205const src = await readFile (' ./input.png' );
145206const svg = await vectorize (src);
146- const optimized = optimise (svg);
207+ const optimized = await optimize (
208+ svg,
209+ /* optional config */ {
210+ plugins: [' preset-default' , { name: ' removeTitle' }], // supports svgo plugin config
211+ multipass: true , // enable multipass optimization
212+ multipassIterations: 5 , // limit multipass iterations to 5
213+ },
214+ );
147215
148216await writeFile (' ./output.svg' , optimized);
149217```
150218
219+ > Note that the optimizer included in this library is a thin wrapper around ` oxvg_optimiser ` and does not intend to provide all the features of ` oxvg ` . For advanced needs, consider using ` @oxvg/napi ` directly.
220+
151221## Demo
152222
153223Generated under the following configuration:
0 commit comments