33How [ ImageScript] ( https://github.com/matmen/ImageScript ) compares to other image libraries for Node.js: speed, file
44sizes and what installing each takes. ImageScript is plain JavaScript with no dependencies, so it runs wherever
55JavaScript does, like Jimp, image-js and PureJsImage; sharp and @napi-rs/canvas wrap native libraries (libvips, Skia),
6- Photon and ImageScript 1 are WebAssembly.
6+ Photon and ImageScript 1 are WebAssembly, and so is jsquash (Squoosh's libavif and libjxl, for AVIF and JPEG XL) .
77
88<img src =" charts/install.svg " alt =" Install size of each library " >
99
1010<img src =" charts/codecs.svg " alt =" Decoding and encoding speed " >
1111
12+ <img src =" charts/modern.svg " alt =" AVIF and JPEG XL decoding and encoding speed " >
13+
1214<img src =" charts/operations.svg " alt =" Speed of image operations " >
1315
1416<img src =" charts/rendering.svg " alt =" Text and SVG rendering speed " >
@@ -19,51 +21,65 @@ Photon and ImageScript 1 are WebAssembly.
1921
2022## Numbers
2123
22- Operations per second (more is faster), measured 2026-10-29 with Node.js v24.22.1 on AMD EPYC 7763 64-Core Processor, ImageScript 3.9.0.
23-
24- | | ** ImageScript 3** | ** ImageScript 1** | ** Jimp** | ** image-js** | ** PureJsImage** | ** Photon** | ** sharp** | ** @napi-rs/canvas ** |
25- | ---| --:| --:| --:| --:| --:| --:| --:| --:|
26- | decode JPEG | ** 14.0** | 6.34 | 6.27 | 6.00 | 7.97 | 21.3 | 64.7 | 47.4 |
27- | decode PNG | ** 36.6** | 24.5 | 15.3 | 8.21 | 12.1 | 40.5 | 50.5 | 53.0 |
28- | decode WebP (lossy) | ** 11.1** | – | – | – | 9.76 | 11.1 | 33.4 | 57.7 |
29- | encode JPEG (quality 80) | ** 14.6** | 19.0 | 2.39 | 9.47 | 7.15 | 8.84 | 55.5 | 34.4 |
30- | encode PNG | ** 7.82** | 3.76 | 4.10 | 3.06 | 1.83 | 9.68 | 7.27 | 2.13 |
31- | encode WebP (lossless) | ** 0.82** | – | – | – | 0.36 | 23.7 | 0.63 | 1.97 |
32- | resize to half | ** 67.6** | 277 | 59.7 | 11.0 | 16.7 | 15.1 | 57.3 | 306 |
33- | rotate by 30° | ** 25.5** | 5.16 | 1.63 | 1.36 | 1.97 | 4.64 | 13.8 | 36.7 |
34- | blur (radius 5) | ** 13.8** | – | 0.01 | 1.22 | – | 6.50 | 29.0 | 58.5 |
35- | composite an overlay | ** 420** | 444 | 30.6 | 115 | – | 158 | 102 | 378 |
36- | crop | ** 675** | 519 | 46.6 | 64.5 | 97.0 | 186 | 1,041 | 1,045 |
37- | clone | ** 1,019** | 786 | 844 | 638 | 1,110 | 333 | 965 | 430 |
38- | render text | ** 1,943** | 56.6 | – | – | – | – | 2,021 | 2,091 |
39- | render SVG | ** 5.54** | fails | – | – | – | – | 17.3 | 84.9 |
40- | decode animated GIF | ** 16.1** | 7.67 | – | – | – | – | 12.7 | – |
41- | encode animated GIF | ** 4.73** | 1.14 | – | – | – | – | 0.87 | – |
24+ Operations per second (more is faster), measured 2026-10-30 with Node.js v24.22.1 on AMD EPYC 9V45 96-Core Processor, ImageScript 3.10.0.
25+
26+ | | ** ImageScript 3** | ** ImageScript 1** | ** Jimp** | ** image-js** | ** PureJsImage** | ** Photon** | ** sharp** | ** jsquash** | ** @napi-rs/canvas ** |
27+ | ---| --:| --:| --:| --:| --:| --:| --:| --:| --:|
28+ | decode JPEG | ** 20.1** | 10.3 | 10.2 | 8.47 | 11.9 | 31.4 | 101 | – | 76.6 |
29+ | decode PNG | ** 42.9** | 33.6 | 27.8 | 13.4 | 18.9 | 56.8 | 73.9 | – | 72.4 |
30+ | decode WebP (lossy) | ** 17.5** | – | – | – | 15.0 | 15.7 | 52.3 | – | 92.5 |
31+ | encode JPEG (quality 80) | ** 23.3** | 28.9 | 4.09 | 13.8 | 10.3 | 14.8 | 82.7 | – | 57.6 |
32+ | encode PNG | ** 10.9** | 6.64 | 6.41 | 4.17 | 2.54 | 14.8 | 9.79 | – | 2.79 |
33+ | encode WebP (lossless) | ** 1.34** | – | – | – | 0.57 | 35.0 | 1.01 | – | 3.00 |
34+ | decode AVIF | ** 6.61** | – | – | – | 0.92 | – | 16.1 | 7.04 | 25.9 |
35+ | decode JPEG XL | ** 3.94** | – | – | – | fails | – | – | 4.22 | 23.4 |
36+ | encode AVIF (quality 60) | ** 0.34** | – | – | – | 1.13 | – | 0.75 | 0.64 | – |
37+ | encode JPEG XL (quality 90) | ** 0.66** | – | – | – | – | – | – | 0.38 | – |
38+ | resize to half | ** 126** | 507 | 110 | 17.0 | 25.3 | 29.4 | 183 | – | 406 |
39+ | rotate by 30° | ** 35.4** | 10.4 | 2.75 | 2.02 | 3.10 | 7.55 | 38.6 | – | 47.0 |
40+ | blur (radius 5) | ** 22.9** | – | 0.02 | 2.38 | – | 10.9 | 106 | – | 95.5 |
41+ | composite an overlay | ** 723** | 980 | 53.9 | 203 | – | 270 | 282 | – | 791 |
42+ | crop | ** 1,088** | 1,091 | 83.0 | 102 | 149 | 335 | 1,681 | – | 2,312 |
43+ | clone | ** 1,488** | 1,320 | 960 | 1,269 | 1,510 | 660 | 1,723 | – | 876 |
44+ | render text | ** 4,115** | 84.5 | – | – | – | – | 2,935 | – | 5,048 |
45+ | render SVG | ** 10.6** | fails | – | – | – | – | 42.9 | – | 161 |
46+ | decode animated GIF | ** 25.8** | 12.7 | – | – | – | – | 21.6 | – | – |
47+ | encode animated GIF | ** 7.14** | 1.96 | – | – | – | – | 1.38 | – | – |
4248
4349Encoded sizes:
4450
45- | | ** ImageScript 3** | ** ImageScript 1** | ** Jimp** | ** image-js** | ** PureJsImage** | ** Photon** | ** sharp** | ** @napi-rs/canvas ** |
46- | ---| --:| --:| --:| --:| --:| --:| --:| --:|
47- | photo PNG | 2.2 MB | 3.0 MB | 2.7 MB | 2.8 MB | 2.1 MB | 2.8 MB | 2.8 MB | 2.1 MB |
48- | photo JPEG | 217 kB | 257 kB | 260 kB | 260 kB | 222 kB | 257 kB | 218 kB | 218 kB |
49- | photo WebP | 1.3 MB | – | – | – | 2.1 MB | 1.7 MB | 1.2 MB | 1.4 MB |
50- | graphic PNG | 891 kB | 802 kB | 2.0 MB | 736 kB | 811 kB | 2.2 MB | 718 kB | 1.0 MB |
51- | graphic JPEG | 275 kB | 314 kB | 315 kB | 315 kB | 286 kB | 314 kB | 275 kB | 276 kB |
52- | graphic WebP | 557 kB | – | – | – | 758 kB | 856 kB | 239 kB | 631 kB |
53- | animation GIF | 825 kB | 1.3 MB | – | – | – | – | 720 kB | – |
54-
55- | | ** ImageScript 3** | ** ImageScript 1** | ** Jimp** | ** image-js** | ** PureJsImage** | ** Photon** | ** sharp** | ** @napi-rs/canvas ** |
56- | ---| ---| ---| ---| ---| ---| ---| ---| ---|
57- | kind | JavaScript | WebAssembly | JavaScript | JavaScript | JavaScript | WebAssembly | native (libvips) | native (Skia) |
58- | package | ` imagescript@3.9.0 ` | ` imagescript@1.3.1 ` | ` jimp@1.6.1 ` | ` image-js@1.7.1 ` | ` purejsimage@0.17.0 ` | ` @silvia-odwyer/photon-node@0.3.4 ` | ` sharp@0.35.5 ` | ` @napi-rs/canvas@1.0.11 ` |
59- | install size | 2.3 MB | 6.7 MB | 26.8 MB | 47.8 MB | 6.7 MB | 2.3 MB | 20.3 MB | 35.0 MB |
60- | packages | 1 | 1 | 64 | 55 | 1 | 1 | 6 | 2 |
51+ | | ** ImageScript 3** | ** ImageScript 1** | ** Jimp** | ** image-js** | ** PureJsImage** | ** Photon** | ** sharp** | ** jsquash** | ** @napi-rs/canvas ** |
52+ | ---| --:| --:| --:| --:| --:| --:| --:| --:| --:|
53+ | photo PNG | 2.2 MB | 3.0 MB | 2.7 MB | 2.8 MB | 2.1 MB | 2.8 MB | 2.8 MB | – | 2.1 MB |
54+ | photo JPEG | 217 kB | 257 kB | 260 kB | 260 kB | 222 kB | 257 kB | 218 kB | – | 218 kB |
55+ | photo WebP | 1.3 MB | – | – | – | 2.1 MB | 1.7 MB | 1.2 MB | – | 1.4 MB |
56+ | graphic PNG | 891 kB | 802 kB | 2.0 MB | 736 kB | 811 kB | 2.2 MB | 718 kB | – | 1.0 MB |
57+ | graphic JPEG | 275 kB | 314 kB | 315 kB | 315 kB | 286 kB | 314 kB | 275 kB | – | 276 kB |
58+ | graphic WebP | 557 kB | – | – | – | 758 kB | 856 kB | 239 kB | – | 631 kB |
59+ | animation GIF | 825 kB | 1.3 MB | – | – | – | – | 720 kB | – | – |
60+ | photo AVIF | 93 kB, 40.9 dB | – | – | – | 992 kB, 32.7 dB | – | 91 kB, 40.6 dB | 80 kB, 40.2 dB | – |
61+ | photo JPEG XL | 245 kB, 44.5 dB | – | – | – | – | – | – | 230 kB, 44.1 dB | – |
62+ | graphic AVIF | 147 kB, 41 dB | – | – | – | 709 kB, 38.5 dB | – | 114 kB, 38.7 dB | 136 kB, 40.5 dB | – |
63+ | graphic JPEG XL | 230 kB, 43.4 dB | – | – | – | – | – | – | 194 kB, 41.9 dB | – |
64+
65+ | | ** ImageScript 3** | ** ImageScript 1** | ** Jimp** | ** image-js** | ** PureJsImage** | ** Photon** | ** sharp** | ** jsquash** | ** @napi-rs/canvas ** |
66+ | ---| ---| ---| ---| ---| ---| ---| ---| ---| ---|
67+ | kind | JavaScript | WebAssembly | JavaScript | JavaScript | JavaScript | WebAssembly | native (libvips) | WebAssembly | native (Skia) |
68+ | package | ` imagescript@3.10.0 ` | ` imagescript@1.3.1 ` | ` jimp@1.6.1 ` | ` image-js@1.7.1 ` | ` purejsimage@0.17.0 ` | ` @silvia-odwyer/photon-node@0.3.4 ` | ` sharp@0.35.5 ` | ` @jsquash/avif@2.1.1 @jsquash/jxl@1.3.0 ` | ` @napi-rs/canvas@1.0.11 ` |
69+ | install size | 2.3 MB | 6.7 MB | 26.8 MB | 47.8 MB | 6.7 MB | 2.3 MB | 20.3 MB | 14.2 MB | 35.0 MB |
70+ | packages | 1 | 1 | 64 | 55 | 1 | 1 | 6 | 3 | 2 |
6171
6272## Method
6373
6474- Inputs (in ` inputs/ ` , sources in [ inputs/SOURCES.md] ( inputs/SOURCES.md ) ): a 1920×1080 photo (JPEG, and made from it
65- when the benchmark starts, PNG and lossy WebP), a 1434×1434 graphic with transparency (PNG), a 445×445 diagram with
66- transparency placed on the photo (PNG), a 300×300 GIF with 101 frames.
75+ when the benchmark starts, PNG, lossy WebP, AVIF by sharp (libaom) and JPEG XL by jsquash (libjxl, effort 7), both
76+ at the qualities below), a 1434×1434 graphic with transparency (PNG), a 445×445 diagram with transparency placed on
77+ the photo (PNG), a 300×300 GIF with 101 frames.
78+ - AVIF and JPEG XL are encoded at the same nominal quality (60 and 90); as the libraries' scales differ,
79+ the file sizes come with their PSNR (of the color over black, decoded by libavif and libjxl). PureJsImage's AVIF
80+ encoder has no quality option, and its JPEG XL decoder doesn't support everything libjxl's files use, which shows as
81+ failing. jsquash only encodes and decodes AVIF and JPEG XL (it gets the other inputs as pixels); @napi-rs/canvas
82+ decodes both with Skia's codecs, but doesn't encode them.
6783- Rendering: "The quick brown fox jumps over the lazy dog, 0123456789" in Source Sans 3 at 64px, and ` tests/svgs/features.svg ` (gradients, clipping,
6884 CSS) at 1920 pixels wide. Jimp, image-js, PureJsImage and Photon can't render TrueType/OpenType fonts or SVGs; ImageScript 1 crashes
6985 on this SVG; @napi-rs/canvas (Skia's SVG module) ignores its CSS, so it draws a little less.
@@ -74,6 +90,9 @@ Encoded sizes:
7490- Where the library lets you choose, resizing is bilinear (ImageScript 1 only has nearest neighbor), blurring is
7591 gaussian with a radius or sigma of 5, rotating grows the image to fit. Each library uses its own defaults
7692 otherwise, e.g. its PNG compression level.
93+ - Threads: ImageScript uses worker threads where they pay off (the processors available, at most 8), and sharp gets
94+ as many (` sharp.concurrency() ` ; its default on glibc Linux without jemalloc, like on CI, is one). The other
95+ libraries run on one thread (jsquash turns its threaded builds off in Node).
7796- [ tinybench] ( https://github.com/tinylibs/tinybench ) , at least 1.5 seconds and 3 runs per measurement, every library
7897 in its own process.
7998- The release workflow runs the benchmark for every release and commits the results, on a GitHub Actions runner;
0 commit comments