imagescript 3.2.1 → 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 +35 -10
- package/THIRD_PARTY_NOTICES.md +298 -2
- package/dist/animation.d.ts +25 -8
- package/dist/animation.js +44 -13
- 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/crc.js +5 -0
- package/dist/codecs/gif.js +10 -35
- 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 +11 -28
- 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/png.js +22 -10
- package/dist/codecs/qoi.js +150 -0
- package/dist/codecs/tga.js +3 -1
- package/dist/codecs/tiff.js +7 -2
- 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/vp8.js +3 -1
- package/dist/codecs/vp8l.js +126 -58
- package/dist/codecs/webp.js +47 -11
- package/dist/codecs/zlib.js +81 -69
- package/dist/frame.d.ts +1 -0
- package/dist/frame.js +4 -2
- package/dist/gif.d.ts +1 -0
- package/dist/gif.js +2 -2
- package/dist/image-type.d.ts +15 -1
- package/dist/image-type.js +45 -2
- package/dist/image.d.ts +89 -13
- package/dist/image.js +266 -113
- package/dist/index.d.ts +3 -2
- package/dist/index.js +4 -4
- package/dist/render/bidi.js +350 -0
- package/dist/render/buffer.js +362 -0
- package/dist/render/color.js +164 -0
- package/dist/render/colr.js +523 -0
- package/dist/render/font.js +173 -241
- package/dist/render/hinting.js +38 -22
- package/dist/render/layout.js +1553 -0
- package/dist/render/linebreak.js +210 -0
- package/dist/render/segment.js +106 -0
- package/dist/render/shape.js +984 -0
- package/dist/render/shapers/arabic.js +284 -0
- package/dist/render/shapers/hangul.js +142 -0
- package/dist/render/shapers/hebrew.js +49 -0
- package/dist/render/shapers/index.js +52 -0
- package/dist/render/shapers/indic.js +579 -0
- package/dist/render/shapers/khmer.js +104 -0
- package/dist/render/shapers/machine.js +129 -0
- package/dist/render/shapers/myanmar.js +121 -0
- package/dist/render/shapers/syllabic.js +100 -0
- package/dist/render/shapers/thai.js +125 -0
- package/dist/render/shapers/use.js +235 -0
- package/dist/render/svg.js +102 -23
- package/dist/render/text.js +373 -103
- package/dist/render/unicode-data.js +63 -0
- package/dist/render/unicode.js +50 -0
- package/dist/text-layout.d.ts +4 -1
- package/dist/text-layout.js +2 -2
- package/dist/util/alpha.js +43 -0
- package/dist/util/blur.js +20 -33
- package/dist/util/bytes.js +40 -0
- package/dist/util/limits.js +34 -0
- package/dist/util/options.js +7 -0
- package/dist/util/resample.js +126 -7
- package/dist/util/rotate.js +43 -0
- package/dist/version.js +1 -1
- package/licenses/HarfBuzz.txt +42 -0
- package/licenses/Unicode-3.0.txt +39 -0
- 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
|
+
}
|