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,317 @@
|
|
|
1
|
+
import { MAX_PIXELS, checkPixels } from "../util/limits.js";
|
|
2
|
+
const BI_RGB = 0, BI_RLE8 = 1, BI_RLE4 = 2, BI_BITFIELDS = 3, BI_JPEG = 4, BI_PNG = 5, BI_ALPHABITFIELDS = 6;
|
|
3
|
+
const u16 = (d, o) => d[o] | d[o + 1] << 8;
|
|
4
|
+
const u32 = (d, o) => (d[o] | d[o + 1] << 8 | d[o + 2] << 16 | d[o + 3] << 24) >>> 0;
|
|
5
|
+
const i32 = (d, o) => d[o] | d[o + 1] << 8 | d[o + 2] << 16 | d[o + 3] << 24;
|
|
6
|
+
const WINDOWS_HEADERS = [40, 52, 56, 108, 124];
|
|
7
|
+
const isHeaderSize = (size) => size === 12 || WINDOWS_HEADERS.includes(size) || (size >= 16 && size <= 64 && size % 4 === 0);
|
|
8
|
+
export function isBMP(d) {
|
|
9
|
+
return d.length >= 26 && d[0] === 0x42 && d[1] === 0x4d && isHeaderSize(u32(d, 14));
|
|
10
|
+
}
|
|
11
|
+
function channel(mask) {
|
|
12
|
+
mask >>>= 0;
|
|
13
|
+
let shift = 0;
|
|
14
|
+
if (mask)
|
|
15
|
+
while (!(mask >>> shift & 1))
|
|
16
|
+
shift++;
|
|
17
|
+
const max = mask >>> shift;
|
|
18
|
+
let table = null;
|
|
19
|
+
if (max && max < 65536) {
|
|
20
|
+
table = new Uint8Array(max + 1);
|
|
21
|
+
for (let v = 0; v <= max; v++)
|
|
22
|
+
table[v] = Math.round(v * 255 / max);
|
|
23
|
+
}
|
|
24
|
+
return { mask, shift, max, table };
|
|
25
|
+
}
|
|
26
|
+
function sample(c, v) {
|
|
27
|
+
if (!c.max)
|
|
28
|
+
return 0;
|
|
29
|
+
const x = (v & c.mask) >>> c.shift;
|
|
30
|
+
return c.table ? c.table[x] : Math.round(x * 255 / c.max);
|
|
31
|
+
}
|
|
32
|
+
function readInfo(d, o) {
|
|
33
|
+
const size = u32(d, o);
|
|
34
|
+
if (!isHeaderSize(size))
|
|
35
|
+
throw new Error(`BMP: unknown header size ${size}`);
|
|
36
|
+
if (o + size > d.length)
|
|
37
|
+
throw new Error('BMP: truncated header');
|
|
38
|
+
if (size === 12) {
|
|
39
|
+
return { width: u16(d, o + 4), height: u16(d, o + 6), topDown: false, bits: u16(d, o + 10), compression: BI_RGB, headerEnd: o + 12, entryBytes: 3, colors: 0, masks: null };
|
|
40
|
+
}
|
|
41
|
+
const os2 = !WINDOWS_HEADERS.includes(size);
|
|
42
|
+
const width = i32(d, o + 4), rawHeight = i32(d, o + 8);
|
|
43
|
+
const bits = u16(d, o + 14);
|
|
44
|
+
let compression = size >= 20 ? u32(d, o + 16) : BI_RGB;
|
|
45
|
+
if (os2 && (compression === 3 || compression === 4))
|
|
46
|
+
throw new Error(`BMP: unsupported OS/2 compression (${compression === 3 ? 'Huffman 1D' : 'RLE24'})`);
|
|
47
|
+
const colors = size >= 36 ? u32(d, o + 32) : 0;
|
|
48
|
+
let masks = null;
|
|
49
|
+
let headerEnd = o + size;
|
|
50
|
+
if (compression === BI_BITFIELDS || compression === BI_ALPHABITFIELDS) {
|
|
51
|
+
if (size >= 52)
|
|
52
|
+
masks = [u32(d, o + 40), u32(d, o + 44), u32(d, o + 48), size >= 56 ? u32(d, o + 52) : 0];
|
|
53
|
+
else {
|
|
54
|
+
const count = compression === BI_ALPHABITFIELDS ? 4 : 3;
|
|
55
|
+
if (headerEnd + count * 4 > d.length)
|
|
56
|
+
throw new Error('BMP: truncated bit fields');
|
|
57
|
+
masks = [u32(d, headerEnd), u32(d, headerEnd + 4), u32(d, headerEnd + 8), count === 4 ? u32(d, headerEnd + 12) : 0];
|
|
58
|
+
headerEnd += count * 4;
|
|
59
|
+
}
|
|
60
|
+
compression = BI_BITFIELDS;
|
|
61
|
+
}
|
|
62
|
+
if (width <= 0 || rawHeight === 0 || rawHeight === -0x80000000)
|
|
63
|
+
throw new Error(`BMP: invalid dimensions ${width}x${rawHeight}`);
|
|
64
|
+
return { width, height: Math.abs(rawHeight), topDown: rawHeight < 0, bits, compression, headerEnd, entryBytes: 4, colors, masks };
|
|
65
|
+
}
|
|
66
|
+
function readPalette(d, info, end) {
|
|
67
|
+
const { bits, entryBytes, headerEnd } = info;
|
|
68
|
+
if (bits > 8)
|
|
69
|
+
return null;
|
|
70
|
+
let count = info.colors || 1 << bits;
|
|
71
|
+
count = Math.max(0, Math.min(count, Math.floor((Math.min(end, d.length) - headerEnd) / entryBytes)));
|
|
72
|
+
const palette = new Uint32Array(Math.max(count, 1 << bits));
|
|
73
|
+
palette.fill(0xff000000);
|
|
74
|
+
for (let i = 0; i < count; i++) {
|
|
75
|
+
const p = headerEnd + i * entryBytes;
|
|
76
|
+
palette[i] = (0xff000000 | d[p] << 16 | d[p + 1] << 8 | d[p + 2]) >>> 0;
|
|
77
|
+
}
|
|
78
|
+
return palette;
|
|
79
|
+
}
|
|
80
|
+
export function decodeDIB(d, o, pixelOffset, maxPixels, ico = false) {
|
|
81
|
+
const info = readInfo(d, o);
|
|
82
|
+
const { width, bits, compression, topDown } = info;
|
|
83
|
+
const height = ico ? info.height >> 1 : info.height;
|
|
84
|
+
if (!height)
|
|
85
|
+
throw new Error('BMP: invalid dimensions');
|
|
86
|
+
if (compression === BI_JPEG || compression === BI_PNG) {
|
|
87
|
+
const start = pixelOffset ?? info.headerEnd;
|
|
88
|
+
return { embedded: compression === BI_JPEG ? 'jpeg' : 'png', data: d.subarray(start) };
|
|
89
|
+
}
|
|
90
|
+
if (![1, 2, 4, 8, 16, 24, 32, 64].includes(bits))
|
|
91
|
+
throw new Error(`BMP: unsupported bit depth ${bits}`);
|
|
92
|
+
if (compression === BI_RLE8 && bits !== 8 || compression === BI_RLE4 && bits !== 4)
|
|
93
|
+
throw new Error('BMP: RLE compression of the wrong bit depth');
|
|
94
|
+
if (compression === BI_BITFIELDS && bits !== 16 && bits !== 32)
|
|
95
|
+
throw new Error('BMP: bit fields need 16 or 32 bit pixels');
|
|
96
|
+
if (compression > BI_PNG)
|
|
97
|
+
throw new Error(`BMP: unsupported compression ${compression}`);
|
|
98
|
+
checkPixels(width, height, maxPixels);
|
|
99
|
+
const palette = readPalette(d, info, pixelOffset ?? d.length);
|
|
100
|
+
const start = pixelOffset ?? info.headerEnd + (bits <= 8 ? info.colors || 1 << bits : info.colors) * info.entryBytes;
|
|
101
|
+
const pixels = new Uint8Array(width * height * 4);
|
|
102
|
+
const out = new Uint32Array(pixels.buffer);
|
|
103
|
+
const row = (y) => (topDown ? y : height - 1 - y) * width;
|
|
104
|
+
if (compression === BI_RLE8 || compression === BI_RLE4) {
|
|
105
|
+
decodeRLE(d, start, width, height, compression === BI_RLE4, palette, out, row);
|
|
106
|
+
return { width, height, pixels };
|
|
107
|
+
}
|
|
108
|
+
const stride = (width * bits + 31 >>> 5) * 4;
|
|
109
|
+
if (start + stride * (height - 1) + Math.ceil(width * bits / 8) > d.length)
|
|
110
|
+
throw new Error('BMP: truncated pixel data');
|
|
111
|
+
if (palette) {
|
|
112
|
+
const mask = (1 << bits) - 1;
|
|
113
|
+
for (let y = 0; y < height; y++) {
|
|
114
|
+
const r = start + y * stride, o = row(y);
|
|
115
|
+
for (let x = 0; x < width; x++) {
|
|
116
|
+
const bit = x * bits;
|
|
117
|
+
out[o + x] = palette[d[r + (bit >> 3)] >> (8 - bits - (bit & 7)) & mask];
|
|
118
|
+
}
|
|
119
|
+
}
|
|
120
|
+
}
|
|
121
|
+
else if (bits === 24) {
|
|
122
|
+
for (let y = 0; y < height; y++) {
|
|
123
|
+
const r = start + y * stride, o = row(y);
|
|
124
|
+
for (let x = 0; x < width; x++) {
|
|
125
|
+
const p = r + x * 3;
|
|
126
|
+
out[o + x] = (0xff000000 | d[p] << 16 | d[p + 1] << 8 | d[p + 2]) >>> 0;
|
|
127
|
+
}
|
|
128
|
+
}
|
|
129
|
+
}
|
|
130
|
+
else if (bits === 64) {
|
|
131
|
+
const srgb = (v) => {
|
|
132
|
+
const c = Math.min(1, Math.max(0, v / 8192));
|
|
133
|
+
return Math.round(255 * (c <= 0.0031308 ? 12.92 * c : 1.055 * c ** (1 / 2.4) - 0.055));
|
|
134
|
+
};
|
|
135
|
+
const s16 = (p) => d[p] | d[p + 1] << 8 << 16 >> 16;
|
|
136
|
+
for (let y = 0; y < height; y++) {
|
|
137
|
+
const r = start + y * stride, o = row(y) * 4;
|
|
138
|
+
for (let x = 0; x < width; x++) {
|
|
139
|
+
const p = r + x * 8, q = o + x * 4;
|
|
140
|
+
pixels[q] = srgb(s16(p + 4));
|
|
141
|
+
pixels[q + 1] = srgb(s16(p + 2));
|
|
142
|
+
pixels[q + 2] = srgb(s16(p));
|
|
143
|
+
pixels[q + 3] = Math.round(Math.min(1, Math.max(0, s16(p + 6) / 8192)) * 255);
|
|
144
|
+
}
|
|
145
|
+
}
|
|
146
|
+
}
|
|
147
|
+
else {
|
|
148
|
+
const masks = info.masks ?? (bits === 16 ? [0x7c00, 0x3e0, 0x1f, 0] : [0xff0000, 0xff00, 0xff, ico && bits === 32 ? 0xff000000 : 0]);
|
|
149
|
+
const [cr, cg, cb, ca] = masks.map(channel);
|
|
150
|
+
const fast = bits === 32 && cr.mask === 0xff0000 && cg.mask === 0xff00 && cb.mask === 0xff;
|
|
151
|
+
let alphaSeen = false;
|
|
152
|
+
for (let y = 0; y < height; y++) {
|
|
153
|
+
const r = start + y * stride, o = row(y);
|
|
154
|
+
for (let x = 0; x < width; x++) {
|
|
155
|
+
const v = bits === 16 ? u16(d, r + x * 2) : u32(d, r + x * 4);
|
|
156
|
+
const a = ca.max ? sample(ca, v) : 255;
|
|
157
|
+
if (ca.max && a)
|
|
158
|
+
alphaSeen = true;
|
|
159
|
+
out[o + x] = fast
|
|
160
|
+
? (a << 24 | (v & 0xff) << 16 | v & 0xff00 | v >> 16 & 0xff) >>> 0
|
|
161
|
+
: (a << 24 | sample(cb, v) << 16 | sample(cg, v) << 8 | sample(cr, v)) >>> 0;
|
|
162
|
+
}
|
|
163
|
+
}
|
|
164
|
+
if (ca.max && !alphaSeen)
|
|
165
|
+
for (let i = 0; i < out.length; i++)
|
|
166
|
+
out[i] |= 0xff000000;
|
|
167
|
+
if (ico && bits === 32 && alphaSeen)
|
|
168
|
+
return { width, height, pixels };
|
|
169
|
+
}
|
|
170
|
+
if (ico) {
|
|
171
|
+
const maskStart = start + stride * height, maskStride = (width + 31 >>> 5) * 4;
|
|
172
|
+
if (maskStart + maskStride * (height - 1) + Math.ceil(width / 8) <= d.length) {
|
|
173
|
+
for (let y = 0; y < height; y++) {
|
|
174
|
+
const r = maskStart + y * maskStride, o = row(y);
|
|
175
|
+
for (let x = 0; x < width; x++)
|
|
176
|
+
if (d[r + (x >> 3)] >> (7 - (x & 7)) & 1)
|
|
177
|
+
out[o + x] = 0;
|
|
178
|
+
}
|
|
179
|
+
}
|
|
180
|
+
}
|
|
181
|
+
return { width, height, pixels };
|
|
182
|
+
}
|
|
183
|
+
function decodeRLE(d, o, width, height, four, palette, out, row) {
|
|
184
|
+
let x = 0, y = 0;
|
|
185
|
+
const put = (index) => {
|
|
186
|
+
if (x < width && y < height)
|
|
187
|
+
out[row(y) + x] = palette[index];
|
|
188
|
+
x++;
|
|
189
|
+
};
|
|
190
|
+
while (y < height) {
|
|
191
|
+
if (o + 1 >= d.length)
|
|
192
|
+
throw new Error('BMP: truncated RLE data');
|
|
193
|
+
const n = d[o], v = d[o + 1];
|
|
194
|
+
o += 2;
|
|
195
|
+
if (n) {
|
|
196
|
+
for (let i = 0; i < n; i++)
|
|
197
|
+
put(four ? (i & 1 ? v & 15 : v >> 4) : v);
|
|
198
|
+
}
|
|
199
|
+
else if (v === 0) {
|
|
200
|
+
x = 0;
|
|
201
|
+
y++;
|
|
202
|
+
}
|
|
203
|
+
else if (v === 1) {
|
|
204
|
+
break;
|
|
205
|
+
}
|
|
206
|
+
else if (v === 2) {
|
|
207
|
+
if (o + 1 >= d.length)
|
|
208
|
+
throw new Error('BMP: truncated RLE data');
|
|
209
|
+
x += d[o];
|
|
210
|
+
y += d[o + 1];
|
|
211
|
+
o += 2;
|
|
212
|
+
}
|
|
213
|
+
else {
|
|
214
|
+
const bytes = four ? v + 1 >> 1 : v;
|
|
215
|
+
if (o + bytes > d.length)
|
|
216
|
+
throw new Error('BMP: truncated RLE data');
|
|
217
|
+
for (let i = 0; i < v; i++)
|
|
218
|
+
put(four ? (i & 1 ? d[o + (i >> 1)] & 15 : d[o + (i >> 1)] >> 4) : d[o + i]);
|
|
219
|
+
o += bytes + (bytes & 1);
|
|
220
|
+
}
|
|
221
|
+
}
|
|
222
|
+
}
|
|
223
|
+
export function decode(d, maxPixels = MAX_PIXELS) {
|
|
224
|
+
if (!isBMP(d))
|
|
225
|
+
throw new Error('BMP: invalid header');
|
|
226
|
+
const offset = u32(d, 10);
|
|
227
|
+
return decodeDIB(d, 14, offset >= 26 && offset < d.length ? offset : null, maxPixels);
|
|
228
|
+
}
|
|
229
|
+
export function encode(rgba, width, height) {
|
|
230
|
+
if (width < 1 || height < 1)
|
|
231
|
+
throw new RangeError('BMP: dimensions must be at least 1');
|
|
232
|
+
const count = width * height;
|
|
233
|
+
if (rgba.byteOffset % 4)
|
|
234
|
+
rgba = rgba.slice(0, count * 4);
|
|
235
|
+
const pixels = new Uint32Array(rgba.buffer, rgba.byteOffset, count);
|
|
236
|
+
let opaque = true;
|
|
237
|
+
for (let i = 3; i < count * 4; i += 4)
|
|
238
|
+
if (rgba[i] !== 255) {
|
|
239
|
+
opaque = false;
|
|
240
|
+
break;
|
|
241
|
+
}
|
|
242
|
+
let colors = null;
|
|
243
|
+
if (opaque) {
|
|
244
|
+
colors = new Map();
|
|
245
|
+
for (let i = 0; i < count && colors; i++) {
|
|
246
|
+
if (!colors.has(pixels[i])) {
|
|
247
|
+
if (colors.size === 256)
|
|
248
|
+
colors = null;
|
|
249
|
+
else
|
|
250
|
+
colors.set(pixels[i], colors.size);
|
|
251
|
+
}
|
|
252
|
+
}
|
|
253
|
+
}
|
|
254
|
+
const bits = colors ? colors.size <= 2 ? 1 : colors.size <= 16 ? 4 : 8 : opaque ? 24 : 32;
|
|
255
|
+
const headerSize = bits === 32 ? 108 : 40;
|
|
256
|
+
const paletteSize = colors ? (1 << bits) * 4 : 0;
|
|
257
|
+
const stride = (width * bits + 31 >>> 5) * 4;
|
|
258
|
+
const offset = 14 + headerSize + paletteSize;
|
|
259
|
+
const size = offset + stride * height;
|
|
260
|
+
if (size > 0xffffffff)
|
|
261
|
+
throw new RangeError('BMP: image too large');
|
|
262
|
+
const out = new Uint8Array(size);
|
|
263
|
+
const view = new DataView(out.buffer);
|
|
264
|
+
out[0] = 0x42;
|
|
265
|
+
out[1] = 0x4d;
|
|
266
|
+
view.setUint32(2, size, true);
|
|
267
|
+
view.setUint32(10, offset, true);
|
|
268
|
+
view.setUint32(14, headerSize, true);
|
|
269
|
+
view.setInt32(18, width, true);
|
|
270
|
+
view.setInt32(22, height, true);
|
|
271
|
+
view.setUint16(26, 1, true);
|
|
272
|
+
view.setUint16(28, bits, true);
|
|
273
|
+
view.setUint32(30, bits === 32 ? BI_BITFIELDS : BI_RGB, true);
|
|
274
|
+
view.setUint32(34, stride * height, true);
|
|
275
|
+
view.setUint32(38, 2835, true);
|
|
276
|
+
view.setUint32(42, 2835, true);
|
|
277
|
+
if (bits === 32) {
|
|
278
|
+
view.setUint32(54, 0x00ff0000, true);
|
|
279
|
+
view.setUint32(58, 0x0000ff00, true);
|
|
280
|
+
view.setUint32(62, 0x000000ff, true);
|
|
281
|
+
view.setUint32(66, 0xff000000, true);
|
|
282
|
+
view.setUint32(70, 0x73524742, true);
|
|
283
|
+
}
|
|
284
|
+
if (colors)
|
|
285
|
+
for (const [color, index] of colors) {
|
|
286
|
+
const p = 14 + headerSize + index * 4;
|
|
287
|
+
out[p] = color >> 16 & 255;
|
|
288
|
+
out[p + 1] = color >> 8 & 255;
|
|
289
|
+
out[p + 2] = color & 255;
|
|
290
|
+
}
|
|
291
|
+
for (let y = 0; y < height; y++) {
|
|
292
|
+
const r = offset + (height - 1 - y) * stride, s = y * width * 4;
|
|
293
|
+
if (colors) {
|
|
294
|
+
const map = colors;
|
|
295
|
+
for (let x = 0; x < width; x++)
|
|
296
|
+
out[r + (x * bits >> 3)] |= map.get(pixels[y * width + x]) << (8 - bits - (x * bits & 7));
|
|
297
|
+
}
|
|
298
|
+
else if (bits === 24) {
|
|
299
|
+
for (let x = 0; x < width; x++) {
|
|
300
|
+
const p = r + x * 3, q = s + x * 4;
|
|
301
|
+
out[p] = rgba[q + 2];
|
|
302
|
+
out[p + 1] = rgba[q + 1];
|
|
303
|
+
out[p + 2] = rgba[q];
|
|
304
|
+
}
|
|
305
|
+
}
|
|
306
|
+
else {
|
|
307
|
+
for (let x = 0; x < width; x++) {
|
|
308
|
+
const p = r + x * 4, q = s + x * 4;
|
|
309
|
+
out[p] = rgba[q + 2];
|
|
310
|
+
out[p + 1] = rgba[q + 1];
|
|
311
|
+
out[p + 2] = rgba[q];
|
|
312
|
+
out[p + 3] = rgba[q + 3];
|
|
313
|
+
}
|
|
314
|
+
}
|
|
315
|
+
}
|
|
316
|
+
return out;
|
|
317
|
+
}
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
/** How linear light brighter than white becomes 8 bit sRGB: tone mapped by Reinhard's operator, or clamped */
|
|
2
|
+
export type ToneMapping = 'reinhard' | 'clamp';
|
|
3
|
+
/**
|
|
4
|
+
* Linear light RGB (3 values per pixel) as sRGB RGBA. Reinhard's extended operator ("Photographic Tone Reproduction
|
|
5
|
+
* for Digital Images", 2002, equation 4) without its automatic exposure, which makes dark scenes like nights grey:
|
|
6
|
+
* the luminance L becomes L (1 + L / Lw²) / (1 + L), the brightest pixel's (at least 1) Lw white, and the channels are
|
|
7
|
+
* scaled by that, keeping their hue; images no brighter than white stay as they are
|
|
8
|
+
*/
|
|
9
|
+
export declare function toSRGB(rgb: Float32Array, toneMapping: ToneMapping): Uint8Array;
|
|
10
|
+
/** the white of SDR content in HDR (BT.2408, cd/m²): linear light 1 */
|
|
11
|
+
export declare const REFERENCE_WHITE = 203;
|
|
12
|
+
/**
|
|
13
|
+
* Non-linear PQ (SMPTE ST 2084, transfer 16) or HLG (BT.2100, 18) RGB (3 values per pixel, primaries as CICP codes) as
|
|
14
|
+
* linear light (in place) with BT.709 primaries, 1 being the reference white (203 cd/m²): HLG is displayed as on a
|
|
15
|
+
* 1000 cd/m² display (BT.2100's OOTF, gamma 1.2)
|
|
16
|
+
*/
|
|
17
|
+
export declare function hdrToLinear(rgb: Float32Array, transfer: number, primaries: number): Float32Array;
|
|
18
|
+
/** Whether the data starts like a Radiance HDR */
|
|
19
|
+
export declare function isHDR(d: Uint8Array): boolean;
|
|
20
|
+
/** Decodes a Radiance HDR into RGBA */
|
|
21
|
+
export declare function decode(d: Uint8Array, maxPixels?: number, toneMapping?: ToneMapping): {
|
|
22
|
+
width: number;
|
|
23
|
+
height: number;
|
|
24
|
+
pixels: Uint8Array;
|
|
25
|
+
};
|
|
@@ -0,0 +1,201 @@
|
|
|
1
|
+
import { MAX_PIXELS, checkPixels } from "../util/limits.js";
|
|
2
|
+
function linearToSRGB(v) {
|
|
3
|
+
if (!(v > 0))
|
|
4
|
+
return 0;
|
|
5
|
+
if (v >= 1)
|
|
6
|
+
return 255;
|
|
7
|
+
return Math.round(255 * (v <= 0.0031308 ? 12.92 * v : 1.055 * v ** (1 / 2.4) - 0.055));
|
|
8
|
+
}
|
|
9
|
+
export function toSRGB(rgb, toneMapping) {
|
|
10
|
+
const count = rgb.length / 3;
|
|
11
|
+
const luminance = (i) => 0.2126 * rgb[i] + 0.7152 * rgb[i + 1] + 0.0722 * rgb[i + 2];
|
|
12
|
+
let white = 1;
|
|
13
|
+
if (toneMapping === 'reinhard')
|
|
14
|
+
for (let i = 0; i < rgb.length; i += 3)
|
|
15
|
+
white = Math.max(white, luminance(i));
|
|
16
|
+
const white2 = white * white;
|
|
17
|
+
const pixels = new Uint8Array(count * 4);
|
|
18
|
+
for (let i = 0, p = 0; i < rgb.length; i += 3, p += 4) {
|
|
19
|
+
let scale = 1;
|
|
20
|
+
if (white > 1) {
|
|
21
|
+
const l = Math.max(0, luminance(i));
|
|
22
|
+
scale = (1 + l / white2) / (1 + l);
|
|
23
|
+
}
|
|
24
|
+
pixels[p] = linearToSRGB(rgb[i] * scale);
|
|
25
|
+
pixels[p + 1] = linearToSRGB(rgb[i + 1] * scale);
|
|
26
|
+
pixels[p + 2] = linearToSRGB(rgb[i + 2] * scale);
|
|
27
|
+
pixels[p + 3] = 255;
|
|
28
|
+
}
|
|
29
|
+
return pixels;
|
|
30
|
+
}
|
|
31
|
+
const PRIMARIES_BT2020 = 9, PRIMARIES_P3 = 12;
|
|
32
|
+
export const REFERENCE_WHITE = 203;
|
|
33
|
+
export function hdrToLinear(rgb, transfer, primaries) {
|
|
34
|
+
const m1 = 2610 / 16384, m2 = 2523 / 4096 * 128, c1 = 3424 / 4096, c2 = 2413 / 4096 * 32, c3 = 2392 / 4096 * 32;
|
|
35
|
+
const pq = (v) => {
|
|
36
|
+
const e = (v <= 0 ? 0 : v >= 1 ? 1 : v) ** (1 / m2);
|
|
37
|
+
return 10000 / REFERENCE_WHITE * (Math.max(e - c1, 0) / (c2 - c3 * e)) ** (1 / m1);
|
|
38
|
+
};
|
|
39
|
+
const a = 0.17883277, b = 1 - 4 * a, c = 0.5 - a * Math.log(4 * a);
|
|
40
|
+
const hlg = (v) => {
|
|
41
|
+
const e = v <= 0 ? 0 : v >= 1 ? 1 : v;
|
|
42
|
+
return e <= 0.5 ? e * e / 3 : (Math.exp((e - c) / a) + b) / 12;
|
|
43
|
+
};
|
|
44
|
+
const matrix = primaries === PRIMARIES_BT2020 ? [1.660491, -0.587641, -0.072850, -0.124550, 1.132900, -0.008349, -0.018151, -0.100579, 1.118730] :
|
|
45
|
+
primaries === PRIMARIES_P3 ? [1.224940, -0.224940, 0, -0.042057, 1.042057, 0, -0.019638, -0.078636, 1.098274] : null;
|
|
46
|
+
for (let i = 0; i < rgb.length; i += 3) {
|
|
47
|
+
let r, g, bl;
|
|
48
|
+
if (transfer === 16) {
|
|
49
|
+
r = pq(rgb[i]);
|
|
50
|
+
g = pq(rgb[i + 1]);
|
|
51
|
+
bl = pq(rgb[i + 2]);
|
|
52
|
+
}
|
|
53
|
+
else {
|
|
54
|
+
r = hlg(rgb[i]);
|
|
55
|
+
g = hlg(rgb[i + 1]);
|
|
56
|
+
bl = hlg(rgb[i + 2]);
|
|
57
|
+
const ys = 0.2627 * r + 0.6780 * g + 0.0593 * bl;
|
|
58
|
+
const gain = ys > 0 ? 1000 / REFERENCE_WHITE * ys ** 0.2 : 0;
|
|
59
|
+
r *= gain;
|
|
60
|
+
g *= gain;
|
|
61
|
+
bl *= gain;
|
|
62
|
+
}
|
|
63
|
+
if (matrix) {
|
|
64
|
+
rgb[i] = matrix[0] * r + matrix[1] * g + matrix[2] * bl;
|
|
65
|
+
rgb[i + 1] = matrix[3] * r + matrix[4] * g + matrix[5] * bl;
|
|
66
|
+
rgb[i + 2] = matrix[6] * r + matrix[7] * g + matrix[8] * bl;
|
|
67
|
+
}
|
|
68
|
+
else {
|
|
69
|
+
rgb[i] = r;
|
|
70
|
+
rgb[i + 1] = g;
|
|
71
|
+
rgb[i + 2] = bl;
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
return rgb;
|
|
75
|
+
}
|
|
76
|
+
const ascii = (d, start, end) => String.fromCharCode(...d.subarray(start, end));
|
|
77
|
+
export function isHDR(d) {
|
|
78
|
+
const start = ascii(d, 0, 10);
|
|
79
|
+
return start === '#?RADIANCE' || start.startsWith('#?RGBE');
|
|
80
|
+
}
|
|
81
|
+
const XYZ_TO_RGB = (() => {
|
|
82
|
+
const inverse = ([a, b, c, d, e, f, g, h, i]) => {
|
|
83
|
+
const det = a * (e * i - f * h) - b * (d * i - f * g) + c * (d * h - e * g);
|
|
84
|
+
return [e * i - f * h, c * h - b * i, b * f - c * e, f * g - d * i, a * i - c * g, c * d - a * f, d * h - e * g, b * g - a * h, a * e - b * d].map(v => v / det);
|
|
85
|
+
};
|
|
86
|
+
const [r, g, b] = [[0.640, 0.330], [0.290, 0.600], [0.150, 0.060]].map(([x, y]) => [x / y, 1, (1 - x - y) / y]);
|
|
87
|
+
const primaries = [r[0], g[0], b[0], r[1], g[1], b[1], r[2], g[2], b[2]];
|
|
88
|
+
const inv = inverse(primaries);
|
|
89
|
+
const scale = [0, 1, 2].map(row => inv[row * 3] + inv[row * 3 + 1] + inv[row * 3 + 2]);
|
|
90
|
+
return inverse(primaries.map((v, i) => v * scale[i % 3]));
|
|
91
|
+
})();
|
|
92
|
+
export function decode(d, maxPixels = MAX_PIXELS, toneMapping = 'reinhard') {
|
|
93
|
+
if (!isHDR(d))
|
|
94
|
+
throw new Error('HDR: invalid header');
|
|
95
|
+
let o = 0, xyz = false;
|
|
96
|
+
for (;;) {
|
|
97
|
+
let end = o;
|
|
98
|
+
while (end < d.length && d[end] !== 0x0a)
|
|
99
|
+
end++;
|
|
100
|
+
if (end >= d.length)
|
|
101
|
+
throw new Error('HDR: truncated header');
|
|
102
|
+
const line = ascii(d, o, end);
|
|
103
|
+
o = end + 1;
|
|
104
|
+
if (!line)
|
|
105
|
+
break;
|
|
106
|
+
if (line.startsWith('FORMAT=')) {
|
|
107
|
+
const format = line.slice(7).trim();
|
|
108
|
+
if (format !== '32-bit_rle_rgbe' && format !== '32-bit_rle_xyze')
|
|
109
|
+
throw new Error(`HDR: unsupported format ${format}`);
|
|
110
|
+
xyz = format === '32-bit_rle_xyze';
|
|
111
|
+
}
|
|
112
|
+
}
|
|
113
|
+
let end = o;
|
|
114
|
+
while (end < d.length && d[end] !== 0x0a)
|
|
115
|
+
end++;
|
|
116
|
+
const resolution = /^([-+])([XY]) (\d+) ([-+])([XY]) (\d+)$/.exec(ascii(d, o, Math.min(end, o + 64)).trim());
|
|
117
|
+
if (!resolution || resolution[2] === resolution[5])
|
|
118
|
+
throw new Error('HDR: invalid resolution');
|
|
119
|
+
o = end + 1;
|
|
120
|
+
const [, sign1, axis1, n1, sign2, , n2] = resolution;
|
|
121
|
+
const lines = +n1, length = +n2;
|
|
122
|
+
const columns = axis1 === 'X';
|
|
123
|
+
const width = columns ? lines : length, height = columns ? length : lines;
|
|
124
|
+
if (!width || !height)
|
|
125
|
+
throw new Error(`HDR: invalid dimensions ${width}x${height}`);
|
|
126
|
+
checkPixels(width, height, maxPixels);
|
|
127
|
+
const rgb = new Float32Array(width * height * 3);
|
|
128
|
+
const line = new Uint8Array(length * 4);
|
|
129
|
+
for (let s = 0; s < lines; s++) {
|
|
130
|
+
o = readLine(d, o, line, length);
|
|
131
|
+
for (let i = 0; i < length; i++) {
|
|
132
|
+
const x = columns ? (sign1 === '+' ? s : width - 1 - s) : sign2 === '+' ? i : width - 1 - i;
|
|
133
|
+
const y = columns ? (sign2 === '-' ? i : height - 1 - i) : sign1 === '-' ? s : height - 1 - s;
|
|
134
|
+
const p = (y * width + x) * 3, e = line[i * 4 + 3];
|
|
135
|
+
if (e) {
|
|
136
|
+
const f = 2 ** (e - 136);
|
|
137
|
+
let r = line[i * 4] * f, g = line[i * 4 + 1] * f, b = line[i * 4 + 2] * f;
|
|
138
|
+
if (xyz) {
|
|
139
|
+
const m = XYZ_TO_RGB;
|
|
140
|
+
[r, g, b] = [m[0] * r + m[1] * g + m[2] * b, m[3] * r + m[4] * g + m[5] * b, m[6] * r + m[7] * g + m[8] * b];
|
|
141
|
+
}
|
|
142
|
+
rgb[p] = r;
|
|
143
|
+
rgb[p + 1] = g;
|
|
144
|
+
rgb[p + 2] = b;
|
|
145
|
+
}
|
|
146
|
+
}
|
|
147
|
+
}
|
|
148
|
+
return { width, height, pixels: toSRGB(rgb, toneMapping) };
|
|
149
|
+
}
|
|
150
|
+
function readLine(d, o, line, length) {
|
|
151
|
+
if (o + 4 > d.length)
|
|
152
|
+
throw new Error('HDR: truncated image data');
|
|
153
|
+
if (length >= 8 && length < 0x8000 && d[o] === 2 && d[o + 1] === 2 && !(d[o + 2] & 0x80)) {
|
|
154
|
+
if ((d[o + 2] << 8 | d[o + 3]) !== length)
|
|
155
|
+
throw new Error('HDR: scan line of the wrong length');
|
|
156
|
+
o += 4;
|
|
157
|
+
for (let c = 0; c < 4; c++) {
|
|
158
|
+
for (let i = 0; i < length;) {
|
|
159
|
+
if (o >= d.length)
|
|
160
|
+
throw new Error('HDR: truncated image data');
|
|
161
|
+
let n = d[o++];
|
|
162
|
+
if (n > 128) {
|
|
163
|
+
n -= 128;
|
|
164
|
+
if (i + n > length || o >= d.length)
|
|
165
|
+
throw new Error('HDR: invalid run');
|
|
166
|
+
const v = d[o++];
|
|
167
|
+
for (; n; n--)
|
|
168
|
+
line[i++ * 4 + c] = v;
|
|
169
|
+
}
|
|
170
|
+
else {
|
|
171
|
+
if (!n || i + n > length || o + n > d.length)
|
|
172
|
+
throw new Error('HDR: invalid run');
|
|
173
|
+
for (; n; n--)
|
|
174
|
+
line[i++ * 4 + c] = d[o++];
|
|
175
|
+
}
|
|
176
|
+
}
|
|
177
|
+
}
|
|
178
|
+
return o;
|
|
179
|
+
}
|
|
180
|
+
for (let i = 0, shift = 0; i < length;) {
|
|
181
|
+
if (o + 4 > d.length)
|
|
182
|
+
throw new Error('HDR: truncated image data');
|
|
183
|
+
if (d[o] === 1 && d[o + 1] === 1 && d[o + 2] === 1) {
|
|
184
|
+
if (!i)
|
|
185
|
+
throw new Error('HDR: repetition without a pixel');
|
|
186
|
+
const n = d[o + 3] * 2 ** shift;
|
|
187
|
+
if (i + n > length)
|
|
188
|
+
throw new Error('HDR: invalid run');
|
|
189
|
+
for (let k = 0; k < n; k++, i++)
|
|
190
|
+
line.copyWithin(i * 4, i * 4 - 4, i * 4);
|
|
191
|
+
shift += 8;
|
|
192
|
+
}
|
|
193
|
+
else {
|
|
194
|
+
line.set(d.subarray(o, o + 4), i * 4);
|
|
195
|
+
i++;
|
|
196
|
+
shift = 0;
|
|
197
|
+
}
|
|
198
|
+
o += 4;
|
|
199
|
+
}
|
|
200
|
+
return o;
|
|
201
|
+
}
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
import { MAX_PIXELS } from "../util/limits.js";
|
|
2
|
+
import { decodeDIB } from "./bmp.js";
|
|
3
|
+
const u16 = (d, o) => d[o] | d[o + 1] << 8;
|
|
4
|
+
const u32 = (d, o) => (d[o] | d[o + 1] << 8 | d[o + 2] << 16 | d[o + 3] << 24) >>> 0;
|
|
5
|
+
const isPNG = (d, o) => d[o] === 0x89 && d[o + 1] === 0x50 && d[o + 2] === 0x4e && d[o + 3] === 0x47;
|
|
6
|
+
export function iconType(d) {
|
|
7
|
+
if (d.length < 22 || u16(d, 0) !== 0 || (u16(d, 2) !== 1 && u16(d, 2) !== 2))
|
|
8
|
+
return null;
|
|
9
|
+
const count = u16(d, 4);
|
|
10
|
+
if (!count || d.length < 6 + count * 16 || d[9] !== 0)
|
|
11
|
+
return null;
|
|
12
|
+
const offset = u32(d, 18);
|
|
13
|
+
if (offset < 6 + count * 16 || offset + 24 > d.length)
|
|
14
|
+
return null;
|
|
15
|
+
return isPNG(d, offset) || [12, 40, 52, 56, 108, 124].includes(u32(d, offset)) ? u16(d, 2) === 1 ? 'ico' : 'cur' : null;
|
|
16
|
+
}
|
|
17
|
+
export function decode(d, maxPixels = MAX_PIXELS) {
|
|
18
|
+
if (!iconType(d))
|
|
19
|
+
throw new Error('ICO: invalid header');
|
|
20
|
+
let best = null;
|
|
21
|
+
for (let i = 0, count = u16(d, 4); i < count; i++) {
|
|
22
|
+
const e = 6 + i * 16;
|
|
23
|
+
const offset = u32(d, e + 12);
|
|
24
|
+
let size = u32(d, e + 8);
|
|
25
|
+
if (offset + 24 > d.length)
|
|
26
|
+
continue;
|
|
27
|
+
if (size < 16 || offset + size > d.length)
|
|
28
|
+
size = d.length - offset;
|
|
29
|
+
let area, bits;
|
|
30
|
+
if (isPNG(d, offset)) {
|
|
31
|
+
const view = new DataView(d.buffer, d.byteOffset + offset);
|
|
32
|
+
area = view.getUint32(16) * view.getUint32(20);
|
|
33
|
+
bits = 32;
|
|
34
|
+
}
|
|
35
|
+
else {
|
|
36
|
+
area = Math.abs(u32(d, offset + 4) * (u32(d, offset + 8) >> 1));
|
|
37
|
+
bits = u16(d, offset + 14);
|
|
38
|
+
}
|
|
39
|
+
if (!best || area > best.area || area === best.area && bits > best.bits)
|
|
40
|
+
best = { offset, size, area, bits };
|
|
41
|
+
}
|
|
42
|
+
if (!best)
|
|
43
|
+
throw new Error('ICO: no image inside the data');
|
|
44
|
+
const data = d.subarray(best.offset, best.offset + best.size);
|
|
45
|
+
if (isPNG(data, 0))
|
|
46
|
+
return { embedded: 'png', data };
|
|
47
|
+
return decodeDIB(data, 0, null, maxPixels, true);
|
|
48
|
+
}
|
package/dist/codecs/jpeg.js
CHANGED
|
@@ -931,7 +931,8 @@ function fdctPass(d, o, s) {
|
|
|
931
931
|
const tmp1 = d[o + s] + d[o + 6 * s], tmp6 = d[o + s] - d[o + 6 * s];
|
|
932
932
|
const tmp2 = d[o + 2 * s] + d[o + 5 * s], tmp5 = d[o + 2 * s] - d[o + 5 * s];
|
|
933
933
|
const tmp3 = d[o + 3 * s] + d[o + 4 * s], tmp4 = d[o + 3 * s] - d[o + 4 * s];
|
|
934
|
-
let tmp10 = tmp0 + tmp3
|
|
934
|
+
let tmp10 = tmp0 + tmp3;
|
|
935
|
+
const tmp13 = tmp0 - tmp3;
|
|
935
936
|
let tmp11 = tmp1 + tmp2, tmp12 = tmp1 - tmp2;
|
|
936
937
|
d[o] = tmp10 + tmp11;
|
|
937
938
|
d[o + 4 * s] = tmp10 - tmp11;
|