rapier-jxl 2.1.0 → 2.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,252 @@
1
+ ;; SPDX-License-Identifier: MIT
2
+ ;; Own bounded integer kernels. Pointers and lengths are checked by kernels.mjs.
3
+ ;; Tiny arithmetic helpers are explicitly inlined: Node 22 does not inline WAT calls
4
+ ;; by default. i64 multiplication followed by arithmetic >>24 is floor, not truncation.
5
+ ;; SIMD averages add one to negative sums before >>1, matching JavaScript's /2 |0.
6
+ (module
7
+ (import "env" "memory" (memory 1 128))
8
+ (func $residual
9
+ (export "residual")
10
+ (param $p i32)
11
+ (param $w i32)
12
+ (param $h i32)
13
+ (param $pred i32)
14
+ (param $offset i32)
15
+ (param $out i32)
16
+ (local $L v128)
17
+ (local $T v128)
18
+ (local $NW v128)
19
+ (local $V v128)
20
+ (local $S v128)
21
+ (local $x i32)
22
+ (local $y i32)
23
+ (local $row i32)
24
+ (local $left i32)
25
+ (local $top i32)
26
+ (local $nw i32)
27
+ (local $g i32)
28
+ (local $v i32)
29
+ (local.set $row (i32.shl (local.get $w) (i32.const 2)))
30
+ (loop $ys
31
+ (local.set $x (i32.const 0))
32
+ (loop $xs
33
+ (block $next
34
+ (if
35
+ (i32.and
36
+ (i32.and (i32.ne (local.get $y) (i32.const 0)) (i32.ne (local.get $x) (i32.const 0)))
37
+ (i32.and
38
+ (i32.le_u (i32.add (local.get $x) (i32.const 4)) (local.get $w))
39
+ (i32.or (i32.eq (local.get $pred) (i32.const 3)) (i32.eq (local.get $pred) (i32.const 5)))))
40
+ (then
41
+ (local.set $L (v128.load (i32.sub (local.get $p) (i32.const 4))))
42
+ (local.set $T (v128.load (i32.sub (local.get $p) (local.get $row))))
43
+ (local.set $S (i32x4.add (local.get $L) (local.get $T)))
44
+ (if
45
+ (i32.eq (local.get $pred) (i32.const 3))
46
+ (then
47
+ (local.set $V
48
+ (i32x4.shr_s
49
+ (i32x4.add
50
+ (local.get $S)
51
+ (v128.and (i32x4.shr_s (local.get $S) (i32.const 31)) (i32x4.splat (i32.const 1))))
52
+ (i32.const 1))))
53
+ (else
54
+ (local.set $NW (v128.load (i32.sub (i32.sub (local.get $p) (local.get $row)) (i32.const 4))))
55
+ (local.set $V
56
+ (i32x4.max_s
57
+ (i32x4.min_s (local.get $L) (local.get $T))
58
+ (i32x4.min_s
59
+ (i32x4.max_s (local.get $L) (local.get $T))
60
+ (i32x4.sub (local.get $S) (local.get $NW)))))))
61
+ (local.set $V
62
+ (i32x4.sub
63
+ (i32x4.sub (v128.load (local.get $p)) (local.get $V))
64
+ (i32x4.splat (local.get $offset))))
65
+ (v128.store
66
+ (local.get $out)
67
+ (v128.xor (i32x4.shl (local.get $V) (i32.const 1)) (i32x4.shr_s (local.get $V) (i32.const 31))))
68
+ (local.set $p (i32.add (local.get $p) (i32.const 16)))
69
+ (local.set $out (i32.add (local.get $out) (i32.const 16)))
70
+ (local.set $x (i32.add (local.get $x) (i32.const 4)))
71
+ (br $next)))
72
+ (local.set $v (i32.const 0))
73
+ (if
74
+ (local.get $pred)
75
+ (then
76
+ (local.set $left
77
+ (if
78
+ (result i32)
79
+ (local.get $x)
80
+ (then (i32.load (i32.sub (local.get $p) (i32.const 4))))
81
+ (else
82
+ (if
83
+ (result i32)
84
+ (local.get $y)
85
+ (then (i32.load (i32.sub (local.get $p) (local.get $row))))
86
+ (else (i32.const 0))))))
87
+ (local.set $top
88
+ (if
89
+ (result i32)
90
+ (local.get $y)
91
+ (then (i32.load (i32.sub (local.get $p) (local.get $row))))
92
+ (else (local.get $left))))
93
+ (local.set $nw
94
+ (if
95
+ (result i32)
96
+ (i32.and (i32.ne (local.get $x) (i32.const 0)) (i32.ne (local.get $y) (i32.const 0)))
97
+ (then (i32.load (i32.sub (i32.sub (local.get $p) (local.get $row)) (i32.const 4))))
98
+ (else (local.get $left))))
99
+ (local.set $g (i32.sub (i32.add (local.get $left) (local.get $top)) (local.get $nw)))
100
+ (if (i32.eq (local.get $pred) (i32.const 1)) (then (local.set $v (local.get $left))))
101
+ (if (i32.eq (local.get $pred) (i32.const 2)) (then (local.set $v (local.get $top))))
102
+ (if
103
+ (i32.eq (local.get $pred) (i32.const 3))
104
+ (then (local.set $v (i32.div_s (i32.add (local.get $left) (local.get $top)) (i32.const 2)))))
105
+ (if
106
+ (i32.eq (local.get $pred) (i32.const 4))
107
+ (then
108
+ (local.set $v
109
+ (select
110
+ (local.get $left)
111
+ (local.get $top)
112
+ (i32.lt_s
113
+ (i32.sub
114
+ (i32.xor
115
+ (i32.sub (local.get $g) (local.get $left))
116
+ (i32.shr_s (i32.sub (local.get $g) (local.get $left)) (i32.const 31)))
117
+ (i32.shr_s (i32.sub (local.get $g) (local.get $left)) (i32.const 31)))
118
+ (i32.sub
119
+ (i32.xor
120
+ (i32.sub (local.get $g) (local.get $top))
121
+ (i32.shr_s (i32.sub (local.get $g) (local.get $top)) (i32.const 31)))
122
+ (i32.shr_s (i32.sub (local.get $g) (local.get $top)) (i32.const 31))))))))
123
+ (if
124
+ (i32.eq (local.get $pred) (i32.const 5))
125
+ (then
126
+ (local.set $v
127
+ (select
128
+ (select (local.get $left) (local.get $top) (i32.lt_s (local.get $left) (local.get $top)))
129
+ (select
130
+ (select (local.get $left) (local.get $top) (i32.gt_s (local.get $left) (local.get $top)))
131
+ (local.get $g)
132
+ (i32.lt_s
133
+ (select
134
+ (local.get $left)
135
+ (local.get $top)
136
+ (i32.gt_s (local.get $left) (local.get $top)))
137
+ (local.get $g)))
138
+ (i32.gt_s
139
+ (select (local.get $left) (local.get $top) (i32.lt_s (local.get $left) (local.get $top)))
140
+ (select
141
+ (select
142
+ (local.get $left)
143
+ (local.get $top)
144
+ (i32.gt_s (local.get $left) (local.get $top)))
145
+ (local.get $g)
146
+ (i32.lt_s
147
+ (select
148
+ (local.get $left)
149
+ (local.get $top)
150
+ (i32.gt_s (local.get $left) (local.get $top)))
151
+ (local.get $g))))))))))
152
+ (i32.store
153
+ (local.get $out)
154
+ (i32.xor
155
+ (i32.shl
156
+ (i32.sub (i32.sub (i32.load (local.get $p)) (local.get $v)) (local.get $offset))
157
+ (i32.const 1))
158
+ (i32.shr_s
159
+ (i32.sub (i32.sub (i32.load (local.get $p)) (local.get $v)) (local.get $offset))
160
+ (i32.const 31))))
161
+ (local.set $out (i32.add (local.get $out) (i32.const 4)))
162
+ (local.set $p (i32.add (local.get $p) (i32.const 4)))
163
+ (local.set $x (i32.add (local.get $x) (i32.const 1))))
164
+ (br_if $xs (i32.lt_u (local.get $x) (local.get $w))))
165
+ (local.set $y (i32.add (local.get $y) (i32.const 1)))
166
+ (br_if $ys (i32.lt_u (local.get $y) (local.get $h)))))
167
+ (func $fill
168
+ (export "fill")
169
+ (param $src i32)
170
+ (param $n i32)
171
+ (param $channels i32)
172
+ (param $p0 i32)
173
+ (param $p1 i32)
174
+ (param $p2 i32)
175
+ (param $p3 i32)
176
+ (local $V v128)
177
+ (local $R v128)
178
+ (local $G v128)
179
+ (local $B v128)
180
+ (local $Co v128)
181
+ (local $Cg v128)
182
+ (local $Tmp v128)
183
+ (local $v i32)
184
+ (local $r i32)
185
+ (local $g i32)
186
+ (local $b i32)
187
+ (local $co i32)
188
+ (local $cg i32)
189
+ (local $tmp i32)
190
+ (local $i i32)
191
+ (block $vectorsDone
192
+ (loop $vectors
193
+ (br_if $vectorsDone (i32.gt_u (i32.add (local.get $i) (i32.const 4)) (local.get $n)))
194
+ (local.set $V (v128.load (local.get $src)))
195
+ (local.set $R (v128.and (local.get $V) (i32x4.splat (i32.const 255))))
196
+ (if
197
+ (i32.ge_u (local.get $channels) (i32.const 3))
198
+ (then
199
+ (local.set $G (v128.and (i32x4.shr_u (local.get $V) (i32.const 8)) (i32x4.splat (i32.const 255))))
200
+ (local.set $B (v128.and (i32x4.shr_u (local.get $V) (i32.const 16)) (i32x4.splat (i32.const 255))))
201
+ (local.set $Co (i32x4.sub (local.get $R) (local.get $B)))
202
+ (local.set $Tmp (i32x4.add (local.get $B) (i32x4.shr_s (local.get $Co) (i32.const 1))))
203
+ (local.set $Cg (i32x4.sub (local.get $G) (local.get $Tmp)))
204
+ (v128.store (local.get $p0) (i32x4.add (local.get $Tmp) (i32x4.shr_s (local.get $Cg) (i32.const 1))))
205
+ (v128.store (local.get $p1) (local.get $Co))
206
+ (v128.store (local.get $p2) (local.get $Cg))
207
+ (if
208
+ (i32.eq (local.get $channels) (i32.const 4))
209
+ (then (v128.store (local.get $p3) (i32x4.shr_u (local.get $V) (i32.const 24))))))
210
+ (else
211
+ (v128.store (local.get $p0) (local.get $R))
212
+ (if
213
+ (i32.eq (local.get $channels) (i32.const 2))
214
+ (then (v128.store (local.get $p1) (i32x4.shr_u (local.get $V) (i32.const 24)))))))
215
+ (local.set $p0 (i32.add (local.get $p0) (i32.const 16)))
216
+ (local.set $p1 (i32.add (local.get $p1) (i32.const 16)))
217
+ (local.set $p2 (i32.add (local.get $p2) (i32.const 16)))
218
+ (local.set $p3 (i32.add (local.get $p3) (i32.const 16)))
219
+ (local.set $src (i32.add (local.get $src) (i32.const 16)))
220
+ (local.set $i (i32.add (local.get $i) (i32.const 4)))
221
+ (br $vectors)))
222
+ (block $done
223
+ (loop $next
224
+ (br_if $done (i32.ge_u (local.get $i) (local.get $n)))
225
+ (local.set $v (i32.load (local.get $src)))
226
+ (local.set $r (i32.and (local.get $v) (i32.const 255)))
227
+ (if
228
+ (i32.ge_u (local.get $channels) (i32.const 3))
229
+ (then
230
+ (local.set $g (i32.and (i32.shr_u (local.get $v) (i32.const 8)) (i32.const 255)))
231
+ (local.set $b (i32.and (i32.shr_u (local.get $v) (i32.const 16)) (i32.const 255)))
232
+ (local.set $co (i32.sub (local.get $r) (local.get $b)))
233
+ (local.set $tmp (i32.add (local.get $b) (i32.shr_s (local.get $co) (i32.const 1))))
234
+ (local.set $cg (i32.sub (local.get $g) (local.get $tmp)))
235
+ (i32.store (local.get $p0) (i32.add (local.get $tmp) (i32.shr_s (local.get $cg) (i32.const 1))))
236
+ (i32.store (local.get $p1) (local.get $co))
237
+ (i32.store (local.get $p2) (local.get $cg))
238
+ (if
239
+ (i32.eq (local.get $channels) (i32.const 4))
240
+ (then (i32.store (local.get $p3) (i32.shr_u (local.get $v) (i32.const 24))))))
241
+ (else
242
+ (i32.store (local.get $p0) (local.get $r))
243
+ (if
244
+ (i32.eq (local.get $channels) (i32.const 2))
245
+ (then (i32.store (local.get $p1) (i32.shr_u (local.get $v) (i32.const 24)))))))
246
+ (local.set $p0 (i32.add (local.get $p0) (i32.const 4)))
247
+ (local.set $p1 (i32.add (local.get $p1) (i32.const 4)))
248
+ (local.set $p2 (i32.add (local.get $p2) (i32.const 4)))
249
+ (local.set $p3 (i32.add (local.get $p3) (i32.const 4)))
250
+ (local.set $src (i32.add (local.get $src) (i32.const 4)))
251
+ (local.set $i (i32.add (local.get $i) (i32.const 1)))
252
+ (br_if $next (i32.lt_u (local.get $i) (local.get $n)))))))
package/kernels.mjs ADDED
@@ -0,0 +1,141 @@
1
+ // Rapier's optional integer WebAssembly kernels. MIT (LICENSE).
2
+ // No fetch, runtime dependency, shared memory or floating-point byte decisions.
3
+ import {kernelHooks} from './kernel-hooks.mjs';
4
+ import {BitWriter, LIMITS} from './bits.mjs';
5
+ import {SCALAR, SIMD, SIMD_PROBE} from './kernels-bytes.mjs';
6
+
7
+ export const {configureKernels, kernelMode} = (() => {
8
+ const P = 65536, R = 327680, PROPERTY = 589824, CONTEXT = 851968;
9
+ const OUTPUT = 1114128, TABLE = 1642496, SOURCE = 1769472, STATE = 2097152;
10
+ const DIV = 16, WEIGHTS = 272, BUCKET = 16656, MAP = 20672, ALPHABET = 257;
11
+ const CUTS = [-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];
12
+ let engine, active = 'off';
13
+ const decode = text => Uint8Array.from(atob(text), c => c.charCodeAt(0));
14
+ const acceptablePlane = (p, w, h, offset) => p instanceof Int16Array && Number.isInteger(w) && Number.isInteger(h) && w > 0 && h > 0 && w <= 16384 && w * h <= 65536 && p.length >= w * h && Number.isInteger(offset) && Math.abs(offset) <= 32768;
15
+ const acceptableWriter = w => !w || (w instanceof BitWriter && w.write === BitWriter.prototype.write && w.grow === BitWriter.prototype.grow);
16
+ // A hybrid-integer configuration the kernels write: split up to 15, msb and lsb within it, and every token a residual below
17
+ // 2^19 can take inside the 224 symbols below the LZ77 lengths. A raw histogram (lossless-coding.mjs) is bins for the
18
+ // residual values and the 33 length tokens: at most the 4,096 of a palette's indices and 33 more, in the arena's table.
19
+ const PLAIN = {split: 0, msb: 0, lsb: 0};
20
+ const acceptableConfig = c => Number.isInteger(c.split) && Number.isInteger(c.msb) && Number.isInteger(c.lsb) && c.split >= 0 && c.split <= 15 && c.msb >= 0 && c.lsb >= 0 && c.msb + c.lsb <= c.split && (1 << c.split) + ((19 - c.split) << (c.msb + c.lsb)) <= 224;
21
+ const RAW_BINS = 4096 + 33;
22
+
23
+ function createEngine() {
24
+ const memory = new WebAssembly.Memory({initial: 48, maximum: 128});
25
+ const names = new Map(Object.entries(new WebAssembly.Instance(new WebAssembly.Module(decode(SCALAR)), {env: {memory}}).exports));
26
+ // Lookup strings are ABI names, never JavaScript property-mangler candidates.
27
+ const scalar = {residual: names.get('residual'), tokens: names.get('tokens'), fill: names.get('fill'), weighted: names.get('weighted')};
28
+ const u8 = new Uint8Array(memory.buffer), i32 = new Int32Array(memory.buffer);
29
+ for (let i = 0; i < 64; i++) i32[(DIV >> 2) + i] = Math.floor(16777216 / (i + 1));
30
+ for (let most = 12; most <= 13; most++) for (let sum = 0; sum < 2048; sum++) {
31
+ const shift = Math.max(0, 26 - Math.clz32(sum + 1));
32
+ i32[(WEIGHTS >> 2) + (most - 12) * 2048 + sum] = 4 + ((most * i32[(DIV >> 2) + (sum >> shift)]) >> shift);
33
+ }
34
+ for (let i = 0; i < 1003; i++) i32[(BUCKET >> 2) + i] = CUTS.filter(cut => cut < i - 501).length;
35
+ return {memory, scalar, simd: null, u8, i32};
36
+ }
37
+
38
+ // Both passes use one bulk crossing. In particular the writer never calls back into
39
+ // JS per token. Code tables use explicit little-endian i32 fields in private memory.
40
+ function tokens(w, targets, count, contexts, config = PLAIN, rawLength = 0) {
41
+ const {scalar, i32, u8} = engine, stride = rawLength || ALPHABET;
42
+ if (!acceptableWriter(w) || !targets.length || targets.length > 34) return false;
43
+ if (w) {
44
+ i32.fill(0, TABLE >> 2, (TABLE >> 2) + targets.length * ALPHABET * 2);
45
+ for (let c = 0; c < targets.length; c++) {
46
+ const code = targets[c];
47
+ if (!code?.lengths || !code?.codes || code.lengths.length > ALPHABET) return false;
48
+ for (let s = 0; s < code.lengths.length; s++) {
49
+ i32[(TABLE >> 2) + (c * ALPHABET + s) * 2] = code.lengths[s];
50
+ i32[(TABLE >> 2) + (c * ALPHABET + s) * 2 + 1] = code.codes[s];
51
+ }
52
+ }
53
+ } else {
54
+ for (let c = 0; c < targets.length; c++) {
55
+ if (!(targets[c] instanceof Uint32Array) || targets[c].length !== stride) return false;
56
+ i32.set(targets[c], (TABLE >> 2) + c * stride);
57
+ }
58
+ }
59
+ const written = scalar.tokens(R, contexts, count, TABLE, OUTPUT, w ? 1 : 0, w ? w.acc : 0, w ? w.pending : 0, config.split, config.msb, config.lsb, rawLength);
60
+ if (w) {
61
+ if (w.at + written + 8 >= w.bytes.length) w.grow(written + 8);
62
+ w.bytes.set(u8.subarray(OUTPUT, OUTPUT + written), w.at);
63
+ w.at += written; w.acc = i32[(OUTPUT - 8) >> 2]; w.pending = i32[(OUTPUT - 4) >> 2];
64
+ } else for (let c = 0; c < targets.length; c++) targets[c].set(i32.subarray((TABLE >> 2) + c * stride, (TABLE >> 2) + (c + 1) * stride));
65
+ return true;
66
+ }
67
+ function channel(w, target, plane, width, height, leaf, raw) {
68
+ const config = leaf.config || PLAIN;
69
+ if (!acceptablePlane(plane, width, height, leaf.offset) || leaf.multiplier !== 1 || !Number.isInteger(leaf.predictor) || leaf.predictor < 0 || leaf.predictor > 5 || !acceptableWriter(w) || !acceptableConfig(config)) return false;
70
+ // A raw histogram is counted, never written, into bins that fit the arena's table.
71
+ if (raw && (w || !(target instanceof Uint32Array) || target.length < 34 || target.length > RAW_BINS)) return false;
72
+ const n = width * height;
73
+ if (w && w.at + n * 8 + 32 > LIMITS.bytes) return false;
74
+ engine.i32.set(plane.subarray(0, n), P >> 2);
75
+ (active === 'simd' ? engine.simd : engine.scalar).residual(P, width, height, leaf.predictor, leaf.offset, R);
76
+ return tokens(w, [target], n, 0, config, raw ? target.length : 0);
77
+ }
78
+ function weighted(w, targets, plane, width, height, offset, contextOf, residuals, properties) {
79
+ if (!acceptablePlane(plane, width, height, offset) || !acceptableWriter(w)) return false;
80
+ const n = width * height, {i32, scalar} = engine;
81
+ if (w && w.at + n * 8 + 32 > LIMITS.bytes) return false;
82
+ if (residuals && (!(residuals instanceof Uint32Array) || residuals.length < n)) return false;
83
+ if (properties && (!(properties instanceof Int32Array) || properties.length < n)) return false;
84
+ if (contextOf.length !== 34 || contextOf.some(c => !Number.isInteger(c) || c < 0 || c >= 34)) return false;
85
+ if (!residuals && contextOf.some(c => c >= targets.length)) return false;
86
+ i32.set(plane.subarray(0, n), P >> 2); i32.set(contextOf, MAP >> 2);
87
+ i32.fill(0, STATE >> 2, (STATE >> 2) + (width + 2) * 10);
88
+ scalar.weighted(P, width, height, offset, R, PROPERTY, CONTEXT, MAP, BUCKET, DIV, WEIGHTS, STATE);
89
+ if (residuals) {
90
+ residuals.set(i32.subarray(R >> 2, (R >> 2) + n));
91
+ if (properties) properties.set(i32.subarray(PROPERTY >> 2, (PROPERTY >> 2) + n));
92
+ return true;
93
+ }
94
+ if (properties) properties.set(i32.subarray(PROPERTY >> 2, (PROPERTY >> 2) + n));
95
+ return tokens(w, targets, n, CONTEXT);
96
+ }
97
+ function fill(rgba, imageWidth, x0, y0, w, h, channels, planes) {
98
+ const n = w * h;
99
+ if (n < 1 || n > 65536 || channels < 1 || channels > 4) return false;
100
+ const {u8, i32} = engine;
101
+ for (let y = 0; y < h; y++) {
102
+ const at = ((y0 + y) * imageWidth + x0) * 4;
103
+ u8.set(rgba.subarray(at, at + w * 4), SOURCE + y * w * 4);
104
+ }
105
+ (active === 'simd' ? engine.simd : engine.scalar).fill(SOURCE, n, channels, P, R, PROPERTY, CONTEXT);
106
+ for (let c = 0; c < channels; c++) {
107
+ const at = [P, R, PROPERTY, CONTEXT][c] >> 2;
108
+ planes[c].set(i32.subarray(at, at + n));
109
+ }
110
+ return true;
111
+ }
112
+
113
+ /** Select this optional door explicitly. Missing/blocked WASM or SIMD returns to JS.
114
+ * `parts` is a deterministic per-realm ablation switch, not a timing-based decision.
115
+ * Set it before an encode, never inside a progress callback.
116
+ */
117
+ function configureKernels(mode = 'auto', parts) {
118
+ if (!['off', 'auto', 'scalar', 'simd'].includes(mode)) throw new RangeError('Unknown JXL kernel mode: ' + mode);
119
+ kernelHooks.channel = kernelHooks.weighted = kernelHooks.fill = null; active = 'off';
120
+ if (mode === 'off') return active;
121
+ try {
122
+ if (typeof WebAssembly !== 'object' || !WebAssembly.validate || new Uint8Array(new Uint32Array([1]).buffer)[0] !== 1) return active;
123
+ // A SIMD request is not silently tested as scalar: unsupported SIMD is JS.
124
+ const simd = mode !== 'scalar' && SIMD && WebAssembly.validate(decode(SIMD_PROBE));
125
+ if (mode !== 'scalar' && !simd) return active;
126
+ engine ||= createEngine();
127
+ if (simd && !engine.simd) {
128
+ const names = new Map(Object.entries(new WebAssembly.Instance(new WebAssembly.Module(decode(SIMD)), {env: {memory: engine.memory}}).exports));
129
+ engine.simd = {residual: names.get('residual'), fill: names.get('fill')};
130
+ }
131
+ active = simd ? 'simd' : 'scalar';
132
+ const enabled = parts ? new Map(Object.entries(parts)) : null;
133
+ if (!enabled || enabled.get('channel')) kernelHooks.channel = channel;
134
+ if (!enabled || enabled.get('weighted')) kernelHooks.weighted = weighted;
135
+ if (!enabled || enabled.get('fill')) kernelHooks.fill = fill;
136
+ } catch { active = 'off'; kernelHooks.channel = kernelHooks.weighted = kernelHooks.fill = null; }
137
+ return active;
138
+ }
139
+ function kernelMode() { return active; }
140
+ return {configureKernels, kernelMode};
141
+ })();
package/local.mjs CHANGED
@@ -2,11 +2,12 @@
2
2
  // Effort-only Modular modelling. Every group owns its tree and histograms; a bounded group of samples is the
