Skip to content

Commit 9279a3a

Browse files
committed
faster cover and bicubic resize, streaming JPEG decode, lossless WebP effort
1 parent 0e2954d commit 9279a3a

16 files changed

Lines changed: 700 additions & 335 deletions

‎CHANGELOG.md‎

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,31 @@
11
# Changelog
22

3+
## 3.15.0
4+
5+
### New
6+
7+
- `effort` applies to lossless `encodeWebP()` (and lossless animation frames) too: `0` is a fast mode (3 predictors,
8+
a short search for repeated pixels, one prefix code, no color transform), `1` is in between, `2` (the default) and
9+
up compress the most. A 1920x1080 photo with 8 threads: 0.20 s for 1.32 MB at effort 0, 0.57 s for 1.20 MB at 1,
10+
0.70 s for 1.22 MB at 2; a 1434x1434 graphic: 76 ms for 346 kB, 188 ms for 301 kB, 199 ms for 277 kB. Over a
11+
corpus of photos and graphics, effort 0 takes about a fifth (photos) to a third (graphics) of the time for files
12+
6-8% larger, effort 1 about two thirds for files up to 2% larger.
13+
14+
### Performance
15+
16+
- Lossless `encodeWebP()` makes photos about 3-4% smaller and graphics about 1.5%, and photos above a megapixel take
17+
less time (the 1920x1080 bench photo: 1.22 MB in 0.70 s instead of 1.34 MB in 0.80 s, with 8 threads): predicted
18+
images also try referencing only the pixel to the left and the one above, kept where that's estimated smaller
19+
(short matches further away take more bits than the pixels they replace; above a megapixel the estimate is made on
20+
three bands of rows, and the search for matches further away is left out).
21+
- `cover()` only resamples the part of the image it keeps instead of resizing all of it and then cropping: a 400x400
22+
thumbnail of a 1920x1080 photo takes 10 ms instead of 15.6 ms. The pixels are the same.
23+
- Bicubic `resize()` of opaque images is about 1.4 times as fast (a 1920x1080 photo to half: 31 ms instead of 43 ms):
24+
it leaves out the alpha channel and reads the source rows in order. The pixels are the same.
25+
- Decoding baseline (non-progressive) JPEGs takes less memory: the blocks are turned into pixels a row at a time
26+
instead of keeping every coefficient until the end (a 1920x1080 photo: 15 MB at the peak instead of 23 MB). The
27+
pixels are the same.
28+
329
## 3.14.0
430

531
### New

‎bench/README.md‎

