rapier-jxl 2.1.0 → 2.2.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (71) hide show
  1. package/AGENTS.md +24 -24
  2. package/README.md +73 -142
  3. package/dist/effort.min.mjs +2 -0
  4. package/dist/jpeg-ans.min.mjs +2 -0
  5. package/dist/jpeg.min.mjs +2 -0
  6. package/dist/photo-ans.min.mjs +2 -0
  7. package/dist/photo.min.mjs +2 -0
  8. package/dist/rapier-jxl.min.mjs +2 -0
  9. package/{sizes.json → dist/sizes.json} +133 -61
  10. package/dist/wasm.min.mjs +2 -0
  11. package/kernels/build.mjs +31 -0
  12. package/kernels/kernels-probe.wat +1 -0
  13. package/kernels/kernels-scalar.wat +831 -0
  14. package/kernels/kernels-simd.wat +252 -0
  15. package/package.json +53 -34
  16. package/{bits.mjs → src/bits.mjs} +5 -2
  17. package/{effort.mjs → src/effort-job.mjs} +73 -84
  18. package/{effort.d.mts → src/effort.d.mts} +2 -1
  19. package/src/effort.mjs +25 -0
  20. package/{frame.mjs → src/frame.mjs} +25 -0
  21. package/src/kernel-control.d.mts +3 -0
  22. package/src/kernel-hooks.mjs +3 -0
  23. package/src/kernels-bytes.mjs +4 -0
  24. package/src/kernels.mjs +141 -0
  25. package/{local.mjs → src/local.mjs} +40 -36
  26. package/src/lossless-coding.mjs +30 -0
  27. package/{lossless.mjs → src/lossless.mjs} +88 -67
  28. package/{modular.mjs → src/modular.mjs} +11 -7
  29. package/src/pool.mjs +136 -0
  30. package/src/rct-search.mjs +61 -0
  31. package/src/wasm.d.mts +4 -0
  32. package/src/wasm.mjs +6 -0
  33. package/{weighted.mjs → src/weighted.mjs} +2 -0
  34. package/ENCODER-COMPARISON.md +0 -185
  35. package/effort.min.mjs +0 -2
  36. package/jpeg-ans.min.mjs +0 -2
  37. package/jpeg.min.mjs +0 -2
  38. package/photo-ans.min.mjs +0 -2
  39. package/photo.min.mjs +0 -2
  40. package/public/encoder-sizes.json +0 -355
  41. package/public/encoders.json +0 -139
  42. package/public/examples/browser.html +0 -39
  43. package/public/examples/worker.mjs +0 -42
  44. package/public/measure-encoders.mjs +0 -60
  45. package/rapier-jxl.min.mjs +0 -2
  46. /package/{admit.mjs → src/admit.mjs} +0 -0
  47. /package/{ans.mjs → src/ans.mjs} +0 -0
  48. /package/{coefficient-ans.mjs → src/coefficient-ans.mjs} +0 -0
  49. /package/{coefficient-effort.mjs → src/coefficient-effort.mjs} +0 -0
  50. /package/{effort-level.mjs → src/effort-level.mjs} +0 -0
  51. /package/{entropy.mjs → src/entropy.mjs} +0 -0
  52. /package/{index.d.mts → src/index.d.mts} +0 -0
  53. /package/{index.mjs → src/index.mjs} +0 -0
  54. /package/{jfif.mjs → src/jfif.mjs} +0 -0
  55. /package/{jpeg-ans.d.mts → src/jpeg-ans.d.mts} +0 -0
  56. /package/{jpeg-ans.mjs → src/jpeg-ans.mjs} +0 -0
  57. /package/{jpeg-job.mjs → src/jpeg-job.mjs} +0 -0
  58. /package/{jpeg.d.mts → src/jpeg.d.mts} +0 -0
  59. /package/{jpeg.mjs → src/jpeg.mjs} +0 -0
  60. /package/{lossy.mjs → src/lossy.mjs} +0 -0
  61. /package/{photo-ans.d.mts → src/photo-ans.d.mts} +0 -0
  62. /package/{photo-ans.mjs → src/photo-ans.mjs} +0 -0
  63. /package/{photo-dct.mjs → src/photo-dct.mjs} +0 -0
  64. /package/{photo-job.mjs → src/photo-job.mjs} +0 -0
  65. /package/{photo-quant.mjs → src/photo-quant.mjs} +0 -0
  66. /package/{photo.d.mts → src/photo.d.mts} +0 -0
  67. /package/{photo.mjs → src/photo.mjs} +0 -0
  68. /package/{prefix.mjs → src/prefix.mjs} +0 -0
  69. /package/{squeeze.mjs → src/squeeze.mjs} +0 -0
  70. /package/{vardct.mjs → src/vardct.mjs} +0 -0
  71. /package/{writer.mjs → src/writer.mjs} +0 -0
@@ -2,6 +2,7 @@
2
2
  // Trees, transforms and channel residuals as the specification's modular decoder reads them. A channel is coded
3
3
  // through the tree leaf it lands on: prediction, then `PackSigned(residual)` as a hybrid-integer token; runs of
4
4
  // zero residuals of eight or more become one zero and an LZ77 copy of length run-1 at distance 1.
5
+ import {kernelHooks} from './kernel-hooks.mjs';
5
6
  import {packSigned} from './bits.mjs';
6
7
  import {buildCode, writeHistograms, uintConfig, countToken, writeHybrid} from './prefix.mjs';
7
8
 
@@ -82,7 +83,7 @@ export function streamTree(sections) { return propertyTree(sections, 1); }
82
83
 
83
84
  // The channel histogram bundle after a tree: histogram 0 holds the LZ77 distance (one symbol, 1: distance one);
84
85
  // histogram i+1 is `freqs[i]`, and every ordered leaf names its histogram through `histogramOf(leaf)`.
