rapier-jxl 2.0.0 → 2.2.0

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 (55) hide show
  1. package/AGENTS.md +10 -1
  2. package/ENCODER-COMPARISON.md +6 -6
  3. package/KERNELS.md +58 -0
  4. package/README.md +75 -18
  5. package/admit.mjs +5 -1
  6. package/ans.mjs +157 -0
  7. package/bits.mjs +5 -2
  8. package/build-jxl-kernels.mjs +29 -0
  9. package/coefficient-ans.mjs +26 -0
  10. package/coefficient-effort.mjs +62 -0
  11. package/effort-job.mjs +229 -0
  12. package/effort-level.mjs +10 -0
  13. package/effort.d.mts +4 -2
  14. package/effort.min.mjs +2 -2
  15. package/effort.mjs +7 -199
  16. package/frame.mjs +29 -1
  17. package/index.mjs +2 -2
  18. package/jfif.mjs +11 -0
  19. package/jpeg-ans.d.mts +3 -0
  20. package/jpeg-ans.min.mjs +2 -0
  21. package/jpeg-ans.mjs +9 -0
  22. package/jpeg-job.mjs +29 -0
  23. package/jpeg.d.mts +6 -2
  24. package/jpeg.min.mjs +2 -2
  25. package/jpeg.mjs +12 -36
  26. package/kernel-control.d.mts +3 -0
  27. package/kernel-hooks.mjs +3 -0
  28. package/kernels-bytes.mjs +4 -0
  29. package/kernels-probe.wat +1 -0
  30. package/kernels-scalar.wat +831 -0
  31. package/kernels-simd.wat +252 -0
  32. package/kernels.mjs +141 -0
  33. package/local.mjs +201 -0
  34. package/lossless-coding.mjs +30 -0
  35. package/lossless.mjs +88 -67
  36. package/modular.mjs +11 -7
  37. package/package.json +40 -6
  38. package/photo-ans.d.mts +3 -0
  39. package/photo-ans.min.mjs +2 -0
  40. package/photo-ans.mjs +9 -0
  41. package/photo-dct.mjs +83 -0
  42. package/photo-job.mjs +27 -0
  43. package/photo-quant.mjs +150 -0
  44. package/photo.d.mts +5 -1
  45. package/photo.min.mjs +2 -2
  46. package/photo.mjs +7 -101
  47. package/pool.mjs +136 -0
  48. package/rapier-jxl.min.mjs +2 -2
  49. package/rct-search.mjs +61 -0
  50. package/sizes.json +237 -56
  51. package/vardct.mjs +45 -8
  52. package/wasm.d.mts +4 -0
  53. package/wasm.min.mjs +2 -0
  54. package/wasm.mjs +6 -0
  55. package/weighted.mjs +22 -10
package/lossless.mjs CHANGED
@@ -1,15 +1,24 @@
1
1
  // Rapier's JPEG XL encoder: lossless pictures. MIT (LICENSE).
2
2
  // The design of libjxl's fast lossless path, written for Rapier: 8-bit grey, grey+alpha, RGB or RGBA; an opaque
3
3
  // alpha is dropped and a grey picture keeps one channel; up to 2048 colours try a palette against direct encoded
4
- // cost. YCoCg decorrelates colour; samples choose gradient/average prediction; each channel takes one prefix code, zero runs
5
- // as LZ77 copies; groups of 256x256 pixels, each an independent section.
4
+ // cost. YCoCg decorrelates colour by default; samples choose gradient/average prediction; each channel takes one prefix
5
+ // code whose hybrid-integer configuration is chosen by exact price, zero runs as LZ77 copies; groups of 256x256 pixels,
6
+ // each an independent section. The effort door's colour-transform search supplies another reversible transform
7
+ // through the private `rct` planner input.
8
+ import {kernelHooks} from './kernel-hooks.mjs';
6
9
  import {BitWriter, complete, part} from './bits.mjs';
7
- import {writeImageHeader, writeModularFrameHeader, groupLayout, assembleCodestream, GROUP_DIM} from './frame.mjs';
10
+ import {writeImageHeader, writeModularFrameHeader, groupLayout, groupRect, groupPass, addCounts, assembleCodestream, GROUP_DIM} from './frame.mjs';
8
11
  import {buildCode, writePrefixCode} from './prefix.mjs';
9
12
  import {AVERAGE_PREDICTOR, GRADIENT_PREDICTOR, ALPHABET, leaf, channelTree, writeTree, writeModularHeader, writeChannelHistograms, codeChannel} from './modular.mjs';
13
+ import {losslessCoding} from './lossless-coding.mjs';
10
14
 
11
15
  const MAX_PALETTE = 2048;
12
16
 
17
+ // A pixel's packed colour, channel c in byte c, as the palette rows are laid out.
18
+ export const paletteKey = (rgba, i, grey, alpha) => grey
19
+ ? (alpha ? rgba[i] + rgba[i + 3] * 256 : rgba[i])
20
+ : rgba[i] + rgba[i + 1] * 256 + rgba[i + 2] * 65536 + (alpha ? rgba[i + 3] * 16777216 : 0);
21
+
13
22
  // What the picture is: which channels it needs and, when few colours, its palette.
