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 +14 -10
- package/ENCODER-COMPARISON.md +6 -6
- package/README.md +75 -38
- package/admit.mjs +56 -0
- package/bits.mjs +30 -10
- package/effort.d.mts +11 -0
- package/effort.min.mjs +2 -0
- package/effort.mjs +217 -0
- package/entropy.mjs +16 -1
- package/frame.mjs +16 -9
- package/index.d.mts +13 -17
- package/index.mjs +30 -83
- package/jfif.mjs +407 -0
- package/jpeg.d.mts +14 -2
- package/jpeg.min.mjs +2 -2
- package/jpeg.mjs +34 -377
- package/lossless.mjs +33 -21
- package/lossy.mjs +41 -16
- package/package.json +9 -8
- package/photo.d.mts +6 -2
- package/photo.min.mjs +2 -2
- package/photo.mjs +73 -42
- package/public/examples/browser.html +4 -2
- package/public/examples/worker.mjs +34 -9
- package/rapier-jxl.min.mjs +2 -2
- package/sizes.json +179 -78
- package/vardct.mjs +30 -10
- package/weighted.mjs +79 -0
- package/writer.mjs +17 -0
- package/lossless.d.mts +0 -3
- package/lossless.min.mjs +0 -2
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
|
-
`
|
|
5
|
-
2. `import {encode
|
|
6
|
-
`rapier-jxl/lossless
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
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 `
|
|
13
|
-
5. Anything larger than an icon runs in a worker. Calls are synchronous, so cancel by terminating the worker
|
|
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
|
|
16
|
-
|
|
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
|
package/ENCODER-COMPARISON.md
CHANGED
|
@@ -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 |
|
|
9
|
-
| Rapier JXL
|
|
10
|
-
| Rapier JXL JPEG |
|
|
11
|
-
| Rapier JXL photo |
|
|
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
|
-
###
|
|
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
|
-
###
|
|
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,
|
|
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
|
-
- **
|
|
11
|
-
|
|
12
|
-
|
|
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
|
|
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
|
|
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
|
-
|
|
32
|
-
|
|
33
|
-
|
|
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
|
|
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
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
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
|
-
|
|
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
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
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
|
-
|
|
76
|
-
|
|
77
|
-
|
|
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
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
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
|
|
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,
|
|
5
|
-
//
|
|
6
|
-
//
|
|
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
|
|
10
|
-
|
|
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 (
|
|
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 <
|
|
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
|
-
|
|
75
|
-
let
|
|
76
|
-
|
|
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};
|