85
- export function writeChannelHistograms(w, orderedLeaves, freqs, histogramOf = leaf => leaf.context) {
86
+ export function writeChannelHistograms(w, orderedLeaves, freqs, histogramOf = leaf => leaf.context, configOf = () => RESIDUAL_CONFIG) {
86
87
  const contextMap = new Uint8Array(orderedLeaves.length + 1);
87
88
  for (const leaf of orderedLeaves) contextMap[leaf.context] = histogramOf(leaf) + 1;
88
89
  const distance = new Uint32Array(2); distance[1] = 1;
@@ -90,21 +91,24 @@ export function writeChannelHistograms(w, orderedLeaves, freqs, histogramOf = le
90
91
  // The decoder derives the histogram count from the map. Squeeze can leave trailing zero-width/height
91
92
  // channels outside every section; writing their unreferenced histograms would shift the following stream.
92
93
  const count = Math.max(...contextMap);
93
- for (let i = 0; i < count; i++) histograms.push({config: RESIDUAL_CONFIG, code: buildCode(freqs[i])});
94
+ for (let i = 0; i < count; i++) histograms.push({config: configOf(i), code: buildCode(freqs[i])});
94
95
  writeHistograms(w, {lz77: LZ77, contextMap, histograms});
95
96
  return histograms;
96
97
  }
97
98
 
98
99
  // Codes one channel plane (Int32Array, width*height) through `leaf`. Counting when `w` is null: `target` is the
99
- // leaf's token histogram (Uint32Array(ALPHABET)). Writing otherwise: `target` is the leaf's prefix code. Lossy
100
- // leaves (multiplier above one) replace the plane's values by the decoder's reconstruction as they go.
101
- export function codeChannel(w, target, plane, width, height, leaf) {
100
+ // leaf's token histogram (Uint32Array(ALPHABET)), or with `raw` the raw histogram of lossless-coding.mjs: a literal's
101
+ // value in its own bin, the 33 length tokens in the last 33. Writing otherwise: `target` is the leaf's prefix code.
102
+ // Lossy leaves (multiplier above one) replace the plane's values by the decoder's reconstruction as they go.
103
+ export function codeChannel(w, target, plane, width, height, leaf, raw = false) {
104
+ if (kernelHooks.channel?.(w, target, plane, width, height, leaf, raw)) return;
102
105
  const predictor = leaf.predictor, offset = leaf.offset, multiplier = leaf.multiplier;
103
- const config = RESIDUAL_CONFIG, lengthConfig = RUN_CONFIG;
106
+ const config = leaf.config || RESIDUAL_CONFIG, lengthConfig = RUN_CONFIG;
104
107
  let run = 0;
105
108
  const emit = (value, tokenConfig = config, base = 0) => {
106
109
  if (w) writeHybrid(w, target, tokenConfig, value, base);
107
- else countToken(tokenConfig, value, target, base);
110
+ else if (raw && !base) target[value]++;
111
+ else countToken(tokenConfig, value, target, raw ? target.length - 33 : base);
108
112
  };
109
113
  const flush = () => {
110
114
  if (!run) return;
package/src/pool.mjs ADDED
@@ -0,0 +1,136 @@
1
+ // SPDX-License-Identifier: MIT
2
+ // Every core: the effort door's exact work over a pool of workers, the same bytes as one thread for any number of them.
3
+ // A frame's groups are coded independently once its global choices are made, and every pass of the door over its
4
+ // groups (the plan's counting and writing, the weighted search's, the local trees') is a function of the pass's setup
5
+ // and the group's pixels alone (frame.mjs, groupPass). So each worker is given its groups' pixels once, by transfer, and
6
+ // keeps them for every pass of the picture; a pass sends its setup and then group numbers; this thread, which has every
7
+ // pixel, codes groups too while it waits. The counts come back per group and are added in group order, the sections
8
+ // are placed in group order, and every choice between passes (predictors, codes, trees, which plan, which stream) stays
9
+ // in this thread. Only the time changes.
10
+ //
11
+ // The workers are the caller's: `spawn()` returns a Worker (postMessage, onmessage, onerror, terminate) whose script
12
+ // hands every message to `servePool` and posts back what it answers, with its transfer list. The pool ends its workers
13
+ // when the job ends or is left. A worker that cannot start or fails leaves its groups to this thread, which runs the
14
+ // same functions on the same pixels: the bytes stand. A picture of one group, or a lossy one, never starts a worker.
15
+ import {groupLayout, groupRect} from './frame.mjs';
16
+ import {planGroup} from './lossless.mjs';
17
+ import {colourTransform} from './rct-search.mjs';
18
+ import {localGroup} from './local.mjs';
19
+ import {searchGroup, effortJob} from './effort-job.mjs';
20
+ import {guard, answer} from './admit.mjs';
21
+
22
+ const WORK = {plan: setup => planGroup(setup, setup.rct === undefined ? undefined : colourTransform(setup.rct, setup.channels)), search: searchGroup, local: localGroup};
23
+ // A group's result: its counts (fresh, the pass's `sizes`), or its section.
24
+ const work = (group, setup, rgba, stride, rect) => { const counts = setup.sizes?.map(n => new Uint32Array(n)); return group(rgba, stride, ...rect, counts) || counts; };
25
+
26
+ // A worker's side. Its tiles: {rgba, w, h} by group; the pass it is working.
27
+ let tiles = [], current = null;
28
+ export function servePool(message) {
29
+ if (message.pool === 'tile') { tiles[message.g] = message; return null; }
30
+ if (message.pool === 'pass') { current = {...message, group: WORK[message.kind](message.setup)}; return null; }
31
+ const {g} = message, {rgba, w, h} = tiles[g];
32
+ try {
33
+ const result = work(current.group, current.setup, rgba, w, [0, 0, w, h]);
34
+ return [{pool: 'done', id: current.id, g, result}, (Array.isArray(result) ? result : [result]).map(a => a.buffer)];
35
+ } catch (error) { return [{pool: 'done', id: current.id, g, error: {name: error.name, code: error.code, message: error.message}}, []]; }
36
+ }
37
+
38
+ // The effort door's encodeSteps (the same options, admitted at once) as an async job: `for await (const done of job)`,
39
+ // `job.hurry` and `job.bytes` as the steps' job; leaving the loop ends the workers. `workers` is how many to start.
40
+ export function encodePool(data, width, height, options, {spawn, workers = 4} = {}) {
41
+ const steps = effortJob(data, width, height, options, true), layout = groupLayout(width, height), groups = layout.groupsX * layout.groupsY;
42
+ let members = null, failed = false, live = 0, serial = 0, ready = [], wake = () => {}, last = 0;
43
+ const end = () => { if (members) for (const worker of members) worker.terminate(); members = []; };
44
+ const fail = () => { failed = true; end(); wake(); };
45
+ // Every message to a worker goes through here: a send that throws (a pixel buffer that cannot be transferred, a
46
+ // worker that is gone) is a failure of the pool like any other, and the groups come back to this thread.
47
+ const send = (worker, message, transfer = []) => {
48
+ if (failed) return false;
49
+ try { worker.postMessage(message, transfer); return true; } catch { fail(); return false; }
50
+ };
51
+ // The workers, and each one's groups' pixels.
52
+ const start = () => {
53
+ members = [];
54
+ try {
55
+ for (let k = 0; k < Math.min(workers, groups); k++) {
56
+ const worker = spawn();
57
+ members.push(worker);
58
+ worker.onmessage = ({data: message}) => { worker.up = true; if (message.id === live) { worker.flight--; ready.push(message); worker.feed(); wake(); } };
59
+ worker.onerror = worker.onmessageerror = event => { event?.preventDefault?.(); if (members.includes(worker)) fail(); };
60
+ }
61
+ } catch { fail(); return; }
62
+ for (let g = 0; g < groups; g++) {
63
+ const [x0, y0, w, h] = groupRect(layout, width, height, g), rgba = new Uint8Array(w * h * 4);
64
+ for (let y = 0; y < h; y++) rgba.set(data.subarray(((y0 + y) * width + x0) * 4, ((y0 + y) * width + x0 + w) * 4), y * w * 4);
65
+ if (!send(members[g % members.length], {pool: 'tile', g, w, h, rgba}, [rgba.buffer])) return;
66
+ }
67
+ };
68
+ // A turn of the event loop, so that the workers' answers come in between this thread's own groups: a channel's
69
+ // message in a browser, which queues behind them, and setImmediate where there is one (Node drains a port's messages
70
+ // in one go, so a channel there would starve the workers' ports).
71
+ let channel;
72
+ const turn = () => new Promise(resolve => {
73
+ if (typeof setImmediate === 'function') { setImmediate(resolve); return; }
74
+ channel ||= new MessageChannel(); channel.port1.onmessage = resolve; channel.port2.postMessage(0);
75
+ });
76
+ // One pass: every group's result, or null when a hurry ended it, or the error a group's work threw. A worker is sent
77
+ // a group at a time until it first answers (it may still be starting), then kept two ahead. This thread is not idle:
78
+ // it takes the last unsent group of the longest queue, else a group sent to a worker that has not answered yet, and
79
+ // only waits when every group left is with a working worker. A group answered twice counts once: it is the same.
80
+ async function* pass({kind, setup, at, stop}) {
81
+ if (!members) start();
82
+ const id = live = ++serial, waiting = new Set(Array.from({length: groups}, (_, g) => g)), results = [];
83
+ const queues = members.map((_, k) => [...waiting].filter(g => g % members.length === k));
84
+ ready = [];
85
+ for (const [k, worker] of members.entries()) {
86
+ worker.flight = 0;
87
+ worker.feed = () => { while (worker.flight < (worker.up ? 2 : 1) && queues[k].length && send(worker, {pool: 'task', g: queues[k][0]})) { worker.flight++; queues[k].shift(); } };
88
+ if (!send(worker, {pool: 'pass', id, kind, setup})) break;
89
+ worker.feed();
90
+ if (failed) break;
91
+ }
92
+ const steal = () => {
93
+ if (failed) return waiting.values().next().value;
94
+ let k = -1;
95
+ queues.forEach((queue, i) => { if (queue.length && (k < 0 || queue.length > queues[k].length)) k = i; });
96
+ if (k >= 0) return queues[k].pop();
97
+ for (const g of waiting) if (!members[g % members.length].up) return g;
98
+ };
99
+ let local, hurried = false;
100
+ while (waiting.size) {
101
+ let done = ready.shift();
102
+ if (!done) {
103
+ const g = steal();
104
+ if (g === undefined) { await new Promise(resolve => { wake = resolve; }); continue; }
105
+ local ||= WORK[kind](setup);
106
+ try { done = {g, result: work(local, setup, data, width, groupRect(layout, width, height, g))}; } catch (error) { live = 0; return error; }
107
+ await turn();
108
+ }
109
+ if (!waiting.has(done.g)) continue;
110
+ if (done.error) { live = 0; return Object.assign(new (done.error.name === 'RangeError' ? RangeError : Error)(done.error.message), done.error.code ? {code: done.error.code} : {}); }
111
+ waiting.delete(done.g); results[done.g] = done.result;
112
+ const g = groups - waiting.size - 1;
113
+ yield last = at(g);
114
+ hurried = it.hurry;
115
+ if (hurried && stop(g)) { live = 0; return null; }
116
+ }
117
+ live = 0;
118
+ return {results, hurried};
119
+ }
120
+ const it = (async function* () {
121
+ let step, reply;
122
+ try {
123
+ // The workers start, and take their pixels, while this thread inspects the picture.
124
+ if (!layout.single && (options?.quality ?? 100) >= 100) start();
125
+ while (!(step = guard(() => reply instanceof Error ? steps.throw(reply) : steps.next(reply))).done) {
126
+ if (typeof step.value === 'number') { yield last = step.value; reply = it.hurry; }
127
+ else reply = yield* pass(step.value);
128
+ }
129
+ } finally { live = -1; end(); channel?.port1.close(); }
130
+ // A candidate given up (the stream limit, or memory for a search) ends early.
131
+ if (last < 1) yield 1;
132
+ return it.bytes = answer(step.value);
133
+ })();
134
+ it.bytes = null; it.hurry = false;
135
+ return it;
136
+ }
@@ -0,0 +1,61 @@
1
+ // SPDX-License-Identifier: MIT
2
+ // Optional lossless colour-transform search. Samples rank the 42 reversible transforms; only a complete
3
+ // candidate can displace an earlier stream. The page's effort-3 path does not pay for this higher rung.
4
+ import {BitWriter} from './bits.mjs';
5
+ import {leaf, codeChannel, writeTransform} from './modular.mjs';
6
+ import {losslessCoding} from './lossless-coding.mjs';
7
+ import {losslessSteps} from './lossless.mjs';
8
+
9
+ // Transform `type` of the specification's 42 (a permutation, then one of seven), filling planes as planeFill does.
10
+ export function colourTransform(type, channels) {
11
+ const permutation = Math.floor(type / 7), custom = type % 7;
12
+ const a = permutation % 3, b = (permutation + 1 + Math.floor(permutation / 3)) % 3;
13
+ const c = (permutation + 2 - Math.floor(permutation / 3)) % 3;
14
+ return {type, fill(planes, rgba, stride, x0, y0, w, h) {
15
+ for (let y = 0; y < h; y++) for (let x = 0, at = y * w, i = ((y0 + y) * stride + x0) * 4; x < w; x++, at++, i += 4) {
16
+ const first = rgba[i + a], second = rgba[i + b], third = rgba[i + c];
17
+ if (custom === 6) {
18
+ const co = first - third, tmp = third + (co >> 1), cg = second - tmp;
19
+ planes[0][at] = tmp + (cg >> 1); planes[1][at] = co; planes[2][at] = cg;
20
+ } else {
21
+ planes[0][at] = first;
22
+ planes[1][at] = second - ((custom >> 1) === 1 ? first : (custom >> 1) === 2 ? (first + third) >> 1 : 0);
23
+ planes[2][at] = third - (custom & 1 ? first : 0);
24
+ }
25
+ if (channels === 4) planes[3][at] = rgba[i + 3];
26
+ }
27
+ }};
28
+ }
29
+
30
+ export function* rctSearchSteps(rgba, width, height, shape, colorSpace, pooled) {
31
+ if (shape.colour !== 3 || width * height < 4096) return null;
32
+ const sw = Math.min(width, 32), sh = Math.min(height, 32);
33
+ const planes = Array.from({length: shape.channels}, () => new Int16Array(sw * sh));
34
+ const types = [6, ...Array.from({length: 42}, (_, i) => i).filter(i => i !== 6)];
35
+ let best;
36
+ for (let k = 0; k < types.length; k++) {
37
+ const rct = colourTransform(types[k], shape.channels);
38
+ const candidates = [5, 3].map(predictor => ({predictor, freqs: Array.from({length: 3}, () => new Uint32Array(1057))}));
39
+ for (let sample = 0; sample < 3; sample++) {
40
+ rct.fill(planes, rgba, width, Math.floor((width - sw) * sample / 2), Math.floor((height - sh) * sample / 2), sw, sh);
41
+ for (const candidate of candidates) for (let c = 0; c < 3; c++) codeChannel(null, candidate.freqs[c], planes[c], sw, sh, leaf(candidate.predictor), true);
42
+ }
43
+ const w = new BitWriter(32);
44
+ if (rct.type) writeTransform(w, {type: 'rct', beginC: 0, rctType: rct.type});
45
+ let bits = w.bitLength;
46
+ for (let c = 0; c < 3; c++) bits += Math.min(...candidates.map(p => losslessCoding(p.freqs[c])[1].bits));
47
+ if (!best || bits < best.bits) best = {rct, bits};
48
+ if (yield (k + 1) / (4 * types.length)) return null;
49
+ }
50
+ if (best.rct.type === 6) return null;
51
+ const plan = losslessSteps(rgba, width, height, {shape: {...shape, palette: null}, colorSpace, rct: best.rct, pooled});
52
+ let step, reply;
53
+ while (!(step = plan.next(reply)).done) {
54
+ // The final group is already complete: finish assembly and retain it even when the caller hurries there. A pooled
55
+ // pass ends at a hurry anywhere before that.
56
+ const done = step.value, inner = typeof done === 'number';
57
+ reply = yield inner ? 0.25 + 0.75 * done : {...done, at: g => 0.25 + 0.75 * done.at(g), stop: g => done.at(g) < 1};
58
+ if (inner ? reply && done < 1 : !reply) return null;
59
+ }
60
+ return step.value;
61
+ }
package/src/wasm.d.mts ADDED
@@ -0,0 +1,4 @@
1
+ export {encode, encodeSteps, LIMITS} from './effort.mjs';
2
+ /** Optional, realm-local acceleration; call before an encode, not from a progress callback. */
3
+ export function configureKernels(mode?: 'off' | 'auto' | 'scalar' | 'simd', parts?: {channel?: boolean; weighted?: boolean; fill?: boolean}): 'off' | 'scalar' | 'simd';
4
+ export function kernelMode(): 'off' | 'scalar' | 'simd';
package/src/wasm.mjs ADDED
@@ -0,0 +1,6 @@
1
+ // Optional all-in-one accelerated effort door. MIT (LICENSE).
2
+ // The one-file build owns the same hooks as its encoder, unlike two separate minified files.
3
+ import {configureKernels, kernelMode} from './kernels.mjs';
4
+ import {encode, encodeSteps, LIMITS} from './effort.mjs';
5
+ configureKernels('auto');
6
+ export {encode, encodeSteps, LIMITS, configureKernels, kernelMode};
@@ -5,6 +5,7 @@
5
5
  // same state for each channel of each group, so the encoder runs it over the plane it codes, and with it the
6
6
  // property it offers a tree (15: of the errors to the west, north, north-west and north-east, the one largest in
7
7
  // size). Integer arithmetic only: every engine predicts alike.
8
+ import {kernelHooks} from './kernel-hooks.mjs';
8
9
  import {packSigned} from './bits.mjs';
9
10
  import {countToken, writeHybrid} from './prefix.mjs';
10
11
  import {LZ77, RESIDUAL_CONFIG} from './modular.mjs';
@@ -30,6 +31,7 @@ let weightedScratch;
30
31
  // multiplier is one. The effort-only local modeller can request the raw unsigned residuals and signed property in
31
32
  // caller-owned arrays instead; it then owns tokenisation, while this one loop remains the predictor's state owner.
32
33
  export function codeWeighted(w, targets, plane, width, height, offset = 0, contextOf = SINGLE, residuals, properties) {
34
+ if (kernelHooks.weighted?.(w, targets, plane, width, height, offset, contextOf, residuals, properties)) return;
33
35
  const stride = width + 2;
34
36
  // A call owns its state until it returns; a nested writer call gets a separate scratch allocation.
35
37
  const state = weightedScratch && weightedScratch[0].length >= 2 * stride ? weightedScratch : Array.from({length: 6}, (_, i) => new Int32Array(i < 5 ? 2 * stride : 8));
@@ -1,185 +0,0 @@
1
- # Encoder payloads measured on 30 September 2026
2
-
3
- Rapier JXL is the smallest JavaScript or WebAssembly JPEG XL encoder among the published payloads measured here.
4
- A bounded survey, not proof of a global minimum; the encoders differ in what they cover.
5
-
6
- | Encoder / entry | Version | Minified JS + WASM bytes | gzip bytes | Included capability |
7
- | --- | --- | ---: | ---: | --- |
8
- | Rapier JXL core | 2.1.0 | 19,539 | 8,511 | 8-bit lossless RGBA, lossy modular with exact alpha |
9
- | Rapier JXL effort door | 2.1.0 | 30,008 | 12,320 | The core, and the weighted predictor searched for lossless |
10
- | Rapier JXL JPEG door | 2.1.0 | 32,218 | 13,439 | JPEG coefficients, orientation; no JPEG reconstruction |
11
- | Rapier JXL photo door | 2.1.0 | 31,288 | 12,872 | 8-bit photographic VarDCT, exact alpha; q100 lossless |
12
- | [jSquash](https://github.com/jamsinclair/jSquash/tree/main/packages/jxl) | 1.3.0 | 1,388,572 | 525,782 | 8-bit lossless/lossy RGBA |
13
- | [Discourse's jSquash package](https://www.npmjs.com/package/@discourse/jxl) | 1.3.0 | 1,388,572 | 525,782 | The same encoder bytes as jSquash |
14
- | [Lacinak's jSquash fork](https://github.com/kelaci/jSquash) | 1.3.0-kelaci.0 | 2,071,514 | 844,866 | High bit-depth input options |
15
- | [squoosh-kit](https://github.com/bnowak008/squoosh-kit/tree/main/packages/jxl) | 0.2.10 | 1,371,789 | 511,927 | 8-bit lossless/lossy RGBA |
16
- | [icodec](https://github.com/Kaciras/icodec) | 0.6.0 | 2,474,954 | 900,957 | 8 to 16-bit lossless/lossy pixels |
17
- | [Cornerstone libjxl](https://github.com/cornerstonejs/codecs/tree/main/packages/libjxl) | 1.1.1 | 2,575,008 | 936,242 | 1 to 16-bit grayscale/RGB; DICOM-oriented API |
18
- | [jxl-wasm](https://github.com/saschanaz/jxl-wasm) | 0.7.0 | 2,533,758 | 961,538 | libjxl CLI, pixels and reversible JPEG |
19
- | [jpeg-to-jxl](https://github.com/ChefJulio/jpeg-to-jxl) | 0.2.0 | 2,732,858 | 1,063,406 | Reversible JPEG recompression, with decode |
20
- | [Squoosh library](https://github.com/GoogleChromeLabs/squoosh/tree/dev/libsquoosh) | 0.5.3 | 1,615,596 | 539,210 | Shared multi-codec JavaScript and one JXL encoder WASM |
21
-
22
- Bytes are decimal. JavaScript is minified with Terser 5.51.2 (two compress passes, mangling, licence comments
23
- kept), WASM is unchanged, each resource is gzipped alone at level 9, then summed: delivery bytes, not tarball
24
- sizes. Every file, URL, npm integrity and SHA-256 is in [`public/encoder-sizes.json`](public/encoder-sizes.json).
25
- The jSquash, fork, squoosh-kit and icodec rows count the encoder factory and its WASM only, without wrappers,
26
- workers or decoders, which favours them. Size says nothing about correctness or compression quality.
27
-
28
- ## Reproduce
29
-
30
- ```sh
31
- npm install --ignore-scripts
32
- node public/measure-encoders.mjs .encoder-size-cache encoder-sizes.json
33
- ```
34
-
35
- The script fetches the pinned npm archives in `public/encoders.json`, checks their integrity, reads the listed
36
- members in memory and minifies them. It never runs downloaded code. Rapier's own bytes come from its stager.
37
-
38
- ## Quality at matched bytes, 30 September 2026
39
-
40
- A small encoder does not mean small or good pictures. This measures Rapier's readable source against the
41
- [libjxl v0.12.0 reference](https://github.com/libjxl/libjxl/releases/tag/v0.12.0) (`cjxl`, `djxl` and
42
- `butteraugli_main` from the official Linux x86-64 static archive, SHA-256
43
- `5318a1ea40adad76d023e0c17a03d4627f8282f83cb2b575e69be8e74f1ff456`) on eleven inputs: four synthetic
44
- textures, the bundled Grace Hopper JPEG, three generated drawings and three generated paintings. It is not a
45
- representative collection. Each Rapier stream sets a byte budget; the native encoder, at effort 7 with its mode
46
- left to `cjxl`, is searched over distances for the largest stream within that budget (a gap of at most 0.5%
47
- counts as matched). Where native distance 0 already fits, the row is marked **†**: an exact stream in fewer
48
- bytes, not a match. Both outputs are decoded by the same pinned `djxl` to 8-bit sRGB; alpha is byte-exact in
49
- every selected decode. PSNR and RGB SSIM are computed over the three RGB channels (single-scale SSIM, 11 × 11
50
- Gaussian, no downsampling); alpha-bearing inputs are matted on white for the table. Butteraugli is the
51
- reference tool at 80 nits, the worse of the white and black mattes for alpha inputs. Higher PSNR and SSIM and
52
- lower Butteraugli are that metric's preference, not a human verdict. **R / N** is Rapier / native.
53
-
54
- ### The photo door
55
-
56
- Native is within 0.402% of the budget in all 20 rows, and better on PSNR and SSIM in 19 of 20 and on
57
- Butteraugli in 19 of 20; the exceptions are Grace q99 (Rapier's PSNR and SSIM) and lit surface q99 (Rapier's
58
- Butteraugli).
59
-
60
- | Input · Rapier q | Rapier bytes | Native bytes | Native d | PSNR dB R / N ↑ | RGB SSIM R / N ↑ | Butteraugli R / N ↓ |
61
- | --- | ---: | ---: | ---: | ---: | ---: | ---: |
62
- | Wood grain · 50 | 6,351 | 6,335 | 5.061 | 33.459 / 37.501 | 0.833394 / 0.920358 | 7.9959 / 3.8476 |
63
- | Wood grain · 80 | 11,640 | 11,632 | 2.246 | 36.866 / 39.628 | 0.900685 / 0.942440 | 4.0035 / 1.8251 |
64
- | Wood grain · 90 | 18,713 | 18,713 | 1.226 | 38.910 / 40.727 | 0.929675 / 0.953458 | 2.3318 / 1.5172 |
65
- | Wood grain · 99 | 76,863 | 76,842 | 0.327 | 45.530 / 47.954 | 0.983684 / 0.991316 | 0.9762 / 0.6128 |
66
- | Landscape · 50 | 4,729 | 4,710 | 4.944 | 34.594 / 38.053 | 0.852275 / 0.908469 | 8.5642 / 3.4545 |
67
- | Landscape · 80 | 7,932 | 7,921 | 2.438 | 37.305 / 39.502 | 0.891356 / 0.921732 | 4.1410 / 2.1015 |
68
- | Landscape · 90 | 12,725 | 12,723 | 1.508 | 39.050 / 40.389 | 0.912417 / 0.931579 | 3.1272 / 1.6892 |
69
- | Landscape · 99 | 64,221 | 64,192 | 0.377 | 45.545 / 47.492 | 0.977931 / 0.986445 | 1.0067 / 0.6164 |
70
- | Folded texture · 50 | 3,563 | 3,552 | 5.427 | 35.334 / 38.643 | 0.839685 / 0.908679 | 7.2341 / 4.0082 |
71
- | Folded texture · 80 | 6,525 | 6,514 | 2.468 | 38.175 / 40.176 | 0.894762 / 0.927122 | 3.6812 / 2.2637 |
72
- | Folded texture · 90 | 10,046 | 10,046 | 1.668 | 39.618 / 40.820 | 0.917830 / 0.934923 | 2.2918 / 1.6726 |
73
- | Folded texture · 99 | 57,111 | 57,111 | 0.412 | 45.611 / 47.539 | 0.979148 / 0.986739 | 0.9172 / 0.7164 |
74
- | Lit surface · 50 | 2,825 | 2,825 | 2.909 | 38.241 / 40.292 | 0.891825 / 0.915090 | 4.4182 / 2.4656 |
75
- | Lit surface · 80 | 4,107 | 4,107 | 2.314 | 39.474 / 40.530 | 0.903290 / 0.918372 | 3.1575 / 2.1072 |
76
- | Lit surface · 90 | 5,465 | 5,453 | 1.988 | 40.214 / 40.738 | 0.912497 / 0.921402 | 2.2827 / 1.9999 |
77
- | Lit surface · 99 | 47,155 | 47,152 | 0.466 | 45.664 / 46.636 | 0.975682 / 0.980349 | 0.9476 / 1.1255 |
78
- | Grace Hopper · 50 | 15,990 | 15,982 | 5.451 | 29.546 / 30.676 | 0.778970 / 0.807254 | 6.6973 / 4.5990 |
79
- | Grace Hopper · 80 | 34,419 | 34,409 | 2.338 | 33.675 / 34.477 | 0.862229 / 0.899859 | 3.8410 / 3.0344 |
80
- | Grace Hopper · 90 | 56,543 | 56,519 | 1.110 | 38.605 / 38.944 | 0.950222 / 0.970007 | 2.3300 / 1.4546 |
81
- | Grace Hopper · 99 | 110,542 | 110,533 | 0.173 | 49.948 / 47.182 | 0.996348 / 0.995977 | 0.4889 / 0.3406 |
82
-
83
- ### The core's lossy modular on drawings and paintings
84
-
85
- Fourteen rows are matched within 0.127%. In the ten **†** rows an exact native stream fits in 3.052% to
86
- 51.144% fewer bytes. Native SSIM and Butteraugli are better in all 24 rows; PSNR favours Rapier for paint-0001
87
- and paint-0002 at q80 and q90.
88
-
89
- | Input · Rapier q | Rapier bytes | Native bytes | Native d | PSNR dB R / N ↑ | RGB SSIM R / N ↑ | Butteraugli R / N ↓ |
90
- | --- | ---: | ---: | ---: | ---: | ---: | ---: |
91
- | recipe-0000 · 50 | 14,972 | 14,953 | 1.102 | 37.276 / 45.992 | 0.989901 / 0.996956 | 3.9870 / 1.0352 |
92
- | recipe-0000 · 80 | 20,051 | 19,439† | 0 | 42.493 / ∞ | 0.995389 / 1.000000 | 2.6508 / 0.0000 |
93
- | recipe-0000 · 90 | 25,232 | 19,439† | 0 | 46.614 / ∞ | 0.997777 / 1.000000 | 1.4840 / 0.0000 |
94
- | recipe-0000 · 99 | 39,788 | 19,439† | 0 | 58.188 / ∞ | 0.999803 / 1.000000 | 0.5505 / 0.0000 |
95
- | recipe-0001 · 50 | 31,284 | 31,279 | 1.642 | 44.663 / 51.571 | 0.998111 / 0.999252 | 3.6761 / 0.9379 |
96
- | recipe-0001 · 80 | 37,226 | 35,491† | 0 | 49.928 / ∞ | 0.999293 / 1.000000 | 2.1718 / 0.0000 |
97
- | recipe-0001 · 90 | 43,439 | 35,491† | 0 | 53.998 / ∞ | 0.999699 / 1.000000 | 0.7681 / 0.0000 |
98
- | recipe-0001 · 99 | 60,225 | 35,491† | 0 | 63.163 / ∞ | 0.999953 / 1.000000 | 0.2608 / 0.0000 |
99
- | recipe-0002 · 50 | 18,746 | 18,736 | 1.651 | 34.484 / 40.065 | 0.978619 / 0.992176 | 5.0667 / 1.4144 |
100
- | recipe-0002 · 80 | 27,036 | 27,025 | 0.749 | 39.856 / 43.976 | 0.990765 / 0.995802 | 2.5048 / 1.0876 |
101
- | recipe-0002 · 90 | 35,330 | 35,306 | 0.425 | 44.044 / 46.482 | 0.995583 / 0.997393 | 1.7128 / 0.6129 |
102
- | recipe-0002 · 99 | 59,624 | 36,102† | 0 | 55.523 / ∞ | 0.999602 / 1.000000 | 0.5762 / 0.0000 |
103
- | paint-0000 · 50 | 45,505 | 45,498 | 1.336 | 36.020 / 40.143 | 0.975668 / 0.991663 | 4.7640 / 1.5576 |
104
- | paint-0000 · 80 | 54,652 | 54,618 | 0.684 | 39.900 / 43.518 | 0.988270 / 0.995770 | 2.7126 / 0.9569 |
105
- | paint-0000 · 90 | 65,877 | 65,843 | 0.289 | 43.809 / 47.922 | 0.994447 / 0.998212 | 1.5637 / 0.6630 |
106
- | paint-0000 · 99 | 97,769 | 74,658† | 0 | 54.474 / ∞ | 0.999312 / 1.000000 | 0.5388 / 0.0000 |
107
- | paint-0001 · 50 | 61,462 | 61,442 | 1.680 | 33.606 / 34.830 | 0.956089 / 0.972432 | 7.1021 / 1.8314 |
108
- | paint-0001 · 80 | 75,929 | 75,926 | 0.830 | 37.751 / 37.312 | 0.980393 / 0.982498 | 3.5325 / 1.1308 |
109
- | paint-0001 · 90 | 93,451 | 93,451 | 0.343 | 41.285 / 40.730 | 0.990186 / 0.990973 | 1.9406 / 0.8474 |
110
- | paint-0001 · 99 | 143,736 | 124,100† | 0 | 51.793 / ∞ | 0.998622 / 1.000000 | 0.6179 / 0.0000 |
111
- | paint-0002 · 50 | 81,312 | 81,304 | 2.020 | 32.334 / 34.083 | 0.937986 / 0.962754 | 5.7590 / 2.1400 |
112
- | paint-0002 · 80 | 98,676 | 98,622 | 1.055 | 36.749 / 36.598 | 0.972358 / 0.976745 | 3.1658 / 1.3790 |
113
- | paint-0002 · 90 | 119,652 | 119,651 | 0.472 | 40.420 / 39.711 | 0.986313 / 0.987143 | 1.8052 / 1.1247 |
114
- | paint-0002 · 99 | 187,934 | 170,069† | 0 | 50.304 / ∞ | 0.997853 / 1.000000 | 0.5696 / 0.0000 |
115
-
116
- ### The JPEG carrier against reversible recompression
117
-
118
- `transcode` against `cjxl --lossless_jpeg=1`. The contracts differ: Rapier carries the coefficients and the
119
- orientation and cannot rebuild the JPEG file; native also stores the reconstruction data and rebuilt every
120
- original exactly. So a smaller Rapier row is not a like-for-like win. Six inputs are tiny conformance fixtures;
121
- Grace is the one photograph.
122
-
123
- | JPEG input | Original bytes | Rapier carrier | Native reversible | JPEG rebuilt by native |
124
- | --- | ---: | ---: | ---: | --- |
125
- | grey-sequential.jpg | 141 | 117 | 177 | yes |
126
- | quant-before-scan.jpg | 141 | 120 | 181 | yes |
127
- | quant-after-scan.jpg | 210 | 120 | refused | no reconstruction data possible |
128
- | colour-sequential.jpg | 925 | 499 | 662 | yes |
129
- | colour-progressive-restarts.jpg | 1,367 | 464 | 820 | yes |
130
- | decoder-difference.jpg | 925 | 499 | 662 | yes |
131
- | reconstruction-difference.jpg | 925 | 510 | 676 | yes |
132
- | grace-hopper.jpg | 86,089 | 60,543 | 58,773 | yes |
133
-
134
- ### Inputs and commands
135
-
136
- Textures are the first four `synthetic-photo` seeds of `tools/corpus/jxl-images.mjs` at 512 × 384; drawings and
137
- paintings the first three seeds of each kind at the same size, through the real Draw and Paint owners with the
138
- bundled Geist fonts and pinned `@napi-rs/canvas` 0.1.100. Grace is the public-domain fixture in
139
- `public/test/photo-corpus/`, decoded once with `ffmpeg -pix_fmt rgba` at its native size.
140
-
141
- | Input | Dimensions | Seed | Pixels with alpha < 255 | RGBA SHA-256 |
142
- | --- | ---: | ---: | ---: | --- |
143
- | Wood grain | 512 × 384 | 3399228925 | 0 | `cfc2495231eb53c4d423ab5ea7692e3cafce6160f07cc51942b910a89d5ffab7` |
144
- | Landscape | 512 × 384 | 1351084136 | 0 | `c6c33cc695b19538ec6780647e32fd4f22b24872292d22707f26ab9ad1a0cef2` |
145
- | Folded texture | 512 × 384 | 3597906643 | 0 | `57a0f40e8e14eb0b9fe5286dbfec7bc7783b1e748c6888ea44791f088b0ea106` |
146
- | Lit surface | 512 × 384 | 1549761854 | 0 | `d998747e1c427144424c4d59f6795f88a2d2983a341dcba54b5a6b346e4f0a0d` |
147
- | Grace Hopper | 512 × 600 | — | 0 | `af6a4dc548da3797b814f4be1b4489effe658ad13ba842d839628d01ba3ee39f` |
148
- | recipe-0000 | 512 × 384 | 2385324683 | 23,538 | `6d8240cac5db3c3db509125658ba32cb919537b113ae9f0877856fe6c091e67f` |
149
- | recipe-0001 | 512 × 384 | 337179894 | 163,701 | `7a5acaf7274bbadfabaa351f0dd3a7931818a438424d17ae555a2c2f72338b9f` |
150
- | recipe-0002 | 512 × 384 | 2584002401 | 20,928 | `08ce520f30a10897b4acbb630e8d39aeeb97d53e02b50ac721ad80260fd2484c` |
151
- | paint-0000 | 512 × 384 | 744793156 | 194,804 | `ad9467838000b99434d7f7cb041111cd0bad8f64fe2511ce98d0d106ebd147a3` |
152
- | paint-0001 | 512 × 384 | 2991615663 | 193,919 | `a3af498e21cade0969d0b3fa63118316a18601aa23bd1b35ffc561e70a123237` |
153
- | paint-0002 | 512 × 384 | 943470874 | 191,708 | `d2c586460a91e5bcb4c213080f46e4945d674ed6920230f50b2ab396076ea3c1` |
154
-
155
- ```sh
156
- cjxl INPUT.pam OUTPUT.jxl --distance=D --effort=7 --num_threads=0 \
157
- --alpha_distance=0 --keep_invisible=1 --premultiply=0 --container=0 \
158
- -x color_space=RGB_D65_SRG_Per_SRG --quiet
159
- djxl INPUT.jxl OUTPUT.pam --bits_per_sample=8 --color_space=RGB_D65_SRG_Per_SRG --num_threads=0 --quiet
160
- butteraugli_main REFERENCE.ppm DECODED.ppm --intensity_target 80
161
- cjxl INPUT.jpg OUTPUT.jxl --lossless_jpeg=1 --effort=7 --num_threads=0 --quiet
162
- djxl OUTPUT.jxl RECONSTRUCTED.jpg --reconstruct_jpeg --num_threads=0 --quiet
163
- ```
164
-
165
- From `repo/` in the development checkout, `tools/probes/jxl-quality.mjs --section=photo|modular|jpeg` makes
166
- the inputs, encodes, runs the bracket search and writes `photo.json`, `modular.json` and `jpeg.json`;
167
- `tools/probes/jxl-quality-metrics.py` computes the metrics (Python 3.12, NumPy 2.3, SciPy 1.17). The full
168
- candidate pools, byte brackets, hashes and metric components are in the development receipts.
169
-
170
- ## Found but not ranked
171
-
172
- | Project | Why not |
173
- | --- | --- |
174
- | [jxl-oxide-wasm](https://github.com/tirr-c/jxl-oxide-wasm) 0.12.6 (1,707,757 bytes, 612,548 gzip) | Decoder only |
175
- | [jxl.js](https://github.com/niutech/jxl.js), jxl-rs-polyfill, TurboJXL, PureJsImage's codec | Decoders |
176
- | [libjxl](https://github.com/libjxl/libjxl/blob/main/doc/building_wasm.md) WASM build | No published encoder payload; the libjxl-based packages above stand for it |
177
- | [jixel](https://github.com/awxkee/jixel) `d84b45ff` | No published WASM |
178
- | [libjxl-tiny](https://github.com/libjxl/libjxl-tiny) `8eae1817` | No published WASM |
179
- | [Hydrium](https://github.com/Traneptora/hydrium) `45227f35` | Native executables only |
180
- | [Imazen jxl-encoder](https://github.com/imazen/jxl-encoder) `0d79a23e` | No published WASM |
181
- | webcvt's jsquash-jxl adapter | The jSquash encoder, already measured |
182
- | sharp, libvips, the `cjxl` npm wrapper | Native binaries |
183
-
184
- Searched: npm's `jpeg-xl`, `jpegxl`, `jxl` and `jixel` results, upstream repositories, the web. A smaller usable
185
- encoder published later belongs in this table, and the claim changes with it.
package/effort.min.mjs DELETED
@@ -1,2 +0,0 @@
1
- /*! Rapier JXL 2.1.0 | MIT License Copyright (c) 2026 rapier.website Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */
2
- const t=Object.freeze({bytes:16777216,pixels:24e6,edge:16384}),n=[1];for(let t=1;t<=32;t++)n.push(2*n[t-1]);class e{constructor(t=4096){this.bytes=new Uint8Array(t),this.at=0,this.t=0,this.pending=0}write(t,e){if(!(e>=0&&e<n[t]))throw new Error("bit write out of range: "+t+" bits, "+e);let o=this.t+e*(1<<this.pending),r=this.pending+t;this.at+5>=this.bytes.length&&this.grow();const s=this.bytes;for(;r>=8;)s[this.at++]=255&o,o=Math.floor(o/256),r-=8;this.t=o,this.pending=r}o(t,e){for(let o=0;o<4;o++){const[r,s]=t[o];if(e>=s&&e-s<n[r])return this.write(2,o),void(r&&this.write(r,e-s))}throw new Error("U32 value out of range: "+e)}i(){this.pending&&(this.at+1>=this.bytes.length&&this.grow(),this.bytes[this.at++]=this.t,this.t=0,this.pending=0)}get u(){return 8*this.at+this.pending}grow(n=0){const e=t.bytes+64;if(this.at+n+16>e)throw Object.assign(new Error("The encoded picture exceeds 16 MiB."),{code:"JXL_SIZE"});const o=new Uint8Array(Math.min(e,Math.max(2*this.bytes.length,this.at+n+16)));o.set(this.bytes.subarray(0,this.at)),this.bytes=o}append(t){if(this.at+t.at+8>=this.bytes.length&&this.grow(t.at+8),this.pending)for(let n=0;n<t.at;n++)this.write(8,t.bytes[n]);else this.bytes.set(t.bytes.subarray(0,t.at),this.at),this.at+=t.at;t.pending&&this.write(t.pending,t.t)}finish(){const t=this.at+(this.pending?1:0),n=this.bytes.slice(0,t);return this.pending&&(n[this.at]=this.t),n}}function*o(t,n,e){let o,r;for(;!(o=t.next(r)).done;)r=yield(n+o.value)/e;return o.value}function r(t){return t>=0?2*t:2*-t-1}function s(t){return 31-Math.clz32(t)}function c(t){return t<=1?0:32-Math.clz32(t-1)}const i=(t,n)=>Object.assign(new Error(n),{code:t});function l(n){if(n>t.bytes)throw i("JXL_SIZE","The encoded picture exceeds 16 MiB.")}function f(t){try{return t()}catch(t){if(t instanceof RangeError&&!t.code)throw i("JXL_MEMORY","Not enough memory for this picture.");throw t}}const a=256;function u(t,n){t.o([[9,1],[13,1],[18,1],[30,1]],n)}function h(t,n,e,o,r,{xyb:s=!1,orientation:c=1,colorSpace:i}={}){t.write(16,2815),t.write(1,0),u(t,e),t.write(3,0),u(t,n),t.write(1,0);const l=1!==c;t.write(1,l?1:0),l&&t.write(6,c-1),t.write(4,8),r?t.write(3,5):t.write(2,0),t.write(1,s?1:0),3===o&&"display-p3"===i?t.write(21,903568):3===o?t.write(1,1):t.write(15,14100),l&&t.write(1,1),t.write(2,0),t.write(1,1),t.i()}function y(t,{alpha:n,shift:e=1}){t.write(9,8),n&&t.write(2,0),t.write(2,e),function(t,n){t.write(n?7:5,0),t.write(11,1)}(t,n)}function w(t,n,e=256){const o=Math.ceil(t/e),r=Math.ceil(n/e);return{p:o,M:r,A:Math.ceil(t/(8*e)),m:Math.ceil(n/(8*e)),U:1===o&&1===r}}function p(t,n){const e=n.map(t=>t.length);!function(t,n){t.write(1,0),t.i();for(const e of n)t.o([[10,0],[14,1024],[22,17408],[30,4211712]],e);t.i()}(t,e);const o=t.finish(),r=o.length+e.reduce((t,n)=>t+n,0);l(r);const s=new Uint8Array(r);s.set(o);let c=o.length;for(const t of n)s.set(t,c),c+=t.length;return s}const d=[1,2,3,4,0,5,17,6,16,7,8,9,10,11,12,13,14,15],M=[2,4,3,2,2,4],A=[0,7,3,2,1,15];function g(t,n){const e=new Uint8Array(t.length),o=[];for(let n=0;n<t.length;n++)t[n]>0&&o.push(n);if(!o.length)return e;if(1===o.length)return e[o[0]]=1,e;for(let r=1;;r*=2){const s=o.map(n=>({w:Math.max(t[n],r),I:n,l:null,r:null})).sort((t,n)=>t.w-n.w||t.I-n.I),c=[];let i=0,l=0;const f=()=>i<s.length&&(l>=c.length||s[i].w<=c[l].w)?s[i++]:c[l++];for(;s.length-i+(c.length-l)>1;){const t=f(),n=f();c.push({w:t.w+n.w,I:-1,l:t,r:n})}const a=[[f(),0]];let u=0;for(;a.length;){const[t,n]=a.pop();t.I>=0?(e[t.I]=n,n>u&&(u=n)):a.push([t.l,n+1],[t.r,n+1])}if(u<=n)return e}}function m(t,n,e,o,r,s){const c=[];for(;c.push(o&r),o>>=s;)o--;for(let o=c.length-1;o>=0;o--)t.push(e),n.push(c[o])}function b(t,n){let e=0;for(let o=0;o<n;o++)e=e<<1|1&t,t>>=1;return e}function U(t){const n=new Uint32Array(17),e=new Uint32Array(17),o=new Uint16Array(t.length);for(const e of t)n[e]++;n[0]=0;for(let t=1,o=0;t<=16;t++)o=o+n[t-1]<<1,e[t]=o;for(let n=0;n<t.length;n++)t[n]&&(o[n]=b(e[t[n]]++,t[n]));return o}function I(t){const n=[];for(let e=0;e<t.length;e++)t[e]>0&&n.push(e);const e=n.length,o=n[e-1]+1||1,r=e<=4?new Uint8Array(o):g(t,15).subarray(0,o);let s=0;if(e<=4){n.sort((n,e)=>t[e]-t[n]||n-e),s=4===e&&t[n[0]]>t[n[2]]+t[n[3]]?1:0;const o=e<2?[0]:2===e?[1,1]:3===e?[1,2,2]:s?[1,2,3,3]:[2,2,2,2];n.forEach((t,n)=>{r[t]=o[n]}),n.sort((t,n)=>r[t]-r[n]||t-n)}return{S:o,C:r,T:U(r),v:e&&e<=4?n:null,q:s}}function S(t,n){if(n.S<=1)return;if(n.v){const e=s(n.S-1)+1;t.write(2,1),t.write(2,n.v.length-1);for(const o of n.v)t.write(e,o);return void(4===n.v.length&&t.write(1,n.q))}const e=n.C,o=[],r=[];let c=e.length-1;for(;c>0&&!e[c];)c--;let i=8;for(let t=0;t<=c;){const n=e[t];let s=1;for(;t+s<=c&&e[t+s]===n;)s++;let l=s;if(n&&n!==i&&(o.push(n),r.push(0),l--,i=n),l<3)for(let t=0;t<l;t++)o.push(n),r.push(0);else m(o,r,n?16:17,l-3,n?3:7,n?2:3);t+=s}const l=new Uint32Array(18);for(const t of o)l[t]++;let f=0;for(const t of l)t&&f++;1===f&&(l[o[0]?0:1]=l[o[0]]);const a=g(l,5),u=U(a);let h=18;for(;h>0&&!a[d[h-1]];)h--;t.write(2,0);for(let n=0;n<h;n++){const e=a[d[n]];t.write(M[e],A[e])}for(let n=0;n<o.length;n++){const e=o[n];t.write(a[e],u[e]),16===e?t.write(2,r[n]):17===e&&t.write(3,r[n])}}function x(t,n=0,e=0){return{split:t,L:n,X:e,J:1<<t}}function C(t,n,e=15){t.write(c(e+1),n.split),n.split!==e&&(t.write(c(n.split+1),n.L),t.write(c(n.split-n.L+1),n.X))}function T(t,n,e){if(n<t.J)return e[0]=n,e[1]=0,void(e[2]=0);const o=s(n),r=n-(1<<o),c=o-t.L-t.X;e[0]=t.J+(o-t.split<<t.L+t.X|r>>o-t.L<<t.X|r&(1<<t.X)-1),e[1]=c,e[2]=n>>t.X&(1<<c)-1}function v(t,n,e,o=0){T(t,n,E),e[o+E[0]]++}const E=[0,0,0];function q(t,n,e,o,r=0){T(e,o,E);const s=r+E[0];t.write(n.C[s],n.T[s]),E[1]&&t.write(E[1],E[2])}function L(t,{N:n=null,_:e,P:o}){t.write(1,n?1:0),n&&(t.o([[0,224],[0,512],[0,4096],[15,8]],n.G),t.o([[0,3],[0,4],[2,5],[8,9]],n.minLength),C(t,n.O,8)),e.length>1&&function(t,n){let e=0;for(const t of n)e=Math.max(e,t);const o=e?c(e+1):0;if(o<=3){if(t.write(1,1),t.write(2,o),o)for(const e of n)t.write(o,e);return}t.write(1,0),t.write(1,0);const r=x(8),s=x(4),i=n.length>=64,l=new Uint32Array(i?257:e+1),f=[];for(let t=0;t<n.length;){let e=1;for(;i&&t+e<n.length&&n[t+e]===n[t];)e++;if(e>=8)f.push([n[t],e-1]),l[n[t]]++,v(s,e-1-7,l,224),t+=e;else{for(let o=0;o<e;o++)f.push([n[t],0]),l[n[t]]++;t+=e}}const a=I(l);if(!i){L(t,{_:new Uint8Array(1),P:[{config:r,code:a}]});for(const[n]of f)q(t,a,r,n);return}const u=new Uint32Array(1);u[0]=1,L(t,{N:{G:224,minLength:7,O:s},_:new Uint8Array([1,0]),P:[{config:x(0),code:I(u)},{config:r,code:a}]});for(const[n,e]of f)q(t,a,r,n),e&&q(t,a,s,e-7,224)}(t,e),t.write(1,1);for(const n of o)C(t,n.config,15);for(const n of o){const e=n.code.S-1;if(e){const n=s(e);t.write(1,1),t.write(4,n),t.write(n,e-(1<<n))}else t.write(1,0)}for(const n of o)S(t,n.code)}const X=3,J=5,N=x(4),_=Object.freeze({G:224,minLength:7,O:N}),P=x(0),z=257;function G(t,n=0,e=1){return{R:t,offset:n,j:e,context:-1}}function O(t,n,e,o){return{Z:t,B:n,left:e,right:o}}function R(t,n){const e=(o,r)=>{if(o===r)return n?t[o].k:t[o];const s=o+r>>1;return O(n,n?t[s].Y:s,e(s+1,r),e(o,s))};return e(0,t.length-1)}function j(t){return R(t,0)}function Z(t,n){const e=[n],o=[],s=[];for(const t of e){if(t.left){o.push(t.Z+1,r(t.B)),e.push(t.left,t.right);continue}t.context=s.length,s.push(t);const n=1===t.j?0:31-Math.clz32(t.j&-t.j);o.push(0,t.R,r(t.offset),n,(t.j>>n)-1)}const c=new Uint32Array(64);for(const t of o)v(P,t,c);const i=I(c);L(t,{_:new Uint8Array(6),P:[{config:P,code:i}]});for(const n of o)q(t,i,P,n);return s}function B(t,n){const e=[[3,0],[6,8],[10,72],[13,1096]];if("rct"===n.type)t.write(2,0),t.o(e,n.D),t.o([[0,6],[2,0],[4,2],[6,10]],n.F??6);else if("palette"===n.type)t.write(2,1),t.o(e,n.D),t.o([[0,1],[0,3],[0,4],[13,1]],n.H),t.o([[8,0],[10,256],[12,1280],[16,5376]],n.K),t.o([[0,0],[8,1],[10,257],[16,1281]],n.V??0),t.write(4,n.R??0);else{if("squeeze"!==n.type)throw new Error("unknown transform "+n.type);{t.write(2,2);const o=n.W??[];t.o([[0,0],[4,1],[6,9],[8,41]],o.length);for(const n of o)t.write(1,n.$?1:0),t.write(1,n.tt?1:0),t.o(e,n.D),t.o([[0,1],[0,2],[0,3],[4,4]],n.H)}}}function k(t,{nt:n=!0,et:e=[]}={}){t.write(1,n?1:0),t.write(1,1),t.o([[0,0],[0,1],[4,2],[8,18]],e.length);for(const n of e)B(t,n)}function Y(t,n,e,o=t=>t.context){const r=new Uint8Array(n.length+1);for(const t of n)r[t.context]=o(t)+1;const s=new Uint32Array(2);s[1]=1;const c=[{config:P,code:I(s)}],i=Math.max(...r);for(let t=0;t<i;t++)c.push({config:P,code:I(e[t])});return L(t,{N:_,_:r,P:c}),c}function D(t,n,e,o,s,c){const i=c.R,l=c.offset,f=c.j,a=P,u=N;let h=0;const y=(e,o=a,r=0)=>{t?q(t,n,o,e,r):v(o,e,n,r)},w=()=>{if(h){if(h>=8)y(0),y(h-7-1,u,224);else for(let t=0;t<h;t++)y(0);h=0}},p=(t,n)=>{const o=e[t];let s;1===f?s=o-n-l:(s=Math.round((o-n-l)/f),e[t]=n+l+s*f),0!==s?(w(),y(r(s))):h++};for(let t=0,n=0;t<s;t++)for(let r=0;r<o;r++,n++){let s;if(0===i)s=0;else{const c=r?e[n-1]:t?e[n-o]:0,l=t?e[n-o]:c;if(1===i)s=c;else if(2===i)s=l;else if(3===i)s=(c+l)/2|0;else{const f=c+l-(r&&t?e[n-o-1]:c);if(5===i){const t=c<l?c:l,n=c<l?l:c;s=f<t?t:f>n?n:f}else{if(4!==i)throw new Error("predictor "+i+" is not coded here");s=Math.abs(f-c)<Math.abs(f-l)?c:l}}}p(n,s)}w()}function F(t,n,e,{palette:o=!0}={}){const r=n*e;let s=!0,c=!0;for(let n=0,e=0;e<r&&(255===t[n+3]||(s=!1,c))&&(t[n]===t[n+1]&&t[n]===t[n+2]||(c=!1,s));e++,n+=4);const i=c?1:3,l=!s,f=i+(l?1:0),a=n=>c?l?t[n]+256*t[n+3]:t[n]:t[n]+256*t[n+1]+65536*t[n+2]+(l?16777216*t[n+3]:0),u=new Map;let h=null;for(let t=0,n=0;o&&n<r;n++,t+=4){const n=a(t);if(0!==n&&!u.has(n)&&(u.set(n,1),u.size>=2048))break}if(o&&u.size<2048){const t=[...u.keys()],n=(t,n)=>t>>>8*n&255,e=t=>(.299*n(t,0)+.587*n(t,1)+.114*n(t,2)+.01)*(4===f?n(t,3):1);t.sort((t,n)=>e(t)-e(n));const o=new Map([[0,0]]);t.forEach((t,n)=>o.set(t,n+1)),h={colours:[0,...t],index:o,key:a,byte:n}}return{colour:i,alpha:l,channels:f,palette:h}}function*Q(t,n,e,{shape:r=F(t,n,e),colorSpace:s}={}){let c,i;const l=r.palette?[r,{...r,palette:null}]:[r];for(let r=0;r<l.length;r++)try{const i=yield*o(H(t,n,e,l[r],s),r,l.length);(!c||i.length<c.length)&&(c=i)}catch(t){if("JXL_SIZE"!==t.code)throw t;i=t}if(!c)throw i;return c}function*H(t,n,o,r,s){const{channels:c,palette:i}=r,l=w(n,o),f=i?2:c,u=Array.from({length:f},()=>G(J)),d=j(u),M=Array.from({length:f},()=>new Int16Array(65536)),A=(e,o,r,s)=>{for(let l=0;l<s;l++){let s=4*((o+l)*n+e),f=l*r;for(let n=0;n<r;n++,s+=4,f++)if(i)M[0][f]=i.index.get(i.key(s));else if(c>=3){const n=t[s],e=t[s+1],o=t[s+2],r=n-o,i=o+(r>>1),l=e-i;M[0][f]=i+(l>>1),M[1][f]=r,M[2][f]=l,4===c&&(M[3][f]=t[s+3])}else M[0][f]=t[s],2===c&&(M[1][f]=t[s+3])}};let g=null;if(i){const t=i.colours.length;g=new Int16Array(t*c),i.colours.forEach((n,e)=>{for(let o=0;o<c;o++)g[o*t+e]=i.byte(n,o)})}const m=t=>u[i?l.U?1:0:t],b=i?1:c,U=l.p*l.M,x=2*U,C=(t,e)=>{const r=t%l.p*a,s=(t/l.p|0)*a,c=Math.min(a,n-r),i=Math.min(a,o-s);A(r,s,c,i),e(c,i)};let T=0;if(!i){const t=t=>{const n=I(t),o=new e(128);S(o,n);let r=o.u;for(let e=0;e<t.length;e++)t[e]&&(r+=t[e]*(n.C[e]+Math.max(0,e<224?e-1:e-236)));return r},r=Math.min(n,32),s=Math.min(o,32),i=[J,X].map(t=>({R:t,ot:u.map(()=>new Uint32Array(z))}));for(const t of[0,.5,1]){A(Math.floor((n-r)*t),Math.floor((o-s)*t),r,s);for(const t of i)for(let n=0;n<c;n++)D(null,t.ot[n],M[n],r,s,G(t.R))}u.forEach((n,e)=>{const o=i.map(n=>t(n.ot[e])),r=o[1]<o[0]?1:0;n.R=i[r].R})}const v=i?[{type:"palette",D:0,H:c,K:i.colours.length}]:c>=3?[{type:"rct",D:0,F:6}]:[],E=u.map(()=>new Uint32Array(z)),q=t=>E[u.indexOf(t)];g&&D(null,q(u[0]),g,i.colours.length,c,u[0]);for(let t=0;t<U;t++)C(t,(t,n)=>{for(let e=0;e<b;e++)D(null,q(m(e)),M[e],t,n,m(e))}),yield++T/x;const L=new e(256);h(L,n,o,r.colour,r.alpha,{colorSpace:s}),y(L,{alpha:r.alpha});const N=new e(4096);N.write(1,1),N.write(1,1);const _=Z(N,d),P=Y(N,_,E,t=>u.indexOf(t)),O=t=>P[u.indexOf(t)+1].code;k(N,{nt:!0,et:v}),g&&D(N,O(u[0]),g,i.colours.length,c,u[0]);const R=[];if(l.U)C(0,(t,n)=>{for(let e=0;e<b;e++)D(N,O(m(e)),M[e],t,n,m(e))}),yield 1,R.push(N.finish());else{R.push(N.finish());for(let t=0;t<l.A*l.m+1;t++)R.push(new Uint8Array(0));for(let t=0;t<U;t++)C(t,(t,n)=>{const o=new e(t*n*b+64);k(o,{nt:!0,et:[]});for(let e=0;e<b;e++)D(o,O(m(e)),M[e],t,n,m(e));R.push(o.finish())}),yield++T/x}return p(L,R)}const K=(t,n)=>t+n+(t>n?1:0)>>1;function V(t,n,e){const o=t>=n&&n>=e?1:t<=n&&n<=e?-1:0;if(!o)return 0;let r=(4*(t*=o)-3*(e*=o)-(n*=o)+6)/12|0;return r-(1&r)>2*(t-n)&&(r=2*(t-n)+1),r+(1&r)>2*(n-e)&&(r=2*(n-e)),o*r}function W(t,n){const{w:e,h:o,data:r}=t,s=n?e:o,c=s+1>>1,i=s-c,l=n?c:e,f=n?o:c,a=n?i:e,u=n?o:i,h=new Int16Array(l*f),y=new Int16Array(a*u),w=n?1:e;for(let t=0;t<f;t++)for(let o=0;o<l;o++){const c=n?o:t,f=n?t*e+2*o:2*t*e+o;if(c===i){h[t*l+o]=r[f];continue}const u=r[f],p=r[f+w],d=K(u,p),M=c+1<i?K(r[f+2*w],r[f+3*w]):1&s?r[f+2*w]:d;h[t*l+o]=d,y[t*a+o]=u-p-V(c?r[f-w]:d,d,M)}const p=(e,o,r)=>({w:o,h:r,rt:t.rt+ +n,st:t.st+!n,data:e});return[p(h,l,f),p(y,a,u)]}function $(t){const n=[],e=t.length;let o=t[0].w,r=t[0].h;for(e>2&&t[1].w===o&&t[1].h===r&&(n.push({$:!0,tt:!1,D:1,H:2}),n.push({$:!1,tt:!1,D:1,H:2})),!(o>r)&&r>8&&(n.push({$:!1,tt:!0,D:0,H:e}),r=r+1>>1);o>8||r>8;)o>8&&(n.push({$:!0,tt:!0,D:0,H:e}),o=o+1>>1),r>8&&(n.push({$:!1,tt:!0,D:0,H:e}),r=r+1>>1);return n}const tt=1024,nt=8192;function*et(t,n,o,{quality:r=90,shape:s=F(t,n,o,{palette:!1}),colorSpace:c}={}){const i=function(t){return t>=100?0:t>=30?.1+.09*(100-t):53/3e3*t*t-1.15*t+25}(r);if(!(i>0))throw new Error("lossy encoding needs a quality below 100");const{colour:l,alpha:f,channels:a}=s,u=n*o,d=Array.from({length:a},()=>new Int16Array(u));for(let n=0,e=0;e<u;e++,n+=4){if(3===l){const o=t[n],r=t[n+1],s=t[n+2],c=o-s,i=s+(c>>1),l=r-i;d[0][e]=i+(l>>1),d[1][e]=c,d[2][e]=l}else d[0][e]=t[n];f&&(d[a-1][e]=t[n+3])}const M=d.map((t,e)=>({w:n,h:o,rt:0,st:0,data:t,component:3===l?e:0===e?0:3})),A=1===n&&o>tt&&3===l,g=A?$(M).filter(t=>!t.$):1!==n&&1!==o||3!==l?[]:$(M),m=function(t,n=$(t)){const e=t.map(t=>({...t,level:0,ct:!1}));for(const t of n){const n=t.D+t.H-1,o=t.tt?n+1:e.length;for(let r=t.D;r<=n;r++){const[n,s]=W(e[r],t.$),c=e[r].level+1;e[r]={...n,level:c,ct:e[r].ct,component:e[r].component},e.splice(o+(r-t.D),0,{...s,level:c,ct:!0,component:e[r].component})}}return e}(M,g.length?g:void 0),b=t=>t.ct?0:J,U=t=>function(t,n,e,o){let r=Math.min(16,n+e);if(r>0&&r--,3===t)return 1;const s=.25*function(t){let n=1;for(let e=0;e<24;e++)n=(4*n+t/(n*n*n*n))/5;return t*n}(o),c=1<<r,i=1===t||2===t?.35*s*Math.max(.5,1024/c):.35*s*1.1*(163.84/c);return Math.max(1,Math.floor(i))}(t.component,t.rt+(A&&t.component>0&&t.component<3?1:0),t.st,i),I=[...3===l?[{type:"rct",D:0,F:6}]:[],{type:"squeeze",W:g}],S=w(n,o,tt),x=S.A*S.m,C=S.p*S.M;let T=0;for(;T<m.length&&m[T].w<=tt&&m[T].h<=tt;)T++;const v=(t,n,e,o)=>{const r=n>>t.rt,s=e>>t.st;return{x:r,y:s,w:Math.min(t.w,r+(o>>t.rt))-r,h:Math.min(t.h,s+(o>>t.st))-s}},E=[{index:0,Y:0,it:[]}];for(let t=0;t<T;t++)E[0].it.push({c:t,rect:{x:0,y:0,w:m[t].w,h:m[t].h}});if(!S.U){for(let t=0;t<x;t++){const n=t%S.A,e=t/S.A|0,o={index:1+t,Y:1+x+t,it:[]};for(let t=T;t<m.length;t++){const r=m[t];if(Math.min(r.rt,r.st)<3)continue;const s=v(r,n*nt,e*nt,nt);s.w>0&&s.h>0&&o.it.push({c:t,rect:s})}E.push(o)}for(let t=0;t<C;t++){const n=t%S.p,e=t/S.p|0,o={index:2+x+t,Y:1+3*x+17+t,it:[]};for(let t=T;t<m.length;t++){const r=m[t];if(Math.min(r.rt,r.st)>2)continue;const s=v(r,n*tt,e*tt,tt);s.w>0&&s.h>0&&o.it.push({c:t,rect:s})}E.push(o)}}for(const t of E)for(const n of t.it){const t=m[n.c];n.lt=G(b(t),0,U(t)),n.lt.channel=n.c}const q=E.filter(t=>t.it.length),L=1===q.length?j(q[0].it.map(t=>t.lt)):function(t){return R(t,1)}(q.map(t=>({Y:t.Y,k:j(t.it.map(t=>t.lt))}))),X=new Int16Array(1048576),N=t=>{const n=m[t.c],e=t.rect;if(e.w===n.w&&e.h===n.h)return n.data;for(let t=0;t<e.h;t++)X.set(n.data.subarray((e.y+t)*n.w+e.x,(e.y+t)*n.w+e.x+e.w),t*e.w);return X},_=[...new Set(E.flatMap(t=>t.it.map(t=>t.c)))].sort((t,n)=>t-n),P=new Map(_.map((t,n)=>[t,n])),O=_.map(()=>new Uint32Array(z)),B=1+2*E.length;let Q=1;yield Q/B;for(const t of E){for(const n of t.it)D(null,O[P.get(n.c)],N(n),n.rect.w,n.rect.h,n.lt);yield++Q/B}const H=new e(256);h(H,n,o,l,f,{colorSpace:c}),y(H,{alpha:f,shift:n>256||o>256?3:1});const K=Array.from({length:S.U?1:2+x+C},()=>null),V=K[0]=new e(65536);V.write(1,1),V.write(1,1);const et=Y(V,Z(V,L),O,t=>P.get(t.channel));k(V,{nt:!0,et:I});for(const t of E){let n=K[t.index];n||(n=K[t.index]=new e(4096),t.it.length&&k(n,{nt:!0,et:[]}));for(const e of t.it)D(n,et[P.get(e.c)+1].code,N(e),e.rect.w,e.rect.h,e.lt);yield++Q/B}return p(H,function(t){return t.map(t=>t?t.finish():new Uint8Array(0))}(K))}const ot=[-500,-392,-255,-191,-127,-95,-63,-47,-31,-23,-15,-11,-7,-4,-3,-1,0,1,3,5,7,11,15,23,31,47,63,95,127,191,255,392,500],rt=Int8Array.from({length:1003},(t,n)=>ot.filter(t=>t<n-501).length),st=Int32Array.from({length:64},(t,n)=>Math.floor(16777216/(n+1))),ct=(t,n)=>{const e=Math.max(0,26-Math.clz32(t+1));return 4+(n*st[t>>e]>>e)},it=[12,13].map(t=>Int32Array.from({length:2048},(n,e)=>ct(e,t))),lt=(t,n)=>t<2048?it[n-12][t]:ct(t,n),ft=new Int32Array(ot.length+1);let at;function ut(t,n,e,o,s,c=0,i=ft,l,f){const a=o+2,u=at&&at[0].length>=2*a?at:Array.from({length:6},(t,n)=>new Int32Array(n<5?2*a:8));at=null;for(const t of u)t.fill(0);const[h,y,w,p,d,M]=u;let A=0;const g=(e,o,r=P,s=0)=>{t?q(t,n[e],r,o,s):v(r,o,n[e],s)},m=()=>{if(A){if(A>_.minLength)g(M[0],0),g(M[1],A-_.minLength-1,_.O,_.G);else for(let t=0;t<A;t++)g(M[t],0);A=0}};for(let t=0,n=0;t<s;t++){const s=1&t?0:a,u=1&t?a:0;for(let a=0;a<o;a++,n++){const b=a?e[n-1]:t?e[n-o]:0,U=t?e[n-o]:b,I=a+1<o&&t?e[n-o+1]:U,S=u+a,x=a<o-1?S+1:S,C=a?S-1:S;let T=lt(h[S]+h[x]+h[C],13),v=lt(y[S]+y[x]+y[C],12),E=lt(w[S]+w[x]+w[C],12),q=lt(p[S]+p[x]+p[C],12);const L=8*U,X=8*b,J=8*I,N=a?d[s+a-1]:0,_=d[S],P=d[C],z=d[x],G=_+N;let O=N;Math.abs(_)>Math.abs(O)&&(O=_),Math.abs(P)>Math.abs(O)&&(O=P),Math.abs(z)>Math.abs(O)&&(O=z);const R=i[rt[O<-501?0:O>501?1002:O+501]];f&&(f[n]=O);const j=X+J-L,Z=L-(16*(G+z)>>5),B=X-(10*(G+P)>>5),k=L-(7*(P+_+z)>>5),Y=27-Math.clz32(T+v+E+q);T>>=Y,v>>=Y,E>>=Y,q>>=Y;const D=T+v+E+q;let F=Math.floor(((D>>1)-1+j*T+Z*v+B*E+k*q)*st[D-1]/16777216);if((_^N|_^P)<=0){const t=Math.min(X,J,L),n=Math.max(X,J,L);F=F<t?t:F>n?n:F}const Q=e[n],H=Q-(F+3>>3)-c;l?l[n]=r(H):0===H?(A<8&&(M[A]=R),A++):(m(),g(R,r(H)));const K=8*Q,V=Math.abs(j-K)+3>>3,W=Math.abs(Z-K)+3>>3,$=Math.abs(B-K)+3>>3,tt=Math.abs(k-K)+3>>3;d[s+a]=F-K,h[s+a]=V,y[s+a]=W,w[s+a]=$,p[s+a]=tt,h[u+a+1]+=V,y[u+a+1]+=W,w[u+a+1]+=$,p[u+a+1]+=tt}}m(),at=u}const ht=t=>Math.max(0,t<224?t-1:t-236),yt=t=>{const n=I(t),o=new e(128);S(o,n);let r=o.u;for(let e=0;e<t.length;e++)t[e]&&(r+=t[e]*(n.C[e]+ht(e)));return r};function wt(t,n,e,o,s){const c=new Uint32Array(n*e);if(6===o)return ut(null,null,t,n,e,0,void 0,c,s),c;for(let s=0,i=0;s<e;s++)for(let e=0;e<n;e++,i++){const l=e?t[i-1]:s?t[i-n]:0,f=s?t[i-n]:l,a=e&&s?t[i-n-1]:l,u=3===o?(l+f)/2|0:Math.max(Math.min(l,f),Math.min(Math.max(l,f),l+f-a));c[i]=r(t[i]-u)}return c}function pt(t,n,e=1){const o=Array.from({length:e},()=>new Uint32Array(z)),r=[],s=(t,e,s=0)=>{const c=n?n[t]:0;r.push(e,s,c),v(s?_.O:P,e,o[c],s)};for(let n=0;n<t.length;){let e=1;if(!t[n])for(;n+e<t.length&&!t[n+e];)e++;if(s(n,t[n]),e>_.minLength)s(n+1,e-1-_.minLength,_.G);else for(let t=1;t<e;t++)s(n+t,0);n+=e}return{ot:o,it:r}}function dt(t,n,e,o,r){const s=pt(t,Uint8Array.from(n,t=>{let n=0,e=r.length;for(;n<e;){const o=n+e>>1;t>r[o]?n=o+1:e=o}return n}),r.length+1),c=s.ot.length,i=s.ot.map(t=>t.some(t=>t)),l=new Float64Array(c+1).fill(1/0),f=new Uint8Array(c+1);l[0]=0;for(let t=1;t<=c;t++){const n=new Uint32Array(z);let e=0;for(let o=t-1;o>=0;o--){if(i[o]){for(let t=0;t<z;t++)n[t]+=s.ot[o][t];e=yt(n)}const r=l[o]+e+24;r<l[t]&&(l[t]=r,f[t]=o)}}const a=[];for(let t=c;t;t=f[t])a.unshift([f[t],t]);const u=a.map(()=>G(e)),h=new Uint8Array(c),y=a.map(([t,n],e)=>{const o=new Uint32Array(z);for(let r=t;r<n;r++){h[r]=e;for(let t=0;t<z;t++)o[t]+=s.ot[r][t]}return o});for(let t=2;t<s.it.length;t+=3)s.it[t]=h[s.it[t]];const w=(t,n)=>{if(t===n)return u[t];const e=t+n>>1;return O(o,r[a[e][1]-1],w(e+1,n),w(t,e))};return{leaves:u,k:w(0,a.length-1),ot:y,it:s.it}}const Mt=[-127,-31,-7,-1,0,1,7,31,127],At=[-191,-127,-63,-31,-7,-1,0,7,31,63,127,191,255];function gt(t,n,e,o){const r=new Int32Array(n*e);for(let s=0,c=0;s<e;s++)for(let e=0;e<n;e++,c++){const i=e?t[c-1]:s?t[c-n]:0,l=s?t[c-n]:i,f=e&&s?t[c-n-1]:i;r[c]=9===o?i+l-f:i-f}return r}function mt(t){const n=new e(512),o=Y(n,Z(n,t.k),t.ot,n=>t.leaves.indexOf(n));let r=n.u;return t.ot.forEach((t,n)=>{const{C:e}=o[n+1].code;for(let n=0;n<t.length;n++)r+=t[n]*((e[n]||0)+ht(n))}),r}function bt(t,n,e,o){return t.map(t=>{let r;for(const s of[5,3,6]){const c=6===s?new Int32Array(n*e):null,i=wt(t,n,e,s,c),l=G(s),f=[{...pt(i),leaves:[l],k:l}];if(c&&f.push(dt(i,c,s,15,Mt)),o>=6&&5===s)for(const o of[9,10]){const r=gt(t,n,e,o),c=9===o?At:Mt;f.push(dt(i,r,s,o,c))}for(const t of f){const n=mt(t);(!r||n<r.ft)&&(r={...t,ft:n})}}return r})}function Ut(t,n){const e=n.flatMap(t=>t.leaves),o=Y(t,Z(t,j(n.map(t=>t.k))),n.flatMap(t=>t.ot),t=>e.indexOf(t));return()=>{for(let r=0;r<n.length;r++){const{it:s}=n[r],c=n[r].leaves.map(t=>o[e.indexOf(t)+1].code);for(let n=0;n<s.length;n+=3){const e=s[n+1];q(t,c[s[n+2]],e?_.O:P,s[n],e)}}}}function*It(t,n,o,r,s,c=4,i=!1){const{channels:f}=r,u=w(n,o),d=u.p*u.M,M=i?r.palette:null;if(i&&!M)return null;const A=Array.from({length:M?1:f},()=>new Int16Array(65536)),g=new e(256);h(g,n,o,r.colour,r.alpha,{colorSpace:s}),y(g,{alpha:r.alpha});const m=M?[{type:"palette",D:0,H:f,K:M.colours.length}]:f>=3?[{type:"rct",D:0,F:6}]:[];let b;if(M){const t=M.colours.length,n=new Int16Array(t*f);M.colours.forEach((e,o)=>{for(let r=0;r<f;r++)n[r*t+o]=M.byte(e,r)}),b=bt([n],t,f,c)}const U=new e(4096),I=[];let S=0;const x=t=>{S+=Math.ceil(t.u/8),l(S),I.push(t.finish())};if(U.write(1,1),U.write(1,1),!u.U){let t;if(b)t=Ut(U,b);else{const t=A.map(()=>G(5));Y(U,Z(U,j(t)),A.map(()=>new Uint32Array(z)),n=>t.indexOf(n))}k(U,{et:m}),t&&t(),x(U);for(let t=0;t<u.A*u.m+1;t++)I.push(new Uint8Array(0))}for(let r=0;r<d;r++){const s=r%u.p*a,i=(r/u.p|0)*a,l=Math.min(a,n-s),h=Math.min(a,o-i);for(let e=0;e<h;e++)for(let o=0,r=e*l,c=4*((i+e)*n+s);o<l;o++,r++,c+=4)if(M)A[0][r]=M.index.get(M.key(c));else if(f>=3){const n=t[c]-t[c+2],e=t[c+2]+(n>>1),o=t[c+1]-e;A[0][r]=e+(o>>1),A[1][r]=n,A[2][r]=o,4===f&&(A[3][r]=t[c+3])}else A[0][r]=t[c],2===f&&(A[1][r]=t[c+3]);const y=bt(A,l,h,c),w=M&&u.U?[...b,...y]:y,p=u.U?U:new e(l*h*A.length+256);u.U||k(p,{nt:!1});const g=Ut(p,w);if(u.U&&k(p,{et:m}),g(),x(p),yield(r+1)/d)return null}return p(g,I)}function St(t,n,e,o){return function(t){let n;for(;!(n=t.next()).done;);return n.value}(xt(t,n,e,o))}function xt(n,e,r,s){const{quality:c,colorSpace:a}=function(t={},n=100){if(null===t||"object"!=typeof t)throw i("JXL_INPUT","Options are an object: {quality, colorSpace}.");if(void 0!==t.quality&&(n=t.quality),"number"!=typeof n||!Number.isFinite(n)||n<1||n>100)throw i("JXL_INPUT","Quality is a number from 1 to 100.");const e=void 0===t.colorSpace?"srgb":t.colorSpace;if("srgb"!==e&&"display-p3"!==e)throw i("JXL_INPUT","The colour space is srgb or display-p3.");return{quality:n,colorSpace:e}}(s),u=void 0===s?.effort?1:s.effort;if(!Number.isInteger(u)||u<1||u>9)throw i("JXL_INPUT","Effort is a whole number from 1 to 9.");return function(n,e,o,r){if(function(n,e,o=t){if(!Number.isInteger(n)||!Number.isInteger(e)||n<1||e<1)throw i("JXL_INPUT","A picture is at least one pixel wide and high, in whole pixels.");if(n>o.edge||e>o.edge)throw i("JXL_DIMENSIONS","A picture is at most "+o.edge+" pixels on a side.");if(n*e>o.pixels)throw i("JXL_DIMENSIONS","A picture is at most "+o.pixels.toLocaleString("en-US")+" pixels.")}(e,o,r),!(n instanceof Uint8Array||n instanceof Uint8ClampedArray))throw i("JXL_INPUT","Pixels are a Uint8Array or Uint8ClampedArray of RGBA bytes.");if(n.length!==e*o*4)throw i("JXL_INPUT","Pixels are width * height * 4 bytes: straight (not premultiplied) RGBA, row by row.")}(n,e,r),function(t){const n=function*(){let e,o;for(;!(e=f(()=>t.next(n.hurry))).done;)yield o=e.value;return o<1&&(yield 1),n.bytes=function(t){return l(t.length),t}(e.value)}();return n.bytes=null,n.hurry=!1,n}(function*(t,n,e,r,s,c){const i=F(t,n,e);if(r<100){if(!i.palette)return yield*et(t,n,e,{quality:r,shape:i,colorSpace:s});const c=yield*o(Q(t,n,e,{shape:i,colorSpace:s}),0,2);let l;try{l=yield*o(et(t,n,e,{quality:r,shape:i,colorSpace:s}),1,2)}catch(t){if("JXL_SIZE"!==t.code&&(!(t instanceof RangeError)||t.code))throw t;l=c}return c.length<=l.length?c:l}if(c<2)return yield*Q(t,n,e,{shape:i,colorSpace:s});let l=yield*o(Q(t,n,e,{shape:i,colorSpace:s}),0,2);if(c>=4){const o=[Et(t,n,e,i.palette?{...i,palette:null}:i,s,3)];i.palette&&o.push(It(t,n,e,i,s,4,!0)),c>=6&&(o.push(It(t,n,e,i,s,6)),i.palette&&o.push(It(t,n,e,i,s,6,!0)));for(let t=0;t<o.length;t++)try{let n,e=!1;for(;!(n=o[t].next(e)).done;)e=yield.5+(t+n.value)/(2*o.length);if(n.value&&n.value.length<l.length&&(l=n.value),e)return l}catch(t){if("JXL_SIZE"!==t.code&&(!(t instanceof RangeError)||t.code))throw t}return l}try{const r=yield*o(Et(t,n,e,i.palette?{...i,palette:null}:i,s,c),1,2);r&&r.length<l.length&&(l=r)}catch(t){if("JXL_SIZE"!==t.code&&(!(t instanceof RangeError)||t.code))throw t}return l}(n,e,r,c,a,u))}const Ct=t=>Math.max(0,t<224?t-1:t-236);function Tt(t){const n=I(t),o=new e(128);S(o,n);let r=o.u;for(let e=0;e<t.length;e++)t[e]&&(r+=t[e]*(n.C[e]+Ct(e)));return r}const vt=t=>{const n=new Uint32Array(z);for(const e of t)for(let t=0;t<z;t++)n[t]+=e[t];return n};function*Et(t,n,o,r,s,c){const{channels:i}=r,l=w(n,o),f=l.p*l.M,u=Array.from({length:i},()=>new Int16Array(65536)),d=(e,o,r,s)=>{for(let c=0;c<s;c++){let s=4*((o+c)*n+e),l=c*r;for(let n=0;n<r;n++,s+=4,l++)if(i>=3){const n=t[s],e=t[s+1],o=t[s+2],r=n-o,c=o+(r>>1),f=e-c;u[0][l]=c+(f>>1),u[1][l]=r,u[2][l]=f,4===i&&(u[3][l]=t[s+3])}else u[0][l]=t[s],2===i&&(u[1][l]=t[s+3])}},M=(t,e)=>{const r=t%l.p*a,s=(t/l.p|0)*a,c=Math.min(a,n-r),i=Math.min(a,o-s);d(r,s,c,i),e(c,i)},A=Math.min(n,32),g=Math.min(o,32),m=[J,X].map(t=>({R:t,ot:u.map(()=>new Uint32Array(z))}));for(const t of[0,.5,1]){d(Math.floor((n-A)*t),Math.floor((o-g)*t),A,g);for(const t of m)for(let n=0;n<i;n++)D(null,t.ot[n],u[n],A,g,G(t.R))}const b=u.map((t,n)=>G(m[Tt(m[1].ot[n])<Tt(m[0].ot[n])?1:0].R)),U=ot.length+1,I=Int32Array.from({length:U},(t,n)=>n),S=u.map(()=>new Uint32Array(z)),x=u.map(()=>Array.from({length:U},()=>new Uint32Array(z)));let C=0;for(let t=0;t<f;t++)if(M(t,(t,n)=>{for(let e=0;e<i;e++)D(null,S[e],u[e],t,n,b[e]),ut(null,x[e],u[e],t,n,0,I)}),yield++C/(2*f))return null;const T=u.map((t,n)=>{const e={ut:Tt(S[n]),leaves:[b[n]],ot:[S[n]],cuts:[]},o=vt(x[n]),r=Tt(o);return r<e.ut?{ut:r,leaves:[G(6)],ot:[o],cuts:[]}:e}),v=c<3?T:T.map((t,n)=>{const e=x[n].map(t=>t.some(t=>t)),o=new Float64Array(U+1).fill(1/0),r=new Int32Array(U+1);o[0]=0;for(let t=1;t<=U;t++){const s=new Uint32Array(z);let c=0;for(let i=t-1;i>=0;i--){if(e[i]){const t=x[n][i];for(let n=0;n<z;n++)s[n]+=t[n];c=Tt(s)}const l=o[i]+c+24;l<o[t]&&(o[t]=l,r[t]=i)}}if(!(o[U]<t.ut))return t;const s=[];for(let t=U;t>0;t=r[t])s.unshift([r[t],t]);return{ut:o[U],leaves:s.map(()=>G(6)),ot:s.map(([t,e])=>vt(x[n].slice(t,e))),cuts:s.slice(0,-1).map(([,t])=>ot[t-1]),ht:Int32Array.from({length:U},(t,n)=>s.findIndex(([t,e])=>n>=t&&n<e))}}),E=[T,v].filter((t,n)=>n?t.some((t,n)=>t!==T[n]):t.some((t,n)=>t.leaves[0]!==b[n]));if(!E.length)return null;const q=t=>({k:j(t.map(t=>((t,n)=>{const e=(o,r)=>{if(o===r)return t[o];const s=o+r>>1;return O(15,n[s],e(s+1,r),e(o,s))};return e(0,t.length-1)})(t.leaves,t.cuts))),leaves:t.flatMap(t=>t.leaves),ot:t.flatMap(t=>t.ot)}),L=t=>{const{k:n,leaves:o,ot:r}=q(t),s=new e(4096),c=Y(s,Z(s,n),r,t=>o.indexOf(t));let i=s.u;return r.forEach((t,n)=>{const{C:e}=c[n+1].code;for(let n=0;n<t.length;n++)t[n]&&(i+=t[n]*(e[n]+Ct(n)))}),i};let N=E;if(E.length>1){const[t,n]=E.map(L),e=27*(l.U?1:f+1)+8;n+e<=t?N=[E[1]]:t+e<=n?N=[E[0]]:n<t&&(N=[E[1],E[0]])}let _=0,P=!1;const R=()=>.5+ ++_/(2*N.length*f);let B=null;for(const t of N){const n=yield*F(t);if(!n)return B;if((!B||n.length<B.length||n.length===B.length&&t===E[0])&&(B=n),P)return B}return B;function*F(t){const{k:c,leaves:a,ot:w}=q(t),d=i>=3?[{type:"rct",D:0,F:6}]:[],A=new e(256);h(A,n,o,r.colour,r.alpha,{colorSpace:s}),y(A,{alpha:r.alpha});const g=new e(4096);g.write(1,1),g.write(1,1);const m=Y(g,Z(g,c),w,t=>a.indexOf(t));k(g,{nt:!0,et:d});const b=t.map(t=>t.leaves.map(t=>m[a.indexOf(t)+1].code)),U=(n,e,o,r)=>{const s=t[e];6===s.leaves[0].R?ut(n,b[e],u[e],o,r,0,s.ht):D(n,b[e][0],u[e],o,r,s.leaves[0])},I=[];if(l.U)M(0,(t,n)=>{for(let e=0;e<i;e++)U(g,e,t,n)}),P=yield R(),I.push(g.finish());else{I.push(g.finish());for(let t=0;t<l.A*l.m+1;t++)I.push(new Uint8Array(0));for(let t=0;t<f;t++)if(M(t,(t,n)=>{const o=new e(t*n*i+64);k(o,{nt:!0,et:[]});for(let e=0;e<i;e++)U(o,e,t,n);I.push(o.finish())}),(P=yield R())&&t+1<f)return null}return p(A,I)}}export{t as LIMITS,St as encode,xt as encodeSteps};