Skip to content

Commit ae7cf3e

Browse files
benchmark ImageScript 3.10.0
1 parent a4db5dd commit ae7cf3e

12 files changed

Lines changed: 425 additions & 276 deletions

‎bench/README.md‎

Lines changed: 58 additions & 39 deletions
Original file line numberDiff line numberDiff line change
@@ -3,12 +3,14 @@
33
How [ImageScript](https://github.com/matmen/ImageScript) compares to other image libraries for Node.js: speed, file
44
sizes and what installing each takes. ImageScript is plain JavaScript with no dependencies, so it runs wherever
55
JavaScript 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

4349
Encoded 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;

‎bench/charts/animation.svg‎

Lines changed: 4 additions & 4 deletions
Loading

0 commit comments

Comments
 (0)