Repository navigation
3.10.0
This release is about speed: AVIF, JPEG XL and lossless WebP encode and decode in worker threads, with byte-for-byte the same files and pixels as on one thread, and SVG rendering is faster too. Like every codec in ImageScript, it's still plain TypeScript: no WebAssembly and no native code. There are no breaking changes.
await image.encodeAVIF(); // in threads: 1.7 s instead of 4.9 s for a 1920x1080 photo
await image.encodeJXL({ quality: 90 }); // 0.86 s instead of 2.2 s
await Image.decode(bigJxl); // lossy 4K: 395 ms instead of 640 ms
await image.encodeWebP({ threads: 1 }); // threads can be limited or turned off everywhereWorker threads
Encoders and decoders split their work into worker threads where it pays off: by default as many as the processors available, at most 8. The new threads option (AVIFEncodeOptions, JXLEncodeOptions, WebPEncodeOptions, DecodeOptions, AnimationDecodeOptions) limits them, 1 turns them off. Every result is the same as without threads.
| 8 threads, Ryzen 7 5800X | before | now |
|---|---|---|
| AVIF encode, 1920x1080, effort 1 | 4.9 s | 1.7 s |
| AVIF encode, 1920x1080, effort 2 | 14.2 s | 3.4 s |
| JPEG XL encode lossy, 3840x2160, effort 1 | 8.5 s | 2.0 s |
| JPEG XL encode lossy, 3840x2160, effort 2 | 19.2 s | 4.8 s |
| JPEG XL decode lossy, 3840x2160 | 640 ms | 395 ms |
| JPEG XL decode lossless, 3840x2160 | 671 ms | 304 ms |
| AVIF decode, 3840x2160 4:2:0 | 466 ms | 355 ms |
| AVIF decode, 6400x2880 grid | 2.68 s | 1.12 s |
| WebP encode lossless, 3862x2172 | 2.34 s | 1.62 s |
- What runs in parallel: AVIF superblock rows in a wavefront (like libaom's row-based multithreading, so no loss in compression) and the filter trials; JPEG XL's XYB conversion, quantization field, varblock search, coefficients and tokens when encoding, its groups, Gaborish, EPF and color conversion when decoding; AVIF's conversion to RGB and grid tiles; WebP's predictor and prefix code search
- When: only for images large enough to gain from it. The thresholds were measured with 1 to 8 threads on 0.25 to 8 megapixels of photos, screenshots, images with few colors and transparency; below them (and for small lossless JPEG XLs) everything runs on the calling thread as before
- Where: Node.js (
worker_threads), Deno, Bun and browsers (Web Workers; bundlers like Vite pick up the worker's module). Browser pages without cross-origin isolation, edge runtimes and other runtimes without shared memory between workers run on one thread - Lossy JPEG XL decoding no longer keeps the coefficients of the whole frame (about 100 MB less at 4K)
Faster on one thread too
- SVG rendering: the rasterizer is 23-36% faster (500 text labels: 15% faster in all), with the same pixels on all of resvg's test files
- JPEG XL: lossless images decode straight to RGBA bytes (6% faster), and the patch search at effort 2 is about 30% faster
- AVIF: 4:2:0 and 4:2:2 images convert to RGB about 7% faster
Tests
Edge runtimes are tested on every commit: Cloudflare's workerd (with and without nodejs_compat) and Vercel's Edge Runtime run every codec and the renderers bundled like deployments, and Chromium runs them bundled by Vite, with threads on a cross-origin isolated page and without on another.
See the changelog for the details.