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.
- package/AGENTS.md +5 -0
- package/ENCODER-COMPARISON.md +6 -6
- package/KERNELS.md +58 -0
- package/README.md +42 -16
- package/bits.mjs +5 -2
- package/build-jxl-kernels.mjs +29 -0
- package/effort-job.mjs +229 -0
- package/effort.d.mts +2 -1
- package/effort.min.mjs +2 -2
- package/effort.mjs +5 -220
- package/frame.mjs +25 -0
- package/jpeg-ans.min.mjs +2 -2
- package/jpeg.min.mjs +2 -2
- package/kernel-control.d.mts +3 -0
- package/kernel-hooks.mjs +3 -0
- package/kernels-bytes.mjs +4 -0
- package/kernels-probe.wat +1 -0
- package/kernels-scalar.wat +831 -0
- package/kernels-simd.wat +252 -0
- package/kernels.mjs +141 -0
- package/local.mjs +40 -36
- package/lossless-coding.mjs +30 -0
- package/lossless.mjs +88 -67
- package/modular.mjs +11 -7
- package/package.json +32 -6
- package/photo-ans.min.mjs +2 -2
- package/photo.min.mjs +2 -2
- package/pool.mjs +136 -0
- package/rapier-jxl.min.mjs +2 -2
- package/rct-search.mjs +61 -0
- package/sizes.json +127 -55
- package/wasm.d.mts +4 -0
- package/wasm.min.mjs +2 -0
- package/wasm.mjs +6 -0
- package/weighted.mjs +2 -0
package/kernels-simd.wat
ADDED
|
@@ -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
|
-
|
|
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
|
|
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 =
|
|
159
|
-
// Refuse before
|
|
160
|
-
sectionBytes +=
|
|
161
|
-
sections.push(
|
|
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 (
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
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
|
-
|
|
173
|
-
|
|
174
|
-
|
|
183
|
+
write(); appendSection(global.finish());
|
|
184
|
+
if (yield 1) return null;
|
|
185
|
+
return assembleCodestream(header, sections);
|
|
175
186
|
}
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
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
|
+
}
|