rapier-jxl 2.0.0 → 2.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/AGENTS.md +10 -1
- package/ENCODER-COMPARISON.md +6 -6
- package/KERNELS.md +58 -0
- package/README.md +75 -18
- package/admit.mjs +5 -1
- package/ans.mjs +157 -0
- package/bits.mjs +5 -2
- package/build-jxl-kernels.mjs +29 -0
- package/coefficient-ans.mjs +26 -0
- package/coefficient-effort.mjs +62 -0
- package/effort-job.mjs +229 -0
- package/effort-level.mjs +10 -0
- package/effort.d.mts +4 -2
- package/effort.min.mjs +2 -2
- package/effort.mjs +7 -199
- package/frame.mjs +29 -1
- package/index.mjs +2 -2
- package/jfif.mjs +11 -0
- package/jpeg-ans.d.mts +3 -0
- package/jpeg-ans.min.mjs +2 -0
- package/jpeg-ans.mjs +9 -0
- package/jpeg-job.mjs +29 -0
- package/jpeg.d.mts +6 -2
- package/jpeg.min.mjs +2 -2
- package/jpeg.mjs +12 -36
- 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 +201 -0
- package/lossless-coding.mjs +30 -0
- package/lossless.mjs +88 -67
- package/modular.mjs +11 -7
- package/package.json +40 -6
- package/photo-ans.d.mts +3 -0
- package/photo-ans.min.mjs +2 -0
- package/photo-ans.mjs +9 -0
- package/photo-dct.mjs +83 -0
- package/photo-job.mjs +27 -0
- package/photo-quant.mjs +150 -0
- package/photo.d.mts +5 -1
- package/photo.min.mjs +2 -2
- package/photo.mjs +7 -101
- package/pool.mjs +136 -0
- package/rapier-jxl.min.mjs +2 -2
- package/rct-search.mjs +61 -0
- package/sizes.json +237 -56
- package/vardct.mjs +45 -8
- package/wasm.d.mts +4 -0
- package/wasm.min.mjs +2 -0
- package/wasm.mjs +6 -0
- package/weighted.mjs +22 -10
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
ADDED
|
@@ -0,0 +1,201 @@
|
|
|
1
|
+
// SPDX-License-Identifier: MIT
|
|
2
|
+
// Effort-only Modular modelling. Every group owns its tree and histograms; a bounded group of samples is the
|
|
3
|
+
// working set, and the complete candidate competes against every lower rung at the door.
|
|
4
|
+
import {BitWriter, packSigned} from './bits.mjs';
|
|
5
|
+
import {writeImageHeader, writeModularFrameHeader, groupLayout, groupRect, groupPass, assembleCodestream, GROUP_DIM} from './frame.mjs';
|
|
6
|
+
import {buildCode, writePrefixCode, countToken, writeHybrid} from './prefix.mjs';
|
|
7
|
+
import {ALPHABET, LZ77, RESIDUAL_CONFIG, leaf, split, channelTree, writeTree, writeModularHeader, writeChannelHistograms} from './modular.mjs';
|
|
8
|
+
import {codeWeighted} from './weighted.mjs';
|
|
9
|
+
import {admitOutputSize} from './admit.mjs';
|
|
10
|
+
import {planeFill} from './lossless.mjs';
|
|
11
|
+
|
|
12
|
+
const localRaw = s => Math.max(0, s < 224 ? s - 1 : s - 236);
|
|
13
|
+
const price = freqs => {
|
|
14
|
+
const code = buildCode(freqs), writer = new BitWriter(128);
|
|
15
|
+
writePrefixCode(writer, code);
|
|
16
|
+
let bits = writer.bitLength;
|
|
17
|
+
for (let s = 0; s < freqs.length; s++) if (freqs[s]) bits += freqs[s] * (code.lengths[s] + localRaw(s));
|
|
18
|
+
return bits;
|
|
19
|
+
};
|
|
20
|
+
|
|
21
|
+
// Residuals are exactly the decoder's unsigned values before entropy coding, including RGB under zero alpha.
|
|
22
|
+
function residuals(plane, width, height, predictor, properties) {
|
|
23
|
+
const out = new Uint32Array(width * height);
|
|
24
|
+
if (predictor === 6) { codeWeighted(null, null, plane, width, height, 0, undefined, out, properties); return out; }
|
|
25
|
+
for (let y = 0, i = 0; y < height; y++) for (let x = 0; x < width; x++, i++) {
|
|
26
|
+
const left = x ? plane[i - 1] : y ? plane[i - width] : 0, top = y ? plane[i - width] : left;
|
|
27
|
+
const nw = x && y ? plane[i - width - 1] : left;
|
|
28
|
+
const prediction = predictor === 3 ? ((left + top) / 2) | 0 : Math.max(Math.min(left, top), Math.min(Math.max(left, top), left + top - nw));
|
|
29
|
+
out[i] = packSigned(plane[i] - prediction);
|
|
30
|
+
}
|
|
31
|
+
return out;
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
function tokens(values, contexts, count = 1) {
|
|
35
|
+
const freqs = Array.from({length: count}, () => new Uint32Array(ALPHABET)), pieces = [];
|
|
36
|
+
const emit = (i, value, base = 0) => {
|
|
37
|
+
const context = contexts ? contexts[i] : 0;
|
|
38
|
+
pieces.push(value, base, context); countToken(base ? LZ77.lengthConfig : RESIDUAL_CONFIG, value, freqs[context], base);
|
|
39
|
+
};
|
|
40
|
+
// A zero run can cross a tree leaf or row. Its first literal and copy use their own samples' contexts.
|
|
41
|
+
for (let i = 0; i < values.length;) {
|
|
42
|
+
let run = 1;
|
|
43
|
+
if (!values[i]) while (i + run < values.length && !values[i + run]) run++;
|
|
44
|
+
emit(i, values[i]);
|
|
45
|
+
if (run > LZ77.minLength) {
|
|
46
|
+
emit(i + 1, run - 1 - LZ77.minLength, LZ77.minSymbol);
|
|
47
|
+
} else for (let n = 1; n < run; n++) emit(i + n, 0);
|
|
48
|
+
i += run;
|
|
49
|
+
}
|
|
50
|
+
return {freqs, pieces};
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
function intervalPlan(values, properties, predictor, property, cuts) {
|
|
54
|
+
const contexts = Uint8Array.from(properties, value => {
|
|
55
|
+
let lo = 0, hi = cuts.length;
|
|
56
|
+
while (lo < hi) { const mid = (lo + hi) >> 1; if (value > cuts[mid]) lo = mid + 1; else hi = mid; }
|
|
57
|
+
return lo;
|
|
58
|
+
});
|
|
59
|
+
const coded = tokens(values, contexts, cuts.length + 1), count = coded.freqs.length, filled = coded.freqs.map(freq => freq.some(n => n));
|
|
60
|
+
const costs = new Float64Array(count + 1).fill(Infinity), from = new Uint8Array(count + 1);
|
|
61
|
+
costs[0] = 0;
|
|
62
|
+
for (let end = 1; end <= count; end++) {
|
|
63
|
+
const freq = new Uint32Array(ALPHABET);
|
|
64
|
+
let own = 0;
|
|
65
|
+
for (let start = end - 1; start >= 0; start--) {
|
|
66
|
+
if (filled[start]) { for (let s = 0; s < ALPHABET; s++) freq[s] += coded.freqs[start][s]; own = price(freq); }
|
|
67
|
+
const bits = costs[start] + own + 24;
|
|
68
|
+
if (bits < costs[end]) { costs[end] = bits; from[end] = start; }
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
const spans = [];
|
|
72
|
+
for (let end = count; end; end = from[end]) spans.unshift([from[end], end]);
|
|
73
|
+
const leaves = spans.map(() => leaf(predictor)), mapping = new Uint8Array(count);
|
|
74
|
+
const freqs = spans.map(([start, end], index) => {
|
|
75
|
+
const freq = new Uint32Array(ALPHABET);
|
|
76
|
+
for (let k = start; k < end; k++) { mapping[k] = index; for (let s = 0; s < ALPHABET; s++) freq[s] += coded.freqs[k][s]; }
|
|
77
|
+
return freq;
|
|
78
|
+
});
|
|
79
|
+
for (let i = 2; i < coded.pieces.length; i += 3) coded.pieces[i] = mapping[coded.pieces[i]];
|
|
80
|
+
const tree = (lo, hi) => {
|
|
81
|
+
if (lo === hi) return leaves[lo];
|
|
82
|
+
const mid = (lo + hi) >> 1;
|
|
83
|
+
return split(property, cuts[spans[mid][1] - 1], tree(mid + 1, hi), tree(lo, mid));
|
|
84
|
+
};
|
|
85
|
+
return {leaves, tree: tree(0, spans.length - 1), freqs, pieces: coded.pieces};
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
const WEIGHTED_LOCAL_CUTS = [-127, -31, -7, -1, 0, 1, 7, 31, 127];
|
|
89
|
+
const GRADIENT_CUTS = [-191, -127, -63, -31, -7, -1, 0, 7, 31, 63, 127, 191, 255];
|
|
90
|
+
function localProperties(plane, width, height, property) {
|
|
91
|
+
const out = new Int32Array(width * height);
|
|
92
|
+
for (let y = 0, i = 0; y < height; y++) for (let x = 0; x < width; x++, i++) {
|
|
93
|
+
const left = x ? plane[i - 1] : y ? plane[i - width] : 0, top = y ? plane[i - width] : left;
|
|
94
|
+
const nw = x && y ? plane[i - width - 1] : left;
|
|
95
|
+
out[i] = property === 9 ? left + top - nw : left - nw;
|
|
96
|
+
}
|
|
97
|
+
return out;
|
|
98
|
+
}
|
|
99
|
+
function planPrice(plan) {
|
|
100
|
+
const writer = new BitWriter(512);
|
|
101
|
+
const histograms = writeChannelHistograms(writer, writeTree(writer, plan.tree), plan.freqs, l => plan.leaves.indexOf(l));
|
|
102
|
+
let bits = writer.bitLength;
|
|
103
|
+
plan.freqs.forEach((freq, i) => { const {lengths} = histograms[i + 1].code; for (let s = 0; s < freq.length; s++) bits += freq[s] * ((lengths[s] || 0) + localRaw(s)); });
|
|
104
|
+
return bits;
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
function plansOf(planes, width, height, rung) {
|
|
108
|
+
return planes.map(plane => {
|
|
109
|
+
let best;
|
|
110
|
+
for (const predictor of [5, 3, 6]) {
|
|
111
|
+
const properties = predictor === 6 ? new Int32Array(width * height) : null;
|
|
112
|
+
const values = residuals(plane, width, height, predictor, properties), one = leaf(predictor);
|
|
113
|
+
const candidates = [{...tokens(values), leaves: [one], tree: one}];
|
|
114
|
+
if (properties) candidates.push(intervalPlan(values, properties, predictor, 15, WEIGHTED_LOCAL_CUTS));
|
|
115
|
+
// Property 9 is the unclamped gradient; property 10 is W - NW. Signed cuts let the latter separate both
|
|
116
|
+
// tails around a flat neighbourhood without treating a high-magnitude negative edge as a small one.
|
|
117
|
+
if (rung >= 6 && predictor === 5) for (const property of [9, 10]) {
|
|
118
|
+
const local = localProperties(plane, width, height, property), cuts = property === 9 ? GRADIENT_CUTS : WEIGHTED_LOCAL_CUTS;
|
|
119
|
+
candidates.push(intervalPlan(values, local, predictor, property, cuts));
|
|
120
|
+
}
|
|
121
|
+
for (const plan of candidates) { const bits = planPrice(plan); if (!best || bits < best.bits) best = {...plan, bits}; }
|
|
122
|
+
}
|
|
123
|
+
return best;
|
|
124
|
+
});
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
function writeModel(writer, plans) {
|
|
128
|
+
const leaves = plans.flatMap(plan => plan.leaves);
|
|
129
|
+
const histograms = writeChannelHistograms(writer, writeTree(writer, channelTree(plans.map(plan => plan.tree))), plans.flatMap(plan => plan.freqs), l => leaves.indexOf(l));
|
|
130
|
+
return () => {
|
|
131
|
+
for (let c = 0; c < plans.length; c++) {
|
|
132
|
+
const {pieces} = plans[c], codes = plans[c].leaves.map(l => histograms[leaves.indexOf(l) + 1].code);
|
|
133
|
+
for (let i = 0; i < pieces.length; i += 3) {
|
|
134
|
+
const base = pieces[i + 1];
|
|
135
|
+
writeHybrid(writer, codes[pieces[i + 2]], base ? LZ77.lengthConfig : RESIDUAL_CONFIG, pieces[i], base);
|
|
136
|
+
}
|
|
137
|
+
}
|
|
138
|
+
};
|
|
139
|
+
}
|
|
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) {
|
|
154
|
+
const {channels} = shape, layout = groupLayout(width, height), groups = layout.groupsX * layout.groupsY;
|
|
155
|
+
const palette = usePalette ? shape.palette : null;
|
|
156
|
+
if (usePalette && !palette) return null;
|
|
157
|
+
const setup = {channels, alpha: shape.alpha, palette: palette && palette.colours, rung};
|
|
158
|
+
const header = new BitWriter(256);
|
|
159
|
+
writeImageHeader(header, width, height, shape.colour, shape.alpha, {colorSpace});
|
|
160
|
+
writeModularFrameHeader(header, {alpha: shape.alpha});
|
|
161
|
+
const transforms = palette ? [{type: 'palette', beginC: 0, numC: channels, nbColors: palette.colours.length}] : channels >= 3 ? [{type: 'rct', beginC: 0, rctType: 6}] : [];
|
|
162
|
+
// The palette's meta channel precedes the picture in the global section; local AC groups contain only indices.
|
|
163
|
+
let palettePlans;
|
|
164
|
+
if (palette) {
|
|
165
|
+
const count = palette.colours.length, values = new Int16Array(count * channels);
|
|
166
|
+
palette.colours.forEach((colour, i) => { for (let c = 0; c < channels; c++) values[c * count + i] = palette.byte(colour, c); });
|
|
167
|
+
palettePlans = plansOf([values], count, channels, rung);
|
|
168
|
+
}
|
|
169
|
+
const global = new BitWriter(4096), sections = [];
|
|
170
|
+
let sectionBytes = 0;
|
|
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);
|
|
175
|
+
};
|
|
176
|
+
global.write(1, 1); global.write(1, 1);
|
|
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)]);
|
|
182
|
+
writeModularHeader(global, {transforms});
|
|
183
|
+
write(); appendSection(global.finish());
|
|
184
|
+
if (yield 1) return null;
|
|
185
|
+
return assembleCodestream(header, sections);
|
|
186
|
+
}
|
|
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));
|
|
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;
|
|
200
|
+
return assembleCodestream(header, sections);
|
|
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
|
+
}
|