imagescript 3.3.0 → 3.4.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/README.md +28 -8
- package/THIRD_PARTY_NOTICES.md +167 -2
- package/dist/animation.d.ts +18 -8
- package/dist/animation.js +23 -2
- package/dist/codecs/av1/cdef.js +197 -0
- package/dist/codecs/av1/data.js +141 -0
- package/dist/codecs/av1/decode.js +1769 -0
- package/dist/codecs/av1/filmgrain.js +219 -0
- package/dist/codecs/av1/frame.js +114 -0
- package/dist/codecs/av1/ipred.js +491 -0
- package/dist/codecs/av1/itx.js +950 -0
- package/dist/codecs/av1/loopfilter.js +149 -0
- package/dist/codecs/av1/msac.js +161 -0
- package/dist/codecs/av1/obu.js +516 -0
- package/dist/codecs/av1/postfilter.js +28 -0
- package/dist/codecs/av1/restoration.js +193 -0
- package/dist/codecs/av1/tables.js +160 -0
- package/dist/codecs/avif.js +581 -0
- package/dist/codecs/bmp.js +317 -0
- package/dist/codecs/hdr.d.ts +25 -0
- package/dist/codecs/hdr.js +201 -0
- package/dist/codecs/ico.js +48 -0
- package/dist/codecs/jpeg.js +2 -1
- package/dist/codecs/jxl/bits.js +126 -0
- package/dist/codecs/jxl/blend.js +117 -0
- package/dist/codecs/jxl/color.js +116 -0
- package/dist/codecs/jxl/dct.js +404 -0
- package/dist/codecs/jxl/entropy.js +556 -0
- package/dist/codecs/jxl/features.js +325 -0
- package/dist/codecs/jxl/filters.js +119 -0
- package/dist/codecs/jxl/frame-header.js +195 -0
- package/dist/codecs/jxl/frame.js +319 -0
- package/dist/codecs/jxl/headers.js +249 -0
- package/dist/codecs/jxl/idct.js +484 -0
- package/dist/codecs/jxl/modular.js +688 -0
- package/dist/codecs/jxl/quant.js +219 -0
- package/dist/codecs/jxl/upsample.js +52 -0
- package/dist/codecs/jxl/vardct.js +472 -0
- package/dist/codecs/jxl.js +89 -0
- package/dist/codecs/netpbm.js +239 -0
- package/dist/codecs/qoi.js +150 -0
- package/dist/codecs/vp8-enc-dsp.js +932 -0
- package/dist/codecs/vp8-enc-tables.js +78 -0
- package/dist/codecs/vp8-enc.js +1930 -0
- package/dist/codecs/webp.js +36 -2
- package/dist/image-type.d.ts +15 -1
- package/dist/image-type.js +45 -2
- package/dist/image.d.ts +47 -6
- package/dist/image.js +49 -3
- package/dist/index.d.ts +3 -2
- package/dist/index.js +2 -2
- package/dist/render/hinting.js +26 -18
- package/dist/render/layout.js +1 -1
- package/dist/render/svg.js +1 -1
- package/dist/render/unicode.js +1 -9
- package/dist/util/bytes.js +9 -0
- package/dist/util/resample.js +4 -2
- package/dist/version.js +1 -1
- package/package.json +25 -2
|
@@ -0,0 +1,472 @@
|
|
|
1
|
+
import { unpackSigned } from "./bits.js";
|
|
2
|
+
import { EntropyDecoder, readClusters, readPermutation } from "./entropy.js";
|
|
3
|
+
import { ModularImage, channel, decodeModular } from "./modular.js";
|
|
4
|
+
import { FLAG_SKIP_ADAPTIVE_LF_SMOOTHING } from "./frame-header.js";
|
|
5
|
+
import { readDequantMatrices, MATRIX_INDEX } from "./quant.js";
|
|
6
|
+
import { dct2d, inverseTransform, scaleF } from "./dct.js";
|
|
7
|
+
const DEFAULT_CTX_MAP = Uint8Array.of(0, 1, 2, 2, 3, 3, 4, 5, 6, 6, 6, 6, 6, 7, 8, 9, 9, 10, 11, 12, 13, 14, 14, 14, 14, 14, 7, 8, 9, 9, 10, 11, 12, 13, 14, 14, 14, 14, 14);
|
|
8
|
+
export function readVarDCTGlobal(r) {
|
|
9
|
+
const globalScale = r.u32([1, 11], [2049, 11], [4097, 12], [8193, 16]), quantLf = r.u32(16, [1, 5], [1, 8], [1, 16]);
|
|
10
|
+
let ctx;
|
|
11
|
+
if (r.bool())
|
|
12
|
+
ctx = { qfThresholds: [], lfThresholds: [[], [], []], ctxMap: DEFAULT_CTX_MAP, numClusters: 15 };
|
|
13
|
+
else {
|
|
14
|
+
let size = 1;
|
|
15
|
+
const lfThresholds = [];
|
|
16
|
+
for (let c = 0; c < 3; c++) {
|
|
17
|
+
const n = r.bits(4), t = [];
|
|
18
|
+
size *= n + 1;
|
|
19
|
+
for (let i = 0; i < n; i++)
|
|
20
|
+
t.push(unpackSigned(r.u32([0, 4], [16, 8], [272, 16], [65808, 32])));
|
|
21
|
+
lfThresholds.push(t);
|
|
22
|
+
}
|
|
23
|
+
const nq = r.bits(4), qfThresholds = [];
|
|
24
|
+
size *= nq + 1;
|
|
25
|
+
for (let i = 0; i < nq; i++)
|
|
26
|
+
qfThresholds.push(1 + r.u32([0, 2], [4, 3], [12, 5], [44, 8]));
|
|
27
|
+
if (size > 64)
|
|
28
|
+
throw new Error('JPEG XL: too many block contexts');
|
|
29
|
+
const [numClusters, ctxMap] = readClusters(r, size * 39);
|
|
30
|
+
if (numClusters > 16)
|
|
31
|
+
throw new Error('JPEG XL: too many block context clusters');
|
|
32
|
+
ctx = { qfThresholds, lfThresholds, ctxMap, numClusters };
|
|
33
|
+
}
|
|
34
|
+
let colourFactor = 84, baseX = 0, baseB = 1, xFactorLf = 128, bFactorLf = 128;
|
|
35
|
+
if (!r.bool()) {
|
|
36
|
+
colourFactor = r.u32(84, 256, [2, 8], [258, 16]);
|
|
37
|
+
baseX = r.f16();
|
|
38
|
+
baseB = r.f16();
|
|
39
|
+
xFactorLf = r.bits(8);
|
|
40
|
+
bFactorLf = r.bits(8);
|
|
41
|
+
}
|
|
42
|
+
return { globalScale, quantLf, ctx, colourFactor, baseX, baseB, xFactorLf, bFactorLf };
|
|
43
|
+
}
|
|
44
|
+
const SIZES = [
|
|
45
|
+
1, 1, 1, 1, 1, 1, 1, 1, 2, 2, 4, 4, 1, 2, 2, 1, 1, 4, 4, 1, 2, 4, 4, 2, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 8, 8, 4, 8, 8, 4,
|
|
46
|
+
16, 16, 8, 16, 16, 8, 32, 32, 16, 32, 32, 16
|
|
47
|
+
];
|
|
48
|
+
const ORDER_ID = [0, 1, 1, 1, 2, 3, 4, 4, 5, 5, 6, 6, 1, 1, 1, 1, 1, 1, 7, 8, 8, 9, 10, 10, 11, 12, 12];
|
|
49
|
+
const ORDER_SIZES = [[8, 8], [8, 8], [16, 16], [32, 32], [16, 8], [32, 8], [32, 16], [64, 64], [64, 32], [128, 128], [128, 64], [256, 256], [256, 128]];
|
|
50
|
+
const special = (t) => (t >= 1 && t <= 3) || (t >= 12 && t <= 17);
|
|
51
|
+
const transposed = (t) => !special(t) && SIZES[2 * t + 1] >= SIZES[2 * t];
|
|
52
|
+
const single = (t) => t === 0 || special(t);
|
|
53
|
+
const naturalOrders = new Map();
|
|
54
|
+
function naturalOrder(id) {
|
|
55
|
+
let o = naturalOrders.get(id);
|
|
56
|
+
if (o)
|
|
57
|
+
return o;
|
|
58
|
+
const [bw, bh] = ORDER_SIZES[id], yScale = bw / bh, lbw = bw / 8, lbh = bh / 8;
|
|
59
|
+
o = new Uint16Array(2 * bw * bh);
|
|
60
|
+
let i = 0;
|
|
61
|
+
for (; i < lbw * lbh; i++) {
|
|
62
|
+
o[2 * i] = i % lbw;
|
|
63
|
+
o[2 * i + 1] = Math.floor(i / lbw);
|
|
64
|
+
}
|
|
65
|
+
for (let dist = 1; dist < 2 * bw; dist++) {
|
|
66
|
+
const margin = Math.max(dist - bw, 0);
|
|
67
|
+
for (let order = margin; order < dist - margin; order++) {
|
|
68
|
+
const x = dist % 2 ? order : dist - 1 - order, y = dist % 2 ? dist - 1 - order : order;
|
|
69
|
+
if (x < lbw && y < lbw)
|
|
70
|
+
continue;
|
|
71
|
+
if (y % yScale)
|
|
72
|
+
continue;
|
|
73
|
+
o[2 * i] = x;
|
|
74
|
+
o[2 * i + 1] = y / yScale;
|
|
75
|
+
i++;
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
naturalOrders.set(id, o);
|
|
79
|
+
return o;
|
|
80
|
+
}
|
|
81
|
+
const ceilLog2 = (x) => x <= 1 ? 0 : 32 - Math.clz32(x - 1);
|
|
82
|
+
function chromaShifts(up) {
|
|
83
|
+
const anyH = up.some(u => u === 1 || u === 2), anyV = up.some(u => u === 1 || u === 3);
|
|
84
|
+
return { h: up.map(u => anyH && (u === 0 || u === 3) ? 1 : 0), v: up.map(u => anyV && (u === 0 || u === 2) ? 1 : 0), anyH, anyV };
|
|
85
|
+
}
|
|
86
|
+
export class VarDCT {
|
|
87
|
+
f;
|
|
88
|
+
g;
|
|
89
|
+
lfDequant;
|
|
90
|
+
bitDepth;
|
|
91
|
+
bw;
|
|
92
|
+
bh;
|
|
93
|
+
hs;
|
|
94
|
+
vs;
|
|
95
|
+
subsampled;
|
|
96
|
+
type;
|
|
97
|
+
hfMul;
|
|
98
|
+
sigma;
|
|
99
|
+
xFromY;
|
|
100
|
+
bFromY;
|
|
101
|
+
tilesPerRow;
|
|
102
|
+
lfQuant;
|
|
103
|
+
lf;
|
|
104
|
+
coeffs;
|
|
105
|
+
matrices = null;
|
|
106
|
+
numPresets = 1;
|
|
107
|
+
passes = [];
|
|
108
|
+
hasLfQuant = false;
|
|
109
|
+
constructor(f, g, lfDequant, bitDepth) {
|
|
110
|
+
this.f = f;
|
|
111
|
+
this.g = g;
|
|
112
|
+
this.lfDequant = lfDequant;
|
|
113
|
+
this.bitDepth = bitDepth;
|
|
114
|
+
const s = chromaShifts(f.jpegUpsampling);
|
|
115
|
+
this.hs = s.h;
|
|
116
|
+
this.vs = s.v;
|
|
117
|
+
this.subsampled = s.anyH || s.anyV;
|
|
118
|
+
let bw = Math.ceil(f.sampleWidth / 8), bh = Math.ceil(f.sampleHeight / 8);
|
|
119
|
+
if (s.anyH)
|
|
120
|
+
bw = Math.ceil(bw / 2) * 2;
|
|
121
|
+
if (s.anyV)
|
|
122
|
+
bh = Math.ceil(bh / 2) * 2;
|
|
123
|
+
this.bw = bw;
|
|
124
|
+
this.bh = bh;
|
|
125
|
+
this.type = new Int8Array(bw * bh).fill(-2);
|
|
126
|
+
this.hfMul = new Int32Array(bw * bh);
|
|
127
|
+
this.sigma = new Float32Array(bw * bh);
|
|
128
|
+
this.tilesPerRow = Math.ceil(bw / 8);
|
|
129
|
+
this.xFromY = new Int32Array(this.tilesPerRow * Math.ceil(bh / 8));
|
|
130
|
+
this.bFromY = new Int32Array(this.tilesPerRow * Math.ceil(bh / 8));
|
|
131
|
+
this.lfQuant = [0, 1, 2].map(c => new Int32Array((bw >> this.hs[c]) * (bh >> this.vs[c])));
|
|
132
|
+
this.lf = [0, 1, 2].map(c => new Float32Array((bw >> this.hs[c]) * (bh >> this.vs[c])));
|
|
133
|
+
this.coeffs = [0, 1, 2].map(c => new Int32Array((bw >> this.hs[c]) * (bh >> this.vs[c]) * 64));
|
|
134
|
+
}
|
|
135
|
+
lfGroupBlocks(g) {
|
|
136
|
+
const dim = this.f.groupDim, x0 = (g % this.f.lfGroupsPerRow) * dim, y0 = Math.floor(g / this.f.lfGroupsPerRow) * dim;
|
|
137
|
+
let w = Math.ceil(Math.min(dim * 8, this.f.sampleWidth - x0 * 8) / 8), h = Math.ceil(Math.min(dim * 8, this.f.sampleHeight - y0 * 8) / 8);
|
|
138
|
+
if (this.subsampled) {
|
|
139
|
+
if (this.hs.some(Boolean))
|
|
140
|
+
w = Math.ceil(w / 2) * 2;
|
|
141
|
+
if (this.vs.some(Boolean))
|
|
142
|
+
h = Math.ceil(h / 2) * 2;
|
|
143
|
+
}
|
|
144
|
+
return [x0, y0, w, h];
|
|
145
|
+
}
|
|
146
|
+
readLfCoefficients(r, g, tree) {
|
|
147
|
+
const extraPrecision = r.bits(2);
|
|
148
|
+
const [x0, y0, w, h] = this.lfGroupBlocks(g);
|
|
149
|
+
const size = (c) => [this.hs[c] ? Math.ceil(w / 2) : w, this.vs[c] ? Math.ceil(h / 2) : h];
|
|
150
|
+
const order = [1, 0, 2];
|
|
151
|
+
const image = new ModularImage(order.map(c => channel(...size(c), this.hs[c], this.vs[c])), this.bitDepth);
|
|
152
|
+
decodeModular(r, image, 1 + g, tree, Infinity, true);
|
|
153
|
+
const scale = 2 ** (9 - extraPrecision) / (this.g.globalScale * this.g.quantLf);
|
|
154
|
+
order.forEach((c, i) => {
|
|
155
|
+
const src = image.channels[i], q = this.lfQuant[c], lf = this.lf[c], stride = this.bw >> this.hs[c];
|
|
156
|
+
const ox = x0 >> this.hs[c], oy = y0 >> this.vs[c], m = this.lfDequant[c] * scale;
|
|
157
|
+
for (let y = 0; y < src.h; y++) {
|
|
158
|
+
for (let x = 0; x < src.w; x++) {
|
|
159
|
+
const v = src.data[y * src.w + x];
|
|
160
|
+
q[(oy + y) * stride + ox + x] = v;
|
|
161
|
+
lf[(oy + y) * stride + ox + x] = v * m;
|
|
162
|
+
}
|
|
163
|
+
}
|
|
164
|
+
});
|
|
165
|
+
this.hasLfQuant = true;
|
|
166
|
+
}
|
|
167
|
+
readHfMetadata(r, g, tree) {
|
|
168
|
+
const [x0, y0, w, h] = this.lfGroupBlocks(g);
|
|
169
|
+
const nbBlocks = 1 + r.bits(ceilLog2(w * h));
|
|
170
|
+
const tw = Math.ceil(w / 8), th = Math.ceil(h / 8);
|
|
171
|
+
const image = new ModularImage([channel(tw, th), channel(tw, th), channel(nbBlocks, 2), channel(w, h)], this.bitDepth);
|
|
172
|
+
decodeModular(r, image, 1 + 2 * this.f.numLfGroups + g, tree, Infinity, true);
|
|
173
|
+
const [xFromY, bFromY, info, sharpness] = image.channels;
|
|
174
|
+
for (let y = 0; y < th; y++) {
|
|
175
|
+
for (let x = 0; x < tw; x++) {
|
|
176
|
+
const i = (y0 / 8 + y) * this.tilesPerRow + x0 / 8 + x;
|
|
177
|
+
this.xFromY[i] = xFromY.data[y * tw + x];
|
|
178
|
+
this.bFromY[i] = bFromY.data[y * tw + x];
|
|
179
|
+
}
|
|
180
|
+
}
|
|
181
|
+
const epf = this.f.epf, sigmaBase = epf ? epf.quantMul * 65536 / this.g.globalScale : 0;
|
|
182
|
+
const bw = this.bw;
|
|
183
|
+
let k = 0;
|
|
184
|
+
for (let y = 0; y < h; y++) {
|
|
185
|
+
for (let x = 0; x < w; x++) {
|
|
186
|
+
if (this.type[(y0 + y) * bw + x0 + x] !== -2)
|
|
187
|
+
continue;
|
|
188
|
+
if (k >= nbBlocks)
|
|
189
|
+
throw new Error('JPEG XL: the varblocks don\'t fill the LF group');
|
|
190
|
+
const type = info.data[k], mul = info.data[nbBlocks + k] + 1;
|
|
191
|
+
k++;
|
|
192
|
+
if (type < 0 || type > 26)
|
|
193
|
+
throw new Error('JPEG XL: invalid varblock type');
|
|
194
|
+
if (mul <= 0)
|
|
195
|
+
throw new Error('JPEG XL: invalid HF multiplier');
|
|
196
|
+
const dw = SIZES[2 * type], dh = SIZES[2 * type + 1];
|
|
197
|
+
if ((x % 32) + dw > 32 || (y % 32) + dh > 32)
|
|
198
|
+
throw new Error('JPEG XL: a varblock across groups');
|
|
199
|
+
if (x + dw > w || y + dh > h)
|
|
200
|
+
throw new Error('JPEG XL: a varblock beyond its LF group');
|
|
201
|
+
for (let dy = 0; dy < dh; dy++) {
|
|
202
|
+
for (let dx = 0; dx < dw; dx++) {
|
|
203
|
+
const i = (y0 + y + dy) * bw + x0 + x + dx;
|
|
204
|
+
if (this.type[i] !== -2)
|
|
205
|
+
throw new Error('JPEG XL: overlapping varblocks');
|
|
206
|
+
this.type[i] = dx || dy ? -1 : type;
|
|
207
|
+
if (epf) {
|
|
208
|
+
const s = sharpness.data[(y + dy) * w + x + dx];
|
|
209
|
+
if (s < 0 || s > 7)
|
|
210
|
+
throw new Error('JPEG XL: invalid EPF sharpness');
|
|
211
|
+
this.sigma[i] = sigmaBase / mul * epf.sharpLut[s];
|
|
212
|
+
}
|
|
213
|
+
}
|
|
214
|
+
}
|
|
215
|
+
this.hfMul[(y0 + y) * bw + x0 + x] = mul;
|
|
216
|
+
}
|
|
217
|
+
}
|
|
218
|
+
}
|
|
219
|
+
readHfGlobal(r, tree) {
|
|
220
|
+
this.matrices = readDequantMatrices(r, this.bitDepth, 1 + 3 * this.f.numLfGroups, tree);
|
|
221
|
+
this.numPresets = r.bits(ceilLog2(this.f.numGroups)) + 1;
|
|
222
|
+
for (let p = 0; p < this.f.numPasses; p++) {
|
|
223
|
+
let used = r.u32(0x5f, 0x13, 0, [0, 13]);
|
|
224
|
+
const orders = [];
|
|
225
|
+
const dec = used ? new EntropyDecoder(r, 8) : null;
|
|
226
|
+
dec?.begin(r);
|
|
227
|
+
for (let id = 0; id < 13; id++, used >>= 1) {
|
|
228
|
+
const natural = naturalOrder(id);
|
|
229
|
+
if (!(used & 1) || !dec) {
|
|
230
|
+
orders.push([natural, natural, natural]);
|
|
231
|
+
continue;
|
|
232
|
+
}
|
|
233
|
+
const [bw, bh] = ORDER_SIZES[id], size = bw * bh;
|
|
234
|
+
const perChannel = [];
|
|
235
|
+
for (let c = 0; c < 3; c++) {
|
|
236
|
+
const perm = readPermutation(r, dec, size, size / 64), o = new Uint16Array(2 * size);
|
|
237
|
+
perm.forEach((v, i) => {
|
|
238
|
+
o[2 * i] = natural[2 * v];
|
|
239
|
+
o[2 * i + 1] = natural[2 * v + 1];
|
|
240
|
+
});
|
|
241
|
+
perChannel.push(o);
|
|
242
|
+
}
|
|
243
|
+
orders.push(perChannel);
|
|
244
|
+
}
|
|
245
|
+
dec?.finish();
|
|
246
|
+
this.passes.push({ orders, dist: new EntropyDecoder(r, 495 * this.numPresets * this.g.ctx.numClusters) });
|
|
247
|
+
}
|
|
248
|
+
}
|
|
249
|
+
readPassGroup(r, pass, group) {
|
|
250
|
+
const f = this.f, ctx = this.g.ctx, hp = this.passes[pass];
|
|
251
|
+
const gdim = f.groupDim / 8, bx0 = (group % f.groupsPerRow) * gdim, by0 = Math.floor(group / f.groupsPerRow) * gdim;
|
|
252
|
+
const width = Math.min(gdim, this.bw - bx0), height = Math.min(gdim, this.bh - by0);
|
|
253
|
+
const shift = f.passShift[pass] ?? 0;
|
|
254
|
+
const hfp = r.bits(ceilLog2(this.numPresets));
|
|
255
|
+
if (hfp >= this.numPresets)
|
|
256
|
+
throw new Error('JPEG XL: invalid HF preset');
|
|
257
|
+
const nc = ctx.numClusters, ctxSize = 495 * nc;
|
|
258
|
+
const dist = hp.dist.fork(), clusters = dist.clusters.subarray(ctxSize * hfp, ctxSize * (hfp + 1));
|
|
259
|
+
dist.begin(r);
|
|
260
|
+
const lfMul = (ctx.lfThresholds[0].length + 1) * (ctx.lfThresholds[1].length + 1) * (ctx.lfThresholds[2].length + 1);
|
|
261
|
+
const hfMul = ctx.qfThresholds.length + 1;
|
|
262
|
+
const hs = this.hs, vs = this.vs, bw = this.bw;
|
|
263
|
+
const nonZeros = [0, 1, 2].map(c => new Uint32Array(hs[c] ? Math.ceil(width / 2) : width));
|
|
264
|
+
for (let y = 0; y < height; y++) {
|
|
265
|
+
for (let x = 0; x < width; x++) {
|
|
266
|
+
const bi = (by0 + y) * bw + bx0 + x, type = this.type[bi];
|
|
267
|
+
if (type < 0)
|
|
268
|
+
continue;
|
|
269
|
+
const qf = this.hfMul[bi];
|
|
270
|
+
const w8 = SIZES[2 * type], h8 = SIZES[2 * type + 1], numBlocks = w8 * h8, numBlocksLog = 31 - Math.clz32(numBlocks);
|
|
271
|
+
const orderId = ORDER_ID[type];
|
|
272
|
+
let lfIdx = 0;
|
|
273
|
+
if (this.hasLfQuant) {
|
|
274
|
+
for (const c of [0, 2, 1]) {
|
|
275
|
+
const t = ctx.lfThresholds[c];
|
|
276
|
+
lfIdx *= t.length + 1;
|
|
277
|
+
const q = this.lfQuant[c][((by0 + y) >> vs[c]) * (bw >> hs[c]) + ((bx0 + x) >> hs[c])];
|
|
278
|
+
for (const th of t)
|
|
279
|
+
if (q > th)
|
|
280
|
+
lfIdx++;
|
|
281
|
+
}
|
|
282
|
+
}
|
|
283
|
+
let hfIdx = 0;
|
|
284
|
+
for (const th of ctx.qfThresholds)
|
|
285
|
+
if (qf > th)
|
|
286
|
+
hfIdx++;
|
|
287
|
+
for (let k = 0; k < 3; k++) {
|
|
288
|
+
const c = [1, 0, 2][k];
|
|
289
|
+
const sx = x >> hs[c], sy = y >> vs[c];
|
|
290
|
+
if (hs[c] || vs[c]) {
|
|
291
|
+
if (sx << hs[c] !== x || sy << vs[c] !== y)
|
|
292
|
+
continue;
|
|
293
|
+
if (this.type[(by0 + sy) * bw + bx0 + sx] < 0)
|
|
294
|
+
continue;
|
|
295
|
+
}
|
|
296
|
+
const blockCtx = ctx.ctxMap[((k * 13 + orderId) * hfMul + hfIdx) * lfMul + lfIdx];
|
|
297
|
+
const nz = nonZeros[c];
|
|
298
|
+
const predicted = sy === 0 ? (sx === 0 ? 32 : nz[sx - 1]) : sx === 0 ? nz[sx] : (nz[sx] + nz[sx - 1] + 1) >> 1;
|
|
299
|
+
const nzCtx = blockCtx + (predicted >= 8 ? 4 + (predicted >> 1) : predicted) * nc;
|
|
300
|
+
let count = dist.readClustered(r, clusters[nzCtx]);
|
|
301
|
+
if (count > 63 << numBlocksLog)
|
|
302
|
+
throw new Error('JPEG XL: too many non-zero coefficients');
|
|
303
|
+
const value = (count + numBlocks - 1) >> numBlocksLog;
|
|
304
|
+
for (let dx = 0; dx < w8; dx++)
|
|
305
|
+
nz[sx + dx] = value;
|
|
306
|
+
if (!count)
|
|
307
|
+
continue;
|
|
308
|
+
const out = this.coeffs[c], stride = (bw >> hs[c]) * 8;
|
|
309
|
+
const base = (((by0 >> vs[c]) + sy) * 8) * stride + ((bx0 >> hs[c]) + sx) * 8;
|
|
310
|
+
const order = hp.orders[orderId][c], tr = transposed(type);
|
|
311
|
+
let prevNonZero = count <= numBlocks * 4 ? 1 : 0;
|
|
312
|
+
const ctxBase = blockCtx * 458 + 37 * nc, n = order.length / 2;
|
|
313
|
+
for (let i = numBlocks; i < n; i++) {
|
|
314
|
+
const cctx = (NUM_NONZERO_CONTEXT[(count - 1) >> numBlocksLog] + FREQ_CONTEXT[(i - numBlocks) >> numBlocksLog]) * 2 + prevNonZero;
|
|
315
|
+
if (cctx >= 458)
|
|
316
|
+
throw new Error('JPEG XL: too many zero coefficients');
|
|
317
|
+
const u = dist.readClustered(r, clusters[ctxBase + cctx]);
|
|
318
|
+
if (u === 0) {
|
|
319
|
+
prevNonZero = 0;
|
|
320
|
+
continue;
|
|
321
|
+
}
|
|
322
|
+
let dx = order[2 * i], dy = order[2 * i + 1];
|
|
323
|
+
if (tr)
|
|
324
|
+
[dx, dy] = [dy, dx];
|
|
325
|
+
out[base + dy * stride + dx] += unpackSigned(u) * 2 ** shift;
|
|
326
|
+
prevNonZero = 1;
|
|
327
|
+
if (--count === 0)
|
|
328
|
+
break;
|
|
329
|
+
}
|
|
330
|
+
}
|
|
331
|
+
}
|
|
332
|
+
}
|
|
333
|
+
dist.finish();
|
|
334
|
+
}
|
|
335
|
+
render(quantBias, quantBiasNumerator, lfFrame) {
|
|
336
|
+
const f = this.f, g = this.g, bw = this.bw, bh = this.bh, hs = this.hs, vs = this.vs;
|
|
337
|
+
let lf = this.lf;
|
|
338
|
+
if (lfFrame)
|
|
339
|
+
lf = lfFrame;
|
|
340
|
+
else {
|
|
341
|
+
if (!this.subsampled) {
|
|
342
|
+
const kx = g.baseX + (g.xFactorLf - 128) / g.colourFactor, kb = g.baseB + (g.bFactorLf - 128) / g.colourFactor;
|
|
343
|
+
const [x, y, b] = lf;
|
|
344
|
+
for (let i = 0; i < y.length; i++) {
|
|
345
|
+
x[i] += kx * y[i];
|
|
346
|
+
b[i] += kb * y[i];
|
|
347
|
+
}
|
|
348
|
+
}
|
|
349
|
+
if (!(f.flags & FLAG_SKIP_ADAPTIVE_LF_SMOOTHING)) {
|
|
350
|
+
const sc = 512 / (g.globalScale * g.quantLf);
|
|
351
|
+
lf = smoothLf(lf, [0, 1, 2].map(c => bw >> hs[c]), [0, 1, 2].map(c => bh >> vs[c]), this.lfDequant.map(m => m * sc), hs, vs);
|
|
352
|
+
}
|
|
353
|
+
}
|
|
354
|
+
const m = this.matrices;
|
|
355
|
+
const qmScale = [0.8 ** (f.xqmScale - 2), 1, 0.8 ** (f.bqmScale - 2)];
|
|
356
|
+
const planes = [0, 1, 2].map(c => new Float32Array(this.coeffs[c].length));
|
|
357
|
+
for (let c = 0; c < 3; c++) {
|
|
358
|
+
const q = this.coeffs[c], out = planes[c], stride = (bw >> hs[c]) * 8, bias = quantBias[c];
|
|
359
|
+
this.forEachVarblock(c, (sbx, sby, type, mul) => {
|
|
360
|
+
const w = SIZES[2 * type] * 8, h = SIZES[2 * type + 1] * 8, idx = MATRIX_INDEX[type];
|
|
361
|
+
const matrix = transposed(type) ? m.transposed[idx][c] : m.matrices[idx][c];
|
|
362
|
+
const k = 65536 / (g.globalScale * mul) * qmScale[c];
|
|
363
|
+
for (let y = 0; y < h; y++) {
|
|
364
|
+
const o = (sby * 8 + y) * stride + sbx * 8;
|
|
365
|
+
for (let x = 0; x < w; x++) {
|
|
366
|
+
let v = q[o + x];
|
|
367
|
+
if (v === 0)
|
|
368
|
+
continue;
|
|
369
|
+
v = Math.abs(v) <= 1 ? v * bias : v - quantBiasNumerator / v;
|
|
370
|
+
out[o + x] = v * matrix[y * w + x] * k;
|
|
371
|
+
}
|
|
372
|
+
}
|
|
373
|
+
});
|
|
374
|
+
}
|
|
375
|
+
if (!this.subsampled) {
|
|
376
|
+
const [x, y, b] = planes, stride = bw * 8;
|
|
377
|
+
for (let py = 0; py < bh * 8; py++) {
|
|
378
|
+
for (let tx = 0; tx < this.tilesPerRow; tx++) {
|
|
379
|
+
const t = (py >> 6) * this.tilesPerRow + tx;
|
|
380
|
+
const kx = g.baseX + this.xFromY[t] / g.colourFactor, kb = g.baseB + this.bFromY[t] / g.colourFactor;
|
|
381
|
+
const end = Math.min(tx * 64 + 64, stride);
|
|
382
|
+
for (let px = tx * 64, i = py * stride + px; px < end; px++, i++) {
|
|
383
|
+
x[i] += kx * y[i];
|
|
384
|
+
b[i] += kb * y[i];
|
|
385
|
+
}
|
|
386
|
+
}
|
|
387
|
+
}
|
|
388
|
+
}
|
|
389
|
+
for (let c = 0; c < 3; c++) {
|
|
390
|
+
const out = planes[c], stride = (bw >> hs[c]) * 8, lfc = lf[c], lfStride = bw >> hs[c];
|
|
391
|
+
this.forEachVarblock(c, (sbx, sby, type) => {
|
|
392
|
+
const w8 = SIZES[2 * type], h8 = SIZES[2 * type + 1];
|
|
393
|
+
const llf = { buf: out, offset: sby * 8 * stride + sbx * 8, stride, w: w8, h: h8 };
|
|
394
|
+
if (single(type))
|
|
395
|
+
out[llf.offset] = lfc[sby * lfStride + sbx];
|
|
396
|
+
else {
|
|
397
|
+
for (let y = 0; y < h8; y++)
|
|
398
|
+
for (let x = 0; x < w8; x++)
|
|
399
|
+
out[llf.offset + y * stride + x] = lfc[(sby + y) * lfStride + sbx + x];
|
|
400
|
+
dct2d(llf, false);
|
|
401
|
+
const lw = 31 - Math.clz32(w8), lh = 31 - Math.clz32(h8);
|
|
402
|
+
for (let y = 0; y < h8; y++)
|
|
403
|
+
for (let x = 0; x < w8; x++)
|
|
404
|
+
out[llf.offset + y * stride + x] /= scaleF(y, 5 - lh) * scaleF(x, 5 - lw);
|
|
405
|
+
}
|
|
406
|
+
inverseTransform({ buf: out, offset: llf.offset, stride, w: w8 * 8, h: h8 * 8 }, type);
|
|
407
|
+
});
|
|
408
|
+
}
|
|
409
|
+
return planes;
|
|
410
|
+
}
|
|
411
|
+
stride(c) {
|
|
412
|
+
return (this.bw >> this.hs[c]) * 8;
|
|
413
|
+
}
|
|
414
|
+
forEachVarblock(c, fn) {
|
|
415
|
+
const bw = this.bw, hs = this.hs[c], vs = this.vs[c], gdim = this.f.groupDim / 8;
|
|
416
|
+
for (let by = 0; by < this.bh; by++) {
|
|
417
|
+
for (let bx = 0; bx < bw; bx++) {
|
|
418
|
+
const type = this.type[by * bw + bx];
|
|
419
|
+
if (type < 0)
|
|
420
|
+
continue;
|
|
421
|
+
if (hs || vs) {
|
|
422
|
+
const gx = bx - bx % gdim, gy = by - by % gdim, lx = bx - gx, ly = by - gy;
|
|
423
|
+
if ((lx >> hs) << hs !== lx || (ly >> vs) << vs !== ly)
|
|
424
|
+
continue;
|
|
425
|
+
if (this.type[(gy + (ly >> vs)) * bw + gx + (lx >> hs)] < 0)
|
|
426
|
+
continue;
|
|
427
|
+
}
|
|
428
|
+
fn(bx >> hs, by >> vs, type, this.hfMul[by * bw + bx]);
|
|
429
|
+
}
|
|
430
|
+
}
|
|
431
|
+
}
|
|
432
|
+
}
|
|
433
|
+
const FREQ_CONTEXT = Uint8Array.of(0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 15, 16, 16, 17, 17, 18, 18, 19, 19, 20, 20, 21, 21, 22, 22, 23, 23, 23, 23, 24, 24, 24, 24, 25, 25, 25, 25, 26, 26, 26, 26, 27, 27, 27, 27, 28, 28, 28, 28, 29, 29, 29, 29, 30, 30, 30, 30);
|
|
434
|
+
const NUM_NONZERO_CONTEXT = Uint8Array.of(0, 31, 62, 62, 93, 93, 93, 93, 123, 123, 123, 123, 152, 152, 152, 152, 152, 152, 152, 152, 180, 180, 180, 180, 180, 180, 180, 180, 180, 180, 180, 180, 206, 206, 206, 206, 206, 206, 206, 206, 206, 206, 206, 206, 206, 206, 206, 206, 206, 206, 206, 206, 206, 206, 206, 206, 206, 206, 206, 206, 206, 206, 206);
|
|
435
|
+
const W_SELF = 0.052262735, W_SIDE = 0.2034514, W_DIAG = 0.03348292;
|
|
436
|
+
function smoothLf(lf, w, h, factors, hs, vs) {
|
|
437
|
+
const subsampled = hs.some(Boolean) || vs.some(Boolean);
|
|
438
|
+
const W = Math.max(...w), H = Math.max(...h);
|
|
439
|
+
if (W <= 2 || H <= 2 || w.some(v => v <= 2) || h.some(v => v <= 2))
|
|
440
|
+
return lf;
|
|
441
|
+
const out = lf.map(c => c.slice());
|
|
442
|
+
const wa = lf.map((src, c) => {
|
|
443
|
+
const cw = w[c], a = new Float32Array(cw * h[c]);
|
|
444
|
+
for (let y = 1; y < h[c] - 1; y++) {
|
|
445
|
+
for (let x = 1; x < cw - 1; x++) {
|
|
446
|
+
const i = y * cw + x;
|
|
447
|
+
a[i] = src[i] * W_SELF + (src[i - 1] + src[i + 1] + src[i - cw] + src[i + cw]) * W_SIDE + (src[i - cw - 1] + src[i - cw + 1] + src[i + cw - 1] + src[i + cw + 1]) * W_DIAG;
|
|
448
|
+
}
|
|
449
|
+
}
|
|
450
|
+
return a;
|
|
451
|
+
});
|
|
452
|
+
for (let y = 1; y < H - 1; y++) {
|
|
453
|
+
for (let x = 1; x < W - 1; x++) {
|
|
454
|
+
const cx = [0, 1, 2].map(c => x >> hs[c]), cy = [0, 1, 2].map(c => y >> vs[c]);
|
|
455
|
+
if (subsampled && [0, 1, 2].some(c => cx[c] === 0 || cx[c] === w[c] - 1 || cy[c] === 0 || cy[c] === h[c] - 1))
|
|
456
|
+
continue;
|
|
457
|
+
let gap = 0.5;
|
|
458
|
+
for (let c = 0; c < 3; c++) {
|
|
459
|
+
const i = cy[c] * w[c] + cx[c];
|
|
460
|
+
gap = Math.max(gap, Math.abs(wa[c][i] - lf[c][i]) / factors[c]);
|
|
461
|
+
}
|
|
462
|
+
const scale = Math.max(3 - 4 * gap, 0);
|
|
463
|
+
for (let c = 0; c < 3; c++) {
|
|
464
|
+
if (subsampled && ((cy[c] << vs[c]) !== y || (cx[c] << hs[c]) !== x))
|
|
465
|
+
continue;
|
|
466
|
+
const i = cy[c] * w[c] + cx[c];
|
|
467
|
+
out[c][i] = (wa[c][i] - lf[c][i]) * scale + lf[c][i];
|
|
468
|
+
}
|
|
469
|
+
}
|
|
470
|
+
}
|
|
471
|
+
return out;
|
|
472
|
+
}
|
|
@@ -0,0 +1,89 @@
|
|
|
1
|
+
import { MAX_PIXELS, checkPixels } from "../util/limits.js";
|
|
2
|
+
import { BitReader } from "./jxl/bits.js";
|
|
3
|
+
import { imageHeader, skipICC } from "./jxl/headers.js";
|
|
4
|
+
import { frameHeader, toc } from "./jxl/frame-header.js";
|
|
5
|
+
import { decodeFrame } from "./jxl/frame.js";
|
|
6
|
+
import { ColorOutput } from "./jxl/color.js";
|
|
7
|
+
const CONTAINER = [0, 0, 0, 12, 0x4a, 0x58, 0x4c, 0x20, 0x0d, 0x0a, 0x87, 0x0a];
|
|
8
|
+
export function isJXL(d) {
|
|
9
|
+
if (d.length >= 2 && d[0] === 0xff && d[1] === 0x0a)
|
|
10
|
+
return true;
|
|
11
|
+
return d.length >= 12 && CONTAINER.every((v, i) => d[i] === v);
|
|
12
|
+
}
|
|
13
|
+
const fourcc = (d, o) => String.fromCharCode(d[o], d[o + 1], d[o + 2], d[o + 3]);
|
|
14
|
+
function codestream(d) {
|
|
15
|
+
if (d[0] === 0xff)
|
|
16
|
+
return d;
|
|
17
|
+
const view = new DataView(d.buffer, d.byteOffset, d.byteLength), parts = [];
|
|
18
|
+
for (let o = 0; o + 8 <= d.length;) {
|
|
19
|
+
let size = view.getUint32(o), header = 8;
|
|
20
|
+
const type = fourcc(d, o + 4);
|
|
21
|
+
if (size === 1) {
|
|
22
|
+
if (o + 16 > d.length)
|
|
23
|
+
throw new Error('JPEG XL: truncated box');
|
|
24
|
+
size = view.getUint32(o + 8) * 2 ** 32 + view.getUint32(o + 12);
|
|
25
|
+
header = 16;
|
|
26
|
+
}
|
|
27
|
+
else if (size === 0)
|
|
28
|
+
size = d.length - o;
|
|
29
|
+
if (size < header || o + size > d.length)
|
|
30
|
+
throw new Error(`JPEG XL: invalid ${type} box`);
|
|
31
|
+
if (type === 'jxlc')
|
|
32
|
+
return d.subarray(o + header, o + size);
|
|
33
|
+
if (type === 'jxlp')
|
|
34
|
+
parts.push(d.subarray(o + header + 4, o + size));
|
|
35
|
+
o += size;
|
|
36
|
+
}
|
|
37
|
+
if (!parts.length)
|
|
38
|
+
throw new Error('JPEG XL: no codestream');
|
|
39
|
+
const out = new Uint8Array(parts.reduce((n, p) => n + p.length, 0));
|
|
40
|
+
let at = 0;
|
|
41
|
+
for (const p of parts) {
|
|
42
|
+
out.set(p, at);
|
|
43
|
+
at += p.length;
|
|
44
|
+
}
|
|
45
|
+
return out;
|
|
46
|
+
}
|
|
47
|
+
export function* frames(data, maxPixels = MAX_PIXELS, toneMapping = 'reinhard') {
|
|
48
|
+
const d = codestream(data);
|
|
49
|
+
const r = new BitReader(d);
|
|
50
|
+
const image = imageHeader(r);
|
|
51
|
+
checkPixels(image.width, image.height, maxPixels);
|
|
52
|
+
if (image.colour.wantICC)
|
|
53
|
+
skipICC(r);
|
|
54
|
+
r.zeroPadToByte();
|
|
55
|
+
let at = r.bytePosition;
|
|
56
|
+
if (image.preview) {
|
|
57
|
+
const pr = new BitReader(d, at);
|
|
58
|
+
at = toc(pr, frameHeader(pr, { ...image, ...image.preview })).end;
|
|
59
|
+
}
|
|
60
|
+
const state = {
|
|
61
|
+
image, output: new ColorOutput(image, toneMapping), references: [null, null, null, null], referenceIsXYB: [false, false, false, false],
|
|
62
|
+
lfFrames: [null, null, null, null], visible: 0, invisible: 0
|
|
63
|
+
};
|
|
64
|
+
const a = image.animation, tick = a ? 1000 * a.tpsDenominator / a.tpsNumerator : 0;
|
|
65
|
+
let shown = 0;
|
|
66
|
+
for (;;) {
|
|
67
|
+
if (at >= d.length) {
|
|
68
|
+
if (shown)
|
|
69
|
+
return;
|
|
70
|
+
throw new Error('JPEG XL: no frame shown');
|
|
71
|
+
}
|
|
72
|
+
const frame = decodeFrame(d, new BitReader(d, at), state);
|
|
73
|
+
at = frame.end;
|
|
74
|
+
if (frame.canvas && (frame.header.isLast || frame.header.duration !== 0)) {
|
|
75
|
+
shown++;
|
|
76
|
+
yield {
|
|
77
|
+
width: image.width, height: image.height, pixels: state.output.toRGBA(frame.canvas), duration: frame.header.duration * tick,
|
|
78
|
+
orientation: image.orientation, loopCount: a ? a.numLoops : 1
|
|
79
|
+
};
|
|
80
|
+
}
|
|
81
|
+
if (frame.header.isLast)
|
|
82
|
+
return;
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
export function decode(data, maxPixels = MAX_PIXELS, toneMapping = 'reinhard') {
|
|
86
|
+
for (const frame of frames(data, maxPixels, toneMapping))
|
|
87
|
+
return frame;
|
|
88
|
+
throw new Error('JPEG XL: no frame shown');
|
|
89
|
+
}
|