Skip to content

Commit 7804990

Browse files
matmenclaude
andcommitted
JXL: lossless decoding in threads from 512 KiB on
A second sweep on other content (a tiled screenshot, 64 colors, alpha gradients at 1-4 megapixels; threads 1 vs the default rules) found one case slower with threads: lossless JPEG XLs of few colors (2 MP: 78 -> 128 ms) decode faster than threads start. Lossless decoding takes about as long as the file is large, so threads need 512 KiB too (photo 2 MP, 1.4 MB: 1.6x; screenshot 2 MP, 0.67 MB: 1.08x; few colors 0.17 MB: now without). Every other case was faster with threads or the same (AVIF 4:4:4 decoding, within 3%). Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01BsU2GAqbnFSNaYDBGD1Km2
1 parent eae6c43 commit 7804990

3 files changed

Lines changed: 9 additions & 6 deletions

File tree

‎CHANGELOG.md‎

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -32,7 +32,8 @@
3232
references stay on one thread.
3333
- The thresholds for threads were measured with 1 to 8 threads on 0.25 to 8 megapixels: lossy JPEG XL encoding at
3434
efforts 1 and 2 and AVIF encoding use 4 threads up to about 1-2 megapixels (more were slower there), JPEG XL at
35-
effort 0 starts at 1.5 megapixels, lossless JPEG XL decoding at 1.5 instead of 4.
35+
effort 0 starts at 1.5 megapixels, lossless JPEG XL decoding at 1.5 instead of 4 (and 512 KiB: smaller files, like
36+
ones of few colors, decode faster than threads start). Checked on screenshots, few colors and alpha too.
3637
- AVIF decoding uses worker threads (`DecodeOptions.threads`) from 4 megapixels on: the conversion to RGB runs in
3738
bands of rows (started at the beginning, the threads load while the AV1 image is decoded) and the tiles of grids are
3839
decoded in parallel, with the same pixels. A 3840x2160 4:2:0 image decodes in 355 ms instead of 466 ms, a 6400x2880

‎src/codecs/jxl.ts‎

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -92,10 +92,11 @@ export async function* frames(data: Uint8Array, maxPixels = MAX_PIXELS, toneMapp
9292
};
9393
const a = image.animation, tick = a ? 1000 * a.tpsDenominator / a.tpsNumerator : 0;
9494
// (threads for the frames' groups and filters, see decodeFrame: at most one per 2 megapixels for XYB images (lossy),
95-
// from 1.5 megapixels on one per 512K pixels for others (lossless), more take longer to start and warm up than they
96-
// save; measured with 1-8 threads on 0.25-8 megapixels)
95+
// from 1.5 megapixels and 512 KiB on one per 512K pixels for others (lossless: they take about as long as they're
96+
// large, smaller ones like few colors' decode faster than threads start), more take longer to start and warm up
97+
// than they save; measured with 1-8 threads on 0.25-8 megapixels of photos, screenshots and few colors)
9798
const pixels = image.width * image.height;
98-
const n = Math.min(threads, image.xybEncoded ? Math.floor(pixels / (2 << 20)) : pixels >= 1536 << 10 ? Math.floor(pixels / (512 << 10)) : 1);
99+
const n = Math.min(threads, image.xybEncoded ? Math.floor(pixels / (2 << 20)) : pixels >= 1536 << 10 && d.length >= 512 << 10 ? Math.floor(pixels / (512 << 10)) : 1);
99100
const pool: FrameThreads = { threads: n > 1 ? startThreads(n) : null, data: null };
100101
try {
101102
let shown = 0;

‎src/image.ts‎

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -221,8 +221,9 @@ export interface DecodeOptions {
221221
toneMapping?: ToneMapping;
222222
/**
223223
* At most this many worker threads decode in parallel (lossy JPEG XLs from 4 megapixels on, one per 2 megapixels
224-
* at most, lossless ones from 1.5 megapixels on; AVIFs from 4 megapixels on: the conversion to RGB, one per
225-
* megapixel, and the tiles of grids), `1` for none. Default: the processors available, at most 8 (1 where workers can't share memory)
224+
* at most, lossless ones from 1.5 megapixels and 512 KiB on; AVIFs from 4 megapixels on: the conversion to RGB,
225+
* one per megapixel, and the tiles of grids), `1` for none. Default: the processors available, at most 8 (1 where
226+
* workers can't share memory)
226227
*/
227228
threads?: number;
228229
}

0 commit comments

Comments
 (0)