rapier-jxl 1.1.21 → 2.0.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.
package/AGENTS.md CHANGED
@@ -1,19 +1,23 @@
1
1
  # For an agent adding Rapier JXL to an app
2
2
 
3
3
  1. `npm install rapier-jxl`, or copy one file from this repository's root: `rapier-jxl.min.mjs` (the core),
4
- `lossless.min.mjs`, `jpeg.min.mjs` or `photo.min.mjs`. No build.
5
- 2. `import {encode, transcode} from 'rapier-jxl'` (or `rapier-jxl/min`); `encodeLosslessRGBA` from
6
- `rapier-jxl/lossless`, `transcode` from `rapier-jxl/jpeg`, `encodePhotoRGBA` from `rapier-jxl/photo`.
7
- 3. Pixels: `encode(rgba, width, height, {quality})`, straight RGBA row by row as `getImageData` gives it. Quality 100
8
- (the default) is lossless, 1 to 99 lossy, alpha exact at every quality. Photographs:
9
- `encodePhotoRGBA(rgba, width, height, {quality: 90})`. The answer is a `Uint8Array` of codestream bytes: save it
4
+ `effort.min.mjs`, `jpeg.min.mjs` or `photo.min.mjs`. No build.
5
+ 2. `import {encode} from 'rapier-jxl'` (or `rapier-jxl/min`), `transcode` from `rapier-jxl/jpeg`, `encodePhoto`
6
+ from `rapier-jxl/photo`. For smaller lossless files at more time, `encode` from `rapier-jxl/effort` with
7
+ `{effort: 3}`.
8
+ 3. Pixels: `encode(rgba, width, height, {quality})`, straight RGBA row by row as `getImageData` gives it; from a
9
+ Display P3 canvas, add `colorSpace: 'display-p3'`. Quality 100 (the default) is lossless, 1 to 99 lossy, alpha
10
+ exact at every quality. Photographs:
11
+ `encodePhoto(rgba, width, height, {quality: 90})`. The answer is a `Uint8Array` of codestream bytes: save it
10
12
  as `.jxl` or in a `Blob` of type `image/jxl`.
11
13
  4. A JPEG: `transcode(jpegBytes)` gives `{bytes, width, height, orientation}` without decoding. On `JXL_JPEG`,
12
- decode it and call `encode` on the pixels.
13
- 5. Anything larger than an icon runs in a worker. Calls are synchronous, so cancel by terminating the worker. A
14
+ decode it and call `encodePhoto` on the pixels.
15
+ 5. Anything larger than an icon runs in a worker. Calls are synchronous, so cancel by terminating the worker, or
16
+ loop over the door's twin (`encodeSteps`, `transcodeSteps`, `encodePhotoSteps`) and leave the loop. A
14
17
  transferred buffer is gone from the sender: copy first if you may retry.
15
- 6. The error codes are `JXL_INPUT`, `JXL_DIMENSIONS`, `JXL_SIZE`, `JXL_MEMORY` and `JXL_JPEG`, no others. Keep the
16
- original pixels until the encode and its storage succeed.
18
+ 6. The error codes are `JXL_INPUT`, `JXL_DIMENSIONS`, `JXL_SIZE`, `JXL_MEMORY` and `JXL_JPEG`, no others;
19
+ `JXL_DIMENSIONS` is past the door's own `LIMITS` (24 million pixels for the core and `effort`, 40 million for
20
+ `photo`, 64 million for a carried JPEG). Keep the original pixels until the encode and its storage succeed.
17
21
  7. Show the result only where the browser decodes JPEG XL (`<picture>` with a fallback, or a one-pixel feature test).
18
22
 
19
23
  Nothing here reads files, fetches or touches the DOM: it runs in a worker, Node or Deno alike. Publish the version
@@ -5,10 +5,10 @@ A bounded survey, not proof of a global minimum; the encoders differ in what the
5
5
 
6
6
  | Encoder / entry | Version | Minified JS + WASM bytes | gzip bytes | Included capability |
7
7
  | --- | --- | ---: | ---: | --- |
8
- | Rapier JXL core | this release | 36,284 | 14,987 | 8-bit lossless RGBA, lossy modular with exact alpha, JPEG coefficients |
9
- | Rapier JXL lossless | this release | 13,754 | 6,131 | 8-bit exact RGBA |
10
- | Rapier JXL JPEG | this release | 27,750 | 11,590 | JPEG coefficients, orientation; no JPEG reconstruction |
11
- | Rapier JXL photo | this release | 22,563 | 9,768 | 8-bit photographic VarDCT, exact alpha; q100 lossless |
8
+ | Rapier JXL core | 2.0.0 | 19,495 | 8,475 | 8-bit lossless RGBA, lossy modular with exact alpha |
9
+ | Rapier JXL effort door | 2.0.0 | 25,229 | 10,606 | The core, and the weighted predictor searched for lossless |
10
+ | Rapier JXL JPEG door | 2.0.0 | 29,592 | 12,533 | JPEG coefficients, orientation; no JPEG reconstruction |
11
+ | Rapier JXL photo door | 2.0.0 | 25,646 | 10,804 | 8-bit photographic VarDCT, exact alpha; q100 lossless |
12
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
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
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 |
@@ -51,7 +51,7 @@ Gaussian, no downsampling); alpha-bearing inputs are matted on white for the tab
51
51
  reference tool at 80 nits, the worse of the white and black mattes for alpha inputs. Higher PSNR and SSIM and
52
52
  lower Butteraugli are that metric's preference, not a human verdict. **R / N** is Rapier / native.
53
53
 
54
- ### Photo entry
54
+ ### The photo door
55
55
 
56
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
57
  Butteraugli in 19 of 20; the exceptions are Grace q99 (Rapier's PSNR and SSIM) and lit surface q99 (Rapier's
@@ -80,7 +80,7 @@ Butteraugli).
80
80
  | Grace Hopper · 90 | 56,543 | 56,519 | 1.110 | 38.605 / 38.944 | 0.950222 / 0.970007 | 2.3300 / 1.4546 |
81
81
  | Grace Hopper · 99 | 110,542 | 110,533 | 0.173 | 49.948 / 47.182 | 0.996348 / 0.995977 | 0.4889 / 0.3406 |
82
82
 
83
- ### Lossy modular entry on drawings and paintings
83
+ ### The core's lossy modular on drawings and paintings
84
84
 
85
85
  Fourteen rows are matched within 0.127%. In the ten **†** rows an exact native stream fits in 3.052% to
86
86
  51.144% fewer bytes. Native SSIM and Butteraugli are better in all 24 rows; PSNR favours Rapier for paint-0001
package/README.md CHANGED
@@ -1,42 +1,45 @@
1
1
  # Rapier JXL
2
2
 