3
3
  // working set, and the complete candidate competes against every lower rung at the door.
4
4
  import {BitWriter, packSigned} from './bits.mjs';
5
- import {writeImageHeader, writeModularFrameHeader, groupLayout, assembleCodestream, GROUP_DIM} from './frame.mjs';
5
+ import {writeImageHeader, writeModularFrameHeader, groupLayout, groupRect, groupPass, assembleCodestream, GROUP_DIM} from './frame.mjs';
6
6
  import {buildCode, writePrefixCode, countToken, writeHybrid} from './prefix.mjs';
7
7
  import {ALPHABET, LZ77, RESIDUAL_CONFIG, leaf, split, channelTree, writeTree, writeModularHeader, writeChannelHistograms} from './modular.mjs';
8
8
  import {codeWeighted} from './weighted.mjs';
9
9
  import {admitOutputSize} from './admit.mjs';
10
+ import {planeFill} from './lossless.mjs';
10
11
 
11
12
  const localRaw = s => Math.max(0, s < 224 ? s - 1 : s - 236);
12
13
  const price = freqs => {
@@ -137,11 +138,23 @@ function writeModel(writer, plans) {
137
138
  };
138
139
  }
139
140
 
140
- export function* localSteps(rgba, width, height, shape, colorSpace, rung = 4, usePalette = false) {
141
+ // A group's own trees and histograms, here or in a pool's worker: the group's section, its bytes returned.
142
+ export function localGroup(setup) {
143
+ const fill = planeFill(setup), planes = Array.from({length: setup.palette ? 1 : setup.channels}, () => new Int16Array(GROUP_DIM * GROUP_DIM));
144
+ return (rgba, stride, x0, y0, w, h) => {
145
+ fill(planes, rgba, stride, x0, y0, w, h);
146
+ const section = new BitWriter(w * h * planes.length + 256);
147
+ writeModularHeader(section, {useGlobalTree: false});
148
+ writeModel(section, plansOf(planes, w, h, setup.rung))();
149
+ return section.finish();
150
+ };
151
+ }
152
+
153
+ export function* localSteps(rgba, width, height, shape, colorSpace, rung = 4, usePalette = false, pooled) {
141
154
  const {channels} = shape, layout = groupLayout(width, height), groups = layout.groupsX * layout.groupsY;
142
155
  const palette = usePalette ? shape.palette : null;
143
156
  if (usePalette && !palette) return null;
144
- const planes = Array.from({length: palette ? 1 : channels}, () => new Int16Array(GROUP_DIM * GROUP_DIM));
157
+ const setup = {channels, alpha: shape.alpha, palette: palette && palette.colours, rung};
145
158
  const header = new BitWriter(256);
146
159
  writeImageHeader(header, width, height, shape.colour, shape.alpha, {colorSpace});
147
160
  writeModularFrameHeader(header, {alpha: shape.alpha});
@@ -155,43 +168,34 @@ export function* localSteps(rgba, width, height, shape, colorSpace, rung = 4, us
155
168
  }
156
169
  const global = new BitWriter(4096), sections = [];
157
170
  let sectionBytes = 0;
158
- const appendSection = writer => {
159
- // Refuse before copying a completed section or retaining later groups of an oversized optional candidate.
160
- sectionBytes += Math.ceil(writer.bitLength / 8); admitOutputSize(sectionBytes);
161
- sections.push(writer.finish());
171
+ const appendSection = bytes => {
172
+ // Refuse before retaining later groups of an oversized optional candidate.
173
+ sectionBytes += bytes.length; admitOutputSize(sectionBytes);
174
+ sections.push(bytes);
162
175
  };
163
176
  global.write(1, 1); global.write(1, 1);
164
- if (!layout.single) {
165
- let write;
166
- if (palettePlans) write = writeModel(global, palettePlans);
167
- else {
168
- const leaves = planes.map(() => leaf(5));
169
- writeChannelHistograms(global, writeTree(global, channelTree(leaves)), planes.map(() => new Uint32Array(ALPHABET)), l => leaves.indexOf(l));
170
- }
177
+ if (layout.single) {
178
+ // One group: everything in the global section, the palette's plans before the picture's.
179
+ const planes = Array.from({length: palette ? 1 : channels}, () => new Int16Array(GROUP_DIM * GROUP_DIM));
180
+ planeFill(setup)(planes, rgba, width, 0, 0, width, height);
181
+ const write = writeModel(global, [...palettePlans || [], ...plansOf(planes, width, height, rung)]);
171
182
  writeModularHeader(global, {transforms});
172
- if (write) write();
173
- appendSection(global);
174
- for (let i = 0; i < layout.dcGroupsX * layout.dcGroupsY + 1; i++) sections.push(new Uint8Array(0));
183
+ write(); appendSection(global.finish());
184
+ if (yield 1) return null;
185
+ return assembleCodestream(header, sections);
175
186
  }
176
- for (let g = 0; g < groups; g++) {
177
- const x0 = g % layout.groupsX * GROUP_DIM, y0 = (g / layout.groupsX | 0) * GROUP_DIM;
178
- const w = Math.min(GROUP_DIM, width - x0), h = Math.min(GROUP_DIM, height - y0);
179
- for (let y = 0; y < h; y++) for (let x = 0, at = y * w, i = ((y0 + y) * width + x0) * 4; x < w; x++, at++, i += 4) {
180
- if (palette) planes[0][at] = palette.index.get(palette.key(i));
181
- else if (channels >= 3) {
182
- const co = rgba[i] - rgba[i + 2], tmp = rgba[i + 2] + (co >> 1), cg = rgba[i + 1] - tmp;
183
- planes[0][at] = tmp + (cg >> 1); planes[1][at] = co; planes[2][at] = cg;
184
- if (channels === 4) planes[3][at] = rgba[i + 3];
185
- } else { planes[0][at] = rgba[i]; if (channels === 2) planes[1][at] = rgba[i + 3]; }
186
- }
187
- const picture = plansOf(planes, w, h, rung);
188
- const plans = palette && layout.single ? [...palettePlans, ...picture] : picture;
189
- const section = layout.single ? global : new BitWriter(w * h * planes.length + 256);
190
- if (!layout.single) writeModularHeader(section, {useGlobalTree: false});
191
- const write = writeModel(section, plans);
192
- if (layout.single) writeModularHeader(section, {transforms});
193
- write(); appendSection(section);
194
- if (yield (g + 1) / groups) return null;
187
+ let write;
188
+ if (palettePlans) write = writeModel(global, palettePlans);
189
+ else {
190
+ const leaves = Array.from({length: channels}, () => leaf(5));
191
+ writeChannelHistograms(global, writeTree(global, channelTree(leaves)), leaves.map(() => new Uint32Array(ALPHABET)), l => leaves.indexOf(l));
195
192
  }
193
+ writeModularHeader(global, {transforms});
194
+ if (write) write();
195
+ appendSection(global.finish());
196
+ for (let i = 0; i < layout.dcGroupsX * layout.dcGroupsY + 1; i++) sections.push(new Uint8Array(0));
197
+ const group = localGroup(setup);
198
+ if ((yield* groupPass({pooled, kind: 'local', setup, at: g => (g + 1) / groups, stop: () => true},
199
+ groups, g => appendSection(group(rgba, width, ...groupRect(layout, width, height, g))), (g, bytes) => appendSection(bytes))) === null) return null;
196
200
  return assembleCodestream(header, sections);
197
201
  }
@@ -0,0 +1,30 @@
1
+ // Rapier's JPEG XL encoder: exact hybrid-integer prices for lossless residuals. MIT (LICENSE).
2
+ // Raw literal counts retain the low-bit distribution that an exponent-only histogram throws away. The
3
+ // run-length alphabet is separate while counting; projection restores it at the standard LZ77 boundary.
4
+ import {BitWriter, floorLog2} from './bits.mjs';
5
+ import {uintConfig, hybridToken, buildCode, writePrefixCode, writeUintConfig} from './prefix.mjs';
6
+ import {ALPHABET} from './modular.mjs';
7
+
8
+ const LOSSLESS_INTEGER_CONFIGS = [uintConfig(0), uintConfig(4), uintConfig(4, 1, 1), uintConfig(3, 0, 1)];
9
+
10
+ export function losslessCoding(raw) {
11
+ const end = raw.length - 33, token = [0, 0, 0];
12
+ let original, best;
13
+ for (const config of LOSSLESS_INTEGER_CONFIGS) {
14
+ const freqs = new Uint32Array(ALPHABET);
15
+ let extra = 0;
16
+ for (let value = 0; value < end; value++) if (raw[value]) {
17
+ hybridToken(config, value, token);
18
+ freqs[token[0]] += raw[value]; extra += raw[value] * token[1];
19
+ }
20
+ for (let s = 0; s < 33; s++) { freqs[224 + s] = raw[end + s]; extra += raw[end + s] * Math.max(0, s - 12); }
21
+ const code = buildCode(freqs), w = new BitWriter(128);
22
+ writeUintConfig(w, config); writePrefixCode(w, code);
23
+ let bits = w.bitLength + extra + (code.alphabetSize === 1 ? 1 : 5 + floorLog2(code.alphabetSize - 1));
24
+ for (let s = 0; s < freqs.length; s++) if (freqs[s]) bits += freqs[s] * code.lengths[s];
25
+ const model = {config, freqs, bits, extra};
26
+ if (!original) original = model;
27
+ if (!best || bits < best.bits) best = model;
28
+ }
29
+ return [original, best];
30
+ }