14
23
  export function inspectPixels(rgba, width, height, {palette: wantPalette = true} = {}) {
15
24
  const pixels = width * height;
@@ -19,10 +28,7 @@ export function inspectPixels(rgba, width, height, {palette: wantPalette = true}
19
28
  if (rgba[i] !== rgba[i + 1] || rgba[i] !== rgba[i + 2]) { grey = false; if (!opaque) break; }
20
29
  }
21
30
  const colour = grey ? 1 : 3, alpha = !opaque, channels = colour + (alpha ? 1 : 0);
22
- // The packed colour of a pixel: channel c in byte c, as the palette rows are laid out.
23
- const key = i => grey
24
- ? (alpha ? rgba[i] + rgba[i + 3] * 256 : rgba[i])
25
- : rgba[i] + rgba[i + 1] * 256 + rgba[i + 2] * 65536 + (alpha ? rgba[i + 3] * 16777216 : 0);
31
+ const key = i => paletteKey(rgba, i, grey, alpha);
26
32
  const seen = new Map();
27
33
  let palette = null;
28
34
  for (let i = 0, p = 0; wantPalette && p < pixels; p++, i += 4) {
@@ -36,24 +42,60 @@ export function inspectPixels(rgba, width, height, {palette: wantPalette = true}
36
42
  const byte = (k, c) => (k >>> 8 * c) & 255;
37
43
  const weight = k => (0.299 * byte(k, 0) + 0.587 * byte(k, 1) + 0.114 * byte(k, 2) + 0.01) * (channels === 4 ? byte(k, 3) : 1);
38
44
  entries.sort((a, b) => weight(a) - weight(b));
39
- const index = new Map([[0, 0]]);
40
- entries.forEach((k, at) => index.set(k, at + 1));
41
- palette = {colours: [0, ...entries], index, key, byte};
45
+ palette = {colours: [0, ...entries], byte};
42
46
  }
43
47
  return {colour, alpha, channels, palette};
44
48
  }
45
49
 
50
+ // Channel planes of a group rectangle, straight from the pixels (rows `stride` pixels apart, so the picture or one
51
+ // group's own copy): a palette's indices, the planner's reversible colour transform, YCoCg, or grey and alpha as they
52
+ // are. `palette` is the colours in index order.
53
+ export function planeFill({channels, alpha, palette}, rct) {
54
+ const grey = channels - (alpha ? 1 : 0) === 1, index = palette && new Map(palette.map((k, at) => [k, at]));
55
+ return (planes, rgba, stride, x0, y0, w, h) => {
56
+ if (rct && !palette && channels >= 3) { rct.fill(planes, rgba, stride, x0, y0, w, h); return; }
57
+ if (!palette && kernelHooks.fill?.(rgba, stride, x0, y0, w, h, channels, planes)) return;
58
+ for (let y = 0; y < h; y++) {
59
+ let i = ((y0 + y) * stride + x0) * 4, at = y * w;
60
+ for (let x = 0; x < w; x++, i += 4, at++) {
61
+ if (palette) { planes[0][at] = index.get(paletteKey(rgba, i, grey, alpha)); continue; }
62
+ if (channels >= 3) {
63
+ const r = rgba[i], g = rgba[i + 1], b = rgba[i + 2];
64
+ const co = r - b, tmp = b + (co >> 1), cg = g - tmp;
65
+ planes[0][at] = tmp + (cg >> 1); planes[1][at] = co; planes[2][at] = cg;
66
+ if (channels === 4) planes[3][at] = rgba[i + 3];
67
+ } else { planes[0][at] = rgba[i]; if (channels === 2) planes[1][at] = rgba[i + 3]; }
68
+ }
69
+ }
70
+ };
71
+ }
72
+
73
+ // A group of a plan, here or in a pool's worker: `picture` names each picture channel's leaf. Without codes it counts
74
+ // the group into `target` (a raw histogram per leaf); with them it writes the group into `target`, or, when none, as
75
+ // its own section, whose bytes it returns.
76
+ export function planGroup(setup, rct) {
77
+ const {leaves, picture, codes} = setup, fill = planeFill(setup, rct), planes = picture.map(() => new Int16Array(GROUP_DIM * GROUP_DIM));
78
+ return (rgba, stride, x0, y0, w, h, target) => {
79
+ fill(planes, rgba, stride, x0, y0, w, h);
80
+ if (!codes) { picture.forEach((l, c) => codeChannel(null, target[l], planes[c], w, h, leaves[l], true)); return; }
81
+ const section = target || new BitWriter(w * h * picture.length + 64);
82
+ if (!target) writeModularHeader(section, {useGlobalTree: true, transforms: []});
83
+ picture.forEach((l, c) => codeChannel(section, codes[l], planes[c], w, h, leaves[l]));
84
+ return target ? undefined : section.finish();
85
+ };
86
+ }
87
+
46
88
  export function encodeLossless(rgba, width, height, options) { return complete(losslessSteps(rgba, width, height, options)); }
47
89
 
48
- // The same, a group of one pass per step.
49
- export function* losslessSteps(rgba, width, height, {shape = inspectPixels(rgba, width, height), colorSpace} = {}) {
90
+ // The same, a group of one pass per step; `pooled`, each pass of groups is one step for a pool (pool.mjs).
91
+ export function* losslessSteps(rgba, width, height, {shape = inspectPixels(rgba, width, height), colorSpace, rct, pooled} = {}) {
50
92
  // The complete stream cost includes the palette, tree, histograms and group headers. A candidate that reaches
51
93
  // the stream limit must not hide a smaller valid representation of the same pixels.
52
94
  let best, oversized;
53
95
  const candidates = shape.palette ? [shape, {...shape, palette: null}] : [shape];
54
96
  for (let i = 0; i < candidates.length; i++) {
55
97
  try {
56
- const bytes = yield* part(planSteps(rgba, width, height, candidates[i], colorSpace), i, candidates.length);
98
+ const bytes = yield* part(planSteps(rgba, width, height, candidates[i], colorSpace, rct, pooled), i, candidates.length);
57
99
  if (!best || bytes.length < best.length) best = bytes;
58
100
  } catch (error) { if (error.code !== 'JXL_SIZE') throw error; oversized = error; }
59
101
  }
@@ -61,29 +103,18 @@ export function* losslessSteps(rgba, width, height, {shape = inspectPixels(rgba,
61
103
  return best;
62
104
  }
63
105
 
64
- function* planSteps(rgba, width, height, shape, colorSpace) {
65
- const {channels, palette} = shape;
66
- const layout = groupLayout(width, height);
106
+ function* planSteps(rgba, width, height, shape, colorSpace, rct, pooled) {
107
+ const {channels, alpha, palette} = shape;
108
+ const layout = groupLayout(width, height), groups = layout.groupsX * layout.groupsY;
67
109
  const streamChannels = palette ? 2 : channels; // the palette's meta channel and the index channel
68
110
  const leaves = Array.from({length: streamChannels}, () => leaf(GRADIENT_PREDICTOR));
69
111
  const tree = channelTree(leaves);
112
+ // What a group of this plan needs, wherever it is coded: the image channels' leaves (in the global section they
113
+ // follow the palette's meta channel; in a group they start at 0).
114
+ const setup = {channels, alpha, palette: palette && palette.colours, rct: rct?.type, leaves, picture: palette ? [layout.single ? 1 : 0] : leaves.map((_, c) => c)};
115
+ const fill = planeFill(setup, rct), planes = Array.from({length: channels}, () => new Int16Array(32 * 32));
116
+ const rect = g => groupRect(layout, width, height, g);
70
117
 
71
- // Channel planes of a group rectangle, computed straight from the pixels.
72
- const planes = Array.from({length: streamChannels}, () => new Int16Array(GROUP_DIM * GROUP_DIM));
73
- const fill = (x0, y0, w, h) => {
74
- for (let y = 0; y < h; y++) {
75
- let i = ((y0 + y) * width + x0) * 4, at = y * w;
76
- for (let x = 0; x < w; x++, i += 4, at++) {
77
- if (palette) { planes[0][at] = palette.index.get(palette.key(i)); continue; }
78
- if (channels >= 3) {
79
- const r = rgba[i], g = rgba[i + 1], b = rgba[i + 2];
80
- const co = r - b, tmp = b + (co >> 1), cg = g - tmp;
81
- planes[0][at] = tmp + (cg >> 1); planes[1][at] = co; planes[2][at] = cg;
82
- if (channels === 4) planes[3][at] = rgba[i + 3];
83
- } else { planes[0][at] = rgba[i]; if (channels === 2) planes[1][at] = rgba[i + 3]; }
84
- }
85
- }
86
- };
87
118
  // The palette meta channel: one row per channel, one column per colour.
88
119
  let paletteRows = null;
89
120
  if (palette) {
@@ -91,22 +122,11 @@ function* planSteps(rgba, width, height, shape, colorSpace) {
91
122
  paletteRows = new Int16Array(n * channels);
92
123
  palette.colours.forEach((k, at) => { for (let c = 0; c < channels; c++) paletteRows[c * n + at] = palette.byte(k, c); });
93
124
  }
94
- // The image channels of a section: in the global section they follow the meta channel; in a group they start at 0.
95
- const pictureLeaf = c => leaves[palette ? (layout.single ? 1 : 0) : c];
96
- const pictureChannels = palette ? 1 : channels;
97
-
98
- // A group's planes filled, then visited; each group of each pass is one step, row by row.
99
- const groups = layout.groupsX * layout.groupsY, total = 2 * groups;
100
- const group = (g, visit) => {
101
- const x0 = g % layout.groupsX * GROUP_DIM, y0 = (g / layout.groupsX | 0) * GROUP_DIM, w = Math.min(GROUP_DIM, width - x0), h = Math.min(GROUP_DIM, height - y0);
102
- fill(x0, y0, w, h);
103
- visit(w, h);
104
- };
105
- let done = 0;
106
125
 
107
126
  // Three small spatial samples choose gradient/average per channel. The same token code prices
108
127
  // both the prefix header and raw residual bits. Fixed YCoCg avoids choosing RGB from a sparse corner
109
- // that misrepresents a drawing; the measured corpus favours this smaller rule, including tiny pictures.
128
+ // that misrepresents a drawing; the measured corpus favours this smaller rule, including tiny pictures. The
129
+ // higher-effort sampler (rct-search.mjs) ranks other transforms, but a complete alternate stream must be smaller.
110
130
  if (!palette) {
111
131
  const cost = freqs => {
112
132
  const code = buildCode(freqs), writer = new BitWriter(128);
@@ -118,7 +138,7 @@ function* planSteps(rgba, width, height, shape, colorSpace) {
118
138
  const sw = Math.min(width, 32), sh = Math.min(height, 32);
119
139
  const candidates = [GRADIENT_PREDICTOR, AVERAGE_PREDICTOR].map(predictor => ({predictor, freqs: leaves.map(() => new Uint32Array(ALPHABET))}));
120
140
  for (const fraction of [0, 0.5, 1]) {
121
- fill(Math.floor((width - sw) * fraction), Math.floor((height - sh) * fraction), sw, sh);
141
+ fill(planes, rgba, width, Math.floor((width - sw) * fraction), Math.floor((height - sh) * fraction), sw, sh);
122
142
  for (const candidate of candidates) for (let c = 0; c < channels; c++) codeChannel(null, candidate.freqs[c], planes[c], sw, sh, leaf(candidate.predictor));
123
143
  }
124
144
  leaves.forEach((l, c) => {
@@ -128,15 +148,22 @@ function* planSteps(rgba, width, height, shape, colorSpace) {
128
148
  }
129
149
  const transforms = palette
130
150
  ? [{type: 'palette', beginC: 0, numC: channels, nbColors: palette.colours.length}]
131
- : channels >= 3 ? [{type: 'rct', beginC: 0, rctType: 6}] : [];
151
+ : channels >= 3 && rct?.type !== 0 ? [{type: 'rct', beginC: 0, rctType: rct?.type ?? 6}] : [];
132
152
 
133
- // Pass one: token histograms per leaf.
134
- const freqs = leaves.map(() => new Uint32Array(ALPHABET)), freqOf = l => freqs[leaves.indexOf(l)];
135
- if (paletteRows) codeChannel(null, freqOf(leaves[0]), paletteRows, palette.colours.length, channels, leaves[0]);
136
- for (let g = 0; g < groups; g++) {
137
- group(g, (w, h) => { for (let c = 0; c < pictureChannels; c++) codeChannel(null, freqOf(pictureLeaf(c)), planes[c], w, h, pictureLeaf(c)); });
138
- yield ++done / total;
139
- }
153
+ // Pass one: token histograms per leaf, a group a step.
154
+ let freqs = leaves.map(() => new Uint32Array((palette ? 4096 : 1024) + 33));
155
+ if (paletteRows) codeChannel(null, freqs[0], paletteRows, palette.colours.length, channels, leaves[0], true);
156
+ const count = planGroup(setup, rct);
157
+ yield* groupPass({pooled, kind: 'plan', setup: {...setup, sizes: freqs.map(f => f.length)}, at: g => (g + 1) / (2 * groups), stop: () => false},
158
+ groups, g => count(rgba, width, ...rect(g), freqs), (g, counts) => addCounts(freqs, counts));
159
+
160
+ const pairs = freqs.map(losslessCoding);
161
+ const saving = pairs.reduce((n, pair) => n + pair[0].bits - pair[1].bits, 0);
162
+ // Seven padding bits per section and at most twenty extra TOC bits: a smaller payload alone need not
163
+ // be a smaller complete stream. Inside that uncertainty interval keep the original representation.
164
+ const choice = saving > 27 * (layout.single ? 1 : groups + 1) + 8 ? 1 : 0;
165
+ leaves.forEach((l, i) => { l.config = pairs[i][choice].config; });
166
+ freqs = pairs.map(pair => pair[choice].freqs);
140
167
 
141
168
  // Pass two: the sections.
142
169
  const header = new BitWriter(256);
@@ -146,27 +173,21 @@ function* planSteps(rgba, width, height, shape, colorSpace) {
146
173
  global.write(1, 1); // default DC quantisation
147
174
  global.write(1, 1); // a global tree
148
175
  const ordered = writeTree(global, tree);
149
- const histograms = writeChannelHistograms(global, ordered, freqs, l => leaves.indexOf(l));
150
- const codeOf = l => histograms[leaves.indexOf(l) + 1].code;
176
+ const histograms = writeChannelHistograms(global, ordered, freqs, l => leaves.indexOf(l), i => leaves[i].config);
177
+ const written = {...setup, codes: leaves.map((_, i) => histograms[i + 1]?.code)}, write = planGroup(written, rct);
151
178
  writeModularHeader(global, {useGlobalTree: true, transforms});
152
- if (paletteRows) codeChannel(global, codeOf(leaves[0]), paletteRows, palette.colours.length, channels, leaves[0]);
179
+ if (paletteRows) codeChannel(global, written.codes[0], paletteRows, palette.colours.length, channels, leaves[0]);
153
180
  const sections = [];
154
181
  if (layout.single) {
155
- group(0, (w, h) => { for (let c = 0; c < pictureChannels; c++) codeChannel(global, codeOf(pictureLeaf(c)), planes[c], w, h, pictureLeaf(c)); });
182
+ write(rgba, width, 0, 0, width, height, global);
156
183
  yield 1;
157
184
  sections.push(global.finish());
158
185
  } else {
159
186
  sections.push(global.finish());
160
187
  for (let i = 0; i < layout.dcGroupsX * layout.dcGroupsY + 1; i++) sections.push(new Uint8Array(0));
161
- for (let g = 0; g < groups; g++) {
162
- group(g, (w, h) => {
163
- const section = new BitWriter(w * h * pictureChannels + 64);
164
- writeModularHeader(section, {useGlobalTree: true, transforms: []});
165
- for (let c = 0; c < pictureChannels; c++) codeChannel(section, codeOf(pictureLeaf(c)), planes[c], w, h, pictureLeaf(c));
166
- sections.push(section.finish());
167
- });
168
- yield ++done / total;
169
- }
188
+ const first = sections.length, place = (g, section) => { sections[first + g] = section; };
189
+ yield* groupPass({pooled, kind: 'plan', setup: written, at: g => (groups + g + 1) / (2 * groups), stop: () => false},
190
+ groups, g => place(g, write(rgba, width, ...rect(g))), place);
170
191
  }
171
192
  return assembleCodestream(header, sections);
172
193
  }
package/modular.mjs CHANGED
@@ -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/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "rapier-jxl",
3
- "version": "2.0.0",
4
- "description": "A JPEG XL encoder in pure JavaScript, lossless and lossy, in one file of 19.5 kB, 8.5 kB gzipped; JPEGs and photographs are their own doors. The encoder inside Rapier.",
3
+ "version": "2.2.0",
4
+ "description": "A JPEG XL encoder in pure JavaScript for the browser, workers, Node and Deno: lossless and lossy, the core one file of 21.3 kB (9.3 kB gzipped); photographs and JPEGs are optional doors. Optional inlined WebAssembly SIMD kernels; no runtime dependencies.",
5
5
  "license": "MIT",
6
6
  "type": "module",
7
7
  "main": "./index.mjs",
@@ -23,16 +23,34 @@
23
23
  "types": "./photo.d.mts",
24
24
  "default": "./photo.mjs"
25
25
  },
26
+ "./jpeg-ans": {
27
+ "types": "./jpeg-ans.d.mts",
28
+ "default": "./jpeg-ans.mjs"
29
+ },
30
+ "./photo-ans": {
31
+ "types": "./photo-ans.d.mts",
32
+ "default": "./photo-ans.mjs"
33
+ },
26
34
  "./effort": {
27
35
  "types": "./effort.d.mts",
28
36
  "default": "./effort.mjs"
29
37
  },
30
38
  "./writer": "./writer.mjs",
31
- "./package.json": "./package.json"
39
+ "./package.json": "./package.json",
40
+ "./kernels": {
41
+ "types": "./kernel-control.d.mts",
42
+ "default": "./kernels.mjs"
43
+ },
44
+ "./wasm": {
45
+ "types": "./wasm.d.mts",
46
+ "default": "./wasm.mjs"
47
+ }
32
48
  },
33
49
  "files": [
34
50
  "*.mjs",
35
51
  "*.d.mts",
52
+ "*.wat",
53
+ "KERNELS.md",
36
54
  "README.md",
37
55
  "AGENTS.md",
38
56
  "ENCODER-COMPARISON.md",
@@ -43,7 +61,10 @@
43
61
  "public/measure-encoders.mjs",
44
62
  "public/examples"
45
63
  ],
46
- "sideEffects": false,
64
+ "sideEffects": [
65
+ "./wasm.mjs",
66
+ "./wasm.min.mjs"
67
+ ],
47
68
  "engines": {
48
69
  "node": ">=18"
49
70
  },
@@ -58,14 +79,27 @@
58
79
  "jxl",
59
80
  "encoder",
60
81
  "image-compression",
82
+ "image",
83
+ "lossless",
84
+ "lossy",
85
+ "jpeg",
86
+ "transcode",
87
+ "photo",
88
+ "canvas",
89
+ "browser",
90
+ "web-worker",
91
+ "javascript",
92
+ "no-dependencies",
61
93
  "rapier"
62
94
  ],
63
95
  "scripts": {
64
96
  "test": "node --test \"public/test/*.test.mjs\"",
65
- "fuzz:scale": "node public/test/fuzz-shards.mjs"
97
+ "fuzz:scale": "node public/test/fuzz-shards.mjs",
98
+ "build:kernels": "node build-jxl-kernels.mjs"
66
99
  },
67
100
  "devDependencies": {
68
101
  "jxl-oxide-wasm": "0.12.6",
69
- "terser": "5.51.2"
102
+ "terser": "5.51.2",
103
+ "wabt": "1.0.37"
70
104
  }
71
105
  }
@@ -0,0 +1,3 @@
1
+ // SPDX-License-Identifier: MIT
2
+ // Same checked photographic surface; effort 2 also compares a completed ANS candidate.
3
+ export * from './photo.mjs';
@@ -0,0 +1,2 @@
1
+ /*! Rapier JXL 2.2.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
+ function*t(t,n,o){let r,e;for(;!(r=t.next(e)).done;)e=yield ft(r.value,t=>(n+t)/o);return r.value}function n(t){return t>=0?2*t:2*-t-1}function o(t){return 31-Math.clz32(t)}function r(t){return t<=1?0:32-Math.clz32(t-1)}function e(t){if(0===t)return 0;const n=t<0?32768:0;if(!((t=Math.abs(t))<65520))throw new Error("half float out of range: "+t);let o=0,r=t;for(;r>=2;)r/=2,o++;for(;r<1;)r*=2,o--;o<-14?(r=16384*t,o=-15):r-=1;let e=Math.round(1024*r);return 1024===e&&(e=0,o++),n|o+15<<10|e}function s(t){if(t>et.bytes)throw lt("JXL_SIZE","The encoded picture exceeds 16 MiB.")}function i(t){try{return t()}catch(t){if(t instanceof RangeError&&!t.code)throw lt("JXL_MEMORY","Not enough memory for this picture.");throw t}}function c(t,n){t.t([[9,1],[13,1],[18,1],[30,1]],n)}function f(t,n,o,r,e,{xyb:s=!1,orientation:i=1,colorSpace:f}={}){t.write(16,2815),t.write(1,0),c(t,o),t.write(3,0),c(t,n),t.write(1,0);const l=1!==i;t.write(1,l?1:0),l&&t.write(6,i-1),t.write(4,8),e?t.write(3,5):t.write(2,0),t.write(1,s?1:0),3===r&&"display-p3"===f?t.write(21,903568):3===r?t.write(1,1):t.write(15,14100),l&&t.write(1,1),t.write(2,0),t.write(1,1),t.o()}function l(t,n){t.write(n?7:5,0),t.write(11,1)}function*a(t,n,o,r){if(t.pooled){const n=yield t;return n?(n.results.forEach((t,n)=>r(n,t)),n.hurried):null}let e=!1;for(let r=0;r<n;r++)if(o(r),e=yield t.at(r),e&&t.stop(r))return null;return e}function u(t,n){const o=n.map(t=>t.length);!function(t,n){t.write(1,0),t.o();for(const o of n)t.t([[10,0],[14,1024],[22,17408],[30,4211712]],o);t.o()}(t,o);const r=t.finish(),e=r.length+o.reduce((t,n)=>t+n,0);s(e);const i=new Uint8Array(e);i.set(r);let c=r.length;for(const t of n)i.set(t,c),c+=t.length;return i}function h(t,n){const o=new Uint8Array(t.length),r=[];for(let n=0;n<t.length;n++)t[n]>0&&r.push(n);if(!r.length)return o;if(1===r.length)return o[r[0]]=1,o;for(let e=1;;e*=2){const s=r.map(n=>({w:Math.max(t[n],e),i:n,l:null,r:null})).sort((t,n)=>t.w-n.w||t.i-n.i),i=[];let c=0,f=0;const l=()=>c<s.length&&(f>=i.length||s[c].w<=i[f].w)?s[c++]:i[f++];for(;s.length-c+(i.length-f)>1;){const t=l(),n=l();i.push({w:t.w+n.w,i:-1,l:t,r:n})}const a=[[l(),0]];let u=0;for(;a.length;){const[t,n]=a.pop();t.i>=0?(o[t.i]=n,n>u&&(u=n)):a.push([t.l,n+1],[t.r,n+1])}if(u<=n)return o}}function y(t,n,o,r,e,s){const i=[];for(;i.push(r&e),r>>=s;)r--;for(let r=i.length-1;r>=0;r--)t.push(o),n.push(i[r])}function w(t,n){let o=0;for(let r=0;r<n;r++)o=o<<1|1&t,t>>=1;return o}function d(t){const n=new Uint32Array(17),o=new Uint32Array(17),r=new Uint16Array(t.length);for(const o of t)n[o]++;n[0]=0;for(let t=1,r=0;t<=16;t++)r=r+n[t-1]<<1,o[t]=r;for(let n=0;n<t.length;n++)t[n]&&(r[n]=w(o[t[n]]++,t[n]));return r}function p(t){const n=[];for(let o=0;o<t.length;o++)t[o]>0&&n.push(o);const o=n.length,r=n[o-1]+1||1,e=o<=4?new Uint8Array(r):h(t,15).subarray(0,r);let s=0;if(o<=4){n.sort((n,o)=>t[o]-t[n]||n-o),s=4===o&&t[n[0]]>t[n[2]]+t[n[3]]?1:0;const r=o<2?[0]:2===o?[1,1]:3===o?[1,2,2]:s?[1,2,3,3]:[2,2,2,2];n.forEach((t,n)=>{e[t]=r[n]}),n.sort((t,n)=>e[t]-e[n]||t-n)}return{u:r,p:e,M:d(e),A:o&&o<=4?n:null,U:s}}function M(t,n){if(n.u<=1)return;if(n.A){const r=o(n.u-1)+1;t.write(2,1),t.write(2,n.A.length-1);for(const o of n.A)t.write(r,o);return void(4===n.A.length&&t.write(1,n.U))}const r=n.p,e=[],s=[];let i=r.length-1;for(;i>0&&!r[i];)i--;let c=8;for(let t=0;t<=i;){const n=r[t];let o=1;for(;t+o<=i&&r[t+o]===n;)o++;let f=o;if(n&&n!==c&&(e.push(n),s.push(0),f--,c=n),f<3)for(let t=0;t<f;t++)e.push(n),s.push(0);else y(e,s,n?16:17,f-3,n?3:7,n?2:3);t+=o}const f=new Uint32Array(18);for(const t of e)f[t]++;let l=0;for(const t of f)t&&l++;1===l&&(f[e[0]?0:1]=f[e[0]]);const a=h(f,5),u=d(a);let w=18;for(;w>0&&!a[ht[w-1]];)w--;t.write(2,0);for(let n=0;n<w;n++){const o=a[ht[n]];t.write(yt[o],wt[o])}for(let n=0;n<e.length;n++){const o=e[n];t.write(a[o],u[o]),16===o?t.write(2,s[n]):17===o&&t.write(3,s[n])}}function A(t,n=0,o=0){return{split:t,m:n,I:o,S:1<<t}}function g(t,n,o=15){t.write(r(o+1),n.split),n.split!==o&&(t.write(r(n.split+1),n.m),t.write(r(n.split-n.m+1),n.I))}function U(t,n,r){if(n<t.S)return r[0]=n,r[1]=0,void(r[2]=0);const e=o(n),s=n-(1<<e),i=e-t.m-t.I;r[0]=t.S+(e-t.split<<t.m+t.I|s>>e-t.m<<t.I|s&(1<<t.I)-1),r[1]=i,r[2]=n>>t.I&(1<<i)-1}function b(t,n,o,r=0){U(t,n,dt),o[r+dt[0]]++}function m(t,n,o,r,e=0){U(o,r,dt);const s=e+dt[0];t.write(n.p[s],n.M[s]),dt[1]&&t.write(dt[1],dt[2])}function I(t,n){let o=0;for(const t of n)o=Math.max(o,t);const e=o?r(o+1):0;if(e<=3){if(t.write(1,1),t.write(2,e),e)for(const o of n)t.write(e,o);return}t.write(1,0),t.write(1,0);const s=A(8),i=A(4),c=n.length>=64,f=new Uint32Array(c?257:o+1),l=[];for(let t=0;t<n.length;){let o=1;for(;c&&t+o<n.length&&n[t+o]===n[t];)o++;if(o>=8)l.push([n[t],o-1]),f[n[t]]++,b(i,o-1-7,f,224),t+=o;else{for(let r=0;r<o;r++)l.push([n[t],0]),f[n[t]]++;t+=o}}const a=p(f);if(!c){x(t,{L:new Uint8Array(1),J:[{config:s,code:a}]});for(const[n]of l)m(t,a,s,n);return}const u=new Uint32Array(1);u[0]=1,x(t,{X:{T:224,minLength:7,_:i},L:new Uint8Array([1,0]),J:[{config:A(0),code:p(u)},{config:s,code:a}]});for(const[n,o]of l)m(t,a,s,n),o&&m(t,a,i,o-7,224)}function x(t,{X:n=null,L:r,J:e}){t.write(1,n?1:0),n&&(t.t([[0,224],[0,512],[0,4096],[15,8]],n.T),t.t([[0,3],[0,4],[2,5],[8,9]],n.minLength),g(t,n._,8)),r.length>1&&I(t,r),t.write(1,1);for(const n of e)g(t,n.config,15);for(const n of e){const r=n.code.u-1;if(r){const n=o(r);t.write(1,1),t.write(4,n),t.write(n,r-(1<<n))}else t.write(1,0)}for(const n of e)M(t,n.code)}function S(t,n=0,o=1){return{N:t,offset:n,C:o,context:-1}}function E(t){return function(t,n){const o=(r,e)=>{if(r===e)return n?t[r].O:t[r];const s=r+e>>1;return function(t,n,o,r){return{P:t,q:n,left:o,right:r}}(n,n?t[s].F:s,o(s+1,e),o(r,s))};return o(0,t.length-1)}(t,0)}function L(t,o){const r=[o],e=[],s=[];for(const t of r){if(t.left){e.push(t.P+1,n(t.q)),r.push(t.left,t.right);continue}t.context=s.length,s.push(t);const o=1===t.C?0:31-Math.clz32(t.C&-t.C);e.push(0,t.N,n(t.offset),o,(t.C>>o)-1)}const i=new Uint32Array(64);for(const t of e)b(Ut,t,i);const c=p(i);x(t,{L:new Uint8Array(6),J:[{config:Ut,code:c}]});for(const n of e)m(t,c,Ut,n);return s}function J(t,n){const o=[[3,0],[6,8],[10,72],[13,1096]];if("rct"===n.type)t.write(2,0),t.t(o,n.G),t.t([[0,6],[2,0],[4,2],[6,10]],n.j??6);else if("palette"===n.type)t.write(2,1),t.t(o,n.G),t.t([[0,1],[0,3],[0,4],[13,1]],n.k),t.t([[8,0],[10,256],[12,1280],[16,5376]],n.R),t.t([[0,0],[8,1],[10,257],[16,1281]],n.B??0),t.write(4,n.N??0);else{if("squeeze"!==n.type)throw new Error("unknown transform "+n.type);{t.write(2,2);const r=n.Z??[];t.t([[0,0],[4,1],[6,9],[8,41]],r.length);for(const n of r)t.write(1,n.D?1:0),t.write(1,n.Y?1:0),t.t(o,n.G),t.t([[0,1],[0,2],[0,3],[4,4]],n.k)}}}function X(t,{H:n=!0,K:o=[]}={}){t.write(1,n?1:0),t.write(1,1),t.t([[0,0],[0,1],[4,2],[8,18]],o.length);for(const n of o)J(t,n)}function v(t,n,o,r=t=>t.context,e=()=>Ut){const s=new Uint8Array(n.length+1);for(const t of n)s[t.context]=r(t)+1;const i=new Uint32Array(2);i[1]=1;const c=[{config:Ut,code:p(i)}],f=Math.max(...s);for(let t=0;t<f;t++)c.push({config:e(t),code:p(o[t])});return x(t,{X:gt,L:s,J:c}),c}function T(t,o,r,e,s,i,c=!1){if(at.channel?.(t,o,r,e,s,i,c))return;const f=i.N,l=i.offset,a=i.C,u=i.config||Ut,h=At;let y=0;const w=(n,r=u,e=0)=>{t?m(t,o,r,n,e):c&&!e?o[n]++:b(r,n,o,c?o.length-33:e)},d=()=>{if(y){if(y>=8)w(0),w(y-7-1,h,224);else for(let t=0;t<y;t++)w(0);y=0}},p=(t,o)=>{const e=r[t];let s;1===a?s=e-o-l:(s=Math.round((e-o-l)/a),r[t]=o+l+s*a),0!==s?(d(),w(n(s))):y++};for(let t=0,n=0;t<s;t++)for(let o=0;o<e;o++,n++){let s;if(0===f)s=0;else{const i=o?r[n-1]:t?r[n-e]:0,c=t?r[n-e]:i;if(1===f)s=i;else if(2===f)s=c;else if(3===f)s=(i+c)/2|0;else{const l=i+c-(o&&t?r[n-e-1]:i);if(5===f){const t=i<c?i:c,n=i<c?c:i;s=l<t?t:l>n?n:l}else{if(4!==f)throw new Error("predictor "+f+" is not coded here");s=Math.abs(l-i)<Math.abs(l-c)?i:c}}}p(n,s)}d()}function _(t){const n=t.length-33,r=[0,0,0];let e,s;for(const i of bt){const c=new Uint32Array(257);let f=0;for(let o=0;o<n;o++)t[o]&&(U(i,o,r),c[r[0]]+=t[o],f+=t[o]*r[1]);for(let o=0;o<33;o++)c[224+o]=t[n+o],f+=t[n+o]*Math.max(0,o-12);const l=p(c),a=new ct(128);g(a,i),M(a,l);let u=a.V+f+(1===l.u?1:5+o(l.u-1));for(let t=0;t<c.length;t++)c[t]&&(u+=c[t]*l.p[t]);const h={config:i,W:c,$:u,extra:f};e||(e=h),(!s||u<s.$)&&(s=h)}return[e,s]}function N(t,n,o,{palette:r=!0}={}){const e=n*o;let s=!0,i=!0;for(let n=0,o=0;o<e&&(255===t[n+3]||(s=!1,i))&&(t[n]===t[n+1]&&t[n]===t[n+2]||(i=!1,s));o++,n+=4);const c=i?1:3,f=!s,l=c+(f?1:0),a=n=>mt(t,n,i,f),u=new Map;let h=null;for(let t=0,n=0;r&&n<e;n++,t+=4){const n=a(t);if(0!==n&&!u.has(n)&&(u.set(n,1),u.size>=2048))break}if(r&&u.size<2048){const t=[...u.keys()],n=(t,n)=>t>>>8*n&255,o=t=>(.299*n(t,0)+.587*n(t,1)+.114*n(t,2)+.01)*(4===l?n(t,3):1);t.sort((t,n)=>o(t)-o(n)),h={colours:[0,...t],byte:n}}return{colour:c,alpha:f,channels:l,palette:h}}function C({channels:t,alpha:n,palette:o},r){const e=t-(n?1:0)==1,s=o&&new Map(o.map((t,n)=>[t,n]));return(i,c,f,l,a,u,h)=>{if(r&&!o&&t>=3)r.fill(i,c,f,l,a,u,h);else if(o||!at.fill?.(c,f,l,a,u,h,t,i))for(let r=0;r<h;r++){let h=4*((a+r)*f+l),y=r*u;for(let r=0;r<u;r++,h+=4,y++)if(o)i[0][y]=s.get(mt(c,h,e,n));else if(t>=3){const n=c[h],o=c[h+1],r=c[h+2],e=n-r,s=r+(e>>1),f=o-s;i[0][y]=s+(f>>1),i[1][y]=e,i[2][y]=f,4===t&&(i[3][y]=c[h+3])}else i[0][y]=c[h],2===t&&(i[1][y]=c[h+3])}}}function O(t,n){const{leaves:o,picture:r,M:e}=t,s=C(t,n),i=r.map(()=>new Int16Array(65536));return(t,n,c,f,l,a,u)=>{if(s(i,t,n,c,f,l,a),!e)return void r.forEach((t,n)=>T(null,u[t],i[n],l,a,o[t],!0));const h=u||new ct(l*a*r.length+64);return u||X(h,{H:!0,K:[]}),r.forEach((t,n)=>T(h,e[t],i[n],l,a,o[t])),u?void 0:h.finish()}}function*P(t,n,o,r,e,s,i){const{channels:c,alpha:h,palette:y}=r,w=function(t,n,o=256){const r=Math.ceil(t/o),e=Math.ceil(n/o);return{tt:r,nt:e,ot:Math.ceil(t/(8*o)),rt:Math.ceil(n/(8*o)),et:1===r&&1===e}}(n,o),d=w.tt*w.nt,A=y?2:c,g=Array.from({length:A},()=>S(Mt)),U=E(g),b={channels:c,alpha:h,palette:y&&y.colours,rct:s?.type,leaves:g,picture:y?[w.et?1:0]:g.map((t,n)=>n)},m=C(b,s),I=Array.from({length:c},()=>new Int16Array(1024)),x=t=>function(t,n,o,r){const e=r%t.tt*ut,s=(r/t.tt|0)*ut;return[e,s,Math.min(ut,n-e),Math.min(ut,o-s)]}(w,n,o,t);let J=null;if(y){const t=y.colours.length;J=new Int16Array(t*c),y.colours.forEach((n,o)=>{for(let r=0;r<c;r++)J[r*t+o]=y.byte(n,r)})}if(!y){const r=t=>{const n=p(t),o=new ct(128);M(o,n);let r=o.V;for(let o=0;o<t.length;o++)t[o]&&(r+=t[o]*(n.p[o]+Math.max(0,o<224?o-1:o-236)));return r},e=Math.min(n,32),s=Math.min(o,32),i=[Mt,pt].map(t=>({N:t,W:g.map(()=>new Uint32Array(257))}));for(const r of[0,.5,1]){m(I,t,n,Math.floor((n-e)*r),Math.floor((o-s)*r),e,s);for(const t of i)for(let n=0;n<c;n++)T(null,t.W[n],I[n],e,s,S(t.N))}g.forEach((t,n)=>{const o=i.map(t=>r(t.W[n])),e=o[1]<o[0]?1:0;t.N=i[e].N})}const N=y?[{type:"palette",G:0,k:c,R:y.colours.length}]:c>=3&&0!==s?.type?[{type:"rct",G:0,j:s?.type??6}]:[];let P=g.map(()=>new Uint32Array(33+(y?4096:1024)));J&&T(null,P[0],J,y.colours.length,c,g[0],!0);const q=O(b,s);yield*a({pooled:i,kind:"plan",setup:{...b,sizes:P.map(t=>t.length)},at:t=>(t+1)/(2*d),stop:()=>!1},d,o=>q(t,n,...x(o),P),(t,n)=>function(t,n){t.forEach((t,o)=>{const r=n[o];for(let n=0;n<t.length;n++)t[n]+=r[n]})}(P,n));const F=P.map(_),G=F.reduce((t,n)=>t+n[0].$-n[1].$,0)>27*(w.et?1:d+1)+8?1:0;g.forEach((t,n)=>{t.config=F[n][G].config}),P=F.map(t=>t[G].W);const j=new ct(256);f(j,n,o,r.colour,r.alpha,{colorSpace:e}),function(t,{alpha:n,shift:o=1}){t.write(9,8),n&&t.write(2,0),t.write(2,o),l(t,n)}(j,{alpha:r.alpha});const k=new ct(4096);k.write(1,1),k.write(1,1);const z=v(k,L(k,U),P,t=>g.indexOf(t),t=>g[t].config),R={...b,M:g.map((t,n)=>z[n+1]?.code)},B=O(R,s);X(k,{H:!0,K:N}),J&&T(k,R.M[0],J,y.colours.length,c,g[0]);const Z=[];if(w.et)B(t,n,0,0,n,o,k),yield 1,Z.push(k.finish());else{Z.push(k.finish());for(let t=0;t<w.ot*w.rt+1;t++)Z.push(new Uint8Array(0));const o=Z.length,r=(t,n)=>{Z[o+t]=n};yield*a({pooled:i,kind:"plan",setup:R,at:t=>(d+t+1)/(2*d),stop:()=>!1},d,o=>r(o,B(t,n,...x(o))),r)}return u(j,Z)}function q(t,n,o,r){let e=0;return e+=t[n]*It[r],e+=t[n+o]*It[r+1],e+=t[n+2*o]*It[r+2],e+=t[n+3*o]*It[r+3],e+=t[n+4*o]*It[r+4],e+=t[n+5*o]*It[r+5],e+=t[n+6*o]*It[r+6],e+=t[n+7*o]*It[r+7],e}function F(t,n,o){const r=new Float64Array(192),e=new Float64Array(64),s=new Float64Array(192);return(i,c)=>{for(let e=0;e<8;e++)for(let s=0;s<8;s++){const f=4*(Math.min(o-1,8*c+e)*n+Math.min(n-1,8*i+s)),l=t[f],a=t[f+1],u=t[f+2],h=8*e+s;r[h]=.299*l+.587*a+.114*u-128,r[64+h]=-.168735892*l-.331264108*a+.5*u,r[128+h]=.5*l-.418687589*a-.081312411*u}for(let t=0;t<3;t++){for(let n=0;n<8;n++)for(let o=0;o<8;o++)e[8*n+o]=q(r,64*t+8*n,1,8*o);for(let n=0;n<8;n++)for(let o=0;o<8;o++)s[64*t+8*n+o]=q(e,o,8,8*n)}return s}}function*G(t,n,o,r,e){const s=Math.ceil(n/8),i=Math.ceil(o/8),c=r>=30?.1+.09*(100-r):53/3e3*r*r-1.15*r+25,f=[0,1,2].map(t=>({h:1,v:1,stride:s,rows:i,quant:Int32Array.from(xt,(n,o)=>Math.max(1,Math.round(16*c*(t?18:10)*(o?n:.25)))),coeffs:new Int16Array(s*i*64)})),l=F(t,n,o),a=(t,n)=>{for(let o=t;o<n;o++)for(let t=0;t<s;t++){const n=l(t,o),r=64*(o*s+t);for(let t=0;t<3;t++)for(let o=0;o<64;o++)f[t].coeffs[r+o]=Math.round(16*n[64*t+o]/f[t].quant[o])}};for(let t=0;t<i;t+=32)a(t,Math.min(i,t+32)),yield Math.min(i,t+32)/i;let u=null;for(let n=3;n<t.length;n+=4)if(255!==t[n]){u=t;break}return{width:n,height:o,components:f,quantScale:16,ycbcr:!0,orientation:1,alpha:u,colorSpace:e}}function j(n,o,r,c,f){const{quality:l,colorSpace:a}=function(t={},n=100){if(null===t||"object"!=typeof t)throw lt("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 lt("JXL_INPUT","Quality is a number from 1 to 100.");const o=void 0===t.colorSpace?"srgb":t.colorSpace;if("srgb"!==o&&"display-p3"!==o)throw lt("JXL_INPUT","The colour space is srgb or display-p3.");return{quality:n,colorSpace:o}}(c,90),u=function(t={}){if(null===t||"object"!=typeof t)throw lt("JXL_INPUT","Options are an object.");const n=void 0===t.effort?1:t.effort;if(!Number.isInteger(n)||n<1||n>9)throw lt("JXL_INPUT","Effort is a whole number from 1 to 9.");return n}(c);return function(t,n,o,r){if(function(t,n,o=et){if(!Number.isInteger(t)||!Number.isInteger(n)||t<1||n<1)throw lt("JXL_INPUT","A picture is at least one pixel wide and high, in whole pixels.");if(t>o.edge||n>o.edge)throw lt("JXL_DIMENSIONS","A picture is at most "+o.edge+" pixels on a side.");if(t*n>o.pixels)throw lt("JXL_DIMENSIONS","A picture is at most "+o.pixels.toLocaleString("en-US")+" pixels.")}(n,o,r),!(t instanceof Uint8Array||t instanceof Uint8ClampedArray))throw lt("JXL_INPUT","Pixels are a Uint8Array or Uint8ClampedArray of RGBA bytes.");if(t.length!==n*o*4)throw lt("JXL_INPUT","Pixels are width * height * 4 bytes: straight (not premultiplied) RGBA, row by row.")}(n,o,r,st),function(t){const n=function*(){let o,r;for(;!(o=i(()=>t.next(n.hurry))).done;)yield r=o.value;return r<1&&(yield 1),n.bytes=(s((e=o.value).length),e);var e}();return n.bytes=null,n.hurry=!1,n}(function*(n,o,r,s,i,c,f){if(s>=100)return yield*function*(n,o,r,{shape:e=N(n,o,r),colorSpace:s,rct:i,pooled:c}={}){let f,l;const a=e.palette?[e,{...e,palette:null}]:[e];for(let e=0;e<a.length;e++)try{const l=yield*t(P(n,o,r,a[e],s,i,c),e,a.length);(!f||l.length<f.length)&&(f=l)}catch(t){if("JXL_SIZE"!==t.code)throw t;l=t}if(!f)throw l;return f}(n,o,r,{colorSpace:i});if(c<5)return yield*t(f(yield*t(G(n,o,r,s,i),0,2),c),1,2);const l=yield*t(G(n,o,r,s,i),0,4);let a=yield*t(f(l,4),1,4);if(yield.5)return a;try{const o=yield*t(function*(t,n,o=0){const{width:r,height:s,components:i,quantScale:c}=n,f=i[0].stride,l=i[0].rows,a=f*l,u=St.length;if(t.byteLength+i.reduce((t,n)=>t+n.coeffs.byteLength,0)+Math.max(a*(10*u+2)+o,7*st.bytes)>Jt)return null;const h=new Float64Array(a*u),y=new Uint16Array(a*u),w=F(t,r,s),d=i.map(t=>t.quant),p=i.map(t=>t.coeffs),M=St.map(t=>d.map(n=>Float64Array.from(n,n=>n*function(t){const n=e(t);let o=1+(1023&n)/1024,r=(n>>10)-15;for(;r<0;)o/=2,r++;for(;r>0;)o*=2,r--;return o}(4/(2040*c))*8*255/t))),A=new Int16Array(192),g=new Float64Array(3),U=new Int32Array(6),b=new Float64Array(6),m=new Float64Array(6);let I=0,x=0;const S=(t,n,o)=>{let r=0,e=0;const s=Et[o]*d[0][1]*d[0][1]/(c*c);for(let i=1;i<64;i++){if(!o){for(let r=0;r<3;r++){const s=p[r][64*n+i];g[r]=Xt(s,r)*M[o][r][i]-t[64*r+i],s&&(e+=2+2*(32-Math.clz32(Math.abs(s))))}r+=vt(g[0],g[1],g[2]);continue}for(let n=0;n<3;n++){const r=t[64*n+i],e=M[o][n][i],s=Math.floor(r/e);for(let t=0;t<2;t++){const o=s+t;U[2*n+t]=o,m[2*n+t]=o?2+2*(32-Math.clz32(Math.abs(o))):0,b[2*n+t]=o<-32768||o>32767?1/0:Xt(o,n)*e-r}}let c=0,f=1/0,l=0;for(let t=0;t<8;t++){const n=vt(b[1&t],b[2+(t>>1&1)],b[4+(t>>2)]),o=n+s*(m[1&t]+m[2+(t>>1&1)]+m[4+(t>>2)]);o<f&&(f=o,c=t,l=n)}if(f===1/0)return{error:1/0,rate:65535};r+=l;for(let t=0;t<3;t++){const n=U[2*t+(c>>t&1)];A[64*t+i]=n,n&&(e+=2+2*(32-Math.clz32(Math.abs(n))))}}return{error:r,rate:e}};for(let t=0;t<l;t++)for(let n=0;n<f;n++){const o=t*f+n,r=w(n,t);for(let t=0;t<u;t++){const n=S(r,o,t),e=o*u+t;h[e]=n.error,y[e]=n.rate,t||(I+=n.error,x+=n.rate)}if(((o+1)%1024==0||o+1===a)&&(yield(o+1)/(3*a)))return null}const E=new Uint8Array(a);let L=0;const J=function*(t,n){let o=0,r=0;for(let e=0;e<a;e++){const s=e*u;let i=0,c=h[s]+t*y[s];for(let n=1;n<u;n++){const o=h[s+n]+t*y[s+n];o<c&&(c=o,i=n)}if(o+=h[s+i],r+=y[s+i],n&&(E[e]=i),((e+1)%1024==0||e+1===a)&&(yield 1/3+(L+(e+1)/a)/147))return null}return L++,{error:o,rate:r}};let X=0,v=Math.max(1,I/Math.max(1,x));for(let t=0;t<24;t++){const t=yield*J(v,!1);if(!t)return null;if(t.error>I)break;v*=2}for(let t=0;t<24;t++){const t=(X+v)/2,n=yield*J(t,!1);if(!n)return null;n.error<=I?X=t:v=t}const T=yield*J(X,!0);if(!T||T.error>I||T.rate>=x||!E.some(t=>t))return null;const _=new Uint8Array(a);for(let t=0;t<l;t++)for(let n=0;n<f;n++){const o=t*f+n,r=E[o];if(_[o]=St[r],r){S(w(n,t),o,r);for(let t=0;t<3;t++)for(let n=1;n<64;n++)p[t][64*o+n]=A[64*t+n]}if(((o+1)%1024==0||o+1===a)&&(yield 2/3+(o+1)/(3*a)))return null}return{...n,quantFieldBase:4,quantFields:_,reconstructionError:{baseline:I,candidate:T.error}}}(n,l,a.byteLength),2,4);o&&(a=yield*t(f(o,4,a),3,4))}catch(t){if((!(t instanceof RangeError)||t.code)&&"JXL_SIZE"!==t.code)throw t}return a}(n,o,r,l,a,u,f))}function k(t,n){return U(t,n,Ct),Ct[0]}function z(t){let n=0;for(t>=2&&t<4294967296&&(n=31-Math.clz32(t),t*=Ot[n]);t>=1.4142135623730951;)t/=2,n++;for(;t<.7071067811865476;)t*=2,n--;const o=(t-1)/(t+1),r=o*o;let e=o,s=o;for(let t=3;t<30;t+=2)s+=(e*=r)/t;return n+2.885390081777927*s}function R(t){let n=0,o=0;for(const o of t)n+=o;for(const r of t)r&&(o+=r*z(n/r));return o}function B(t,{maxClusters:n=48,newClusterCost:o=320}={}){const r=[];for(let n=0;n<t.st;n++)t.it[n]>0&&r.push(n);r.sort((n,o)=>t.it[o]-t.it[n]);const e=[];for(const s of r){const r=t.ct([s],_t),i=R(r);let c=-1,f=1/0;for(let t=0;t<e.length;t++){const n=e[t],o=new Uint32Array(64);for(let t=0;t<64;t++)o[t]=n.W[t]+r[t];const s=R(o)-n.$-i;s<f&&(f=s,c=t)}if(c<0||e.length<n&&f>o)e.push({st:[s],W:r,$:i});else{const t=e[c];t.st.push(s);for(let n=0;n<64;n++)t.W[n]+=r[n];t.$=R(t.W)}}e.length||e.push({st:[],W:new Uint32Array(64),$:0});const s=new Uint8Array(t.st);let i=2*t.st;const c=e.map((n,o)=>{for(const t of n.st)s[t]=o;let r=Tt[0],e=1/0;for(const o of Tt){const s=t.ft(n.st,o);s<e&&(e=s,r=o)}const c=p(t.ct(n.st,r,128));return i+=e+3*c.u+40,{config:r,code:c}});return{L:s,J:c,write:(t,n,o)=>{const r=c[s[n]];m(t,r.code,r.config,o)},$:i}}function Z(t,n="JXL_JPEG"){return Object.assign(new Error("JPEG: "+t),{code:n})}function D(t,n,o){const r=[];if(n>0){const o=new Uint32Array(2048);let e=0;for(const n of t)o[Math.min(2047,Math.max(0,n+1024))]++,e++;let s=0,i=e/(n+1);for(let t=0;t<2048&&r.length<n;t++)s+=o[t],s>i&&(r.push(t-1025),i=e*(r.length+1)/(n+1))}const e=r.length+1,s=new Uint8Array(39*e);for(let t=0;t<e;t++)for(let n=0;n<qt;n++)s[(0+n)*e+t]=t,s[(13+n)*e+t]=o?e:e+(t>>1),s[(26+n)*e+t]=o?e:e+(e-1>>1)+1+(t>>1);let i=0;for(const t of s)i=Math.max(i,t+1);return{thresholds:r,lt:e,map:s,ut:i,ht:t=>{let n=0;for(const o of r)t>o&&n++;return n},yt:(t,n)=>s[(t<2?1^t:2)*qt*e+n],wt:(t,n)=>(Math.min(t,64)<8?Math.min(t,64):4+(Math.min(t,64)>>1))*i+n,dt:t=>37*i+458*t,st:495*i}}function Y(t,n,o){const r=n.map(()=>S(o)),e=r.map(()=>new Uint32Array(257));n.forEach((t,n)=>T(null,e[n],t.data,t.w,t.h,r[n])),X(t,{H:!1,K:[]});const s=L(t,E(r)),i=v(t,s,e,t=>r.indexOf(t));n.forEach((n,o)=>T(t,i[o+1].code,n.data,n.w,n.h,r[o]))}function*H(t,o={}){const{width:i,height:c,components:a,alpha:h}=t,y=1===a.length,w=t.quantScale||1;let d=t.quantFieldBase||1;for(;d/(2040*w)<1/16384;)d*=2;const M=t.ycbcr||y,g=y?[0,0,0]:M?[1,0,2]:[0,1,2],U=g.map(t=>a[t]),S=o.orders?g.map(t=>o.orders[t].map(t=>jt[t])):[jt,jt,jt],E=t=>1===t?0:2===t?1:-1,L=U.map(t=>E(t.h)),J=U.map(t=>E(t.v));if(L.includes(-1)||J.includes(-1))throw Z("sampling factors other than one or two");const X=Math.max(...L),v=Math.max(...J),T=t=>X-L[t],_=t=>v-J[t];if(!M&&(X||v))throw Z("an RGB JPEG with subsampling");const N=Math.ceil(i/(8<<X))<<X,C=Math.ceil(c/(8<<v))<<v,O=Math.ceil(i/ut),P=Math.ceil(c/ut),q=O*P,F=Math.ceil(N/256),G=F*Math.ceil(C/256),j=1===q,k=(t,n,o)=>{const r=U[t];return y&&1!==t?-1:64*(o*r.stride+n)},z=t=>{let n=1024;for(;t%n;)n>>=1;return n},R=U.map(t=>M?1:t.quant[0]/z(t.quant[0])),H=U.map(t=>M?0:1024/z(t.quant[0])),Q=new Int32Array(N*C);for(let t=0;t<C;t++)for(let n=0;n<N;n++)Q[t*N+n]=U[1].coeffs[k(1,n,t)]*R[1]+H[1];const K=U[1].quant,V=(K[8]+K[16]+K[24]+K[32]+K[40])/w,W=N*C,$=W<256?0:Math.max(1,Math.min(7,r(W)-r(V)-7));let tt=D(Q,$,y);const nt=new ct(256);if(f(nt,i,c,y?1:3,!!h,{orientation:t.orientation,colorSpace:t.colorSpace}),nt.write(1,0),nt.write(2,0),nt.write(1,0),nt.write(2,2),nt.write(8,111),nt.write(1,M?1:0),M)for(let t=0;t<3;t++)nt.write(2,0===L[t]&&0===J[t]?0:1===L[t]&&1===J[t]?1:1===L[t]?2:3);nt.write(2,0),h&&nt.write(2,0),l(nt,h);const ot=[0,1,2].map(()=>new Int32Array(1024)),rt=(t,o)=>{const r=t%O*32,e=32*(t/O|0),s=Math.min(32,N-r),i=Math.min(32,C-e);for(let t=0;t<i;t++)for(let i=0;i<s;i++)for(const s of[1,0,2]){const c=T(s),f=_(s),l=i>>c,a=t>>f;if(l<<c!==i||a<<f!==t)continue;const u=k(s,(r>>c)+l,(e>>f)+a),h=U[s].coeffs;let y=0;if(u>=0)for(let t=1;t<64;t++)h[u+t]&&y++;const w=ot[s],d=0===l?0===a?32:w[32*(a-1)]:0===a?w[32*a+l-1]:w[32*(a-1)+l]+w[32*a+l-1]+1>>1;w[32*a+l]=y;const p=tt.yt(s,tt.ht(Q[(e+t)*N+r+i]));o(tt.wt(d,p),y);const M=tt.dt(p);let A=y>4?0:1,g=y;for(let t=1;t<64&&g;t++){const r=h[u+S[s][t]];o(M+kt(g,t,A),n(r)),A=r?1:0,g-=A}}},et=($>0?3:2)*q+G;let st=0;const it=function*(t){tt=t;const n=new Nt(t.st);for(let t=0;t<q;t++)rt(t,(t,o)=>n.add(t,o)),yield++st/et;return(o.coding||B)(n,o.clusters)};let ft=yield*it(tt);if($>0){const t=tt,n=D(Q,0,y),o=yield*it(n);o.$<=ft.$?ft=o:tt=t}const lt=Array.from({length:j?1:2+G+q},()=>null),at=t=>lt[j?0:t]||(lt[j?0:t]=new ct(4096)),ht=new Uint32Array(lt.length);let yt=0;const wt=t=>{t=j?0:t;const n=Math.ceil(lt[t].V/8);yt+=n-ht[t],ht[t]=n,s(yt)},dt=at(0);dt.write(1,0);const pt=(t,n)=>{const o=t*ut,r=n*ut,e=Math.min(ut,i-o),s=Math.min(ut,c-r),f=new Int32Array(e*s);for(let t=0;t<s;t++)for(let n=0;n<e;n++)f[t*e+n]=h[4*((r+t)*i+o+n)+3];return{w:e,h:s,data:f}};for(let t=0;t<3;t++)dt.write(16,e(U[t].quant[0]/R[t]/(2040*w)*128));dt.write(2,3),dt.write(16,57343),dt.write(2,1),dt.write(5,0),dt.write(1,0),dt.write(4,0),dt.write(4,tt.thresholds.length);for(const t of tt.thresholds)dt.t([[4,0],[8,16],[16,272],[32,65808]],n(t));dt.write(4,0),dt.write(4,0),I(dt,tt.map),dt.write(1,0),dt.write(2,0),dt.write(16,0),dt.write(16,0),dt.write(8,128),dt.write(8,128),dt.write(1,0),h&&Y(dt,[j?pt(0,0):{w:0,h:0,data:new Int32Array(0)}],Mt),wt(0);for(let n=0;n<G;n++){const o=at(1+n),e=n%F*256,s=256*(n/F|0),i=Math.min(256,N-e),c=Math.min(256,C-s);o.write(2,0),Y(o,[1,0,2].map(t=>{const n=i>>T(t),o=c>>_(t),r=new Int32Array(n*o),f=R[t],l=H[t];for(let i=0;i<o;i++)for(let o=0;o<n;o++){const c=k(t,(e>>T(t))+o,(s>>_(t))+i);r[i*n+o]=c<0?0:U[t].coeffs[c]*f+l}return{w:n,h:o,data:r}}),Mt);const f=i*c,l=r(f);l&&o.write(l,f-1);const a=i+7>>3,u=c+7>>3,h=new Int32Array(2*f).fill(d-1,f);if(t.quantFields)for(let n=0;n<c;n++)for(let o=0;o<i;o++)h[f+n*i+o]=t.quantFields[(s+n)*N+e+o]-1;Y(o,[{w:a,h:u,data:new Int32Array(a*u)},{w:a,h:u,data:new Int32Array(a*u)},{w:f,h:2,data:h},{w:i,h:c,data:new Int32Array(f)}],d>1?Mt:0),wt(1+n),yield++st/et}const At=at(1+G);At.write(1,0),At.write(3,7),At.write(16,e(d/(2040*w))),Y(At,[0,1,2].map(t=>{const n=new Int32Array(64),o=U[t].quant;for(let t=0;t<8;t++)for(let r=0;r<8;r++)n[8*r+t]=o[8*t+r];return{w:8,h:8,data:n}}),Mt);for(let t=1;t<17;t++)At.write(3,0);if(q>1&&At.write(r(q),0),o.orders){At.t([[0,95],[0,19],[0,0],[13,0]],1);const t=[];for(const n of g){const r=Array.from({length:64},(t,n)=>n),e=o.orders[n].map(t=>{const n=r.indexOf(t);return r.splice(n,1),n});let s=64;for(;s>1&&!e[s-1];)s--;t.push(s-1,...e.slice(1,s))}const n=A(0),r=new Uint32Array(64);for(const o of t)b(n,o,r);const e=p(r);x(At,{L:new Uint8Array(8),J:[{config:n,code:e}]});for(const o of t)m(At,e,n,o)}else At.write(2,2);ft.writeHistograms?ft.writeHistograms(At):x(At,{L:ft.L,J:ft.J}),wt(1+G);for(let t=0;t<q;t++){const n=at(2+G+t);rt(t,(t,o)=>ft.write(n,t,o)),ft.flush&&ft.flush(n),h&&!j&&Y(n,[pt(t%O,Math.floor(t/O))],Mt),wt(2+G+t),yield++st/et}return u(nt,function(t){return t.map(t=>t?t.finish():new Uint8Array(0))}(lt))}function*Q(t,n,o){if(n<3&&!o)return yield*H(t);let r=o;try{const o=H(t);let e,s;for(;!(e=o.next()).done;)if(s=yield e.value/2,s&&r&&e.value<1)return o.return(),r;if((!r||e.value.length<r.length)&&(r=e.value),s||n<3)return r;const i=n<4?1:3,c=H(t,{clusters:{maxClusters:32,newClusterCost:160}});for(;!(e=c.next()).done;)if(s=yield.5+e.value/(2*i),s&&e.value<1)return c.return(),r;if(e.value.length<r.length&&(r=e.value),s||n<4)return r;const f=function*(t){const n=Pt.map(t=>(7&t)<<3|t>>3),o=t.map(()=>new Uint32Array(64)),r=t.reduce((t,n)=>t+n.coeffs.length,0);let e=0;for(let s=0;s<t.length;s++){const{coeffs:i}=t[s],c=o[s];for(let t=0;t<i.length;t+=65536){const o=Math.min(t+65536,i.length);for(let r=t;r<o;r+=64)for(let t=1;t<64;t++)i[r+n[t]]&&c[t]++;if(e+=o-t,yield e/r)return null}}return o.map(t=>[0,...Array.from({length:63},(t,n)=>n+1).sort((n,o)=>Math.floor(t[o]/8)-Math.floor(t[n]/8)||n-o)])}(t.components);for(;!(e=f.next()).done;)if(yield.5+(1+e.value)/6)return f.return(),r;const l=H(t,{orders:e.value});for(;!(e=l.next()).done;)if(s=yield.5+(2+e.value)/6,s&&e.value<1)return l.return(),r;return e.value.length<r.length?e.value:r}catch(t){if(!r||(!(t instanceof RangeError)||t.code)&&"JXL_SIZE"!==t.code)throw t;return r}}function K(t,n){if(!n)return void t.write(1,0);const r=o(n);t.write(1,1),t.write(3,r),t.write(r,n-(1<<r))}function V(t){let n=0,o=0;for(let r=0;r<t.length;r++)n+=t[r],t[r]&&(o=r+1);const r=new Uint16Array(o||1);if(!n)return r[0]=4096,r;let e=0;for(let s=0;s<o;s++)t[s]&&(e+=r[s]=Math.max(1,Math.round(4096*t[s]/n)));for(;4096!==e;){const s=e<4096?1:-1;let i=-1,c=-1/0;for(let e=0;e<o;e++)if(t[e]&&(s>0||r[e]>1)){const o=s*(4096*t[e]-r[e]*n);o>c&&(i=e,c=o)}r[i]+=s,e+=s}return r}function W(t,n){const r=[];let e=0;for(let t=0;t<n.length;t++)n[t]&&r.push(t),n[t]>n[e]&&(e=t);if(r.length<=2){t.write(1,1),t.write(1,r.length-1);for(const n of r)K(t,n);return void(2===r.length&&t.write(12,n[r[0]]))}t.write(1,0),t.write(1,0),t.write(3,7),t.write(3,5),K(t,n.length-3);const s=new Uint8Array(n.length);let i=0;for(let t=0;t<n.length;t++)t!==e&&n[t]&&(s[t]=o(n[t])+1,i=Math.max(i,s[t]+(t<e?1:0)));s[e]=i;for(const n of s)t.write(zt[n],Rt[n]);for(let o=0;o<n.length;o++)if(o!==e&&s[o]>1){const r=s[o]-1;t.write(r,n[o]-(1<<r))}}function $(t,n){const o=1<<n,r=4096>>n,e=new Uint16Array(t.length+1),s=new Uint16Array(4096);let i=-1;for(let n=0;n<t.length;n++)e[n+1]=e[n]+t[n],4096===t[n]&&(i=n);if(i>=0){for(let t=0;t<4096;t++)s[t]=t;return{starts:e,reverse:s}}const c=new Int32Array(o),f=new Uint16Array(o),l=new Int32Array(o),a=[],u=[];c.set(t);for(let t=0;t<o;t++)c[t]<r?a.push(t):c[t]>r&&u.push(t);for(;u.length;){const t=u.pop(),n=a.pop(),o=r-c[n];c[t]-=o,f[n]=t,l[n]=c[t]-c[n],c[t]<r?a.push(t):c[t]>r&&u.push(t)}for(let t=0;t<o;t++)for(let n=0;n<r;n++){const o=n<c[t],i=o?t:f[t],a=o?n:n+l[t];s[e[i]+a]=t*r+n}return{starts:e,reverse:s}}function tt(t,n){const o=B(t,n),e=o.J.map(()=>[]);for(let n=0;n<t.st;n++)t.it[n]&&e[o.L[n]].push(n);const s=o.J.map(({config:n},o)=>({config:n,counts:V(t.ct(e[o],n,256))})),i=Math.max(5,r(Math.max(...s.map(t=>t.counts.length))));for(const t of s)Object.assign(t,$(t.counts,i));const c=new ct;c.write(1,0),o.L.length>1&&I(c,o.L),c.write(1,0),c.write(2,i-5);for(const t of s)g(c,t.config,i);for(const t of s)W(c,t.counts);const f=new Uint32Array(Bt),l=new Uint8Array(Bt),a=new Uint16Array(Bt),u=[0,0,0];let h=0;return{$:o.$,write:(t,n,r)=>{f[h]=r,l[h++]=o.L[n]},flush:t=>{let n=1245184;for(let t=h-1;t>=0;t--){const o=s[l[t]];U(o.config,f[t],u);const r=u[0],e=o.counts[r];f[t]=u[2],l[t]=u[1],n>>>20>=e&&(a[t]=65535&n,l[t]|=64,n>>>=16);const i=Math.floor(n/e);n=4096*i+o.reverse[o.starts[r]+n-i*e]}t.write(32,n);for(let n=0;n<h;n++){64&l[n]&&t.write(16,a[n]);const o=63&l[n];o&&t.write(o,f[n])}h=0},writeHistograms:t=>t.append(c)}}function*nt(n,o,r){if(o<2)return yield*Q(n,o,r);let e=yield*t(Q(n,o,r),0,2);if(yield.5)return e;try{const t=H(n,{coding:tt});let o;for(;!(o=t.next()).done;)if((yield.5+o.value/2)&&o.value<1)return t.return(),e;o.value.length<e.length&&(e=o.value)}catch(t){if((!(t instanceof RangeError)||t.code)&&"JXL_SIZE"!==t.code)throw t}return e}function ot(t,n,o,r){return function(t){let n;for(;!(n=t.next()).done;);return n.value}(rt(t,n,o,r))}function rt(t,n,o,r){return j(t,n,o,r,nt)}const et=Object.freeze({bytes:16777216,pixels:24e6,edge:16384}),st=Object.freeze({bytes:16777216,pixels:4e7,edge:16384}),it=[1];for(let t=1;t<=32;t++)it.push(2*it[t-1]);class ct{constructor(t=4096){this.bytes=new Uint8Array(t),this.at=0,this.Mt=0,this.pending=0}write(t,n){if(!(n>=0&&n<it[t]))throw new Error("bit write out of range: "+t+" bits, "+n);let o=this.Mt+n*(1<<this.pending),r=this.pending+t;this.at+5>=this.bytes.length&&this.grow();const e=this.bytes;for(;r>=8;)e[this.at++]=255&o,o=Math.floor(o/256),r-=8;this.Mt=o,this.pending=r}t(t,n){for(let o=0;o<4;o++){const[r,e]=t[o];if(n>=e&&n-e<it[r])return this.write(2,o),void(r&&this.write(r,n-e))}throw new Error("U32 value out of range: "+n)}o(){this.pending&&(this.at+1>=this.bytes.length&&this.grow(),this.bytes[this.at++]=this.Mt,this.Mt=0,this.pending=0)}get V(){return 8*this.at+this.pending}grow(t=0){const n=et.bytes+64;if(this.at+t+16>n)throw Object.assign(new Error("The encoded picture exceeds 16 MiB."),{code:"JXL_SIZE"});const o=new Uint8Array(Math.min(n,Math.max(2*this.bytes.length,this.at+t+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.Mt)}finish(){const t=this.at+(this.pending?1:0),n=this.bytes.slice(0,t);return this.pending&&(n[this.at]=this.Mt),n}}const ft=(t,n)=>"number"==typeof t?n(t):{...t,at:o=>n(t.at(o))},lt=(t,n)=>Object.assign(new Error(n),{code:t}),at={channel:null,weighted:null,fill:null},ut=256,ht=[1,2,3,4,0,5,17,6,16,7,8,9,10,11,12,13,14,15],yt=[2,4,3,2,2,4],wt=[0,7,3,2,1,15],dt=[0,0,0],pt=3,Mt=5,At=A(4),gt=Object.freeze({T:224,minLength:7,_:At}),Ut=A(0),bt=[A(0),A(4),A(4,1,1),A(3,0,1)],mt=(t,n,o,r)=>o?r?t[n]+256*t[n+3]:t[n]:t[n]+256*t[n+1]+65536*t[n+2]+(r?16777216*t[n+3]:0),It=Float64Array.of(.3535533905932738,.3535533905932738,.3535533905932738,.3535533905932738,.3535533905932738,.3535533905932738,.3535533905932738,.3535533905932738,.4903926402016152,.4157348061512726,.27778511650980114,.09754516100806417,-.0975451610080641,-.277785116509801,-.4157348061512727,-.4903926402016152,.46193976625564337,.19134171618254492,-.19134171618254486,-.46193976625564337,-.4619397662556434,-.19134171618254517,.191341716182545,.46193976625564326,.4157348061512726,-.0975451610080641,-.4903926402016152,-.2777851165098011,.2777851165098009,.4903926402016152,.09754516100806439,-.41573480615127256,.3535533905932738,-.35355339059327373,-.35355339059327384,.3535533905932737,.35355339059327384,-.35355339059327334,-.35355339059327356,.3535533905932733,.27778511650980114,-.4903926402016152,.09754516100806415,.4157348061512728,-.41573480615127256,-.09754516100806401,.4903926402016153,-.27778511650980076,.19134171618254492,-.4619397662556434,.46193976625564326,-.19134171618254495,-.19134171618254528,.46193976625564337,-.4619397662556432,.19134171618254478,.09754516100806417,-.2777851165098011,.4157348061512728,-.4903926402016153,.4903926402016152,-.4157348061512725,.27778511650980076,-.09754516100806429),xt=Float64Array.from({length:64},(t,n)=>{const o=.26236426446749106*Math.max(0,5*Math.sqrt((7&n)*(7&n)+(n>>3)*(n>>3))/Math.sqrt(98)-1);let r=1,e=1;for(let t=1;t<30;t++)e+=r=r*o/t;return e}),St=[4,4,4,4,3,2,6,8],Et=[0,.125,.25,.5,.25,.25,.25,.25],Lt=[.9299455010825141,.945349926692846,.9500648966626563],Jt=10.5*st.pixels,Xt=(t,n)=>Math.abs(t)<2?t*Lt[n]:t-.145/t,vt=(t,n,o)=>{const r=t+1.402*o,e=t-.344136286*n-.714136286*o,s=t+1.772*n;return r*r+e*e+s*s},Tt=[A(0),A(1),A(2),A(3),A(4),A(4,1,1),A(5,1,1),A(2,0,1),A(3,0,1)],_t=A(4,1,1);class Nt{constructor(t){this.st=t,this.small=new Uint32Array(64*t),this.At=Array.from({length:t},()=>null),this.it=new Float64Array(t)}add(t,n){if(this.it[t]++,n<64)return void this.small[64*t+n]++;const o=this.At[t]||(this.At[t]=new Map);o.set(n,(o.get(n)||0)+1)}ct(t,n,o=64){const r=new Uint32Array(o);for(const o of t){const t=64*o;for(let o=0;o<64;o++)this.small[t+o]&&(r[k(n,o)]+=this.small[t+o]);const e=this.At[o];if(e)for(const[t,o]of e)r[k(n,t)]+=o}return r}ft(t,n){let r=R(this.ct(t,n));for(const e of t){const t=64*e;for(let e=n.S;e<64;e++)this.small[t+e]&&(r+=this.small[t+e]*(o(e)-n.m-n.I));const s=this.At[e];if(s)for(const[t,e]of s)r+=e*(o(t)-n.m-n.I)}return r}}const Ct=[0,0,0],Ot=[1];for(let t=1;t<32;t++)Ot.push(Ot[t-1]/2);const Pt=[0,1,8,16,9,2,3,10,17,24,32,25,18,11,4,5,12,19,26,33,40,48,41,34,27,20,13,6,7,14,21,28,35,42,49,56,57,50,43,36,29,22,15,23,30,37,44,51,58,59,52,45,38,31,39,46,53,60,61,54,47,55,62,63],qt=13,Ft=[0,0,1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,15,16,16,17,17,18,18,19,19,20,20,21,21,22,22,23,23,23,23,24,24,24,24,25,25,25,25,26,26,26,26,27,27,27,27,28,28,28,28,29,29,29,29,30,30,30,30],Gt=[0,0,31,62,62,93,93,93,93,123,123,123,123,152,152,152,152,152,152,152,152,180,180,180,180,180,180,180,180,180,180,180,180,206,206,206,206,206,206,206,206,206,206,206,206,206,206,206,206,206,206,206,206,206,206,206,206,206,206,206,206,206,206,206],jt=Pt.map(t=>(7&t)<<3|t>>3),kt=(t,n,o)=>2*(Gt[t]+Ft[n])+o,zt=[5,4,4,4,4,4,3,3,3,3,3,6,7],Rt=[17,11,15,3,9,7,4,2,5,6,0,33,1],Bt=196608;export{st as LIMITS,ot as encodePhoto,rt as encodePhotoSteps};
package/photo-ans.mjs ADDED
@@ -0,0 +1,9 @@
1
+ // SPDX-License-Identifier: MIT
2
+ // Photographic pixels through one checked job owner; quality 100 remains exact.
3
+ import {PHOTO_LIMITS, complete} from './bits.mjs';
4
+ import {photoJob} from './photo-job.mjs';
5
+ import {coefficientAnsSteps} from './coefficient-ans.mjs';
6
+
7
+ export {PHOTO_LIMITS as LIMITS};
8
+ export function encodePhoto(data, width, height, options) { return complete(encodePhotoSteps(data, width, height, options)); }
9
+ export function encodePhotoSteps(data, width, height, options) { return photoJob(data, width, height, options, coefficientAnsSteps); }
package/photo-dct.mjs ADDED
@@ -0,0 +1,83 @@
1
+ // SPDX-License-Identifier: MIT
2
+ // Neither table comes from Math.cos, Math.hypot or `**`, which each engine rounds its own way; the same pixels must
3
+ // give the same bytes everywhere. The DCT8 basis, (u ? 1 : √½) · cos((2x + 1) · u · π / 16) / 2 at u · 8 + x, is
4
+ // written out as V8 computed it (the cosines of the larger angles carry their rounding, and a coefficient on a
5
+ // rounding edge would move without it). The quantisation shape, 1.3 ** max(0, hypot(u, v) · 5 / √98 − 1) at
6
+ // v · 8 + u, is the exponential series of its logarithm: libjxl 0.7's DCT8 luma distance bands
7
+ // (lib/jxl/quant_weights.cc), the reciprocal weight growing with radial frequency. Both chroma planes keep full
8
+ // resolution; coarser chroma AC steps reserve bits for luma, while finer DC steps preserve smooth gradients.
9
+ const COSINES = Float64Array.of(0.3535533905932738, 0.3535533905932738, 0.3535533905932738, 0.3535533905932738,
10
+ 0.3535533905932738, 0.3535533905932738, 0.3535533905932738, 0.3535533905932738, 0.4903926402016152,
11
+ 0.4157348061512726, 0.27778511650980114, 0.09754516100806417, -0.0975451610080641, -0.277785116509801,
12
+ -0.4157348061512727, -0.4903926402016152, 0.46193976625564337, 0.19134171618254492, -0.19134171618254486,
13
+ -0.46193976625564337, -0.4619397662556434, -0.19134171618254517, 0.191341716182545, 0.46193976625564326,
14
+ 0.4157348061512726, -0.0975451610080641, -0.4903926402016152, -0.2777851165098011, 0.2777851165098009,
15
+ 0.4903926402016152, 0.09754516100806439, -0.41573480615127256, 0.3535533905932738, -0.35355339059327373,
16
+ -0.35355339059327384, 0.3535533905932737, 0.35355339059327384, -0.35355339059327334, -0.35355339059327356,
17
+ 0.3535533905932733, 0.27778511650980114, -0.4903926402016152, 0.09754516100806415, 0.4157348061512728,
18
+ -0.41573480615127256, -0.09754516100806401, 0.4903926402016153, -0.27778511650980076, 0.19134171618254492,
19
+ -0.4619397662556434, 0.46193976625564326, -0.19134171618254495, -0.19134171618254528, 0.46193976625564337,
20
+ -0.4619397662556432, 0.19134171618254478, 0.09754516100806417, -0.2777851165098011, 0.4157348061512728,
21
+ -0.4903926402016153, 0.4903926402016152, -0.4157348061512725, 0.27778511650980076, -0.09754516100806429);
22
+ export {COSINES as dct8Basis};
23
+ export const QUANT_SHAPE = Float64Array.from({length: 64}, (_, k) => {
24
+ const y = Math.max(0, Math.sqrt((k & 7) * (k & 7) + (k >> 3) * (k >> 3)) * 5 / Math.sqrt(98) - 1) * 0.26236426446749106;
25
+ let term = 1, sum = 1;
26
+ for (let n = 1; n < 30; n++) sum += term = term * y / n;
27
+ return sum;
28
+ });
29
+
30
+ // Eight terms in the original accumulation order, including the initial zero. The two separable passes
31
+ // share this fixed dot product without a loop or a changed floating-point sum.
32
+ function dot8(data, at, step, basis) {
33
+ let sum = 0;
34
+ sum += data[at] * COSINES[basis];
35
+ sum += data[at + step] * COSINES[basis + 1];
36
+ sum += data[at + 2 * step] * COSINES[basis + 2];
37
+ sum += data[at + 3 * step] * COSINES[basis + 3];
38
+ sum += data[at + 4 * step] * COSINES[basis + 4];
39
+ sum += data[at + 5 * step] * COSINES[basis + 5];
40
+ sum += data[at + 6 * step] * COSINES[basis + 6];
41
+ sum += data[at + 7 * step] * COSINES[basis + 7];
42
+ return sum;
43
+ }
44
+
45
+ // Reused scratch for the baseline and the photographic quantisation search. A transform always visits its eight
46
+ // terms in the same order; edge replication agrees with the baseline's complete 8 by 8 blocks.
47
+ export function dctBlocks(data, width, height) {
48
+ const block = new Float64Array(192), intermediate = new Float64Array(64), sums = new Float64Array(192);
49
+ return (bx, by) => {
50
+ for (let y = 0; y < 8; y++) for (let x = 0; x < 8; x++) {
51
+ const at = (Math.min(height - 1, by * 8 + y) * width + Math.min(width - 1, bx * 8 + x)) * 4;
52
+ const r = data[at], g = data[at + 1], b = data[at + 2], k = y * 8 + x;
53
+ block[k] = 0.299 * r + 0.587 * g + 0.114 * b - 128;
54
+ block[64 + k] = -0.168735892 * r - 0.331264108 * g + 0.5 * b;
55
+ block[128 + k] = 0.5 * r - 0.418687589 * g - 0.081312411 * b;
56
+ }
57
+ for (let c = 0; c < 3; c++) {
58
+ for (let y = 0; y < 8; y++) for (let u = 0; u < 8; u++) intermediate[y * 8 + u] = dot8(block, c * 64 + y * 8, 1, u * 8);
59
+ for (let v = 0; v < 8; v++) for (let u = 0; u < 8; u++) sums[c * 64 + v * 8 + u] = dot8(intermediate, u, 8, v * 8);
60
+ }
61
+ return sums;
62
+ };
63
+ }
64
+
65
+ // A row of groups (32 block rows) per step.
66
+ export function* photoCoefficientSteps(data, width, height, quality, colorSpace) {
67
+ const stride = Math.ceil(width / 8), rows = Math.ceil(height / 8), quantScale = 16;
68
+ const distance = quality >= 30 ? 0.1 + (100 - quality) * 0.09 : 53 / 3000 * quality * quality - 23 / 20 * quality + 25;
69
+ const components = [0, 1, 2].map(c => ({h: 1, v: 1, stride, rows,
70
+ quant: Int32Array.from(QUANT_SHAPE, (shape, k) => Math.max(1, Math.round(quantScale * distance * (c ? 18 : 10) * (k ? shape : 0.25)))),
71
+ coeffs: new Int16Array(stride * rows * 64)}));
72
+ const transform = dctBlocks(data, width, height);
73
+ const band = (from, to) => {
74
+ for (let by = from; by < to; by++) for (let bx = 0; bx < stride; bx++) {
75
+ const sums = transform(bx, by), offset = (by * stride + bx) * 64;
76
+ for (let c = 0; c < 3; c++) for (let k = 0; k < 64; k++) components[c].coeffs[offset + k] = Math.round(sums[c * 64 + k] * quantScale / components[c].quant[k]);
77
+ }
78
+ };
79
+ for (let by = 0; by < rows; by += 32) { band(by, Math.min(rows, by + 32)); yield Math.min(rows, by + 32) / rows; }
80
+ let alpha = null;
81
+ for (let i = 3; i < data.length; i += 4) if (data[i] !== 255) { alpha = data; break; }
82
+ return {width, height, components, quantScale, ycbcr: true, orientation: 1, alpha, colorSpace};
83
+ }
package/photo-job.mjs ADDED
@@ -0,0 +1,27 @@
1
+ // SPDX-License-Identifier: MIT
2
+ // One checked photograph job shared by the prefix and optional ANS doors.
3
+ import {PHOTO_LIMITS, part} from './bits.mjs';
4
+ import {admitEffort} from './effort-level.mjs';
5
+ import {losslessSteps} from './lossless.mjs';
6
+ import {admitOptions, admitPixels, job} from './admit.mjs';
7
+ import {photoCoefficientSteps} from './photo-dct.mjs';
8
+ import {quantisationSteps} from './photo-quant.mjs';
9
+
10
+ export function photoJob(data, width, height, options, coefficients) {
11
+ const {quality, colorSpace} = admitOptions(options, 90), effort = admitEffort(options);
12
+ admitPixels(data, width, height, PHOTO_LIMITS);
13
+ return job(photoSteps(data, width, height, quality, colorSpace, effort, coefficients));
14
+ }
15
+
16
+ function* photoSteps(data, width, height, quality, colorSpace, effort, encodeCoefficients) {
17
+ if (quality >= 100) return yield* losslessSteps(data, width, height, {colorSpace});
18
+ if (effort < 5) return yield* part(encodeCoefficients(yield* part(photoCoefficientSteps(data, width, height, quality, colorSpace), 0, 2), effort), 1, 2);
19
+ const coefficients = yield* part(photoCoefficientSteps(data, width, height, quality, colorSpace), 0, 4);
20
+ let best = yield* part(encodeCoefficients(coefficients, 4), 1, 4);
21
+ if (yield 0.5) return best;
22
+ try {
23
+ const chosen = yield* part(quantisationSteps(data, coefficients, best.byteLength), 2, 4);
24
+ if (chosen) best = yield* part(encodeCoefficients(chosen, 4, best), 3, 4);
25
+ } catch (error) { if (!(error instanceof RangeError && !error.code) && error.code !== 'JXL_SIZE') throw error; }
26
+ return best;
27
+ }