3
- A JPEG XL encoder in pure JavaScript: one file, 36.3 kB, 15.0 kB gzipped, no WebAssembly, no
3
+ A JPEG XL encoder in pure JavaScript: one file, 19.5 kB, 8.5 kB gzipped, no WebAssembly, no
4
4
  dependencies, MIT. The encoder inside [Rapier](https://rapier.website), published on its own. The smallest
5
5
  JavaScript or WebAssembly JPEG XL encoder among the payloads [we measured](ENCODER-COMPARISON.md).
6
6
 
7
7
  - **Lossless.** Every pixel back as it went in: 8-bit grey, grey with alpha, RGB, RGBA.
8
8
  - **Lossy**, quality 1 to 99, for flat-colour rasters (screenshots, pixel art, scanned line art). Alpha stays exact.
9
9
  A picture of few colours is written exact when that is smaller.
10
- - **Photographs**, optionally: `rapier-jxl/photo`, DCT8 compression with exact alpha, adding nothing to the core.
11
- - **A JPEG carried as its coefficients**: one call, no decode, the way libjxl transcodes. Not carried: the
12
- reconstruction data (the JPEG file cannot be rebuilt), ICC, Exif beyond the orientation, XMP.
10
+ - **Smaller exact pictures, slower**: `rapier-jxl/effort`, the same `encode` with `{effort: 2}` or `3`, each never
11
+ larger than the effort below. Effort 1, its default, is the core's bytes.
12
+ - **Photographs**: `rapier-jxl/photo`, DCT8 compression with exact alpha, a door of its own.
13
+ - **A JPEG carried as its coefficients**: `rapier-jxl/jpeg`, one call, no decode, the way libjxl transcodes. Not
14
+ carried: the reconstruction data (the JPEG file cannot be rebuilt), the ICC bytes, Exif beyond the orientation, XMP.
15
+ - **sRGB or Display P3.** `{colorSpace: 'display-p3'}` declares a wide-gamut canvas's samples. A JPEG's profile is
16
+ read by what it does, not what it says: sRGB and Display P3 are carried and declared.
13
17
 
14
18
  ## Use it
15
19
 
16
20
  ```js
17
- import {encode, transcode} from 'rapier-jxl';
21
+ import {encode} from 'rapier-jxl';
22
+ import {transcode} from 'rapier-jxl/jpeg';
23
+ import {encodePhoto} from 'rapier-jxl/photo';
18
24
 
19
25
  const {data, width, height} = context.getImageData(0, 0, canvas.width, canvas.height);
20
26
  const exact = encode(data, width, height); // lossless
21
27
  const small = encode(data, width, height, {quality: 80}); // lossy
28
+ const photo = encodePhoto(data, width, height); // quality 90; 100 is exact
22
29
  const blob = new Blob([small], {type: 'image/jxl'});
23
30
 
24
31
  const {bytes, width: w, height: h, orientation} = transcode(new Uint8Array(await file.arrayBuffer()));
25
32
  ```
26
33
 
27
34
  `encode(data, width, height, {quality = 100})` takes straight RGBA bytes, row by row, and returns a `Uint8Array`
28
- holding a bare JPEG XL codestream. `transcode(jpeg)` returns `{bytes, width, height, orientation}`: the size as
29
- shown, the orientation kept in the header.
35
+ holding a bare JPEG XL codestream; `encodePhoto` takes the same. `transcode(jpeg)` returns
36
+ `{bytes, width, height, orientation}`: the size as shown, the orientation kept in the header.
30
37
 
31
- ```js
32
- import {encodePhotoRGBA} from 'rapier-jxl/photo';
33
- const photo = encodePhotoRGBA(data, width, height, {quality: 90}); // 100 is exact
34
- ```
35
-
36
- Entries: `rapier-jxl` (the readable modules), `rapier-jxl/min` (one minified file), `rapier-jxl/lossless`,
37
- `rapier-jxl/jpeg`, `rapier-jxl/photo`. Each minified file carries its MIT notice, TypeScript declarations sit
38
- beside each entry, and a worker and a page are under `public/examples/`. Quality numbers are not the same fidelity
39
- across encoders or pictures, and lossy is not always smaller than lossless.
38
+ Doors: `rapier-jxl` (the core), `rapier-jxl/effort`, `rapier-jxl/jpeg`, `rapier-jxl/photo`, each readable, so a
39
+ bundler carries their shared modules once; `rapier-jxl/min` is the core as one minified file. `rapier-jxl/writer`
40
+ gives a module's author the layers beneath the doors (readable only; they change only with the major version).
41
+ TypeScript declarations sit beside each door, and a worker and a page are under `public/examples/`. Quality numbers
42
+ are not the same fidelity across encoders or pictures, and lossy is not always smaller than lossless.
40
43
 
41
44
  ### In a worker
42
45
 
@@ -44,7 +47,8 @@ Encoding is synchronous. Run it off the main thread:
44
47
 
45
48
  ```js
46
49
  // jxl-worker.mjs
47
- import {encode, transcode} from 'rapier-jxl';
50
+ import {encode} from 'rapier-jxl';
51
+ import {transcode} from 'rapier-jxl/jpeg';
48
52
  self.onmessage = ({data: {id, op, ...ask}}) => {
49
53
  try {
50
54
  const out = op === 'transcode' ? transcode(ask.jpeg) : {bytes: encode(ask.data, ask.width, ask.height, {quality: ask.quality})};
@@ -55,39 +59,72 @@ self.onmessage = ({data: {id, op, ...ask}}) => {
55
59
 
56
60
  To cancel, terminate the worker and drop its request id. Keep the input in the caller if you may retry.
57
61
 
62
+ Each door has a twin that does the same work in steps: `encodeSteps`, `transcodeSteps`, `encodePhotoSteps` return a
63
+ job, `for (const done of job)` runs one group of one pass per step (`done` is the fraction, the last exactly 1), and
64
+ `job.bytes` is the stream after the loop, the same bytes the door writes. Leaving the loop cancels, so a worker can
65
+ take messages and report progress between steps (`public/examples/worker.mjs`). `job.hurry = true` (the example's
66
+ `deadline` sets it) ends the effort door's search at its next step with the smallest stream written so far, never
67
+ larger than effort 1's.
68
+
58
69
  ### Limits and errors
59
70
 
60
- One picture at a time, at most 16,384 pixels a side, 24 million pixels and a 16 MiB stream. Arguments are checked
61
- before any work. A refusal is an `Error` whose `code` is `JXL_INPUT`, `JXL_DIMENSIONS`, `JXL_SIZE`, `JXL_MEMORY` or
62
- `JXL_JPEG` (arithmetic coding, 12-bit, lossless, CMYK, a DNL height, a colour profile other than sRGB, or a JPEG
63
- cut short: decode it and encode the pixels instead).
71
+ One picture at a time, at most 16,384 pixels a side and a 16 MiB stream. Each door's `LIMITS` sets its pixels by its
72
+ memory, so that none needs more at its limit than the core at its own: 24 million for the core and `effort` (a lossy
73
+ picture holds its planes whole, 15.7 bytes a pixel at its peak besides the input), 40 million for `photo` (6.5), and
74
+ 64 million for a JPEG that `jpeg` carries (3.3 at 4:2:0, 6.4 at 4:4:4), so a phone's 24 and 48 megapixel
75
+ photographs are carried. Arguments are checked before any work. A refusal is an `Error` whose `code` is `JXL_INPUT`,
76
+ `JXL_DIMENSIONS`, `JXL_SIZE`, `JXL_MEMORY` or `JXL_JPEG` (arithmetic coding, 12-bit, lossless, CMYK, a DNL height, a
77
+ colour profile other than sRGB or Display P3, or a JPEG cut short: decode it and encode the pixels instead).
64
78
 
65
- ## Sizes
79
+ ### From 1.x
80
+
81
+ 2.0.0 is the package's own version (1.x took Rapier's), and the core is pixels only:
66
82
 
67
- | file | bytes | gzip | Brotli |
68
- | --- | ---: | ---: | ---: |
69
- | `rapier-jxl.min.mjs`, core | 36,284 | 14,987 | 13,315 |
70
- | `lossless.min.mjs`, `encodeLosslessRGBA` alone | 13,754 | 6,131 | 5,401 |
71
- | `jpeg.min.mjs`, `transcode` alone | 27,750 | 11,590 | 10,217 |
72
- | `photo.min.mjs`, photographic pixels | 22,563 | 9,768 | 8,635 |
73
- | all readable modules, including photo | 102,565 | 31,745 | |
83
+ - `transcode` is in `rapier-jxl/jpeg`, and `encodePhotoRGBA` is `encodePhoto` in `rapier-jxl/photo`.
84
+ - `encodeLosslessRGBA(data, width, height)` and `rapier-jxl/lossless` are `encode(data, width, height)`;
85
+ `encodeLossyRGBA(data, width, height, quality)` is `encode(data, width, height, {quality})`.
86
+ - `encodeLossless`, `encodeLossy`, `inspectPixels`, `parseJPEG` and `transcodeJPEG` are in `rapier-jxl/writer`.
87
+ - A JPEG's colour profile is read by what it does: Display P3 (an iPhone's) is carried and declared, where 1.x
88
+ refused it; a profile of lookup tables is refused, even one named sRGB.
89
+ - Lossless streams and carried JPEGs are 1.x's bytes. Lossy streams of a picture wider or taller than 256 pixels,
90
+ or of a colour picture one pixel wide or high, and every photo stream changed where Chrome's decoder (jxl-rs
91
+ 0.7.4) misread a valid stream; each decodes to the same pixels as before through jxl-oxide and libjxl.
74
92
 
75
- Exact bytes of this release's files, measured by the script that stages this repository; `sizes.json` carries
76
- their hashes and tools (terser 5.51.2, Node v22.22.2; gzip 9, Brotli 11). Each minified file is proved at
77
- staging to write the same bytes as its readable source.
93
+ New: `rapier-jxl/effort`, each door's twin in steps with `hurry`, `colorSpace: 'display-p3'`, each door's own
94
+ `LIMITS`.
95
+
96
+ ## Sizes
78
97
 
79
- What it writes: bare codestreams, 8-bit, prefix codes (never ANS), one frame, no preview, animation, ICC (sRGB is
80
- declared), XYB, chroma-from-luma or filters. Lossless in modular mode, a palette of up to 2,048 colours weighed
81
- against direct coding by actual length, groups of 256, reversible YCoCg, a predictor chosen per channel. Lossy
82
- through Squeeze with exact alpha. Carried JPEGs in VarDCT with the JPEG's own tables. The photo entry writes DCT8
83
- coefficients for the same writer.
98
+ | file | bytes | gzip | Brotli | added to the core, gzip |
99
+ | --- | ---: | ---: | ---: | ---: |
100
+ | `rapier-jxl.min.mjs`, the core: `encode` | 19,495 | 8,475 | 7,494 | |
101
+ | `effort.min.mjs`: `encode` with effort | 25,229 | 10,606 | 9,351 | 2,131 |
102
+ | `jpeg.min.mjs`: `transcode` | 29,592 | 12,533 | 11,072 | 7,673 |
103
+ | `photo.min.mjs`: `encodePhoto` | 25,646 | 10,804 | 9,537 | 4,242 |
104
+ | every door in one bundle | 47,499 | 19,228 | 16,977 | |
105
+ | all readable modules | 136,580 | 42,707 | | |
106
+
107
+ Exact bytes of release 2.0.0's files, measured by the script that stages this repository; `sizes.json`
108
+ carries their hashes and tools (terser 5.51.2, Node v22.22.2; gzip 9, Brotli 11). Each minified file stands alone
109
+ and is proved at staging to write the same bytes as its readable source; the last column is what a door adds to a
110
+ bundle that already holds the core. `effort`'s `encode` is the core's at effort 1, so it takes the core's place, in
111
+ that column and in the bundle of every door.
112
+
113
+ What it writes: bare codestreams, 8-bit, prefix codes (never ANS), one frame, no preview, animation, ICC (sRGB or
114
+ Display P3 is declared), XYB, chroma-from-luma or filters. Lossless in modular mode, a palette of up to 2,048
115
+ colours weighed against direct coding by actual length, groups of 256, reversible YCoCg, a predictor chosen per
116
+ channel; at effort 2 and 3 also the weighted predictor, its contexts split by its own error. Lossy through Squeeze
117
+ with exact alpha. Carried JPEGs in VarDCT with the JPEG's own tables. The photo door
118
+ writes DCT8 coefficients for the same writer.
84
119
 
85
120
  ## Checked
86
121
 
87
122
  Tests and seeded structure-aware fuzzing in `public/test/`: `npm test`, decoded through
88
123
  [jxl-oxide](https://github.com/tirr-c/jxl-oxide) 0.12.6 and FFmpeg's native libjxl (a missing native decoder is
89
124
  reported, and fails in CI). Exact pixels and alpha where promised, fidelity where relevant, the accepted JPEG
90
- forms, refusal of malformed input. The 30 September run put 2,000,000 JPEG mutations and 32,768 pixel cases
125
+ forms, refusal of malformed input. The same input writes the same bytes in every JavaScript engine: nothing that
126
+ decides a byte uses a function engines round differently, and `public/test/bytes.test.mjs` holds the streams' hashes
127
+ under Node and Bun. The 30 September run put 2,000,000 JPEG mutations and 32,768 pixel cases
91
128
  through both decoders. The two decoders differ by one RGB unit on some JPEG and photo streams (floating-point
92
129
  reconstruction), kept in `public/test/seeds/`; alpha and modular output are exact. With a C compiler and libjxl
93
130
  headers, `npm run fuzz:scale -- --out fuzz-run --workers 4` repeats the fixed budget.
package/admit.mjs ADDED
@@ -0,0 +1,56 @@
1
+ // Rapier's JPEG XL encoder: what every door admits, and how each refuses. MIT (LICENSE).
2
+ // One owner for the checked doors: the options read before any work, the limits, the five codes, and the size and
3
+ // memory refusals after it. A module's door refuses the same way (writer.mjs).
4
+ import {LIMITS} from './bits.mjs';
5
+
6
+ export const fault = (code, message) => Object.assign(new Error(message), {code});
7
+
8
+ // The options every door reads, before any work: an object, or nothing; quality a number from 1 to 100, the door's
9
+ // own default when absent; the samples' colour space, the web platform's name for it, sRGB when absent.
10
+ export function admitOptions(options = {}, quality = 100) {
11
+ if (options === null || typeof options !== 'object') throw fault('JXL_INPUT', 'Options are an object: {quality, colorSpace}.');
12
+ if (options.quality !== undefined) quality = options.quality;
13
+ if (typeof quality !== 'number' || !Number.isFinite(quality) || quality < 1 || quality > 100) throw fault('JXL_INPUT', 'Quality is a number from 1 to 100.');
14
+ const colorSpace = options.colorSpace === undefined ? 'srgb' : options.colorSpace;
15
+ if (colorSpace !== 'srgb' && colorSpace !== 'display-p3') throw fault('JXL_INPUT', 'The colour space is srgb or display-p3.');
16
+ return {quality, colorSpace};
17
+ }
18
+
19
+ // A door's size, against the door's own limits (the core's when none are named).
20
+ export function admitSize(width, height, limits = LIMITS) {
21
+ if (!Number.isInteger(width) || !Number.isInteger(height) || width < 1 || height < 1) throw fault('JXL_INPUT', 'A picture is at least one pixel wide and high, in whole pixels.');
22
+ if (width > limits.edge || height > limits.edge) throw fault('JXL_DIMENSIONS', 'A picture is at most ' + limits.edge + ' pixels on a side.');
23
+ if (width * height > limits.pixels) throw fault('JXL_DIMENSIONS', 'A picture is at most ' + limits.pixels.toLocaleString('en-US') + ' pixels.');
24
+ }
25
+
26
+ export function admitPixels(data, width, height, limits) {
27
+ admitSize(width, height, limits);
28
+ if (!(data instanceof Uint8Array) && !(data instanceof Uint8ClampedArray)) throw fault('JXL_INPUT', 'Pixels are a Uint8Array or Uint8ClampedArray of RGBA bytes.');
29
+ if (data.length !== width * height * 4) throw fault('JXL_INPUT', 'Pixels are width * height * 4 bytes: straight (not premultiplied) RGBA, row by row.');
30
+ }
31
+
32
+ export function answer(bytes) {
33
+ if (bytes.length > LIMITS.bytes) throw fault('JXL_SIZE', 'The encoded picture exceeds 16 MiB.');
34
+ return bytes;
35
+ }
36
+
37
+ // A plain RangeError from inside the work is the engine out of memory; a coded error passes through as it is.
38
+ export function guard(work) {
39
+ try { return work(); }
40
+ catch (error) { if (error instanceof RangeError && !error.code) throw fault('JXL_MEMORY', 'Not enough memory for this picture.'); throw error; }
41
+ }
42
+
43
+ // A door's work as a job: iterate it for the fraction done, in (0, 1], the last exactly 1, and read `bytes` after the
44
+ // last step (the job also returns them). Leaving the loop is a cancel with nothing to release. `hurry` asks a search to
45
+ // finish with the best candidate it has priced; effort 1 searches nothing, so its bytes are the same either way.
46
+ export function job(steps) {
47
+ const it = (function* () {
48
+ let step, last;
49
+ while (!(step = guard(() => steps.next(it.hurry))).done) yield last = step.value;
50
+ // A candidate given up (the stream limit, or memory for the lossy attempt beside an exact stream) ends early.
51
+ if (last < 1) yield 1;
52
+ return it.bytes = answer(step.value);
53
+ })();
54
+ it.bytes = null; it.hurry = false;
55
+ return it;
56
+ }
package/bits.mjs CHANGED
@@ -1,13 +1,20 @@
1
1
  // Rapier's JPEG XL encoder: the bit writer, and the limits every part keeps. MIT (LICENSE).
2
2
  // JPEG XL packs bits least-significant first; `write` takes up to 32 bits at a time.
3
3
 
4
- // What one call takes at most: the 16 MiB codestream, 24 million pixels, 16,384 on a side. The checked API refuses a
5
- // larger ask before any work, the JPEG reader before it allocates a plane, and a writer that would grow past the
6
- // stream's bound stops there instead of filling memory first.
4
+ // What one call takes at most, door by door: the 16 MiB codestream, 16,384 pixels on a side, and as many pixels as the
5
+ // door's memory allows within what the core's lossy path needs at its 24 million (a measured peak of 15.7 bytes a
6
+ // pixel besides the input). The core and the effort door take 24 million; the photo door, 6.5 bytes a pixel at its
7
+ // limit, 40 million; the JPEG carrier, 6.4 bytes a pixel at 4:4:4 and 3.3 at 4:2:0, 64 million. The checked API
8
+ // refuses a larger ask before any work, the JPEG reader before it allocates a plane, and a writer that would grow past
9
+ // the stream's bound stops there instead of filling memory first.
7
10
  export const LIMITS = Object.freeze({bytes: 16 * 1024 * 1024, pixels: 24_000_000, edge: 16384});
11
+ export const PHOTO_LIMITS = /*#__PURE__*/ Object.freeze({bytes: 16 * 1024 * 1024, pixels: 40_000_000, edge: 16384});
12
+ export const JPEG_LIMITS = /*#__PURE__*/ Object.freeze({bytes: 16 * 1024 * 1024, pixels: 64_000_000, edge: 16384});
8
13
 
9
- // The hot writer reuses exact powers; unusual counts retain the range check's arithmetic.
10
- const POWERS = Array.from({length: 33}, (_, count) => 2 ** count);
14
+ // The hot writer's powers of two, made by doubling (`**` is not exact in every engine); a count outside 0 to 32 finds
15
+ // none, so its value is out of range.
16
+ const POWERS = [1];
17
+ for (let count = 1; count <= 32; count++) POWERS.push(POWERS[count - 1] * 2);
11
18
 
12
19
  export class BitWriter {
13
20
  constructor(capacity = 4096) {
@@ -17,7 +24,7 @@ export class BitWriter {
17
24
  this.pending = 0; // how many bits `acc` holds, always below 8 between calls
18
25
  }
19
26
  write(count, value) {
20
- if (count > 32 || value < 0 || value >= (POWERS[count] ?? 2 ** count)) throw new Error('bit write out of range: ' + count + ' bits, ' + value);
27
+ if (!(value >= 0 && value < POWERS[count])) throw new Error('bit write out of range: ' + count + ' bits, ' + value);
21
28
  let acc = this.acc + value * (1 << this.pending), pending = this.pending + count;
22
29
  if (this.at + 5 >= this.bytes.length) this.grow();
23
30
  const bytes = this.bytes;
@@ -28,7 +35,7 @@ export class BitWriter {
28
35
  writeU32(choices, value) {
29
36
  for (let selector = 0; selector < 4; selector++) {
30
37
  const [bits, offset] = choices[selector];
31
- if (value >= offset && value - offset < 2 ** bits) { this.write(2, selector); if (bits) this.write(bits, value - offset); return; }
38
+ if (value >= offset && value - offset < POWERS[bits]) { this.write(2, selector); if (bits) this.write(bits, value - offset); return; }
32
39
  }
33
40
  throw new Error('U32 value out of range: ' + value);
34
41
  }
@@ -59,6 +66,16 @@ export class BitWriter {
59
66
  }
60
67
  }
61
68
 
69
+ // The work as steps: a stepped writer yields the fraction of its work done, counted in whole steps so the last is
70
+ // exactly 1, receives whether its caller is in a hurry, and returns its bytes. `complete` runs one to the end; `part`
71
+ // makes a nested one's fractions the index-th of `count` equal shares of its caller's.
72
+ export function complete(steps) { let step; while (!(step = steps.next()).done); return step.value; }
73
+ export function* part(steps, index, count) {
74
+ let step, hurry;
75
+ while (!(step = steps.next(hurry)).done) hurry = yield (index + step.value) / count;
76
+ return step.value;
77
+ }
78
+
62
79
  // Residuals travel unsigned: 0, -1, 1, -2, 2 ... become 0, 1, 2, 3, 4 ...
63
80
  export function packSigned(value) { return value >= 0 ? value * 2 : -value * 2 - 1; }
64
81
 
@@ -71,9 +88,12 @@ export function float16Bits(value) {
71
88
  const sign = value < 0 ? 0x8000 : 0;
72
89
  value = Math.abs(value);
73
90
  if (!(value < 65520)) throw new Error('half float out of range: ' + value);
74
- let exponent = Math.floor(Math.log2(value));
75
- let mantissa = value / 2 ** exponent - 1;
76
- if (exponent < -14) { mantissa = value / 2 ** -14; exponent = -15; }
91
+ // The exponent by halving and doubling, both exact (Math.log2 rounds differently in each engine).
92
+ let exponent = 0, mantissa = value;
93
+ while (mantissa >= 2) { mantissa /= 2; exponent++; }
94
+ while (mantissa < 1) { mantissa *= 2; exponent--; }
95
+ if (exponent < -14) { mantissa = value * 16384; exponent = -15; }
96
+ else mantissa -= 1;
77
97
  let m = Math.round(mantissa * 1024);
78
98
  if (m === 1024) { m = 0; exponent++; }
79
99
  return sign | ((exponent + 15) << 10) | m;
package/effort.d.mts ADDED
@@ -0,0 +1,11 @@
1
+ // SPDX-License-Identifier: MIT
2
+ import type {Pixels, EncodeOptions, Job} from './index.mjs';
3
+ export {LIMITS} from './index.mjs';
4
+ export type {Pixels, Limits, EncodeOptions, ErrorCode, EncoderError, Job} from './index.mjs';
5
+ /** The core's encode with a search above it for exact pictures: effort 1 (the default) writes the core's bytes; 2 tries
6
+ * the weighted predictor on every channel, 3 also splits its contexts by the predictor's error. Never larger than the
7
+ * effort below; slower. */
8
+ export function encode(data: Pixels, width: number, height: number, options?: EncodeOptions): Uint8Array;
9
+ /** encode's work and bytes as steps. Above effort 1 an exact picture's first half (fractions up to 0.5) writes effort
10
+ * 1's stream and the second searches; `hurry` ends the search with the smallest stream written so far. */
11
+ export function encodeSteps(data: Pixels, width: number, height: number, options?: EncodeOptions): Job;
package/effort.min.mjs ADDED
@@ -0,0 +1,2 @@
1
+ /*! Rapier JXL 2.0.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,e){let o,r;for(;!(o=t.next(r)).done;)r=yield(n+o.value)/e;return o.value}function n(t){return t>=0?2*t:2*-t-1}function e(t){return 31-Math.clz32(t)}function o(t){return t<=1?0:32-Math.clz32(t-1)}function r(t,n){t.t([[9,1],[13,1],[18,1],[30,1]],n)}function s(t,n,e,o,s,{xyb:i=!1,orientation:c=1,colorSpace:l}={}){t.write(16,2815),t.write(1,0),r(t,e),t.write(3,0),r(t,n),t.write(1,0);const f=1!==c;t.write(1,f?1:0),f&&t.write(6,c-1),t.write(4,8),s?t.write(3,5):t.write(2,0),t.write(1,i?1:0),3===o&&"display-p3"===l?t.write(21,903568):3===o?t.write(1,1):t.write(15,14100),f&&t.write(1,1),t.write(2,0),t.write(1,1),t.o()}function i(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 c(t,n,e=256){const o=Math.ceil(t/e),r=Math.ceil(n/e);return{i:o,u:r,p:Math.ceil(t/(8*e)),M:Math.ceil(n/(8*e)),A:1===o&&1===r}}function l(t,n){const e=n.map(t=>t.length);!function(t,n){t.write(1,0),t.o();for(const e of n)t.t([[10,0],[14,1024],[22,17408],[30,4211712]],e);t.o()}(t,e);const o=t.finish(),r=new Uint8Array(o.length+e.reduce((t,n)=>t+n,0));r.set(o);let s=o.length;for(const t of n)r.set(t,s),s+=t.length;return r}function f(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),m:n,l:null,r:null})).sort((t,n)=>t.w-n.w||t.m-n.m),i=[];let c=0,l=0;const f=()=>c<s.length&&(l>=i.length||s[c].w<=i[l].w)?s[c++]:i[l++];for(;s.length-c+(i.length-l)>1;){const t=f(),n=f();i.push({w:t.w+n.w,m:-1,l:t,r:n})}const a=[[f(),0]];let u=0;for(;a.length;){const[t,n]=a.pop();t.m>=0?(e[t.m]=n,n>u&&(u=n)):a.push([t.l,n+1],[t.r,n+1])}if(u<=n)return e}}function a(t,n,e,o,r,s){const i=[];for(;i.push(o&r),o>>=s;)o--;for(let o=i.length-1;o>=0;o--)t.push(e),n.push(i[o])}function u(t,n){let e=0;for(let o=0;o<n;o++)e=e<<1|1&t,t>>=1;return e}function h(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]=u(e[t[n]]++,t[n]));return o}function y(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):f(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{U:o,I:r,S:h(r),T:e&&e<=4?n:null,v:s}}function w(t,n){if(n.U<=1)return;if(n.T){const o=e(n.U-1)+1;t.write(2,1),t.write(2,n.T.length-1);for(const e of n.T)t.write(o,e);return void(4===n.T.length&&t.write(1,n.v))}const o=n.I,r=[],s=[];let i=o.length-1;for(;i>0&&!o[i];)i--;let c=8;for(let t=0;t<=i;){const n=o[t];let e=1;for(;t+e<=i&&o[t+e]===n;)e++;let l=e;if(n&&n!==c&&(r.push(n),s.push(0),l--,c=n),l<3)for(let t=0;t<l;t++)r.push(n),s.push(0);else a(r,s,n?16:17,l-3,n?3:7,n?2:3);t+=e}const l=new Uint32Array(18);for(const t of r)l[t]++;let u=0;for(const t of l)t&&u++;1===u&&(l[r[0]?0:1]=l[r[0]]);const y=f(l,5),w=h(y);let p=18;for(;p>0&&!y[D[p-1]];)p--;t.write(2,0);for(let n=0;n<p;n++){const e=y[D[n]];t.write(F[e],Q[e])}for(let n=0;n<r.length;n++){const e=r[n];t.write(y[e],w[e]),16===e?t.write(2,s[n]):17===e&&t.write(3,s[n])}}function p(t,n=0,e=0){return{split:t,C:n,q:e,L:1<<t}}function d(t,n,e=15){t.write(o(e+1),n.split),n.split!==e&&(t.write(o(n.split+1),n.C),t.write(o(n.split-n.C+1),n.q))}function M(t,n,o){if(n<t.L)return o[0]=n,o[1]=0,void(o[2]=0);const r=e(n),s=n-(1<<r),i=r-t.C-t.q;o[0]=t.L+(r-t.split<<t.C+t.q|s>>r-t.C<<t.q|s&(1<<t.q)-1),o[1]=i,o[2]=n>>t.q&(1<<i)-1}function A(t,n,e,o=0){M(t,n,H),e[o+H[0]]++}function g(t,n,e,o,r=0){M(e,o,H);const s=r+H[0];t.write(n.I[s],n.S[s]),H[1]&&t.write(H[1],H[2])}function m(t,{N:n=null,P:r,X:s}){t.write(1,n?1:0),n&&(t.t([[0,224],[0,512],[0,4096],[15,8]],n.J),t.t([[0,3],[0,4],[2,5],[8,9]],n.minLength),d(t,n._,8)),r.length>1&&function(t,n){let e=0;for(const t of n)e=Math.max(e,t);const r=e?o(e+1):0;if(r<=3){if(t.write(1,1),t.write(2,r),r)for(const e of n)t.write(r,e);return}t.write(1,0),t.write(1,0);const s=p(8),i=p(4),c=n.length>=64,l=new Uint32Array(c?257:e+1),f=[];for(let t=0;t<n.length;){let e=1;for(;c&&t+e<n.length&&n[t+e]===n[t];)e++;if(e>=8)f.push([n[t],e-1]),l[n[t]]++,A(i,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=y(l);if(!c){m(t,{P:new Uint8Array(1),X:[{config:s,code:a}]});for(const[n]of f)g(t,a,s,n);return}const u=new Uint32Array(1);u[0]=1,m(t,{N:{J:224,minLength:7,_:i},P:new Uint8Array([1,0]),X:[{config:p(0),code:y(u)},{config:s,code:a}]});for(const[n,e]of f)g(t,a,s,n),e&&g(t,a,i,e-7,224)}(t,r),t.write(1,1);for(const n of s)d(t,n.config,15);for(const n of s){const o=n.code.U-1;if(o){const n=e(o);t.write(1,1),t.write(4,n),t.write(n,o-(1<<n))}else t.write(1,0)}for(const n of s)w(t,n.code)}function b(t,n=0,e=1){return{G:t,offset:n,O:e,context:-1}}function U(t,n,e,o){return{j:t,R:n,left:e,right:o}}function I(t,n){const e=(o,r)=>{if(o===r)return n?t[o].B:t[o];const s=o+r>>1;return U(n,n?t[s].k:s,e(s+1,r),e(o,s))};return e(0,t.length-1)}function S(t){return I(t,0)}function x(t,e){const o=[e],r=[],s=[];for(const t of o){if(t.left){r.push(t.j+1,n(t.R)),o.push(t.left,t.right);continue}t.context=s.length,s.push(t);const e=1===t.O?0:31-Math.clz32(t.O&-t.O);r.push(0,t.G,n(t.offset),e,(t.O>>e)-1)}const i=new Uint32Array(64);for(const t of r)A(tt,t,i);const c=y(i);m(t,{P:new Uint8Array(6),X:[{config:tt,code:c}]});for(const n of r)g(t,c,tt,n);return s}function T(t,n){const e=[[3,0],[6,8],[10,72],[13,1096]];if("rct"===n.type)t.write(2,0),t.t(e,n.Y),t.t([[0,6],[2,0],[4,2],[6,10]],n.Z??6);else if("palette"===n.type)t.write(2,1),t.t(e,n.Y),t.t([[0,1],[0,3],[0,4],[13,1]],n.D),t.t([[8,0],[10,256],[12,1280],[16,5376]],n.F),t.t([[0,0],[8,1],[10,257],[16,1281]],n.H??0),t.write(4,n.G??0);else{if("squeeze"!==n.type)throw new Error("unknown transform "+n.type);{t.write(2,2);const o=n.K??[];t.t([[0,0],[4,1],[6,9],[8,41]],o.length);for(const n of o)t.write(1,n.V?1:0),t.write(1,n.W?1:0),t.t(e,n.Y),t.t([[0,1],[0,2],[0,3],[4,4]],n.D)}}}function v(t,{$:n=!0,tt:e=[]}={}){t.write(1,n?1:0),t.write(1,1),t.t([[0,0],[0,1],[4,2],[8,18]],e.length);for(const n of e)T(t,n)}function C(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 i=[{config:tt,code:y(s)}],c=Math.max(...r);for(let t=0;t<c;t++)i.push({config:tt,code:y(e[t])});return m(t,{N:$,P:r,X:i}),i}function q(t,e,o,r,s,i){const c=i.G,l=i.offset,f=i.O,a=tt,u=W;let h=0;const y=(n,o=a,r=0)=>{t?g(t,e,o,n,r):A(o,n,e,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,e)=>{const r=o[t];let s;1===f?s=r-e-l:(s=Math.round((r-e-l)/f),o[t]=e+l+s*f),0!==s?(w(),y(n(s))):h++};for(let t=0,n=0;t<s;t++)for(let e=0;e<r;e++,n++){let s;if(0===c)s=0;else{const i=e?o[n-1]:t?o[n-r]:0,l=t?o[n-r]:i;if(1===c)s=i;else if(2===c)s=l;else if(3===c)s=(i+l)/2|0;else{const f=i+l-(e&&t?o[n-r-1]:i);if(5===c){const t=i<l?i:l,n=i<l?l:i;s=f<t?t:f>n?n:f}else{if(4!==c)throw new Error("predictor "+c+" is not coded here");s=Math.abs(f-i)<Math.abs(f-l)?i:l}}}p(n,s)}w()}function E(t,n,e,{palette:o=!0}={}){const r=n*e;let s=!0,i=!0;for(let n=0,e=0;e<r&&(255===t[n+3]||(s=!1,i))&&(t[n]===t[n+1]&&t[n]===t[n+2]||(i=!1,s));e++,n+=4);const c=i?1:3,l=!s,f=c+(l?1:0),a=n=>i?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:c,alpha:l,channels:f,palette:h}}function*L(n,e,o,{shape:r=E(n,e,o),colorSpace:s}={}){let i,c;const l=r.palette?[r,{...r,palette:null}]:[r];for(let r=0;r<l.length;r++)try{const c=yield*t(N(n,e,o,l[r],s),r,l.length);(!i||c.length<i.length)&&(i=c)}catch(t){if("JXL_SIZE"!==t.code)throw t;c=t}if(!i)throw c;return i}function*N(t,n,e,o,r){const{channels:f,palette:a}=o,u=c(n,e),h=a?2:f,p=Array.from({length:h},()=>b(V)),d=S(p),M=Array.from({length:h},()=>new Int16Array(65536)),A=(e,o,r,s)=>{for(let i=0;i<s;i++){let s=4*((o+i)*n+e),c=i*r;for(let n=0;n<r;n++,s+=4,c++)if(a)M[0][c]=a.index.get(a.key(s));else if(f>=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][c]=i+(l>>1),M[1][c]=r,M[2][c]=l,4===f&&(M[3][c]=t[s+3])}else M[0][c]=t[s],2===f&&(M[1][c]=t[s+3])}};let g=null;if(a){const t=a.colours.length;g=new Int16Array(t*f),a.colours.forEach((n,e)=>{for(let o=0;o<f;o++)g[o*t+e]=a.byte(n,o)})}const m=t=>p[a?u.A?1:0:t],U=a?1:f,I=u.i*u.u,T=2*I,E=(t,o)=>{const r=t%u.i*Z,s=(t/u.i|0)*Z,i=Math.min(Z,n-r),c=Math.min(Z,e-s);A(r,s,i,c),o(i,c)};let L=0;if(!a){const t=t=>{const n=y(t),e=new Y(128);w(e,n);let o=e.nt;for(let e=0;e<t.length;e++)t[e]&&(o+=t[e]*(n.I[e]+Math.max(0,e<224?e-1:e-236)));return o},o=Math.min(n,32),r=Math.min(e,32),s=[V,K].map(t=>({G:t,et:p.map(()=>new Uint32Array(nt))}));for(const t of[0,.5,1]){A(Math.floor((n-o)*t),Math.floor((e-r)*t),o,r);for(const t of s)for(let n=0;n<f;n++)q(null,t.et[n],M[n],o,r,b(t.G))}p.forEach((n,e)=>{const o=s.map(n=>t(n.et[e])),r=o[1]<o[0]?1:0;n.G=s[r].G})}const N=a?[{type:"palette",Y:0,D:f,F:a.colours.length}]:f>=3?[{type:"rct",Y:0,Z:6}]:[],P=p.map(()=>new Uint32Array(nt)),X=t=>P[p.indexOf(t)];g&&q(null,X(p[0]),g,a.colours.length,f,p[0]);for(let t=0;t<I;t++)E(t,(t,n)=>{for(let e=0;e<U;e++)q(null,X(m(e)),M[e],t,n,m(e))}),yield++L/T;const J=new Y(256);s(J,n,e,o.colour,o.alpha,{colorSpace:r}),i(J,{alpha:o.alpha});const _=new Y(4096);_.write(1,1),_.write(1,1);const z=x(_,d),G=C(_,z,P,t=>p.indexOf(t)),O=t=>G[p.indexOf(t)+1].code;v(_,{$:!0,tt:N}),g&&q(_,O(p[0]),g,a.colours.length,f,p[0]);const j=[];if(u.A)E(0,(t,n)=>{for(let e=0;e<U;e++)q(_,O(m(e)),M[e],t,n,m(e))}),yield 1,j.push(_.finish());else{j.push(_.finish());for(let t=0;t<u.p*u.M+1;t++)j.push(new Uint8Array(0));for(let t=0;t<I;t++)E(t,(t,n)=>{const e=new Y(t*n*U+64);v(e,{$:!0,tt:[]});for(let o=0;o<U;o++)q(e,O(m(o)),M[o],t,n,m(o));j.push(e.finish())}),yield++L/T}return l(J,j)}function P(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 X(t,n){const{w:e,h:o,data:r}=t,s=n?e:o,i=s+1>>1,c=s-i,l=n?i:e,f=n?o:i,a=n?c:e,u=n?o:c,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 i=n?o:t,f=n?t*e+2*o:2*t*e+o;if(i===c){h[t*l+o]=r[f];continue}const u=r[f],p=r[f+w],d=et(u,p),M=i+1<c?et(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-P(i?r[f-w]:d,d,M)}const p=(e,o,r)=>({w:o,h:r,ot:t.ot+ +n,rt:t.rt+!n,data:e});return[p(h,l,f),p(y,a,u)]}function J(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({V:!0,W:!1,Y:1,D:2}),n.push({V:!1,W:!1,Y:1,D:2})),!(o>r)&&r>8&&(n.push({V:!1,W:!0,Y:0,D:e}),r=r+1>>1);o>8||r>8;)o>8&&(n.push({V:!0,W:!0,Y:0,D:e}),o=o+1>>1),r>8&&(n.push({V:!1,W:!0,Y:0,D:e}),r=r+1>>1);return n}function*_(t,n,e,{quality:o=90,shape:r=E(t,n,e,{palette:!1}),colorSpace:f}={}){const a=function(t){return t>=100?0:t>=30?.1+.09*(100-t):53/3e3*t*t-1.15*t+25}(o);if(!(a>0))throw new Error("lossy encoding needs a quality below 100");const{colour:u,alpha:h,channels:y}=r,w=n*e,p=Array.from({length:y},()=>new Int16Array(w));for(let n=0,e=0;e<w;e++,n+=4){if(3===u){const o=t[n],r=t[n+1],s=t[n+2],i=o-s,c=s+(i>>1),l=r-c;p[0][e]=c+(l>>1),p[1][e]=i,p[2][e]=l}else p[0][e]=t[n];h&&(p[y-1][e]=t[n+3])}const d=p.map((t,o)=>({w:n,h:e,ot:0,rt:0,data:t,component:3===u?o:0===o?0:3})),M=1===n&&e>ot&&3===u,A=M?J(d).filter(t=>!t.V):1!==n&&1!==e||3!==u?[]:J(d),g=function(t,n=J(t)){const e=t.map(t=>({...t,level:0,st:!1}));for(const t of n){const n=t.Y+t.D-1,o=t.W?n+1:e.length;for(let r=t.Y;r<=n;r++){const[n,s]=X(e[r],t.V),i=e[r].level+1;e[r]={...n,level:i,st:e[r].st,component:e[r].component},e.splice(o+(r-t.Y),0,{...s,level:i,st:!0,component:e[r].component})}}return e}(d,A.length?A:void 0),m=t=>t.st?0:V,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),i=1<<r,c=1===t||2===t?.35*s*Math.max(.5,1024/i):.35*s*1.1*(163.84/i);return Math.max(1,Math.floor(c))}(t.component,t.ot+(M&&t.component>0&&t.component<3?1:0),t.rt,a),T=[...3===u?[{type:"rct",Y:0,Z:6}]:[],{type:"squeeze",K:A}],L=c(n,e,ot),N=L.p*L.M,P=L.i*L.u;let _=0;for(;_<g.length&&g[_].w<=ot&&g[_].h<=ot;)_++;const z=(t,n,e,o)=>{const r=n>>t.ot,s=e>>t.rt;return{x:r,y:s,w:Math.min(t.w,r+(o>>t.ot))-r,h:Math.min(t.h,s+(o>>t.rt))-s}},G=[{index:0,k:0,it:[]}];for(let t=0;t<_;t++)G[0].it.push({c:t,rect:{x:0,y:0,w:g[t].w,h:g[t].h}});if(!L.A){for(let t=0;t<N;t++){const n=t%L.p,e=t/L.p|0,o={index:1+t,k:1+N+t,it:[]};for(let t=_;t<g.length;t++){const r=g[t];if(Math.min(r.ot,r.rt)<3)continue;const s=z(r,n*rt,e*rt,rt);s.w>0&&s.h>0&&o.it.push({c:t,rect:s})}G.push(o)}for(let t=0;t<P;t++){const n=t%L.i,e=t/L.i|0,o={index:2+N+t,k:1+3*N+17+t,it:[]};for(let t=_;t<g.length;t++){const r=g[t];if(Math.min(r.ot,r.rt)>2)continue;const s=z(r,n*ot,e*ot,ot);s.w>0&&s.h>0&&o.it.push({c:t,rect:s})}G.push(o)}}for(const t of G)for(const n of t.it){const t=g[n.c];n.ct=b(m(t),0,U(t)),n.ct.channel=n.c}const O=G.filter(t=>t.it.length),j=1===O.length?S(O[0].it.map(t=>t.ct)):function(t){return I(t,1)}(O.map(t=>({k:t.k,B:S(t.it.map(t=>t.ct))}))),R=new Int16Array(1048576),B=t=>{const n=g[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++)R.set(n.data.subarray((e.y+t)*n.w+e.x,(e.y+t)*n.w+e.x+e.w),t*e.w);return R},k=[...new Set(G.flatMap(t=>t.it.map(t=>t.c)))].sort((t,n)=>t-n),Z=new Map(k.map((t,n)=>[t,n])),D=k.map(()=>new Uint32Array(nt)),F=1+2*G.length;let Q=1;yield Q/F;for(const t of G){for(const n of t.it)q(null,D[Z.get(n.c)],B(n),n.rect.w,n.rect.h,n.ct);yield++Q/F}const H=new Y(256);s(H,n,e,u,h,{colorSpace:f}),i(H,{alpha:h,shift:n>256||e>256?3:1});const K=Array.from({length:L.A?1:2+N+P},()=>null),W=K[0]=new Y(65536);W.write(1,1),W.write(1,1);const $=C(W,x(W,j),D,t=>Z.get(t.channel));v(W,{$:!0,tt:T});for(const t of G){let n=K[t.index];n||(n=K[t.index]=new Y(4096),t.it.length&&v(n,{$:!0,tt:[]}));for(const e of t.it)q(n,$[Z.get(e.c)+1].code,B(e),e.rect.w,e.rect.h,e.ct);yield++Q/F}return l(H,function(t){return t.map(t=>t?t.finish():new Uint8Array(0))}(K))}function z(t,e,o,r,s,i=0,c=ft){const l=r+2,f=new Int32Array(2*l),a=new Int32Array(2*l),u=new Int32Array(2*l),h=new Int32Array(2*l),y=new Int32Array(2*l),w=new Int32Array(8);let p=0;const d=(n,o,r=tt,s=0)=>{t?g(t,e[n],r,o,s):A(r,o,e[n],s)},M=()=>{if(p){if(p>$.minLength)d(w[0],0),d(w[1],p-$.minLength-1,$._,$.J);else for(let t=0;t<p;t++)d(w[t],0);p=0}};for(let t=0,e=0;t<s;t++){const s=1&t?0:l,A=1&t?l:0;for(let l=0;l<r;l++,e++){const g=l?o[e-1]:t?o[e-r]:0,m=t?o[e-r]:g,b=l&&t?o[e-r-1]:g,U=l+1<r&&t?o[e-r+1]:m,I=t>1?o[e-2*r]:m,S=A+l,x=l<r-1?S+1:S,T=l?S-1:S;let v=lt(f[S]+f[x]+f[T],13),C=lt(a[S]+a[x]+a[T],12),q=lt(u[S]+u[x]+u[T],12),E=lt(h[S]+h[x]+h[T],12);const L=8*m,N=8*g,P=8*U,X=8*b,J=8*I,_=l?y[s+l-1]:0,z=y[S],G=y[T],O=y[x],j=z+_;let R=_;Math.abs(z)>Math.abs(R)&&(R=z),Math.abs(G)>Math.abs(R)&&(R=G),Math.abs(O)>Math.abs(R)&&(R=O);const B=c[it[R<-501?0:R>501?1002:R+501]],k=N+P-L,Y=L-(16*(j+O)>>5),Z=N-(10*(j+G)>>5),D=L-(7*G+7*z+7*O+0*(J-L)+0*(X-N)>>5),F=27-Math.clz32(v+C+q+E);v>>=F,C>>=F,q>>=F,E>>=F;const Q=v+C+q+E;let H=Math.floor(((Q>>1)-1+k*v+Y*C+Z*q+D*E)*ct[Q-1]/16777216);if((z^_|z^G)<=0){const t=Math.min(N,P,L),n=Math.max(N,P,L);H=H<t?t:H>n?n:H}const K=o[e],V=K-(H+3>>3)-i;0===V?(p<8&&(w[p]=B),p++):(M(),d(B,n(V)));const W=8*K,$=Math.abs(k-W)+3>>3,tt=Math.abs(Y-W)+3>>3,nt=Math.abs(Z-W)+3>>3,et=Math.abs(D-W)+3>>3;y[s+l]=H-W,f[s+l]=$,a[s+l]=tt,u[s+l]=nt,h[s+l]=et,f[A+l+1]+=$,a[A+l+1]+=tt,u[A+l+1]+=nt,h[A+l+1]+=et}}M()}function G(t){try{return t()}catch(t){if(t instanceof RangeError&&!t.code)throw at("JXL_MEMORY","Not enough memory for this picture.");throw t}}function O(t,n,e,o){return function(t){let n;for(;!(n=t.next()).done;);return n.value}(j(t,n,e,o))}function j(n,e,o,r){const{quality:f,colorSpace:a}=function(t={},n=100){if(null===t||"object"!=typeof t)throw at("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 at("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 at("JXL_INPUT","The colour space is srgb or display-p3.");return{quality:n,colorSpace:e}}(r),u=void 0===r?.effort?1:r.effort;if(!Number.isInteger(u)||u<1||u>9)throw at("JXL_INPUT","Effort is a whole number from 1 to 9.");return function(t,n,e,o){if(function(t,n,e=B){if(!Number.isInteger(t)||!Number.isInteger(n)||t<1||n<1)throw at("JXL_INPUT","A picture is at least one pixel wide and high, in whole pixels.");if(t>e.edge||n>e.edge)throw at("JXL_DIMENSIONS","A picture is at most "+e.edge+" pixels on a side.");if(t*n>e.pixels)throw at("JXL_DIMENSIONS","A picture is at most "+e.pixels.toLocaleString("en-US")+" pixels.")}(n,e,o),!(t instanceof Uint8Array||t instanceof Uint8ClampedArray))throw at("JXL_INPUT","Pixels are a Uint8Array or Uint8ClampedArray of RGBA bytes.");if(t.length!==n*e*4)throw at("JXL_INPUT","Pixels are width * height * 4 bytes: straight (not premultiplied) RGBA, row by row.")}(n,e,o),function(t){const n=function*(){let e,o;for(;!(e=G(()=>t.next(n.hurry))).done;)yield o=e.value;return o<1&&(yield 1),n.bytes=function(t){if(t.length>B.bytes)throw at("JXL_SIZE","The encoded picture exceeds 16 MiB.");return t}(e.value)}();return n.bytes=null,n.hurry=!1,n}(function*(n,e,o,r,f,a){const u=E(n,e,o);if(r<100){if(!u.palette)return yield*_(n,e,o,{quality:r,shape:u,colorSpace:f});const s=yield*t(L(n,e,o,{shape:u,colorSpace:f}),0,2);let i;try{i=yield*t(_(n,e,o,{quality:r,shape:u,colorSpace:f}),1,2)}catch(t){if(!(t instanceof RangeError)||t.code)throw t;i=s}return s.length<=i.length?s:i}if(a<2)return yield*L(n,e,o,{shape:u,colorSpace:f});let h=yield*t(L(n,e,o,{shape:u,colorSpace:f}),0,2);try{const r=yield*t(function*(t,n,e,o,r,f){function*a(t){const{B:c,leaves:f,et:a}=_(t),p=u>=3?[{type:"rct",Y:0,Z:6}]:[],M=new Y(256);s(M,n,e,o.colour,o.alpha,{colorSpace:r}),i(M,{alpha:o.alpha});const A=new Y(4096);A.write(1,1),A.write(1,1);const g=C(A,x(A,c),a,t=>f.indexOf(t));v(A,{$:!0,tt:p});const m=t.map(t=>t.leaves.map(t=>g[f.indexOf(t)+1].code)),b=(n,e,o,r)=>{const s=t[e];6===s.leaves[0].G?z(n,m[e],w[e],o,r,0,s.lt):q(n,m[e][0],w[e],o,r,s.leaves[0])},U=[];if(h.A){if(d(0,(t,n)=>{for(let e=0;e<u;e++)b(A,e,t,n)}),yield B())return null;U.push(A.finish())}else{U.push(A.finish());for(let t=0;t<h.p*h.M+1;t++)U.push(new Uint8Array(0));for(let t=0;t<y;t++)if(d(t,(t,n)=>{const e=new Y(t*n*u+64);v(e,{$:!0,tt:[]});for(let o=0;o<u;o++)b(e,o,t,n);U.push(e.finish())}),yield B())return null}return l(M,U)}const{channels:u}=o,h=c(n,e),y=h.i*h.u,w=Array.from({length:u},()=>new Int16Array(65536)),p=(e,o,r,s)=>{for(let i=0;i<s;i++){let s=4*((o+i)*n+e),c=i*r;for(let n=0;n<r;n++,s+=4,c++)if(u>=3){const n=t[s],e=t[s+1],o=t[s+2],r=n-o,i=o+(r>>1),l=e-i;w[0][c]=i+(l>>1),w[1][c]=r,w[2][c]=l,4===u&&(w[3][c]=t[s+3])}else w[0][c]=t[s],2===u&&(w[1][c]=t[s+3])}},d=(t,o)=>{const r=t%h.i*Z,s=(t/h.i|0)*Z,i=Math.min(Z,n-r),c=Math.min(Z,e-s);p(r,s,i,c),o(i,c)},M=Math.min(n,32),A=Math.min(e,32),g=[V,K].map(t=>({G:t,et:w.map(()=>new Uint32Array(nt))}));for(const t of[0,.5,1]){p(Math.floor((n-M)*t),Math.floor((e-A)*t),M,A);for(const t of g)for(let n=0;n<u;n++)q(null,t.et[n],w[n],M,A,b(t.G))}const m=w.map((t,n)=>b(g[R(g[1].et[n])<R(g[0].et[n])?1:0].G)),I=st.length+1,T=Int32Array.from({length:I},(t,n)=>n),E=w.map(()=>new Uint32Array(nt)),L=w.map(()=>Array.from({length:I},()=>new Uint32Array(nt)));let N=0;for(let t=0;t<y;t++)if(d(t,(t,n)=>{for(let e=0;e<u;e++)q(null,E[e],w[e],t,n,m[e]),z(null,L[e],w[e],t,n,0,T)}),yield++N/(2*y))return null;const P=w.map((t,n)=>{const e={ft:R(E[n]),leaves:[m[n]],et:[E[n]],cuts:[]},o=ht(L[n]),r=R(o);return r<e.ft?{ft:r,leaves:[b(6)],et:[o],cuts:[]}:e}),X=f<3?P:P.map((t,n)=>{const e=L[n].map(t=>t.some(t=>t)),o=new Float64Array(I+1).fill(1/0),r=new Int32Array(I+1);o[0]=0;for(let t=1;t<=I;t++){const s=new Uint32Array(nt);let i=0;for(let c=t-1;c>=0;c--){if(e[c]){const t=L[n][c];for(let n=0;n<nt;n++)s[n]+=t[n];i=R(s)}const l=o[c]+i+24;l<o[t]&&(o[t]=l,r[t]=c)}}if(!(o[I]<t.ft))return t;const s=[];for(let t=I;t>0;t=r[t])s.unshift([r[t],t]);return{ft:o[I],leaves:s.map(()=>b(6)),et:s.map(([t,e])=>ht(L[n].slice(t,e))),cuts:s.slice(0,-1).map(([,t])=>st[t-1]),lt:Int32Array.from({length:I},(t,n)=>s.findIndex(([t,e])=>n>=t&&n<e))}}),J=[P,X].filter((t,n)=>n?t.some((t,n)=>t!==P[n]):t.some((t,n)=>t.leaves[0]!==m[n]));if(!J.length)return null;const _=t=>({B:S(t.map(t=>((t,n)=>{const e=(o,r)=>{if(o===r)return t[o];const s=o+r>>1;return U(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),et:t.flatMap(t=>t.et)}),G=t=>{const{B:n,leaves:e,et:o}=_(t),r=new Y(4096),s=C(r,x(r,n),o,t=>e.indexOf(t));let i=r.nt;return o.forEach((t,n)=>{const{I:e}=s[n+1].code;for(let n=0;n<t.length;n++)t[n]&&(i+=t[n]*(e[n]+ut(n)))}),i};let O=J;if(J.length>1){const[t,n]=J.map(G),e=27*(h.A?1:y+1)+8;n+e<=t?O=[J[1]]:t+e<=n&&(O=[J[0]])}let j=0;const B=()=>.5+ ++j/(2*O.length*y);let k=null;for(const t of O){const n=yield*a(t);if(!n)return k;(!k||n.length<k.length)&&(k=n)}return k}(n,e,o,u.palette?{...u,palette:null}:u,f,a),1,2);r&&r.length<h.length&&(h=r)}catch(t){if(!(t instanceof RangeError)||t.code)throw t}return h}(n,e,o,f,a,u))}function R(t){const n=y(t),e=new Y(128);w(e,n);let o=e.nt;for(let e=0;e<t.length;e++)t[e]&&(o+=t[e]*(n.I[e]+ut(e)));return o}const B=Object.freeze({bytes:16777216,pixels:24e6,edge:16384}),k=[1];for(let t=1;t<=32;t++)k.push(2*k[t-1]);class Y{constructor(t=4096){this.bytes=new Uint8Array(t),this.at=0,this.ut=0,this.pending=0}write(t,n){if(!(n>=0&&n<k[t]))throw new Error("bit write out of range: "+t+" bits, "+n);let e=this.ut+n*(1<<this.pending),o=this.pending+t;this.at+5>=this.bytes.length&&this.grow();const r=this.bytes;for(;o>=8;)r[this.at++]=255&e,e=Math.floor(e/256),o-=8;this.ut=e,this.pending=o}t(t,n){for(let e=0;e<4;e++){const[o,r]=t[e];if(n>=r&&n-r<k[o])return this.write(2,e),void(o&&this.write(o,n-r))}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.ut,this.ut=0,this.pending=0)}get nt(){return 8*this.at+this.pending}grow(t=0){const n=B.bytes+64;if(this.at+t+16>n)throw Object.assign(new Error("The encoded picture exceeds 16 MiB."),{code:"JXL_SIZE"});const e=new Uint8Array(Math.min(n,Math.max(2*this.bytes.length,this.at+t+16)));e.set(this.bytes.subarray(0,this.at)),this.bytes=e}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.ut)}finish(){const t=this.at+(this.pending?1:0),n=this.bytes.slice(0,t);return this.pending&&(n[this.at]=this.ut),n}}const Z=256,D=[1,2,3,4,0,5,17,6,16,7,8,9,10,11,12,13,14,15],F=[2,4,3,2,2,4],Q=[0,7,3,2,1,15],H=[0,0,0],K=3,V=5,W=p(4),$=Object.freeze({J:224,minLength:7,_:W}),tt=p(0),nt=257,et=(t,n)=>t+n+(t>n?1:0)>>1,ot=1024,rt=8192,st=[-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],it=Int8Array.from({length:1003},(t,n)=>st.filter(t=>t<n-501).length),ct=Int32Array.from({length:64},(t,n)=>Math.floor(16777216/(n+1))),lt=(t,n)=>{const e=Math.max(0,26-Math.clz32(t+1));return 4+(n*ct[t>>e]>>e)},ft=new Int32Array(st.length+1),at=(t,n)=>Object.assign(new Error(n),{code:t}),ut=t=>Math.max(0,t<224?t-1:t-236),ht=t=>{const n=new Uint32Array(nt);for(const e of t)for(let t=0;t<nt;t++)n[t]+=e[t];return n};export{B as LIMITS,O as encode,j as encodeSteps};