Lines changed: 27 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -17,48 +17,48 @@ Photon is WebAssembly, and so is jsquash (Squoosh's libavif and libjxl, for AVIF
1717

1818
## Numbers
1919

20-
Time per operation (less is faster), measured 2026-11-05 with Node.js v24.11.1 on AMD Ryzen 7 5800X 8-Core Processor, ImageScript 3.14.0.
20+
Time per operation (less is faster), measured 2026-11-05 with Node.js v24.11.1 on AMD Ryzen 7 5800X 8-Core Processor, ImageScript 3.15.0.
2121

2222
| | **ImageScript** | **Jimp** | **image-js** | **PureJsImage** | **Photon** | **sharp** | **jsquash** | **@napi-rs/canvas** |
2323
|---|--:|--:|--:|--:|--:|--:|--:|--:|
24-
| decode JPEG | **46 ms** | 102 ms | 112 ms | 86 ms | 32 ms | 9.4 ms | – | 14 ms |
25-
| decode PNG | **18 ms** | 41 ms | 78 ms | 40 ms | 17 ms | 12 ms | – | 13 ms |
24+
| decode JPEG | **43 ms** | 102 ms | 112 ms | 86 ms | 32 ms | 9.4 ms | – | 14 ms |
25+
| decode PNG | **20 ms** | 41 ms | 78 ms | 40 ms | 17 ms | 12 ms | – | 13 ms |
2626
| decode WebP (lossy) | **57 ms** | – | – | 67 ms | 60 ms | 24 ms | – | 12 ms |
27-
| decode AVIF | **156 ms** | – | – | 1.2 s | – | 60 ms | 146 ms | 43 ms |
28-
| decode JPEG XL | **262 ms** | – | – | fails | – | – | 316 ms | 48 ms |
29-
| decode animated GIF | **41 ms** | – | – | – | – | 37 ms | – | – |
30-
| decode animated WebP | **185 ms** | – | – | – | – | 257 ms | – | – |
31-
| encode JPEG (quality 80) | **41 ms** | 282 ms | 74 ms | 96 ms | 76 ms | 12 ms | – | 19 ms |
32-
| encode PNG | **113 ms** | 159 ms | 251 ms | 363 ms | 69 ms | 91 ms | – | 316 ms |
33-
| encode WebP (quality 80) | **135 ms** | – | – | 87 ms | – | 126 ms | – | 107 ms |
34-
| encode WebP (lossless) | **833 ms** | – | – | 1.8 s | 29 ms | 1.0 s | – | 322 ms |
27+
| decode AVIF | **157 ms** | – | – | 1.2 s | – | 60 ms | 146 ms | 43 ms |
28+
| decode JPEG XL | **257 ms** | – | – | fails | – | – | 316 ms | 48 ms |
29+
| decode animated GIF | **43 ms** | – | – | – | – | 37 ms | – | – |
30+
| decode animated WebP | **187 ms** | – | – | – | – | 257 ms | – | – |
31+
| encode JPEG (quality 80) | **40 ms** | 282 ms | 74 ms | 96 ms | 76 ms | 12 ms | – | 19 ms |
32+
| encode PNG | **114 ms** | 159 ms | 251 ms | 363 ms | 69 ms | 91 ms | – | 316 ms |
33+
| encode WebP (quality 80) | **138 ms** | – | – | 87 ms | – | 126 ms | – | 107 ms |
34+
| encode WebP (lossless) | **735 ms** | – | – | 1.8 s | 29 ms | 1.0 s | – | 322 ms |
3535
| encode AVIF (quality 60) | **1.7 s** | – | – | 965 ms | – | 684 ms | 1.6 s | – |
36-
| encode JPEG XL (quality 90) | **710 ms** | – | – | – | – | – | 1.7 s | – |
37-
| encode JPEG XL (lossless) | **265 ms** | – | – | – | – | – | 306 ms | – |
38-
| encode animated GIF | **149 ms** | – | – | – | – | 772 ms | – | – |
39-
| encode animated WebP (q 80) | **624 ms** | – | – | – | – | 519 ms | – | – |
40-
| resize to half | **9.2 ms** | 11 ms | 61 ms | 42 ms | 45 ms | 5.5 ms | – | 1.8 ms |
41-
| resize to half, bicubic | **43 ms** | 102 ms | 261 ms | – | 71 ms | 5.8 ms | – | – |
42-
| thumbnail (cover 400×400) | **16 ms** | 35 ms | – | 27 ms | – | 3.8 ms | – | 0.64 ms |
36+
| encode JPEG XL (quality 90) | **724 ms** | – | – | – | – | – | 1.7 s | – |
37+
| encode JPEG XL (lossless) | **276 ms** | – | – | – | – | – | 306 ms | – |
38+
| encode animated GIF | **150 ms** | – | – | – | – | 772 ms | – | – |
39+
| encode animated WebP (q 80) | **651 ms** | – | – | – | – | 519 ms | – | – |
40+
| resize to half | **9.4 ms** | 11 ms | 61 ms | 42 ms | 45 ms | 5.5 ms | – | 1.8 ms |
41+
| resize to half, bicubic | **29 ms** | 102 ms | 261 ms | – | 71 ms | 5.8 ms | – | – |
42+
| thumbnail (cover 400×400) | **12 ms** | 35 ms | – | 27 ms | – | 3.8 ms | – | 0.64 ms |
4343
| crop | **1.3 ms** | 12 ms | 11 ms | 6.6 ms | 3.4 ms | 0.52 ms | – | 0.42 ms |
44-
| rotate by 30° | **26 ms** | 402 ms | 501 ms | 339 ms | 146 ms | 11 ms | – | 18 ms |
45-
| rotate by 90° | **7.1 ms** | 34 ms | 15 ms | 84 ms | 98 ms | 1.5 ms | – | 8.5 ms |
44+
| rotate by 30° | **27 ms** | 402 ms | 501 ms | 339 ms | 146 ms | 11 ms | – | 18 ms |
45+
| rotate by 90° | **7.3 ms** | 34 ms | 15 ms | 84 ms | 98 ms | 1.5 ms | – | 8.5 ms |
4646
| flip | **1.4 ms** | 67 ms | 8.5 ms | 39 ms | 14 ms | 1.2 ms | – | 6.7 ms |
47-
| blur (radius 5) | **51 ms** | 54 s | 569 ms | – | 103 ms | 6.1 ms | – | 12 ms |
47+
| blur (radius 5) | **52 ms** | 54 s | 569 ms | – | 103 ms | 6.1 ms | – | 12 ms |
4848
| sharpen | **137 ms** | – | – | – | 71 ms | 116 ms | – | – |
4949
| composite an overlay | **1.6 ms** | 22 ms | 7.1 ms | – | 4.5 ms | 2.0 ms | – | 1.6 ms |
50-
| greyscale | **4.5 ms** | 84 ms | 13 ms | – | 4.7 ms | 9.7 ms | – | 5.1 ms |
51-
| brightness and contrast | **7.1 ms** | 185 ms | – | – | 19 ms | 4.2 ms | – | 9.3 ms |
52-
| render text | **0.33 ms** | – | – | – | – | 0.23 ms | – | 0.17 ms |
53-
| render SVG | **97 ms** | – | – | – | – | 25 ms | – | 8.3 ms |
50+
| greyscale | **4.6 ms** | 84 ms | 13 ms | – | 4.7 ms | 9.7 ms | – | 5.1 ms |
51+
| brightness and contrast | **7.3 ms** | 185 ms | – | – | 19 ms | 4.2 ms | – | 9.3 ms |
52+
| render text | **0.34 ms** | – | – | – | – | 0.23 ms | – | 0.17 ms |
53+
| render SVG | **100 ms** | – | – | – | – | 25 ms | – | 8.3 ms |
5454

5555
Encoded sizes:
5656

5757
| | **ImageScript** | **Jimp** | **image-js** | **PureJsImage** | **Photon** | **sharp** | **jsquash** | **@napi-rs/canvas** |
5858
|---|--:|--:|--:|--:|--:|--:|--:|--:|
5959
| photo PNG | 2.0 MB | 2.7 MB | 2.8 MB | 2.1 MB | 2.8 MB | 2.8 MB | – | 2.1 MB |
6060
| photo JPEG | 218 kB, 40.6 dB | 260 kB, 40.9 dB | 260 kB, 40.9 dB | 222 kB, 40.5 dB | 257 kB, 40.4 dB | 218 kB, 40.6 dB | – | 218 kB, 40.6 dB |
61-
| photo WebP | 1.3 MB | – | – | 2.1 MB | 1.7 MB | 1.2 MB | – | 1.4 MB |
61+
| photo WebP | 1.2 MB | – | – | 2.1 MB | 1.7 MB | 1.2 MB | – | 1.4 MB |
6262
| graphic PNG | 795 kB | 2.0 MB | 736 kB | 811 kB | 2.2 MB | 718 kB | – | 1.0 MB |
6363
| graphic JPEG | 276 kB, 37.3 dB | 315 kB, 37.4 dB | 315 kB, 37.4 dB | 286 kB, 37.3 dB | 314 kB, 37.4 dB | 275 kB, 37.3 dB | – | 276 kB, 37.3 dB |
6464
| graphic WebP | 277 kB | – | – | 758 kB | 856 kB | 239 kB | – | 631 kB |
@@ -71,7 +71,7 @@ Encoded sizes:
7171
| photo JPEG XL lossless | 1.2 MB | – | – | – | – | – | 1.2 MB | – |
7272
| graphic WebP lossy | 241 kB, 38.4 dB | – | – | 301 kB, 38 dB | – | 233 kB, 39 dB | – | 234 kB, 39 dB |
7373
| graphic JPEG XL lossless | 663 kB | – | – | – | – | – | 666 kB | – |
74-
| animation WebP | 534 kB, 33.6 dB | – | – | – | – | 683 kB, 33.1 dB | – | – |
74+
| animation WebP | 529 kB, 33.6 dB | – | – | – | – | 683 kB, 33.1 dB | – | – |
7575

7676
| | **ImageScript** | **Jimp** | **image-js** | **PureJsImage** | **Photon** | **sharp** | **jsquash** | **@napi-rs/canvas** |
7777
|---|---|---|---|---|---|---|---|---|

‎bench/charts/overview-native.svg‎

Lines changed: 1 addition & 1 deletion
Loading

‎bench/charts/overview-portable.svg‎

Lines changed: 1 addition & 1 deletion
Loading

0 commit comments

Comments
 (0)