Skip to content

3.10.0

Choose a tag to compare

@matmen matmen released this 30 Oct 05:37
· 62 commits to master since this release
Immutable release. Only release title and notes can be modified.

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 everywhere

Worker 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.