roslib-ts 1.0.0-beta.7 → 1.0.0-beta.8
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/dist/index.cjs.js +2057 -6
- package/dist/index.cjs.js.map +1 -1
- package/dist/index.d.ts +2 -0
- package/dist/index.esm.js +2057 -6
- package/dist/index.esm.js.map +1 -1
- package/dist/types/EnhancedRos.d.ts +1 -0
- package/dist/types/EnhancedRos.d.ts.map +1 -1
- package/dist/types/Ros.d.ts +1 -0
- package/dist/types/Ros.d.ts.map +1 -1
- package/dist/types/util/decompressPng.d.ts +13 -0
- package/dist/types/util/decompressPng.d.ts.map +1 -0
- package/package.json +5 -1
package/dist/index.cjs.js
CHANGED
|
@@ -60,6 +60,2041 @@ class EventEmitter {
|
|
|
60
60
|
}
|
|
61
61
|
}
|
|
62
62
|
|
|
63
|
+
// DEFLATE is a complex format; to read this code, you should probably check the RFC first:
|
|
64
|
+
// https://tools.ietf.org/html/rfc1951
|
|
65
|
+
// You may also wish to take a look at the guide I made about this program:
|
|
66
|
+
// https://gist.github.com/101arrowz/253f31eb5abc3d9275ab943003ffecad
|
|
67
|
+
// Some of the following code is similar to that of UZIP.js:
|
|
68
|
+
// https://github.com/photopea/UZIP.js
|
|
69
|
+
// However, the vast majority of the codebase has diverged from UZIP.js to increase performance and reduce bundle size.
|
|
70
|
+
// Sometimes 0 will appear where -1 would be more appropriate. This is because using a uint
|
|
71
|
+
// is better for memory in most engines (I *think*).
|
|
72
|
+
|
|
73
|
+
// aliases for shorter compressed code (most minifers don't do this)
|
|
74
|
+
var u8 = Uint8Array, u16 = Uint16Array, i32 = Int32Array;
|
|
75
|
+
// fixed length extra bits
|
|
76
|
+
var fleb = new u8([0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 2, 2, 2, 2, 3, 3, 3, 3, 4, 4, 4, 4, 5, 5, 5, 5, 0, /* unused */ 0, 0, /* impossible */ 0]);
|
|
77
|
+
// fixed distance extra bits
|
|
78
|
+
var fdeb = new u8([0, 0, 0, 0, 1, 1, 2, 2, 3, 3, 4, 4, 5, 5, 6, 6, 7, 7, 8, 8, 9, 9, 10, 10, 11, 11, 12, 12, 13, 13, /* unused */ 0, 0]);
|
|
79
|
+
// code length index map
|
|
80
|
+
var clim = new u8([16, 17, 18, 0, 8, 7, 9, 6, 10, 5, 11, 4, 12, 3, 13, 2, 14, 1, 15]);
|
|
81
|
+
// get base, reverse index map from extra bits
|
|
82
|
+
var freb = function (eb, start) {
|
|
83
|
+
var b = new u16(31);
|
|
84
|
+
for (var i = 0; i < 31; ++i) {
|
|
85
|
+
b[i] = start += 1 << eb[i - 1];
|
|
86
|
+
}
|
|
87
|
+
// numbers here are at max 18 bits
|
|
88
|
+
var r = new i32(b[30]);
|
|
89
|
+
for (var i = 1; i < 30; ++i) {
|
|
90
|
+
for (var j = b[i]; j < b[i + 1]; ++j) {
|
|
91
|
+
r[j] = ((j - b[i]) << 5) | i;
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
return { b: b, r: r };
|
|
95
|
+
};
|
|
96
|
+
var _a = freb(fleb, 2), fl = _a.b, revfl = _a.r;
|
|
97
|
+
// we can ignore the fact that the other numbers are wrong; they never happen anyway
|
|
98
|
+
fl[28] = 258, revfl[258] = 28;
|
|
99
|
+
var _b = freb(fdeb, 0), fd = _b.b;
|
|
100
|
+
// map of value to reverse (assuming 16 bits)
|
|
101
|
+
var rev = new u16(32768);
|
|
102
|
+
for (var i = 0; i < 32768; ++i) {
|
|
103
|
+
// reverse table algorithm from SO
|
|
104
|
+
var x = ((i & 0xAAAA) >> 1) | ((i & 0x5555) << 1);
|
|
105
|
+
x = ((x & 0xCCCC) >> 2) | ((x & 0x3333) << 2);
|
|
106
|
+
x = ((x & 0xF0F0) >> 4) | ((x & 0x0F0F) << 4);
|
|
107
|
+
rev[i] = (((x & 0xFF00) >> 8) | ((x & 0x00FF) << 8)) >> 1;
|
|
108
|
+
}
|
|
109
|
+
// create huffman tree from u8 "map": index -> code length for code index
|
|
110
|
+
// mb (max bits) must be at most 15
|
|
111
|
+
// TODO: optimize/split up?
|
|
112
|
+
var hMap = (function (cd, mb, r) {
|
|
113
|
+
var s = cd.length;
|
|
114
|
+
// index
|
|
115
|
+
var i = 0;
|
|
116
|
+
// u16 "map": index -> # of codes with bit length = index
|
|
117
|
+
var l = new u16(mb);
|
|
118
|
+
// length of cd must be 288 (total # of codes)
|
|
119
|
+
for (; i < s; ++i) {
|
|
120
|
+
if (cd[i])
|
|
121
|
+
++l[cd[i] - 1];
|
|
122
|
+
}
|
|
123
|
+
// u16 "map": index -> minimum code for bit length = index
|
|
124
|
+
var le = new u16(mb);
|
|
125
|
+
for (i = 1; i < mb; ++i) {
|
|
126
|
+
le[i] = (le[i - 1] + l[i - 1]) << 1;
|
|
127
|
+
}
|
|
128
|
+
var co;
|
|
129
|
+
if (r) {
|
|
130
|
+
// u16 "map": index -> number of actual bits, symbol for code
|
|
131
|
+
co = new u16(1 << mb);
|
|
132
|
+
// bits to remove for reverser
|
|
133
|
+
var rvb = 15 - mb;
|
|
134
|
+
for (i = 0; i < s; ++i) {
|
|
135
|
+
// ignore 0 lengths
|
|
136
|
+
if (cd[i]) {
|
|
137
|
+
// num encoding both symbol and bits read
|
|
138
|
+
var sv = (i << 4) | cd[i];
|
|
139
|
+
// free bits
|
|
140
|
+
var r_1 = mb - cd[i];
|
|
141
|
+
// start value
|
|
142
|
+
var v = le[cd[i] - 1]++ << r_1;
|
|
143
|
+
// m is end value
|
|
144
|
+
for (var m = v | ((1 << r_1) - 1); v <= m; ++v) {
|
|
145
|
+
// every 16 bit value starting with the code yields the same result
|
|
146
|
+
co[rev[v] >> rvb] = sv;
|
|
147
|
+
}
|
|
148
|
+
}
|
|
149
|
+
}
|
|
150
|
+
}
|
|
151
|
+
else {
|
|
152
|
+
co = new u16(s);
|
|
153
|
+
for (i = 0; i < s; ++i) {
|
|
154
|
+
if (cd[i]) {
|
|
155
|
+
co[i] = rev[le[cd[i] - 1]++] >> (15 - cd[i]);
|
|
156
|
+
}
|
|
157
|
+
}
|
|
158
|
+
}
|
|
159
|
+
return co;
|
|
160
|
+
});
|
|
161
|
+
// fixed length tree
|
|
162
|
+
var flt = new u8(288);
|
|
163
|
+
for (var i = 0; i < 144; ++i)
|
|
164
|
+
flt[i] = 8;
|
|
165
|
+
for (var i = 144; i < 256; ++i)
|
|
166
|
+
flt[i] = 9;
|
|
167
|
+
for (var i = 256; i < 280; ++i)
|
|
168
|
+
flt[i] = 7;
|
|
169
|
+
for (var i = 280; i < 288; ++i)
|
|
170
|
+
flt[i] = 8;
|
|
171
|
+
// fixed distance tree
|
|
172
|
+
var fdt = new u8(32);
|
|
173
|
+
for (var i = 0; i < 32; ++i)
|
|
174
|
+
fdt[i] = 5;
|
|
175
|
+
// fixed length map
|
|
176
|
+
var flrm = /*#__PURE__*/ hMap(flt, 9, 1);
|
|
177
|
+
// fixed distance map
|
|
178
|
+
var fdrm = /*#__PURE__*/ hMap(fdt, 5, 1);
|
|
179
|
+
// find max of array
|
|
180
|
+
var max = function (a) {
|
|
181
|
+
var m = a[0];
|
|
182
|
+
for (var i = 1; i < a.length; ++i) {
|
|
183
|
+
if (a[i] > m)
|
|
184
|
+
m = a[i];
|
|
185
|
+
}
|
|
186
|
+
return m;
|
|
187
|
+
};
|
|
188
|
+
// read d, starting at bit p and mask with m
|
|
189
|
+
var bits = function (d, p, m) {
|
|
190
|
+
var o = (p / 8) | 0;
|
|
191
|
+
return ((d[o] | (d[o + 1] << 8)) >> (p & 7)) & m;
|
|
192
|
+
};
|
|
193
|
+
// read d, starting at bit p continuing for at least 16 bits
|
|
194
|
+
var bits16 = function (d, p) {
|
|
195
|
+
var o = (p / 8) | 0;
|
|
196
|
+
return ((d[o] | (d[o + 1] << 8) | (d[o + 2] << 16)) >> (p & 7));
|
|
197
|
+
};
|
|
198
|
+
// get end of byte
|
|
199
|
+
var shft = function (p) { return ((p + 7) / 8) | 0; };
|
|
200
|
+
// typed array slice - allows garbage collector to free original reference,
|
|
201
|
+
// while being more compatible than .slice
|
|
202
|
+
var slc = function (v, s, e) {
|
|
203
|
+
if (s == null || s < 0)
|
|
204
|
+
s = 0;
|
|
205
|
+
if (e == null || e > v.length)
|
|
206
|
+
e = v.length;
|
|
207
|
+
// can't use .constructor in case user-supplied
|
|
208
|
+
return new u8(v.subarray(s, e));
|
|
209
|
+
};
|
|
210
|
+
// error codes
|
|
211
|
+
var ec = [
|
|
212
|
+
'unexpected EOF',
|
|
213
|
+
'invalid block type',
|
|
214
|
+
'invalid length/literal',
|
|
215
|
+
'invalid distance',
|
|
216
|
+
'stream finished',
|
|
217
|
+
'no stream handler',
|
|
218
|
+
,
|
|
219
|
+
'no callback',
|
|
220
|
+
'invalid UTF-8 data',
|
|
221
|
+
'extra field too long',
|
|
222
|
+
'date not in range 1980-2099',
|
|
223
|
+
'filename too long',
|
|
224
|
+
'stream finishing',
|
|
225
|
+
'invalid zip data'
|
|
226
|
+
// determined by unknown compression method
|
|
227
|
+
];
|
|
228
|
+
var err = function (ind, msg, nt) {
|
|
229
|
+
var e = new Error(msg || ec[ind]);
|
|
230
|
+
e.code = ind;
|
|
231
|
+
if (Error.captureStackTrace)
|
|
232
|
+
Error.captureStackTrace(e, err);
|
|
233
|
+
if (!nt)
|
|
234
|
+
throw e;
|
|
235
|
+
return e;
|
|
236
|
+
};
|
|
237
|
+
// expands raw DEFLATE data
|
|
238
|
+
var inflt = function (dat, st, buf, dict) {
|
|
239
|
+
// source length dict length
|
|
240
|
+
var sl = dat.length, dl = 0;
|
|
241
|
+
if (!sl || st.f && !st.l)
|
|
242
|
+
return buf || new u8(0);
|
|
243
|
+
var noBuf = !buf;
|
|
244
|
+
// have to estimate size
|
|
245
|
+
var resize = noBuf || st.i != 2;
|
|
246
|
+
// no state
|
|
247
|
+
var noSt = st.i;
|
|
248
|
+
// Assumes roughly 33% compression ratio average
|
|
249
|
+
if (noBuf)
|
|
250
|
+
buf = new u8(sl * 3);
|
|
251
|
+
// ensure buffer can fit at least l elements
|
|
252
|
+
var cbuf = function (l) {
|
|
253
|
+
var bl = buf.length;
|
|
254
|
+
// need to increase size to fit
|
|
255
|
+
if (l > bl) {
|
|
256
|
+
// Double or set to necessary, whichever is greater
|
|
257
|
+
var nbuf = new u8(Math.max(bl * 2, l));
|
|
258
|
+
nbuf.set(buf);
|
|
259
|
+
buf = nbuf;
|
|
260
|
+
}
|
|
261
|
+
};
|
|
262
|
+
// last chunk bitpos bytes
|
|
263
|
+
var final = st.f || 0, pos = st.p || 0, bt = st.b || 0, lm = st.l, dm = st.d, lbt = st.m, dbt = st.n;
|
|
264
|
+
// total bits
|
|
265
|
+
var tbts = sl * 8;
|
|
266
|
+
do {
|
|
267
|
+
if (!lm) {
|
|
268
|
+
// BFINAL - this is only 1 when last chunk is next
|
|
269
|
+
final = bits(dat, pos, 1);
|
|
270
|
+
// type: 0 = no compression, 1 = fixed huffman, 2 = dynamic huffman
|
|
271
|
+
var type = bits(dat, pos + 1, 3);
|
|
272
|
+
pos += 3;
|
|
273
|
+
if (!type) {
|
|
274
|
+
// go to end of byte boundary
|
|
275
|
+
var s = shft(pos) + 4, l = dat[s - 4] | (dat[s - 3] << 8), t = s + l;
|
|
276
|
+
if (t > sl) {
|
|
277
|
+
if (noSt)
|
|
278
|
+
err(0);
|
|
279
|
+
break;
|
|
280
|
+
}
|
|
281
|
+
// ensure size
|
|
282
|
+
if (resize)
|
|
283
|
+
cbuf(bt + l);
|
|
284
|
+
// Copy over uncompressed data
|
|
285
|
+
buf.set(dat.subarray(s, t), bt);
|
|
286
|
+
// Get new bitpos, update byte count
|
|
287
|
+
st.b = bt += l, st.p = pos = t * 8, st.f = final;
|
|
288
|
+
continue;
|
|
289
|
+
}
|
|
290
|
+
else if (type == 1)
|
|
291
|
+
lm = flrm, dm = fdrm, lbt = 9, dbt = 5;
|
|
292
|
+
else if (type == 2) {
|
|
293
|
+
// literal lengths
|
|
294
|
+
var hLit = bits(dat, pos, 31) + 257, hcLen = bits(dat, pos + 10, 15) + 4;
|
|
295
|
+
var tl = hLit + bits(dat, pos + 5, 31) + 1;
|
|
296
|
+
pos += 14;
|
|
297
|
+
// length+distance tree
|
|
298
|
+
var ldt = new u8(tl);
|
|
299
|
+
// code length tree
|
|
300
|
+
var clt = new u8(19);
|
|
301
|
+
for (var i = 0; i < hcLen; ++i) {
|
|
302
|
+
// use index map to get real code
|
|
303
|
+
clt[clim[i]] = bits(dat, pos + i * 3, 7);
|
|
304
|
+
}
|
|
305
|
+
pos += hcLen * 3;
|
|
306
|
+
// code lengths bits
|
|
307
|
+
var clb = max(clt), clbmsk = (1 << clb) - 1;
|
|
308
|
+
// code lengths map
|
|
309
|
+
var clm = hMap(clt, clb, 1);
|
|
310
|
+
for (var i = 0; i < tl;) {
|
|
311
|
+
var r = clm[bits(dat, pos, clbmsk)];
|
|
312
|
+
// bits read
|
|
313
|
+
pos += r & 15;
|
|
314
|
+
// symbol
|
|
315
|
+
var s = r >> 4;
|
|
316
|
+
// code length to copy
|
|
317
|
+
if (s < 16) {
|
|
318
|
+
ldt[i++] = s;
|
|
319
|
+
}
|
|
320
|
+
else {
|
|
321
|
+
// copy count
|
|
322
|
+
var c = 0, n = 0;
|
|
323
|
+
if (s == 16)
|
|
324
|
+
n = 3 + bits(dat, pos, 3), pos += 2, c = ldt[i - 1];
|
|
325
|
+
else if (s == 17)
|
|
326
|
+
n = 3 + bits(dat, pos, 7), pos += 3;
|
|
327
|
+
else if (s == 18)
|
|
328
|
+
n = 11 + bits(dat, pos, 127), pos += 7;
|
|
329
|
+
while (n--)
|
|
330
|
+
ldt[i++] = c;
|
|
331
|
+
}
|
|
332
|
+
}
|
|
333
|
+
// length tree distance tree
|
|
334
|
+
var lt = ldt.subarray(0, hLit), dt = ldt.subarray(hLit);
|
|
335
|
+
// max length bits
|
|
336
|
+
lbt = max(lt);
|
|
337
|
+
// max dist bits
|
|
338
|
+
dbt = max(dt);
|
|
339
|
+
lm = hMap(lt, lbt, 1);
|
|
340
|
+
dm = hMap(dt, dbt, 1);
|
|
341
|
+
}
|
|
342
|
+
else
|
|
343
|
+
err(1);
|
|
344
|
+
if (pos > tbts) {
|
|
345
|
+
if (noSt)
|
|
346
|
+
err(0);
|
|
347
|
+
break;
|
|
348
|
+
}
|
|
349
|
+
}
|
|
350
|
+
// Make sure the buffer can hold this + the largest possible addition
|
|
351
|
+
// Maximum chunk size (practically, theoretically infinite) is 2^17
|
|
352
|
+
if (resize)
|
|
353
|
+
cbuf(bt + 131072);
|
|
354
|
+
var lms = (1 << lbt) - 1, dms = (1 << dbt) - 1;
|
|
355
|
+
var lpos = pos;
|
|
356
|
+
for (;; lpos = pos) {
|
|
357
|
+
// bits read, code
|
|
358
|
+
var c = lm[bits16(dat, pos) & lms], sym = c >> 4;
|
|
359
|
+
pos += c & 15;
|
|
360
|
+
if (pos > tbts) {
|
|
361
|
+
if (noSt)
|
|
362
|
+
err(0);
|
|
363
|
+
break;
|
|
364
|
+
}
|
|
365
|
+
if (!c)
|
|
366
|
+
err(2);
|
|
367
|
+
if (sym < 256)
|
|
368
|
+
buf[bt++] = sym;
|
|
369
|
+
else if (sym == 256) {
|
|
370
|
+
lpos = pos, lm = null;
|
|
371
|
+
break;
|
|
372
|
+
}
|
|
373
|
+
else {
|
|
374
|
+
var add = sym - 254;
|
|
375
|
+
// no extra bits needed if less
|
|
376
|
+
if (sym > 264) {
|
|
377
|
+
// index
|
|
378
|
+
var i = sym - 257, b = fleb[i];
|
|
379
|
+
add = bits(dat, pos, (1 << b) - 1) + fl[i];
|
|
380
|
+
pos += b;
|
|
381
|
+
}
|
|
382
|
+
// dist
|
|
383
|
+
var d = dm[bits16(dat, pos) & dms], dsym = d >> 4;
|
|
384
|
+
if (!d)
|
|
385
|
+
err(3);
|
|
386
|
+
pos += d & 15;
|
|
387
|
+
var dt = fd[dsym];
|
|
388
|
+
if (dsym > 3) {
|
|
389
|
+
var b = fdeb[dsym];
|
|
390
|
+
dt += bits16(dat, pos) & (1 << b) - 1, pos += b;
|
|
391
|
+
}
|
|
392
|
+
if (pos > tbts) {
|
|
393
|
+
if (noSt)
|
|
394
|
+
err(0);
|
|
395
|
+
break;
|
|
396
|
+
}
|
|
397
|
+
if (resize)
|
|
398
|
+
cbuf(bt + 131072);
|
|
399
|
+
var end = bt + add;
|
|
400
|
+
if (bt < dt) {
|
|
401
|
+
var shift = dl - dt, dend = Math.min(dt, end);
|
|
402
|
+
if (shift + bt < 0)
|
|
403
|
+
err(3);
|
|
404
|
+
for (; bt < dend; ++bt)
|
|
405
|
+
buf[bt] = dict[shift + bt];
|
|
406
|
+
}
|
|
407
|
+
for (; bt < end; ++bt)
|
|
408
|
+
buf[bt] = buf[bt - dt];
|
|
409
|
+
}
|
|
410
|
+
}
|
|
411
|
+
st.l = lm, st.p = lpos, st.b = bt, st.f = final;
|
|
412
|
+
if (lm)
|
|
413
|
+
final = 1, st.m = lbt, st.d = dm, st.n = dbt;
|
|
414
|
+
} while (!final);
|
|
415
|
+
// don't reallocate for streams or user buffers
|
|
416
|
+
return bt != buf.length && noBuf ? slc(buf, 0, bt) : buf.subarray(0, bt);
|
|
417
|
+
};
|
|
418
|
+
// empty
|
|
419
|
+
var et = /*#__PURE__*/ new u8(0);
|
|
420
|
+
// zlib start
|
|
421
|
+
var zls = function (d, dict) {
|
|
422
|
+
if ((d[0] & 15) != 8 || (d[0] >> 4) > 7 || ((d[0] << 8 | d[1]) % 31))
|
|
423
|
+
err(6, 'invalid zlib data');
|
|
424
|
+
if ((d[1] >> 5 & 1) == +!dict)
|
|
425
|
+
err(6, 'invalid zlib data: ' + (d[1] & 32 ? 'need' : 'unexpected') + ' dictionary');
|
|
426
|
+
return (d[1] >> 3 & 4) + 2;
|
|
427
|
+
};
|
|
428
|
+
/**
|
|
429
|
+
* Streaming DEFLATE decompression
|
|
430
|
+
*/
|
|
431
|
+
var Inflate = /*#__PURE__*/ (function () {
|
|
432
|
+
function Inflate(opts, cb) {
|
|
433
|
+
// no StrmOpt here to avoid adding to workerizer
|
|
434
|
+
if (typeof opts == 'function')
|
|
435
|
+
cb = opts, opts = {};
|
|
436
|
+
this.ondata = cb;
|
|
437
|
+
var dict = opts && opts.dictionary && opts.dictionary.subarray(-32768);
|
|
438
|
+
this.s = { i: 0, b: dict ? dict.length : 0 };
|
|
439
|
+
this.o = new u8(32768);
|
|
440
|
+
this.p = new u8(0);
|
|
441
|
+
if (dict)
|
|
442
|
+
this.o.set(dict);
|
|
443
|
+
}
|
|
444
|
+
Inflate.prototype.e = function (c) {
|
|
445
|
+
if (!this.ondata)
|
|
446
|
+
err(5);
|
|
447
|
+
if (this.d)
|
|
448
|
+
err(4);
|
|
449
|
+
if (!this.p.length)
|
|
450
|
+
this.p = c;
|
|
451
|
+
else if (c.length) {
|
|
452
|
+
var n = new u8(this.p.length + c.length);
|
|
453
|
+
n.set(this.p), n.set(c, this.p.length), this.p = n;
|
|
454
|
+
}
|
|
455
|
+
};
|
|
456
|
+
Inflate.prototype.c = function (final) {
|
|
457
|
+
this.s.i = +(this.d = final || false);
|
|
458
|
+
var bts = this.s.b;
|
|
459
|
+
var dt = inflt(this.p, this.s, this.o);
|
|
460
|
+
this.ondata(slc(dt, bts, this.s.b), this.d);
|
|
461
|
+
this.o = slc(dt, this.s.b - 32768), this.s.b = this.o.length;
|
|
462
|
+
this.p = slc(this.p, (this.s.p / 8) | 0), this.s.p &= 7;
|
|
463
|
+
};
|
|
464
|
+
/**
|
|
465
|
+
* Pushes a chunk to be inflated
|
|
466
|
+
* @param chunk The chunk to push
|
|
467
|
+
* @param final Whether this is the final chunk
|
|
468
|
+
*/
|
|
469
|
+
Inflate.prototype.push = function (chunk, final) {
|
|
470
|
+
this.e(chunk), this.c(final);
|
|
471
|
+
};
|
|
472
|
+
return Inflate;
|
|
473
|
+
}());
|
|
474
|
+
/**
|
|
475
|
+
* Streaming Zlib decompression
|
|
476
|
+
*/
|
|
477
|
+
var Unzlib = /*#__PURE__*/ (function () {
|
|
478
|
+
function Unzlib(opts, cb) {
|
|
479
|
+
Inflate.call(this, opts, cb);
|
|
480
|
+
this.v = opts && opts.dictionary ? 2 : 1;
|
|
481
|
+
}
|
|
482
|
+
/**
|
|
483
|
+
* Pushes a chunk to be unzlibbed
|
|
484
|
+
* @param chunk The chunk to push
|
|
485
|
+
* @param final Whether this is the last chunk
|
|
486
|
+
*/
|
|
487
|
+
Unzlib.prototype.push = function (chunk, final) {
|
|
488
|
+
Inflate.prototype.e.call(this, chunk);
|
|
489
|
+
if (this.v) {
|
|
490
|
+
if (this.p.length < 6 && !final)
|
|
491
|
+
return;
|
|
492
|
+
this.p = this.p.subarray(zls(this.p, this.v - 1)), this.v = 0;
|
|
493
|
+
}
|
|
494
|
+
if (final) {
|
|
495
|
+
if (this.p.length < 4)
|
|
496
|
+
err(6, 'invalid zlib data');
|
|
497
|
+
this.p = this.p.subarray(0, -4);
|
|
498
|
+
}
|
|
499
|
+
// necessary to prevent TS from using the closure value
|
|
500
|
+
// This allows for workerization to function correctly
|
|
501
|
+
Inflate.prototype.c.call(this, final);
|
|
502
|
+
};
|
|
503
|
+
return Unzlib;
|
|
504
|
+
}());
|
|
505
|
+
/**
|
|
506
|
+
* Expands Zlib data
|
|
507
|
+
* @param data The data to decompress
|
|
508
|
+
* @param opts The decompression options
|
|
509
|
+
* @returns The decompressed version of the data
|
|
510
|
+
*/
|
|
511
|
+
function unzlibSync(data, opts) {
|
|
512
|
+
return inflt(data.subarray(zls(data, opts), -4), { i: 2 }, opts, opts);
|
|
513
|
+
}
|
|
514
|
+
// text decoder
|
|
515
|
+
var td = typeof TextDecoder != 'undefined' && /*#__PURE__*/ new TextDecoder();
|
|
516
|
+
// text decoder stream
|
|
517
|
+
var tds = 0;
|
|
518
|
+
try {
|
|
519
|
+
td.decode(et, { stream: true });
|
|
520
|
+
tds = 1;
|
|
521
|
+
}
|
|
522
|
+
catch (e) { }
|
|
523
|
+
|
|
524
|
+
/**
|
|
525
|
+
* Decode bytes to text
|
|
526
|
+
* @param bytes - Bytes to decode
|
|
527
|
+
* @param encoding - Text encoding
|
|
528
|
+
* @returns The decoded text
|
|
529
|
+
*/
|
|
530
|
+
function decode(bytes, encoding = 'utf8') {
|
|
531
|
+
const decoder = new TextDecoder(encoding);
|
|
532
|
+
return decoder.decode(bytes);
|
|
533
|
+
}
|
|
534
|
+
const encoder = new TextEncoder();
|
|
535
|
+
/**
|
|
536
|
+
* Encode text to utf8
|
|
537
|
+
* @param str - Text to encode
|
|
538
|
+
* @returns The encoded bytes
|
|
539
|
+
*/
|
|
540
|
+
function encode(str) {
|
|
541
|
+
return encoder.encode(str);
|
|
542
|
+
}
|
|
543
|
+
|
|
544
|
+
const defaultByteLength = 1024 * 8;
|
|
545
|
+
const hostBigEndian = (() => {
|
|
546
|
+
const array = new Uint8Array(4);
|
|
547
|
+
const view = new Uint32Array(array.buffer);
|
|
548
|
+
return !((view[0] = 1) & array[0]);
|
|
549
|
+
})();
|
|
550
|
+
const typedArrays = {
|
|
551
|
+
int8: globalThis.Int8Array,
|
|
552
|
+
uint8: globalThis.Uint8Array,
|
|
553
|
+
int16: globalThis.Int16Array,
|
|
554
|
+
uint16: globalThis.Uint16Array,
|
|
555
|
+
int32: globalThis.Int32Array,
|
|
556
|
+
uint32: globalThis.Uint32Array,
|
|
557
|
+
uint64: globalThis.BigUint64Array,
|
|
558
|
+
int64: globalThis.BigInt64Array,
|
|
559
|
+
float32: globalThis.Float32Array,
|
|
560
|
+
float64: globalThis.Float64Array,
|
|
561
|
+
};
|
|
562
|
+
class IOBuffer {
|
|
563
|
+
/**
|
|
564
|
+
* Reference to the internal ArrayBuffer object.
|
|
565
|
+
*/
|
|
566
|
+
buffer;
|
|
567
|
+
/**
|
|
568
|
+
* Byte length of the internal ArrayBuffer.
|
|
569
|
+
*/
|
|
570
|
+
byteLength;
|
|
571
|
+
/**
|
|
572
|
+
* Byte offset of the internal ArrayBuffer.
|
|
573
|
+
*/
|
|
574
|
+
byteOffset;
|
|
575
|
+
/**
|
|
576
|
+
* Byte length of the internal ArrayBuffer.
|
|
577
|
+
*/
|
|
578
|
+
length;
|
|
579
|
+
/**
|
|
580
|
+
* The current offset of the buffer's pointer.
|
|
581
|
+
*/
|
|
582
|
+
offset;
|
|
583
|
+
lastWrittenByte;
|
|
584
|
+
littleEndian;
|
|
585
|
+
_data;
|
|
586
|
+
_mark;
|
|
587
|
+
_marks;
|
|
588
|
+
/**
|
|
589
|
+
* Create a new IOBuffer.
|
|
590
|
+
* @param data - The data to construct the IOBuffer with.
|
|
591
|
+
* If data is a number, it will be the new buffer's length<br>
|
|
592
|
+
* If data is `undefined`, the buffer will be initialized with a default length of 8Kb<br>
|
|
593
|
+
* If data is an ArrayBuffer, SharedArrayBuffer, an ArrayBufferView (Typed Array), an IOBuffer instance,
|
|
594
|
+
* or a Node.js Buffer, a view will be created over the underlying ArrayBuffer.
|
|
595
|
+
* @param options - An object for the options.
|
|
596
|
+
* @returns A new IOBuffer instance.
|
|
597
|
+
*/
|
|
598
|
+
constructor(data = defaultByteLength, options = {}) {
|
|
599
|
+
let dataIsGiven = false;
|
|
600
|
+
if (typeof data === 'number') {
|
|
601
|
+
data = new ArrayBuffer(data);
|
|
602
|
+
}
|
|
603
|
+
else {
|
|
604
|
+
dataIsGiven = true;
|
|
605
|
+
this.lastWrittenByte = data.byteLength;
|
|
606
|
+
}
|
|
607
|
+
const offset = options.offset ? options.offset >>> 0 : 0;
|
|
608
|
+
const byteLength = data.byteLength - offset;
|
|
609
|
+
let dvOffset = offset;
|
|
610
|
+
if (ArrayBuffer.isView(data) || data instanceof IOBuffer) {
|
|
611
|
+
if (data.byteLength !== data.buffer.byteLength) {
|
|
612
|
+
dvOffset = data.byteOffset + offset;
|
|
613
|
+
}
|
|
614
|
+
data = data.buffer;
|
|
615
|
+
}
|
|
616
|
+
if (dataIsGiven) {
|
|
617
|
+
this.lastWrittenByte = byteLength;
|
|
618
|
+
}
|
|
619
|
+
else {
|
|
620
|
+
this.lastWrittenByte = 0;
|
|
621
|
+
}
|
|
622
|
+
this.buffer = data;
|
|
623
|
+
this.length = byteLength;
|
|
624
|
+
this.byteLength = byteLength;
|
|
625
|
+
this.byteOffset = dvOffset;
|
|
626
|
+
this.offset = 0;
|
|
627
|
+
this.littleEndian = true;
|
|
628
|
+
this._data = new DataView(this.buffer, dvOffset, byteLength);
|
|
629
|
+
this._mark = 0;
|
|
630
|
+
this._marks = [];
|
|
631
|
+
}
|
|
632
|
+
/**
|
|
633
|
+
* Checks if the memory allocated to the buffer is sufficient to store more
|
|
634
|
+
* bytes after the offset.
|
|
635
|
+
* @param byteLength - The needed memory in bytes.
|
|
636
|
+
* @returns `true` if there is sufficient space and `false` otherwise.
|
|
637
|
+
*/
|
|
638
|
+
available(byteLength = 1) {
|
|
639
|
+
return this.offset + byteLength <= this.length;
|
|
640
|
+
}
|
|
641
|
+
/**
|
|
642
|
+
* Check if little-endian mode is used for reading and writing multi-byte
|
|
643
|
+
* values.
|
|
644
|
+
* @returns `true` if little-endian mode is used, `false` otherwise.
|
|
645
|
+
*/
|
|
646
|
+
isLittleEndian() {
|
|
647
|
+
return this.littleEndian;
|
|
648
|
+
}
|
|
649
|
+
/**
|
|
650
|
+
* Set little-endian mode for reading and writing multi-byte values.
|
|
651
|
+
* @returns This.
|
|
652
|
+
*/
|
|
653
|
+
setLittleEndian() {
|
|
654
|
+
this.littleEndian = true;
|
|
655
|
+
return this;
|
|
656
|
+
}
|
|
657
|
+
/**
|
|
658
|
+
* Check if big-endian mode is used for reading and writing multi-byte values.
|
|
659
|
+
* @returns `true` if big-endian mode is used, `false` otherwise.
|
|
660
|
+
*/
|
|
661
|
+
isBigEndian() {
|
|
662
|
+
return !this.littleEndian;
|
|
663
|
+
}
|
|
664
|
+
/**
|
|
665
|
+
* Switches to big-endian mode for reading and writing multi-byte values.
|
|
666
|
+
* @returns This.
|
|
667
|
+
*/
|
|
668
|
+
setBigEndian() {
|
|
669
|
+
this.littleEndian = false;
|
|
670
|
+
return this;
|
|
671
|
+
}
|
|
672
|
+
/**
|
|
673
|
+
* Move the pointer n bytes forward.
|
|
674
|
+
* @param n - Number of bytes to skip.
|
|
675
|
+
* @returns This.
|
|
676
|
+
*/
|
|
677
|
+
skip(n = 1) {
|
|
678
|
+
this.offset += n;
|
|
679
|
+
return this;
|
|
680
|
+
}
|
|
681
|
+
/**
|
|
682
|
+
* Move the pointer n bytes backward.
|
|
683
|
+
* @param n - Number of bytes to move back.
|
|
684
|
+
* @returns This.
|
|
685
|
+
*/
|
|
686
|
+
back(n = 1) {
|
|
687
|
+
this.offset -= n;
|
|
688
|
+
return this;
|
|
689
|
+
}
|
|
690
|
+
/**
|
|
691
|
+
* Move the pointer to the given offset.
|
|
692
|
+
* @param offset - The offset to move to.
|
|
693
|
+
* @returns This.
|
|
694
|
+
*/
|
|
695
|
+
seek(offset) {
|
|
696
|
+
this.offset = offset;
|
|
697
|
+
return this;
|
|
698
|
+
}
|
|
699
|
+
/**
|
|
700
|
+
* Store the current pointer offset.
|
|
701
|
+
* @see {@link IOBuffer#reset}
|
|
702
|
+
* @returns This.
|
|
703
|
+
*/
|
|
704
|
+
mark() {
|
|
705
|
+
this._mark = this.offset;
|
|
706
|
+
return this;
|
|
707
|
+
}
|
|
708
|
+
/**
|
|
709
|
+
* Move the pointer back to the last pointer offset set by mark.
|
|
710
|
+
* @see {@link IOBuffer#mark}
|
|
711
|
+
* @returns This.
|
|
712
|
+
*/
|
|
713
|
+
reset() {
|
|
714
|
+
this.offset = this._mark;
|
|
715
|
+
return this;
|
|
716
|
+
}
|
|
717
|
+
/**
|
|
718
|
+
* Push the current pointer offset to the mark stack.
|
|
719
|
+
* @see {@link IOBuffer#popMark}
|
|
720
|
+
* @returns This.
|
|
721
|
+
*/
|
|
722
|
+
pushMark() {
|
|
723
|
+
this._marks.push(this.offset);
|
|
724
|
+
return this;
|
|
725
|
+
}
|
|
726
|
+
/**
|
|
727
|
+
* Pop the last pointer offset from the mark stack, and set the current
|
|
728
|
+
* pointer offset to the popped value.
|
|
729
|
+
* @see {@link IOBuffer#pushMark}
|
|
730
|
+
* @returns This.
|
|
731
|
+
*/
|
|
732
|
+
popMark() {
|
|
733
|
+
const offset = this._marks.pop();
|
|
734
|
+
if (offset === undefined) {
|
|
735
|
+
throw new Error('Mark stack empty');
|
|
736
|
+
}
|
|
737
|
+
this.seek(offset);
|
|
738
|
+
return this;
|
|
739
|
+
}
|
|
740
|
+
/**
|
|
741
|
+
* Move the pointer offset back to 0.
|
|
742
|
+
* @returns This.
|
|
743
|
+
*/
|
|
744
|
+
rewind() {
|
|
745
|
+
this.offset = 0;
|
|
746
|
+
return this;
|
|
747
|
+
}
|
|
748
|
+
/**
|
|
749
|
+
* Make sure the buffer has sufficient memory to write a given byteLength at
|
|
750
|
+
* the current pointer offset.
|
|
751
|
+
* If the buffer's memory is insufficient, this method will create a new
|
|
752
|
+
* buffer (a copy) with a length that is twice (byteLength + current offset).
|
|
753
|
+
* @param byteLength - The needed memory in bytes.
|
|
754
|
+
* @returns This.
|
|
755
|
+
*/
|
|
756
|
+
ensureAvailable(byteLength = 1) {
|
|
757
|
+
if (!this.available(byteLength)) {
|
|
758
|
+
const lengthNeeded = this.offset + byteLength;
|
|
759
|
+
const newLength = lengthNeeded * 2;
|
|
760
|
+
const newArray = new Uint8Array(newLength);
|
|
761
|
+
newArray.set(new Uint8Array(this.buffer));
|
|
762
|
+
this.buffer = newArray.buffer;
|
|
763
|
+
this.length = newLength;
|
|
764
|
+
this.byteLength = newLength;
|
|
765
|
+
this._data = new DataView(this.buffer);
|
|
766
|
+
}
|
|
767
|
+
return this;
|
|
768
|
+
}
|
|
769
|
+
/**
|
|
770
|
+
* Read a byte and return false if the byte's value is 0, or true otherwise.
|
|
771
|
+
* Moves pointer forward by one byte.
|
|
772
|
+
* @returns The read boolean.
|
|
773
|
+
*/
|
|
774
|
+
readBoolean() {
|
|
775
|
+
return this.readUint8() !== 0;
|
|
776
|
+
}
|
|
777
|
+
/**
|
|
778
|
+
* Read a signed 8-bit integer and move pointer forward by 1 byte.
|
|
779
|
+
* @returns The read byte.
|
|
780
|
+
*/
|
|
781
|
+
readInt8() {
|
|
782
|
+
return this._data.getInt8(this.offset++);
|
|
783
|
+
}
|
|
784
|
+
/**
|
|
785
|
+
* Read an unsigned 8-bit integer and move pointer forward by 1 byte.
|
|
786
|
+
* @returns The read byte.
|
|
787
|
+
*/
|
|
788
|
+
readUint8() {
|
|
789
|
+
return this._data.getUint8(this.offset++);
|
|
790
|
+
}
|
|
791
|
+
/**
|
|
792
|
+
* Alias for {@link IOBuffer#readUint8}.
|
|
793
|
+
* @returns The read byte.
|
|
794
|
+
*/
|
|
795
|
+
readByte() {
|
|
796
|
+
return this.readUint8();
|
|
797
|
+
}
|
|
798
|
+
/**
|
|
799
|
+
* Read `n` bytes and move pointer forward by `n` bytes.
|
|
800
|
+
* @param n - Number of bytes to read.
|
|
801
|
+
* @returns The read bytes.
|
|
802
|
+
*/
|
|
803
|
+
readBytes(n = 1) {
|
|
804
|
+
return this.readArray(n, 'uint8');
|
|
805
|
+
}
|
|
806
|
+
/**
|
|
807
|
+
* Creates an array of corresponding to the type `type` and size `size`.
|
|
808
|
+
* For example, type `uint8` will create a `Uint8Array`.
|
|
809
|
+
* @param size - size of the resulting array
|
|
810
|
+
* @param type - number type of elements to read
|
|
811
|
+
* @returns The read array.
|
|
812
|
+
*/
|
|
813
|
+
readArray(size, type) {
|
|
814
|
+
const bytes = typedArrays[type].BYTES_PER_ELEMENT * size;
|
|
815
|
+
const offset = this.byteOffset + this.offset;
|
|
816
|
+
const slice = this.buffer.slice(offset, offset + bytes);
|
|
817
|
+
if (this.littleEndian === hostBigEndian &&
|
|
818
|
+
type !== 'uint8' &&
|
|
819
|
+
type !== 'int8') {
|
|
820
|
+
const slice = new Uint8Array(this.buffer.slice(offset, offset + bytes));
|
|
821
|
+
slice.reverse();
|
|
822
|
+
const returnArray = new typedArrays[type](slice.buffer);
|
|
823
|
+
this.offset += bytes;
|
|
824
|
+
returnArray.reverse();
|
|
825
|
+
return returnArray;
|
|
826
|
+
}
|
|
827
|
+
const returnArray = new typedArrays[type](slice);
|
|
828
|
+
this.offset += bytes;
|
|
829
|
+
return returnArray;
|
|
830
|
+
}
|
|
831
|
+
/**
|
|
832
|
+
* Read a 16-bit signed integer and move pointer forward by 2 bytes.
|
|
833
|
+
* @returns The read value.
|
|
834
|
+
*/
|
|
835
|
+
readInt16() {
|
|
836
|
+
const value = this._data.getInt16(this.offset, this.littleEndian);
|
|
837
|
+
this.offset += 2;
|
|
838
|
+
return value;
|
|
839
|
+
}
|
|
840
|
+
/**
|
|
841
|
+
* Read a 16-bit unsigned integer and move pointer forward by 2 bytes.
|
|
842
|
+
* @returns The read value.
|
|
843
|
+
*/
|
|
844
|
+
readUint16() {
|
|
845
|
+
const value = this._data.getUint16(this.offset, this.littleEndian);
|
|
846
|
+
this.offset += 2;
|
|
847
|
+
return value;
|
|
848
|
+
}
|
|
849
|
+
/**
|
|
850
|
+
* Read a 32-bit signed integer and move pointer forward by 4 bytes.
|
|
851
|
+
* @returns The read value.
|
|
852
|
+
*/
|
|
853
|
+
readInt32() {
|
|
854
|
+
const value = this._data.getInt32(this.offset, this.littleEndian);
|
|
855
|
+
this.offset += 4;
|
|
856
|
+
return value;
|
|
857
|
+
}
|
|
858
|
+
/**
|
|
859
|
+
* Read a 32-bit unsigned integer and move pointer forward by 4 bytes.
|
|
860
|
+
* @returns The read value.
|
|
861
|
+
*/
|
|
862
|
+
readUint32() {
|
|
863
|
+
const value = this._data.getUint32(this.offset, this.littleEndian);
|
|
864
|
+
this.offset += 4;
|
|
865
|
+
return value;
|
|
866
|
+
}
|
|
867
|
+
/**
|
|
868
|
+
* Read a 32-bit floating number and move pointer forward by 4 bytes.
|
|
869
|
+
* @returns The read value.
|
|
870
|
+
*/
|
|
871
|
+
readFloat32() {
|
|
872
|
+
const value = this._data.getFloat32(this.offset, this.littleEndian);
|
|
873
|
+
this.offset += 4;
|
|
874
|
+
return value;
|
|
875
|
+
}
|
|
876
|
+
/**
|
|
877
|
+
* Read a 64-bit floating number and move pointer forward by 8 bytes.
|
|
878
|
+
* @returns The read value.
|
|
879
|
+
*/
|
|
880
|
+
readFloat64() {
|
|
881
|
+
const value = this._data.getFloat64(this.offset, this.littleEndian);
|
|
882
|
+
this.offset += 8;
|
|
883
|
+
return value;
|
|
884
|
+
}
|
|
885
|
+
/**
|
|
886
|
+
* Read a 64-bit signed integer number and move pointer forward by 8 bytes.
|
|
887
|
+
* @returns The read value.
|
|
888
|
+
*/
|
|
889
|
+
readBigInt64() {
|
|
890
|
+
const value = this._data.getBigInt64(this.offset, this.littleEndian);
|
|
891
|
+
this.offset += 8;
|
|
892
|
+
return value;
|
|
893
|
+
}
|
|
894
|
+
/**
|
|
895
|
+
* Read a 64-bit unsigned integer number and move pointer forward by 8 bytes.
|
|
896
|
+
* @returns The read value.
|
|
897
|
+
*/
|
|
898
|
+
readBigUint64() {
|
|
899
|
+
const value = this._data.getBigUint64(this.offset, this.littleEndian);
|
|
900
|
+
this.offset += 8;
|
|
901
|
+
return value;
|
|
902
|
+
}
|
|
903
|
+
/**
|
|
904
|
+
* Read a 1-byte ASCII character and move pointer forward by 1 byte.
|
|
905
|
+
* @returns The read character.
|
|
906
|
+
*/
|
|
907
|
+
readChar() {
|
|
908
|
+
// eslint-disable-next-line unicorn/prefer-code-point
|
|
909
|
+
return String.fromCharCode(this.readInt8());
|
|
910
|
+
}
|
|
911
|
+
/**
|
|
912
|
+
* Read `n` 1-byte ASCII characters and move pointer forward by `n` bytes.
|
|
913
|
+
* @param n - Number of characters to read.
|
|
914
|
+
* @returns The read characters.
|
|
915
|
+
*/
|
|
916
|
+
readChars(n = 1) {
|
|
917
|
+
let result = '';
|
|
918
|
+
for (let i = 0; i < n; i++) {
|
|
919
|
+
result += this.readChar();
|
|
920
|
+
}
|
|
921
|
+
return result;
|
|
922
|
+
}
|
|
923
|
+
/**
|
|
924
|
+
* Read the next `n` bytes, return a UTF-8 decoded string and move pointer
|
|
925
|
+
* forward by `n` bytes.
|
|
926
|
+
* @param n - Number of bytes to read.
|
|
927
|
+
* @returns The decoded string.
|
|
928
|
+
*/
|
|
929
|
+
readUtf8(n = 1) {
|
|
930
|
+
return decode(this.readBytes(n));
|
|
931
|
+
}
|
|
932
|
+
/**
|
|
933
|
+
* Read the next `n` bytes, return a string decoded with `encoding` and move pointer
|
|
934
|
+
* forward by `n` bytes.
|
|
935
|
+
* If no encoding is passed, the function is equivalent to @see {@link IOBuffer#readUtf8}
|
|
936
|
+
* @param n - Number of bytes to read.
|
|
937
|
+
* @param encoding - The encoding to use. Default is 'utf8'.
|
|
938
|
+
* @returns The decoded string.
|
|
939
|
+
*/
|
|
940
|
+
decodeText(n = 1, encoding = 'utf8') {
|
|
941
|
+
return decode(this.readBytes(n), encoding);
|
|
942
|
+
}
|
|
943
|
+
/**
|
|
944
|
+
* Write 0xff if the passed value is truthy, 0x00 otherwise and move pointer
|
|
945
|
+
* forward by 1 byte.
|
|
946
|
+
* @param value - The value to write.
|
|
947
|
+
* @returns This.
|
|
948
|
+
*/
|
|
949
|
+
writeBoolean(value) {
|
|
950
|
+
this.writeUint8(value ? 0xff : 0x00);
|
|
951
|
+
return this;
|
|
952
|
+
}
|
|
953
|
+
/**
|
|
954
|
+
* Write `value` as an 8-bit signed integer and move pointer forward by 1 byte.
|
|
955
|
+
* @param value - The value to write.
|
|
956
|
+
* @returns This.
|
|
957
|
+
*/
|
|
958
|
+
writeInt8(value) {
|
|
959
|
+
this.ensureAvailable(1);
|
|
960
|
+
this._data.setInt8(this.offset++, value);
|
|
961
|
+
this._updateLastWrittenByte();
|
|
962
|
+
return this;
|
|
963
|
+
}
|
|
964
|
+
/**
|
|
965
|
+
* Write `value` as an 8-bit unsigned integer and move pointer forward by 1
|
|
966
|
+
* byte.
|
|
967
|
+
* @param value - The value to write.
|
|
968
|
+
* @returns This.
|
|
969
|
+
*/
|
|
970
|
+
writeUint8(value) {
|
|
971
|
+
this.ensureAvailable(1);
|
|
972
|
+
this._data.setUint8(this.offset++, value);
|
|
973
|
+
this._updateLastWrittenByte();
|
|
974
|
+
return this;
|
|
975
|
+
}
|
|
976
|
+
/**
|
|
977
|
+
* An alias for {@link IOBuffer#writeUint8}.
|
|
978
|
+
* @param value - The value to write.
|
|
979
|
+
* @returns This.
|
|
980
|
+
*/
|
|
981
|
+
writeByte(value) {
|
|
982
|
+
return this.writeUint8(value);
|
|
983
|
+
}
|
|
984
|
+
/**
|
|
985
|
+
* Write all elements of `bytes` as uint8 values and move pointer forward by
|
|
986
|
+
* `bytes.length` bytes.
|
|
987
|
+
* @param bytes - The array of bytes to write.
|
|
988
|
+
* @returns This.
|
|
989
|
+
*/
|
|
990
|
+
writeBytes(bytes) {
|
|
991
|
+
this.ensureAvailable(bytes.length);
|
|
992
|
+
// eslint-disable-next-line @typescript-eslint/prefer-for-of
|
|
993
|
+
for (let i = 0; i < bytes.length; i++) {
|
|
994
|
+
this._data.setUint8(this.offset++, bytes[i]);
|
|
995
|
+
}
|
|
996
|
+
this._updateLastWrittenByte();
|
|
997
|
+
return this;
|
|
998
|
+
}
|
|
999
|
+
/**
|
|
1000
|
+
* Write `value` as a 16-bit signed integer and move pointer forward by 2
|
|
1001
|
+
* bytes.
|
|
1002
|
+
* @param value - The value to write.
|
|
1003
|
+
* @returns This.
|
|
1004
|
+
*/
|
|
1005
|
+
writeInt16(value) {
|
|
1006
|
+
this.ensureAvailable(2);
|
|
1007
|
+
this._data.setInt16(this.offset, value, this.littleEndian);
|
|
1008
|
+
this.offset += 2;
|
|
1009
|
+
this._updateLastWrittenByte();
|
|
1010
|
+
return this;
|
|
1011
|
+
}
|
|
1012
|
+
/**
|
|
1013
|
+
* Write `value` as a 16-bit unsigned integer and move pointer forward by 2
|
|
1014
|
+
* bytes.
|
|
1015
|
+
* @param value - The value to write.
|
|
1016
|
+
* @returns This.
|
|
1017
|
+
*/
|
|
1018
|
+
writeUint16(value) {
|
|
1019
|
+
this.ensureAvailable(2);
|
|
1020
|
+
this._data.setUint16(this.offset, value, this.littleEndian);
|
|
1021
|
+
this.offset += 2;
|
|
1022
|
+
this._updateLastWrittenByte();
|
|
1023
|
+
return this;
|
|
1024
|
+
}
|
|
1025
|
+
/**
|
|
1026
|
+
* Write `value` as a 32-bit signed integer and move pointer forward by 4
|
|
1027
|
+
* bytes.
|
|
1028
|
+
* @param value - The value to write.
|
|
1029
|
+
* @returns This.
|
|
1030
|
+
*/
|
|
1031
|
+
writeInt32(value) {
|
|
1032
|
+
this.ensureAvailable(4);
|
|
1033
|
+
this._data.setInt32(this.offset, value, this.littleEndian);
|
|
1034
|
+
this.offset += 4;
|
|
1035
|
+
this._updateLastWrittenByte();
|
|
1036
|
+
return this;
|
|
1037
|
+
}
|
|
1038
|
+
/**
|
|
1039
|
+
* Write `value` as a 32-bit unsigned integer and move pointer forward by 4
|
|
1040
|
+
* bytes.
|
|
1041
|
+
* @param value - The value to write.
|
|
1042
|
+
* @returns This.
|
|
1043
|
+
*/
|
|
1044
|
+
writeUint32(value) {
|
|
1045
|
+
this.ensureAvailable(4);
|
|
1046
|
+
this._data.setUint32(this.offset, value, this.littleEndian);
|
|
1047
|
+
this.offset += 4;
|
|
1048
|
+
this._updateLastWrittenByte();
|
|
1049
|
+
return this;
|
|
1050
|
+
}
|
|
1051
|
+
/**
|
|
1052
|
+
* Write `value` as a 32-bit floating number and move pointer forward by 4
|
|
1053
|
+
* bytes.
|
|
1054
|
+
* @param value - The value to write.
|
|
1055
|
+
* @returns This.
|
|
1056
|
+
*/
|
|
1057
|
+
writeFloat32(value) {
|
|
1058
|
+
this.ensureAvailable(4);
|
|
1059
|
+
this._data.setFloat32(this.offset, value, this.littleEndian);
|
|
1060
|
+
this.offset += 4;
|
|
1061
|
+
this._updateLastWrittenByte();
|
|
1062
|
+
return this;
|
|
1063
|
+
}
|
|
1064
|
+
/**
|
|
1065
|
+
* Write `value` as a 64-bit floating number and move pointer forward by 8
|
|
1066
|
+
* bytes.
|
|
1067
|
+
* @param value - The value to write.
|
|
1068
|
+
* @returns This.
|
|
1069
|
+
*/
|
|
1070
|
+
writeFloat64(value) {
|
|
1071
|
+
this.ensureAvailable(8);
|
|
1072
|
+
this._data.setFloat64(this.offset, value, this.littleEndian);
|
|
1073
|
+
this.offset += 8;
|
|
1074
|
+
this._updateLastWrittenByte();
|
|
1075
|
+
return this;
|
|
1076
|
+
}
|
|
1077
|
+
/**
|
|
1078
|
+
* Write `value` as a 64-bit signed bigint and move pointer forward by 8
|
|
1079
|
+
* bytes.
|
|
1080
|
+
* @param value - The value to write.
|
|
1081
|
+
* @returns This.
|
|
1082
|
+
*/
|
|
1083
|
+
writeBigInt64(value) {
|
|
1084
|
+
this.ensureAvailable(8);
|
|
1085
|
+
this._data.setBigInt64(this.offset, value, this.littleEndian);
|
|
1086
|
+
this.offset += 8;
|
|
1087
|
+
this._updateLastWrittenByte();
|
|
1088
|
+
return this;
|
|
1089
|
+
}
|
|
1090
|
+
/**
|
|
1091
|
+
* Write `value` as a 64-bit unsigned bigint and move pointer forward by 8
|
|
1092
|
+
* bytes.
|
|
1093
|
+
* @param value - The value to write.
|
|
1094
|
+
* @returns This.
|
|
1095
|
+
*/
|
|
1096
|
+
writeBigUint64(value) {
|
|
1097
|
+
this.ensureAvailable(8);
|
|
1098
|
+
this._data.setBigUint64(this.offset, value, this.littleEndian);
|
|
1099
|
+
this.offset += 8;
|
|
1100
|
+
this._updateLastWrittenByte();
|
|
1101
|
+
return this;
|
|
1102
|
+
}
|
|
1103
|
+
/**
|
|
1104
|
+
* Write the charCode of `str`'s first character as an 8-bit unsigned integer
|
|
1105
|
+
* and move pointer forward by 1 byte.
|
|
1106
|
+
* @param str - The character to write.
|
|
1107
|
+
* @returns This.
|
|
1108
|
+
*/
|
|
1109
|
+
writeChar(str) {
|
|
1110
|
+
// eslint-disable-next-line unicorn/prefer-code-point
|
|
1111
|
+
return this.writeUint8(str.charCodeAt(0));
|
|
1112
|
+
}
|
|
1113
|
+
/**
|
|
1114
|
+
* Write the charCodes of all `str`'s characters as 8-bit unsigned integers
|
|
1115
|
+
* and move pointer forward by `str.length` bytes.
|
|
1116
|
+
* @param str - The characters to write.
|
|
1117
|
+
* @returns This.
|
|
1118
|
+
*/
|
|
1119
|
+
writeChars(str) {
|
|
1120
|
+
for (let i = 0; i < str.length; i++) {
|
|
1121
|
+
// eslint-disable-next-line unicorn/prefer-code-point
|
|
1122
|
+
this.writeUint8(str.charCodeAt(i));
|
|
1123
|
+
}
|
|
1124
|
+
return this;
|
|
1125
|
+
}
|
|
1126
|
+
/**
|
|
1127
|
+
* UTF-8 encode and write `str` to the current pointer offset and move pointer
|
|
1128
|
+
* forward according to the encoded length.
|
|
1129
|
+
* @param str - The string to write.
|
|
1130
|
+
* @returns This.
|
|
1131
|
+
*/
|
|
1132
|
+
writeUtf8(str) {
|
|
1133
|
+
return this.writeBytes(encode(str));
|
|
1134
|
+
}
|
|
1135
|
+
/**
|
|
1136
|
+
* Export a Uint8Array view of the internal buffer.
|
|
1137
|
+
* The view starts at the byte offset and its length
|
|
1138
|
+
* is calculated to stop at the last written byte or the original length.
|
|
1139
|
+
* @returns A new Uint8Array view.
|
|
1140
|
+
*/
|
|
1141
|
+
toArray() {
|
|
1142
|
+
return new Uint8Array(this.buffer, this.byteOffset, this.lastWrittenByte);
|
|
1143
|
+
}
|
|
1144
|
+
/**
|
|
1145
|
+
* Get the total number of bytes written so far, regardless of the current offset.
|
|
1146
|
+
* @returns - Total number of bytes.
|
|
1147
|
+
*/
|
|
1148
|
+
getWrittenByteLength() {
|
|
1149
|
+
return this.lastWrittenByte - this.byteOffset;
|
|
1150
|
+
}
|
|
1151
|
+
/**
|
|
1152
|
+
* Update the last written byte offset
|
|
1153
|
+
* @private
|
|
1154
|
+
*/
|
|
1155
|
+
_updateLastWrittenByte() {
|
|
1156
|
+
if (this.offset > this.lastWrittenByte) {
|
|
1157
|
+
this.lastWrittenByte = this.offset;
|
|
1158
|
+
}
|
|
1159
|
+
}
|
|
1160
|
+
}
|
|
1161
|
+
|
|
1162
|
+
const crcTable = [];
|
|
1163
|
+
for (let n = 0; n < 256; n++) {
|
|
1164
|
+
let c = n;
|
|
1165
|
+
for (let k = 0; k < 8; k++) {
|
|
1166
|
+
if (c & 1) {
|
|
1167
|
+
c = 0xedb88320 ^ (c >>> 1);
|
|
1168
|
+
}
|
|
1169
|
+
else {
|
|
1170
|
+
c = c >>> 1;
|
|
1171
|
+
}
|
|
1172
|
+
}
|
|
1173
|
+
crcTable[n] = c;
|
|
1174
|
+
}
|
|
1175
|
+
const initialCrc = 0xffffffff;
|
|
1176
|
+
function updateCrc(currentCrc, data, length) {
|
|
1177
|
+
let c = currentCrc;
|
|
1178
|
+
for (let n = 0; n < length; n++) {
|
|
1179
|
+
c = crcTable[(c ^ data[n]) & 0xff] ^ (c >>> 8);
|
|
1180
|
+
}
|
|
1181
|
+
return c;
|
|
1182
|
+
}
|
|
1183
|
+
function crc(data, length) {
|
|
1184
|
+
return (updateCrc(initialCrc, data, length) ^ initialCrc) >>> 0;
|
|
1185
|
+
}
|
|
1186
|
+
function checkCrc(buffer, crcLength, chunkName) {
|
|
1187
|
+
const expectedCrc = buffer.readUint32();
|
|
1188
|
+
const actualCrc = crc(new Uint8Array(buffer.buffer, buffer.byteOffset + buffer.offset - crcLength - 4, crcLength), crcLength); // "- 4" because we already advanced by reading the CRC
|
|
1189
|
+
if (actualCrc !== expectedCrc) {
|
|
1190
|
+
throw new Error(`CRC mismatch for chunk ${chunkName}. Expected ${expectedCrc}, found ${actualCrc}`);
|
|
1191
|
+
}
|
|
1192
|
+
}
|
|
1193
|
+
|
|
1194
|
+
function unfilterNone(currentLine, newLine, bytesPerLine) {
|
|
1195
|
+
for (let i = 0; i < bytesPerLine; i++) {
|
|
1196
|
+
newLine[i] = currentLine[i];
|
|
1197
|
+
}
|
|
1198
|
+
}
|
|
1199
|
+
function unfilterSub(currentLine, newLine, bytesPerLine, bytesPerPixel) {
|
|
1200
|
+
let i = 0;
|
|
1201
|
+
for (; i < bytesPerPixel; i++) {
|
|
1202
|
+
// just copy first bytes
|
|
1203
|
+
newLine[i] = currentLine[i];
|
|
1204
|
+
}
|
|
1205
|
+
for (; i < bytesPerLine; i++) {
|
|
1206
|
+
newLine[i] = (currentLine[i] + newLine[i - bytesPerPixel]) & 0xff;
|
|
1207
|
+
}
|
|
1208
|
+
}
|
|
1209
|
+
function unfilterUp(currentLine, newLine, prevLine, bytesPerLine) {
|
|
1210
|
+
let i = 0;
|
|
1211
|
+
if (prevLine.length === 0) {
|
|
1212
|
+
// just copy bytes for first line
|
|
1213
|
+
for (; i < bytesPerLine; i++) {
|
|
1214
|
+
newLine[i] = currentLine[i];
|
|
1215
|
+
}
|
|
1216
|
+
}
|
|
1217
|
+
else {
|
|
1218
|
+
for (; i < bytesPerLine; i++) {
|
|
1219
|
+
newLine[i] = (currentLine[i] + prevLine[i]) & 0xff;
|
|
1220
|
+
}
|
|
1221
|
+
}
|
|
1222
|
+
}
|
|
1223
|
+
function unfilterAverage(currentLine, newLine, prevLine, bytesPerLine, bytesPerPixel) {
|
|
1224
|
+
let i = 0;
|
|
1225
|
+
if (prevLine.length === 0) {
|
|
1226
|
+
for (; i < bytesPerPixel; i++) {
|
|
1227
|
+
newLine[i] = currentLine[i];
|
|
1228
|
+
}
|
|
1229
|
+
for (; i < bytesPerLine; i++) {
|
|
1230
|
+
newLine[i] = (currentLine[i] + (newLine[i - bytesPerPixel] >> 1)) & 0xff;
|
|
1231
|
+
}
|
|
1232
|
+
}
|
|
1233
|
+
else {
|
|
1234
|
+
for (; i < bytesPerPixel; i++) {
|
|
1235
|
+
newLine[i] = (currentLine[i] + (prevLine[i] >> 1)) & 0xff;
|
|
1236
|
+
}
|
|
1237
|
+
for (; i < bytesPerLine; i++) {
|
|
1238
|
+
newLine[i] =
|
|
1239
|
+
(currentLine[i] + ((newLine[i - bytesPerPixel] + prevLine[i]) >> 1)) &
|
|
1240
|
+
0xff;
|
|
1241
|
+
}
|
|
1242
|
+
}
|
|
1243
|
+
}
|
|
1244
|
+
function unfilterPaeth(currentLine, newLine, prevLine, bytesPerLine, bytesPerPixel) {
|
|
1245
|
+
let i = 0;
|
|
1246
|
+
if (prevLine.length === 0) {
|
|
1247
|
+
for (; i < bytesPerPixel; i++) {
|
|
1248
|
+
newLine[i] = currentLine[i];
|
|
1249
|
+
}
|
|
1250
|
+
for (; i < bytesPerLine; i++) {
|
|
1251
|
+
newLine[i] = (currentLine[i] + newLine[i - bytesPerPixel]) & 0xff;
|
|
1252
|
+
}
|
|
1253
|
+
}
|
|
1254
|
+
else {
|
|
1255
|
+
for (; i < bytesPerPixel; i++) {
|
|
1256
|
+
newLine[i] = (currentLine[i] + prevLine[i]) & 0xff;
|
|
1257
|
+
}
|
|
1258
|
+
for (; i < bytesPerLine; i++) {
|
|
1259
|
+
newLine[i] =
|
|
1260
|
+
(currentLine[i] +
|
|
1261
|
+
paethPredictor(newLine[i - bytesPerPixel], prevLine[i], prevLine[i - bytesPerPixel])) &
|
|
1262
|
+
0xff;
|
|
1263
|
+
}
|
|
1264
|
+
}
|
|
1265
|
+
}
|
|
1266
|
+
function paethPredictor(a, b, c) {
|
|
1267
|
+
const p = a + b - c;
|
|
1268
|
+
const pa = Math.abs(p - a);
|
|
1269
|
+
const pb = Math.abs(p - b);
|
|
1270
|
+
const pc = Math.abs(p - c);
|
|
1271
|
+
if (pa <= pb && pa <= pc)
|
|
1272
|
+
return a;
|
|
1273
|
+
else if (pb <= pc)
|
|
1274
|
+
return b;
|
|
1275
|
+
else
|
|
1276
|
+
return c;
|
|
1277
|
+
}
|
|
1278
|
+
|
|
1279
|
+
/**
|
|
1280
|
+
* Apllies filter on scanline based on the filter type.
|
|
1281
|
+
* @param filterType - The filter type to apply.
|
|
1282
|
+
* @param currentLine - The current line of pixel data.
|
|
1283
|
+
* @param newLine - The new line of pixel data.
|
|
1284
|
+
* @param prevLine - The previous line of pixel data.
|
|
1285
|
+
* @param passLineBytes - The number of bytes in the pass line.
|
|
1286
|
+
* @param bytesPerPixel - The number of bytes per pixel.
|
|
1287
|
+
*/
|
|
1288
|
+
function applyUnfilter(filterType, currentLine, newLine, prevLine, passLineBytes, bytesPerPixel) {
|
|
1289
|
+
switch (filterType) {
|
|
1290
|
+
case 0:
|
|
1291
|
+
unfilterNone(currentLine, newLine, passLineBytes);
|
|
1292
|
+
break;
|
|
1293
|
+
case 1:
|
|
1294
|
+
unfilterSub(currentLine, newLine, passLineBytes, bytesPerPixel);
|
|
1295
|
+
break;
|
|
1296
|
+
case 2:
|
|
1297
|
+
unfilterUp(currentLine, newLine, prevLine, passLineBytes);
|
|
1298
|
+
break;
|
|
1299
|
+
case 3:
|
|
1300
|
+
unfilterAverage(currentLine, newLine, prevLine, passLineBytes, bytesPerPixel);
|
|
1301
|
+
break;
|
|
1302
|
+
case 4:
|
|
1303
|
+
unfilterPaeth(currentLine, newLine, prevLine, passLineBytes, bytesPerPixel);
|
|
1304
|
+
break;
|
|
1305
|
+
default:
|
|
1306
|
+
throw new Error(`Unsupported filter: ${filterType}`);
|
|
1307
|
+
}
|
|
1308
|
+
}
|
|
1309
|
+
|
|
1310
|
+
const uint16$1 = new Uint16Array([0x00ff]);
|
|
1311
|
+
const uint8$1 = new Uint8Array(uint16$1.buffer);
|
|
1312
|
+
const osIsLittleEndian$1 = uint8$1[0] === 0xff;
|
|
1313
|
+
/**
|
|
1314
|
+
* Decodes the Adam7 interlaced PNG data.
|
|
1315
|
+
* @param params - DecodeInterlaceNullParams
|
|
1316
|
+
* @returns - array of pixel data.
|
|
1317
|
+
*/
|
|
1318
|
+
function decodeInterlaceAdam7(params) {
|
|
1319
|
+
const { data, width, height, channels, depth } = params;
|
|
1320
|
+
// Adam7 interlacing pattern
|
|
1321
|
+
const passes = [
|
|
1322
|
+
{ x: 0, y: 0, xStep: 8, yStep: 8 }, // Pass 1
|
|
1323
|
+
{ x: 4, y: 0, xStep: 8, yStep: 8 }, // Pass 2
|
|
1324
|
+
{ x: 0, y: 4, xStep: 4, yStep: 8 }, // Pass 3
|
|
1325
|
+
{ x: 2, y: 0, xStep: 4, yStep: 4 }, // Pass 4
|
|
1326
|
+
{ x: 0, y: 2, xStep: 2, yStep: 4 }, // Pass 5
|
|
1327
|
+
{ x: 1, y: 0, xStep: 2, yStep: 2 }, // Pass 6
|
|
1328
|
+
{ x: 0, y: 1, xStep: 1, yStep: 2 }, // Pass 7
|
|
1329
|
+
];
|
|
1330
|
+
const bytesPerPixel = Math.ceil(depth / 8) * channels;
|
|
1331
|
+
const resultData = new Uint8Array(height * width * bytesPerPixel);
|
|
1332
|
+
let offset = 0;
|
|
1333
|
+
// Process each pass
|
|
1334
|
+
for (let passIndex = 0; passIndex < 7; passIndex++) {
|
|
1335
|
+
const pass = passes[passIndex];
|
|
1336
|
+
// Calculate pass dimensions
|
|
1337
|
+
const passWidth = Math.ceil((width - pass.x) / pass.xStep);
|
|
1338
|
+
const passHeight = Math.ceil((height - pass.y) / pass.yStep);
|
|
1339
|
+
if (passWidth <= 0 || passHeight <= 0)
|
|
1340
|
+
continue;
|
|
1341
|
+
const passLineBytes = passWidth * bytesPerPixel;
|
|
1342
|
+
const prevLine = new Uint8Array(passLineBytes);
|
|
1343
|
+
// Process each scanline in this pass
|
|
1344
|
+
for (let y = 0; y < passHeight; y++) {
|
|
1345
|
+
// First byte is the filter type
|
|
1346
|
+
const filterType = data[offset++];
|
|
1347
|
+
const currentLine = data.subarray(offset, offset + passLineBytes);
|
|
1348
|
+
offset += passLineBytes;
|
|
1349
|
+
// Create a new line for the unfiltered data
|
|
1350
|
+
const newLine = new Uint8Array(passLineBytes);
|
|
1351
|
+
// Apply the appropriate unfilter
|
|
1352
|
+
applyUnfilter(filterType, currentLine, newLine, prevLine, passLineBytes, bytesPerPixel);
|
|
1353
|
+
prevLine.set(newLine);
|
|
1354
|
+
for (let x = 0; x < passWidth; x++) {
|
|
1355
|
+
const outputX = pass.x + x * pass.xStep;
|
|
1356
|
+
const outputY = pass.y + y * pass.yStep;
|
|
1357
|
+
if (outputX >= width || outputY >= height)
|
|
1358
|
+
continue;
|
|
1359
|
+
for (let i = 0; i < bytesPerPixel; i++) {
|
|
1360
|
+
resultData[(outputY * width + outputX) * bytesPerPixel + i] =
|
|
1361
|
+
newLine[x * bytesPerPixel + i];
|
|
1362
|
+
}
|
|
1363
|
+
}
|
|
1364
|
+
}
|
|
1365
|
+
}
|
|
1366
|
+
if (depth === 16) {
|
|
1367
|
+
const uint16Data = new Uint16Array(resultData.buffer);
|
|
1368
|
+
if (osIsLittleEndian$1) {
|
|
1369
|
+
for (let k = 0; k < uint16Data.length; k++) {
|
|
1370
|
+
// PNG is always big endian. Swap the bytes.
|
|
1371
|
+
uint16Data[k] = swap16$1(uint16Data[k]);
|
|
1372
|
+
}
|
|
1373
|
+
}
|
|
1374
|
+
return uint16Data;
|
|
1375
|
+
}
|
|
1376
|
+
else {
|
|
1377
|
+
return resultData;
|
|
1378
|
+
}
|
|
1379
|
+
}
|
|
1380
|
+
function swap16$1(val) {
|
|
1381
|
+
return ((val & 0xff) << 8) | ((val >> 8) & 0xff);
|
|
1382
|
+
}
|
|
1383
|
+
|
|
1384
|
+
const uint16 = new Uint16Array([0x00ff]);
|
|
1385
|
+
const uint8 = new Uint8Array(uint16.buffer);
|
|
1386
|
+
const osIsLittleEndian = uint8[0] === 0xff;
|
|
1387
|
+
const empty = new Uint8Array(0);
|
|
1388
|
+
function decodeInterlaceNull(params) {
|
|
1389
|
+
const { data, width, height, channels, depth } = params;
|
|
1390
|
+
const bytesPerPixel = Math.ceil(depth / 8) * channels;
|
|
1391
|
+
const bytesPerLine = Math.ceil((depth / 8) * channels * width);
|
|
1392
|
+
const newData = new Uint8Array(height * bytesPerLine);
|
|
1393
|
+
let prevLine = empty;
|
|
1394
|
+
let offset = 0;
|
|
1395
|
+
let currentLine;
|
|
1396
|
+
let newLine;
|
|
1397
|
+
for (let i = 0; i < height; i++) {
|
|
1398
|
+
currentLine = data.subarray(offset + 1, offset + 1 + bytesPerLine);
|
|
1399
|
+
newLine = newData.subarray(i * bytesPerLine, (i + 1) * bytesPerLine);
|
|
1400
|
+
switch (data[offset]) {
|
|
1401
|
+
case 0:
|
|
1402
|
+
unfilterNone(currentLine, newLine, bytesPerLine);
|
|
1403
|
+
break;
|
|
1404
|
+
case 1:
|
|
1405
|
+
unfilterSub(currentLine, newLine, bytesPerLine, bytesPerPixel);
|
|
1406
|
+
break;
|
|
1407
|
+
case 2:
|
|
1408
|
+
unfilterUp(currentLine, newLine, prevLine, bytesPerLine);
|
|
1409
|
+
break;
|
|
1410
|
+
case 3:
|
|
1411
|
+
unfilterAverage(currentLine, newLine, prevLine, bytesPerLine, bytesPerPixel);
|
|
1412
|
+
break;
|
|
1413
|
+
case 4:
|
|
1414
|
+
unfilterPaeth(currentLine, newLine, prevLine, bytesPerLine, bytesPerPixel);
|
|
1415
|
+
break;
|
|
1416
|
+
default:
|
|
1417
|
+
throw new Error(`Unsupported filter: ${data[offset]}`);
|
|
1418
|
+
}
|
|
1419
|
+
prevLine = newLine;
|
|
1420
|
+
offset += bytesPerLine + 1;
|
|
1421
|
+
}
|
|
1422
|
+
if (depth === 16) {
|
|
1423
|
+
const uint16Data = new Uint16Array(newData.buffer);
|
|
1424
|
+
if (osIsLittleEndian) {
|
|
1425
|
+
for (let k = 0; k < uint16Data.length; k++) {
|
|
1426
|
+
// PNG is always big endian. Swap the bytes.
|
|
1427
|
+
uint16Data[k] = swap16(uint16Data[k]);
|
|
1428
|
+
}
|
|
1429
|
+
}
|
|
1430
|
+
return uint16Data;
|
|
1431
|
+
}
|
|
1432
|
+
else {
|
|
1433
|
+
return newData;
|
|
1434
|
+
}
|
|
1435
|
+
}
|
|
1436
|
+
function swap16(val) {
|
|
1437
|
+
return ((val & 0xff) << 8) | ((val >> 8) & 0xff);
|
|
1438
|
+
}
|
|
1439
|
+
|
|
1440
|
+
// https://www.w3.org/TR/PNG/#5PNG-file-signature
|
|
1441
|
+
const pngSignature = Uint8Array.of(137, 80, 78, 71, 13, 10, 26, 10);
|
|
1442
|
+
function checkSignature(buffer) {
|
|
1443
|
+
if (!hasPngSignature(buffer.readBytes(pngSignature.length))) {
|
|
1444
|
+
throw new Error('wrong PNG signature');
|
|
1445
|
+
}
|
|
1446
|
+
}
|
|
1447
|
+
function hasPngSignature(array) {
|
|
1448
|
+
if (array.length < pngSignature.length) {
|
|
1449
|
+
return false;
|
|
1450
|
+
}
|
|
1451
|
+
for (let i = 0; i < pngSignature.length; i++) {
|
|
1452
|
+
if (array[i] !== pngSignature[i]) {
|
|
1453
|
+
return false;
|
|
1454
|
+
}
|
|
1455
|
+
}
|
|
1456
|
+
return true;
|
|
1457
|
+
}
|
|
1458
|
+
|
|
1459
|
+
// https://www.w3.org/TR/png/#11tEXt
|
|
1460
|
+
const textChunkName = 'tEXt';
|
|
1461
|
+
const NULL = 0;
|
|
1462
|
+
const latin1Decoder = new TextDecoder('latin1');
|
|
1463
|
+
function validateKeyword(keyword) {
|
|
1464
|
+
validateLatin1(keyword);
|
|
1465
|
+
if (keyword.length === 0 || keyword.length > 79) {
|
|
1466
|
+
throw new Error('keyword length must be between 1 and 79');
|
|
1467
|
+
}
|
|
1468
|
+
}
|
|
1469
|
+
// eslint-disable-next-line no-control-regex
|
|
1470
|
+
const latin1Regex = /^[\u0000-\u00FF]*$/;
|
|
1471
|
+
function validateLatin1(text) {
|
|
1472
|
+
if (!latin1Regex.test(text)) {
|
|
1473
|
+
throw new Error('invalid latin1 text');
|
|
1474
|
+
}
|
|
1475
|
+
}
|
|
1476
|
+
function decodetEXt(text, buffer, length) {
|
|
1477
|
+
const keyword = readKeyword(buffer);
|
|
1478
|
+
text[keyword] = readLatin1(buffer, length - keyword.length - 1);
|
|
1479
|
+
}
|
|
1480
|
+
// https://www.w3.org/TR/png/#11keywords
|
|
1481
|
+
function readKeyword(buffer) {
|
|
1482
|
+
buffer.mark();
|
|
1483
|
+
while (buffer.readByte() !== NULL) {
|
|
1484
|
+
/* advance */
|
|
1485
|
+
}
|
|
1486
|
+
const end = buffer.offset;
|
|
1487
|
+
buffer.reset();
|
|
1488
|
+
const keyword = latin1Decoder.decode(buffer.readBytes(end - buffer.offset - 1));
|
|
1489
|
+
// NULL
|
|
1490
|
+
buffer.skip(1);
|
|
1491
|
+
validateKeyword(keyword);
|
|
1492
|
+
return keyword;
|
|
1493
|
+
}
|
|
1494
|
+
function readLatin1(buffer, length) {
|
|
1495
|
+
return latin1Decoder.decode(buffer.readBytes(length));
|
|
1496
|
+
}
|
|
1497
|
+
|
|
1498
|
+
const ColorType = {
|
|
1499
|
+
UNKNOWN: -1,
|
|
1500
|
+
GREYSCALE: 0,
|
|
1501
|
+
TRUECOLOUR: 2,
|
|
1502
|
+
INDEXED_COLOUR: 3,
|
|
1503
|
+
GREYSCALE_ALPHA: 4,
|
|
1504
|
+
TRUECOLOUR_ALPHA: 6,
|
|
1505
|
+
};
|
|
1506
|
+
const CompressionMethod = {
|
|
1507
|
+
UNKNOWN: -1,
|
|
1508
|
+
DEFLATE: 0,
|
|
1509
|
+
};
|
|
1510
|
+
const FilterMethod = {
|
|
1511
|
+
UNKNOWN: -1,
|
|
1512
|
+
ADAPTIVE: 0,
|
|
1513
|
+
};
|
|
1514
|
+
const InterlaceMethod = {
|
|
1515
|
+
UNKNOWN: -1,
|
|
1516
|
+
NO_INTERLACE: 0,
|
|
1517
|
+
ADAM7: 1,
|
|
1518
|
+
};
|
|
1519
|
+
const DisposeOpType = {
|
|
1520
|
+
NONE: 0,
|
|
1521
|
+
BACKGROUND: 1,
|
|
1522
|
+
PREVIOUS: 2,
|
|
1523
|
+
};
|
|
1524
|
+
const BlendOpType = {
|
|
1525
|
+
SOURCE: 0,
|
|
1526
|
+
OVER: 1,
|
|
1527
|
+
};
|
|
1528
|
+
|
|
1529
|
+
class PngDecoder extends IOBuffer {
|
|
1530
|
+
_checkCrc;
|
|
1531
|
+
_inflator;
|
|
1532
|
+
_png;
|
|
1533
|
+
_apng;
|
|
1534
|
+
_end;
|
|
1535
|
+
_hasPalette;
|
|
1536
|
+
_palette;
|
|
1537
|
+
_hasTransparency;
|
|
1538
|
+
_transparency;
|
|
1539
|
+
_compressionMethod;
|
|
1540
|
+
_filterMethod;
|
|
1541
|
+
_interlaceMethod;
|
|
1542
|
+
_colorType;
|
|
1543
|
+
_isAnimated;
|
|
1544
|
+
_numberOfFrames;
|
|
1545
|
+
_numberOfPlays;
|
|
1546
|
+
_frames;
|
|
1547
|
+
_writingDataChunks;
|
|
1548
|
+
_chunks;
|
|
1549
|
+
_inflatorResult;
|
|
1550
|
+
constructor(data, options = {}) {
|
|
1551
|
+
super(data);
|
|
1552
|
+
const { checkCrc = false } = options;
|
|
1553
|
+
this._checkCrc = checkCrc;
|
|
1554
|
+
this._inflator = new Unzlib((chunk, final) => {
|
|
1555
|
+
this._chunks.push(chunk);
|
|
1556
|
+
if (final) {
|
|
1557
|
+
const totalLength = this._chunks.reduce((sum, c) => sum + c.length, 0);
|
|
1558
|
+
this._inflatorResult = new Uint8Array(totalLength);
|
|
1559
|
+
let offset = 0;
|
|
1560
|
+
for (const chunk of this._chunks) {
|
|
1561
|
+
this._inflatorResult.set(chunk, offset);
|
|
1562
|
+
offset += chunk.length;
|
|
1563
|
+
}
|
|
1564
|
+
this._chunks = [];
|
|
1565
|
+
}
|
|
1566
|
+
});
|
|
1567
|
+
this._chunks = [];
|
|
1568
|
+
this._png = {
|
|
1569
|
+
width: -1,
|
|
1570
|
+
height: -1,
|
|
1571
|
+
channels: -1,
|
|
1572
|
+
data: new Uint8Array(0),
|
|
1573
|
+
depth: 1,
|
|
1574
|
+
text: {},
|
|
1575
|
+
};
|
|
1576
|
+
this._apng = {
|
|
1577
|
+
width: -1,
|
|
1578
|
+
height: -1,
|
|
1579
|
+
channels: -1,
|
|
1580
|
+
depth: 1,
|
|
1581
|
+
numberOfFrames: 1,
|
|
1582
|
+
numberOfPlays: 0,
|
|
1583
|
+
text: {},
|
|
1584
|
+
frames: [],
|
|
1585
|
+
};
|
|
1586
|
+
this._end = false;
|
|
1587
|
+
this._hasPalette = false;
|
|
1588
|
+
this._palette = [];
|
|
1589
|
+
this._hasTransparency = false;
|
|
1590
|
+
this._transparency = new Uint16Array(0);
|
|
1591
|
+
this._compressionMethod = CompressionMethod.UNKNOWN;
|
|
1592
|
+
this._filterMethod = FilterMethod.UNKNOWN;
|
|
1593
|
+
this._interlaceMethod = InterlaceMethod.UNKNOWN;
|
|
1594
|
+
this._colorType = ColorType.UNKNOWN;
|
|
1595
|
+
this._isAnimated = false;
|
|
1596
|
+
this._numberOfFrames = 1;
|
|
1597
|
+
this._numberOfPlays = 0;
|
|
1598
|
+
this._frames = [];
|
|
1599
|
+
this._writingDataChunks = false;
|
|
1600
|
+
this._inflatorResult = new Uint8Array(0);
|
|
1601
|
+
// PNG is always big endian
|
|
1602
|
+
// https://www.w3.org/TR/PNG/#7Integers-and-byte-order
|
|
1603
|
+
this.setBigEndian();
|
|
1604
|
+
}
|
|
1605
|
+
decode() {
|
|
1606
|
+
checkSignature(this);
|
|
1607
|
+
while (!this._end) {
|
|
1608
|
+
const length = this.readUint32();
|
|
1609
|
+
const type = this.readChars(4);
|
|
1610
|
+
this.decodeChunk(length, type);
|
|
1611
|
+
}
|
|
1612
|
+
this._inflator.push(new Uint8Array(0), true);
|
|
1613
|
+
this.decodeImage();
|
|
1614
|
+
return this._png;
|
|
1615
|
+
}
|
|
1616
|
+
decodeApng() {
|
|
1617
|
+
checkSignature(this);
|
|
1618
|
+
while (!this._end) {
|
|
1619
|
+
const length = this.readUint32();
|
|
1620
|
+
const type = this.readChars(4);
|
|
1621
|
+
this.decodeApngChunk(length, type);
|
|
1622
|
+
}
|
|
1623
|
+
this.decodeApngImage();
|
|
1624
|
+
return this._apng;
|
|
1625
|
+
}
|
|
1626
|
+
// https://www.w3.org/TR/PNG/#5Chunk-layout
|
|
1627
|
+
decodeChunk(length, type) {
|
|
1628
|
+
const offset = this.offset;
|
|
1629
|
+
switch (type) {
|
|
1630
|
+
// 11.2 Critical chunks
|
|
1631
|
+
case 'IHDR': // 11.2.2 IHDR Image header
|
|
1632
|
+
this.decodeIHDR();
|
|
1633
|
+
break;
|
|
1634
|
+
case 'PLTE': // 11.2.3 PLTE Palette
|
|
1635
|
+
this.decodePLTE(length);
|
|
1636
|
+
break;
|
|
1637
|
+
case 'IDAT': // 11.2.4 IDAT Image data
|
|
1638
|
+
this.decodeIDAT(length);
|
|
1639
|
+
break;
|
|
1640
|
+
case 'IEND': // 11.2.5 IEND Image trailer
|
|
1641
|
+
this._end = true;
|
|
1642
|
+
break;
|
|
1643
|
+
// 11.3 Ancillary chunks
|
|
1644
|
+
case 'tRNS': // 11.3.2.1 tRNS Transparency
|
|
1645
|
+
this.decodetRNS(length);
|
|
1646
|
+
break;
|
|
1647
|
+
case 'iCCP': // 11.3.3.3 iCCP Embedded ICC profile
|
|
1648
|
+
this.decodeiCCP(length);
|
|
1649
|
+
break;
|
|
1650
|
+
case textChunkName: // 11.3.4.3 tEXt Textual data
|
|
1651
|
+
decodetEXt(this._png.text, this, length);
|
|
1652
|
+
break;
|
|
1653
|
+
case 'pHYs': // 11.3.5.3 pHYs Physical pixel dimensions
|
|
1654
|
+
this.decodepHYs();
|
|
1655
|
+
break;
|
|
1656
|
+
default:
|
|
1657
|
+
this.skip(length);
|
|
1658
|
+
break;
|
|
1659
|
+
}
|
|
1660
|
+
if (this.offset - offset !== length) {
|
|
1661
|
+
throw new Error(`Length mismatch while decoding chunk ${type}`);
|
|
1662
|
+
}
|
|
1663
|
+
if (this._checkCrc) {
|
|
1664
|
+
checkCrc(this, length + 4, type);
|
|
1665
|
+
}
|
|
1666
|
+
else {
|
|
1667
|
+
this.skip(4);
|
|
1668
|
+
}
|
|
1669
|
+
}
|
|
1670
|
+
decodeApngChunk(length, type) {
|
|
1671
|
+
const offset = this.offset;
|
|
1672
|
+
if (type !== 'fdAT' && type !== 'IDAT' && this._writingDataChunks) {
|
|
1673
|
+
this.pushDataToFrame();
|
|
1674
|
+
}
|
|
1675
|
+
switch (type) {
|
|
1676
|
+
case 'acTL':
|
|
1677
|
+
this.decodeACTL();
|
|
1678
|
+
break;
|
|
1679
|
+
case 'fcTL':
|
|
1680
|
+
this.decodeFCTL();
|
|
1681
|
+
break;
|
|
1682
|
+
case 'fdAT':
|
|
1683
|
+
this.decodeFDAT(length);
|
|
1684
|
+
break;
|
|
1685
|
+
default:
|
|
1686
|
+
this.decodeChunk(length, type);
|
|
1687
|
+
this.offset = offset + length;
|
|
1688
|
+
break;
|
|
1689
|
+
}
|
|
1690
|
+
if (this.offset - offset !== length) {
|
|
1691
|
+
throw new Error(`Length mismatch while decoding chunk ${type}`);
|
|
1692
|
+
}
|
|
1693
|
+
if (this._checkCrc) {
|
|
1694
|
+
checkCrc(this, length + 4, type);
|
|
1695
|
+
}
|
|
1696
|
+
else {
|
|
1697
|
+
this.skip(4);
|
|
1698
|
+
}
|
|
1699
|
+
}
|
|
1700
|
+
// https://www.w3.org/TR/PNG/#11IHDR
|
|
1701
|
+
decodeIHDR() {
|
|
1702
|
+
const image = this._png;
|
|
1703
|
+
image.width = this.readUint32();
|
|
1704
|
+
image.height = this.readUint32();
|
|
1705
|
+
image.depth = checkBitDepth(this.readUint8());
|
|
1706
|
+
const colorType = this.readUint8();
|
|
1707
|
+
this._colorType = colorType;
|
|
1708
|
+
let channels;
|
|
1709
|
+
switch (colorType) {
|
|
1710
|
+
case ColorType.GREYSCALE:
|
|
1711
|
+
channels = 1;
|
|
1712
|
+
break;
|
|
1713
|
+
case ColorType.TRUECOLOUR:
|
|
1714
|
+
channels = 3;
|
|
1715
|
+
break;
|
|
1716
|
+
case ColorType.INDEXED_COLOUR:
|
|
1717
|
+
channels = 1;
|
|
1718
|
+
break;
|
|
1719
|
+
case ColorType.GREYSCALE_ALPHA:
|
|
1720
|
+
channels = 2;
|
|
1721
|
+
break;
|
|
1722
|
+
case ColorType.TRUECOLOUR_ALPHA:
|
|
1723
|
+
channels = 4;
|
|
1724
|
+
break;
|
|
1725
|
+
// Kept for exhaustiveness.
|
|
1726
|
+
// eslint-disable-next-line unicorn/no-useless-switch-case
|
|
1727
|
+
case ColorType.UNKNOWN:
|
|
1728
|
+
default:
|
|
1729
|
+
throw new Error(`Unknown color type: ${colorType}`);
|
|
1730
|
+
}
|
|
1731
|
+
this._png.channels = channels;
|
|
1732
|
+
this._compressionMethod = this.readUint8();
|
|
1733
|
+
if (this._compressionMethod !== CompressionMethod.DEFLATE) {
|
|
1734
|
+
throw new Error(`Unsupported compression method: ${this._compressionMethod}`);
|
|
1735
|
+
}
|
|
1736
|
+
this._filterMethod = this.readUint8();
|
|
1737
|
+
this._interlaceMethod = this.readUint8();
|
|
1738
|
+
}
|
|
1739
|
+
decodeACTL() {
|
|
1740
|
+
this._numberOfFrames = this.readUint32();
|
|
1741
|
+
this._numberOfPlays = this.readUint32();
|
|
1742
|
+
this._isAnimated = true;
|
|
1743
|
+
}
|
|
1744
|
+
decodeFCTL() {
|
|
1745
|
+
const image = {
|
|
1746
|
+
sequenceNumber: this.readUint32(),
|
|
1747
|
+
width: this.readUint32(),
|
|
1748
|
+
height: this.readUint32(),
|
|
1749
|
+
xOffset: this.readUint32(),
|
|
1750
|
+
yOffset: this.readUint32(),
|
|
1751
|
+
delayNumber: this.readUint16(),
|
|
1752
|
+
delayDenominator: this.readUint16(),
|
|
1753
|
+
disposeOp: this.readUint8(),
|
|
1754
|
+
blendOp: this.readUint8(),
|
|
1755
|
+
data: new Uint8Array(0),
|
|
1756
|
+
};
|
|
1757
|
+
this._frames.push(image);
|
|
1758
|
+
}
|
|
1759
|
+
// https://www.w3.org/TR/PNG/#11PLTE
|
|
1760
|
+
decodePLTE(length) {
|
|
1761
|
+
if (length % 3 !== 0) {
|
|
1762
|
+
throw new RangeError(`PLTE field length must be a multiple of 3. Got ${length}`);
|
|
1763
|
+
}
|
|
1764
|
+
const l = length / 3;
|
|
1765
|
+
this._hasPalette = true;
|
|
1766
|
+
const palette = [];
|
|
1767
|
+
this._palette = palette;
|
|
1768
|
+
for (let i = 0; i < l; i++) {
|
|
1769
|
+
palette.push([this.readUint8(), this.readUint8(), this.readUint8()]);
|
|
1770
|
+
}
|
|
1771
|
+
}
|
|
1772
|
+
// https://www.w3.org/TR/PNG/#11IDAT
|
|
1773
|
+
decodeIDAT(length) {
|
|
1774
|
+
this._writingDataChunks = true;
|
|
1775
|
+
const dataLength = length;
|
|
1776
|
+
const dataOffset = this.offset + this.byteOffset;
|
|
1777
|
+
try {
|
|
1778
|
+
this._inflator.push(new Uint8Array(this.buffer, dataOffset, dataLength), false);
|
|
1779
|
+
}
|
|
1780
|
+
catch (error) {
|
|
1781
|
+
throw new Error('Error while decompressing the data:', { cause: error });
|
|
1782
|
+
}
|
|
1783
|
+
this.skip(length);
|
|
1784
|
+
}
|
|
1785
|
+
decodeFDAT(length) {
|
|
1786
|
+
this._writingDataChunks = true;
|
|
1787
|
+
let dataLength = length;
|
|
1788
|
+
let dataOffset = this.offset + this.byteOffset;
|
|
1789
|
+
dataOffset += 4;
|
|
1790
|
+
dataLength -= 4;
|
|
1791
|
+
try {
|
|
1792
|
+
this._inflator.push(new Uint8Array(this.buffer, dataOffset, dataLength), false);
|
|
1793
|
+
}
|
|
1794
|
+
catch (error) {
|
|
1795
|
+
throw new Error('Error while decompressing the data:', { cause: error });
|
|
1796
|
+
}
|
|
1797
|
+
this.skip(length);
|
|
1798
|
+
}
|
|
1799
|
+
// https://www.w3.org/TR/PNG/#11tRNS
|
|
1800
|
+
decodetRNS(length) {
|
|
1801
|
+
switch (this._colorType) {
|
|
1802
|
+
case ColorType.GREYSCALE:
|
|
1803
|
+
case ColorType.TRUECOLOUR: {
|
|
1804
|
+
if (length % 2 !== 0) {
|
|
1805
|
+
throw new RangeError(`tRNS chunk length must be a multiple of 2. Got ${length}`);
|
|
1806
|
+
}
|
|
1807
|
+
if (length / 2 > this._png.width * this._png.height) {
|
|
1808
|
+
throw new Error(`tRNS chunk contains more alpha values than there are pixels (${length / 2} vs ${this._png.width * this._png.height})`);
|
|
1809
|
+
}
|
|
1810
|
+
this._hasTransparency = true;
|
|
1811
|
+
this._transparency = new Uint16Array(length / 2);
|
|
1812
|
+
for (let i = 0; i < length / 2; i++) {
|
|
1813
|
+
this._transparency[i] = this.readUint16();
|
|
1814
|
+
}
|
|
1815
|
+
break;
|
|
1816
|
+
}
|
|
1817
|
+
case ColorType.INDEXED_COLOUR: {
|
|
1818
|
+
if (length > this._palette.length) {
|
|
1819
|
+
throw new Error(`tRNS chunk contains more alpha values than there are palette colors (${length} vs ${this._palette.length})`);
|
|
1820
|
+
}
|
|
1821
|
+
let i = 0;
|
|
1822
|
+
for (; i < length; i++) {
|
|
1823
|
+
const alpha = this.readByte();
|
|
1824
|
+
this._palette[i].push(alpha);
|
|
1825
|
+
}
|
|
1826
|
+
for (; i < this._palette.length; i++) {
|
|
1827
|
+
this._palette[i].push(255);
|
|
1828
|
+
}
|
|
1829
|
+
break;
|
|
1830
|
+
}
|
|
1831
|
+
// Kept for exhaustiveness.
|
|
1832
|
+
/* eslint-disable unicorn/no-useless-switch-case */
|
|
1833
|
+
case ColorType.UNKNOWN:
|
|
1834
|
+
case ColorType.GREYSCALE_ALPHA:
|
|
1835
|
+
case ColorType.TRUECOLOUR_ALPHA:
|
|
1836
|
+
default: {
|
|
1837
|
+
throw new Error(`tRNS chunk is not supported for color type ${this._colorType}`);
|
|
1838
|
+
}
|
|
1839
|
+
/* eslint-enable unicorn/no-useless-switch-case */
|
|
1840
|
+
}
|
|
1841
|
+
}
|
|
1842
|
+
// https://www.w3.org/TR/PNG/#11iCCP
|
|
1843
|
+
decodeiCCP(length) {
|
|
1844
|
+
const name = readKeyword(this);
|
|
1845
|
+
const compressionMethod = this.readUint8();
|
|
1846
|
+
if (compressionMethod !== CompressionMethod.DEFLATE) {
|
|
1847
|
+
throw new Error(`Unsupported iCCP compression method: ${compressionMethod}`);
|
|
1848
|
+
}
|
|
1849
|
+
const compressedProfile = this.readBytes(length - name.length - 2);
|
|
1850
|
+
this._png.iccEmbeddedProfile = {
|
|
1851
|
+
name,
|
|
1852
|
+
profile: unzlibSync(compressedProfile),
|
|
1853
|
+
};
|
|
1854
|
+
}
|
|
1855
|
+
// https://www.w3.org/TR/PNG/#11pHYs
|
|
1856
|
+
decodepHYs() {
|
|
1857
|
+
const ppuX = this.readUint32();
|
|
1858
|
+
const ppuY = this.readUint32();
|
|
1859
|
+
const unitSpecifier = this.readByte();
|
|
1860
|
+
this._png.resolution = {
|
|
1861
|
+
x: ppuX,
|
|
1862
|
+
y: ppuY,
|
|
1863
|
+
unit: unitSpecifier,
|
|
1864
|
+
};
|
|
1865
|
+
}
|
|
1866
|
+
decodeApngImage() {
|
|
1867
|
+
this._apng.width = this._png.width;
|
|
1868
|
+
this._apng.height = this._png.height;
|
|
1869
|
+
this._apng.channels = this._png.channels;
|
|
1870
|
+
this._apng.depth = this._png.depth;
|
|
1871
|
+
this._apng.numberOfFrames = this._numberOfFrames;
|
|
1872
|
+
this._apng.numberOfPlays = this._numberOfPlays;
|
|
1873
|
+
this._apng.text = this._png.text;
|
|
1874
|
+
this._apng.resolution = this._png.resolution;
|
|
1875
|
+
for (let i = 0; i < this._numberOfFrames; i++) {
|
|
1876
|
+
const newFrame = {
|
|
1877
|
+
sequenceNumber: this._frames[i].sequenceNumber,
|
|
1878
|
+
delayNumber: this._frames[i].delayNumber,
|
|
1879
|
+
delayDenominator: this._frames[i].delayDenominator,
|
|
1880
|
+
data: this._apng.depth === 8
|
|
1881
|
+
? new Uint8Array(this._apng.width * this._apng.height * this._apng.channels)
|
|
1882
|
+
: new Uint16Array(this._apng.width * this._apng.height * this._apng.channels),
|
|
1883
|
+
};
|
|
1884
|
+
const frame = this._frames.at(i);
|
|
1885
|
+
if (frame) {
|
|
1886
|
+
frame.data = decodeInterlaceNull({
|
|
1887
|
+
data: frame.data,
|
|
1888
|
+
width: frame.width,
|
|
1889
|
+
height: frame.height,
|
|
1890
|
+
channels: this._apng.channels,
|
|
1891
|
+
depth: this._apng.depth,
|
|
1892
|
+
});
|
|
1893
|
+
if (this._hasPalette) {
|
|
1894
|
+
this._apng.palette = this._palette;
|
|
1895
|
+
}
|
|
1896
|
+
if (this._hasTransparency) {
|
|
1897
|
+
this._apng.transparency = this._transparency;
|
|
1898
|
+
}
|
|
1899
|
+
if (i === 0 ||
|
|
1900
|
+
(frame.xOffset === 0 &&
|
|
1901
|
+
frame.yOffset === 0 &&
|
|
1902
|
+
frame.width === this._png.width &&
|
|
1903
|
+
frame.height === this._png.height)) {
|
|
1904
|
+
newFrame.data = frame.data;
|
|
1905
|
+
}
|
|
1906
|
+
else {
|
|
1907
|
+
const prevFrame = this._apng.frames.at(i - 1);
|
|
1908
|
+
this.disposeFrame(frame, prevFrame, newFrame);
|
|
1909
|
+
this.addFrameDataToCanvas(newFrame, frame);
|
|
1910
|
+
}
|
|
1911
|
+
this._apng.frames.push(newFrame);
|
|
1912
|
+
}
|
|
1913
|
+
}
|
|
1914
|
+
return this._apng;
|
|
1915
|
+
}
|
|
1916
|
+
disposeFrame(frame, prevFrame, imageFrame) {
|
|
1917
|
+
switch (frame.disposeOp) {
|
|
1918
|
+
case DisposeOpType.NONE:
|
|
1919
|
+
break;
|
|
1920
|
+
case DisposeOpType.BACKGROUND:
|
|
1921
|
+
for (let row = 0; row < this._png.height; row++) {
|
|
1922
|
+
for (let col = 0; col < this._png.width; col++) {
|
|
1923
|
+
const index = (row * frame.width + col) * this._png.channels;
|
|
1924
|
+
for (let channel = 0; channel < this._png.channels; channel++) {
|
|
1925
|
+
imageFrame.data[index + channel] = 0;
|
|
1926
|
+
}
|
|
1927
|
+
}
|
|
1928
|
+
}
|
|
1929
|
+
break;
|
|
1930
|
+
case DisposeOpType.PREVIOUS:
|
|
1931
|
+
imageFrame.data.set(prevFrame.data);
|
|
1932
|
+
break;
|
|
1933
|
+
default:
|
|
1934
|
+
throw new Error('Unknown disposeOp');
|
|
1935
|
+
}
|
|
1936
|
+
}
|
|
1937
|
+
addFrameDataToCanvas(imageFrame, frame) {
|
|
1938
|
+
const maxValue = 1 << this._png.depth;
|
|
1939
|
+
const calculatePixelIndices = (row, col) => {
|
|
1940
|
+
const index = ((row + frame.yOffset) * this._png.width + frame.xOffset + col) *
|
|
1941
|
+
this._png.channels;
|
|
1942
|
+
const frameIndex = (row * frame.width + col) * this._png.channels;
|
|
1943
|
+
return { index, frameIndex };
|
|
1944
|
+
};
|
|
1945
|
+
switch (frame.blendOp) {
|
|
1946
|
+
case BlendOpType.SOURCE:
|
|
1947
|
+
for (let row = 0; row < frame.height; row++) {
|
|
1948
|
+
for (let col = 0; col < frame.width; col++) {
|
|
1949
|
+
const { index, frameIndex } = calculatePixelIndices(row, col);
|
|
1950
|
+
for (let channel = 0; channel < this._png.channels; channel++) {
|
|
1951
|
+
imageFrame.data[index + channel] =
|
|
1952
|
+
frame.data[frameIndex + channel];
|
|
1953
|
+
}
|
|
1954
|
+
}
|
|
1955
|
+
}
|
|
1956
|
+
break;
|
|
1957
|
+
// https://www.w3.org/TR/png-3/#13Alpha-channel-processing
|
|
1958
|
+
case BlendOpType.OVER:
|
|
1959
|
+
for (let row = 0; row < frame.height; row++) {
|
|
1960
|
+
for (let col = 0; col < frame.width; col++) {
|
|
1961
|
+
const { index, frameIndex } = calculatePixelIndices(row, col);
|
|
1962
|
+
for (let channel = 0; channel < this._png.channels; channel++) {
|
|
1963
|
+
const sourceAlpha = frame.data[frameIndex + this._png.channels - 1] / maxValue;
|
|
1964
|
+
const foregroundValue = channel % (this._png.channels - 1) === 0
|
|
1965
|
+
? 1
|
|
1966
|
+
: frame.data[frameIndex + channel];
|
|
1967
|
+
const value = Math.floor(sourceAlpha * foregroundValue +
|
|
1968
|
+
(1 - sourceAlpha) * imageFrame.data[index + channel]);
|
|
1969
|
+
imageFrame.data[index + channel] += value;
|
|
1970
|
+
}
|
|
1971
|
+
}
|
|
1972
|
+
}
|
|
1973
|
+
break;
|
|
1974
|
+
default:
|
|
1975
|
+
throw new Error('Unknown blendOp');
|
|
1976
|
+
}
|
|
1977
|
+
}
|
|
1978
|
+
decodeImage() {
|
|
1979
|
+
const data = this._inflatorResult;
|
|
1980
|
+
if (this._filterMethod !== FilterMethod.ADAPTIVE) {
|
|
1981
|
+
throw new Error(`Filter method ${this._filterMethod} not supported`);
|
|
1982
|
+
}
|
|
1983
|
+
if (this._interlaceMethod === InterlaceMethod.NO_INTERLACE) {
|
|
1984
|
+
this._png.data = decodeInterlaceNull({
|
|
1985
|
+
data,
|
|
1986
|
+
width: this._png.width,
|
|
1987
|
+
height: this._png.height,
|
|
1988
|
+
channels: this._png.channels,
|
|
1989
|
+
depth: this._png.depth,
|
|
1990
|
+
});
|
|
1991
|
+
}
|
|
1992
|
+
else if (this._interlaceMethod === InterlaceMethod.ADAM7) {
|
|
1993
|
+
this._png.data = decodeInterlaceAdam7({
|
|
1994
|
+
data,
|
|
1995
|
+
width: this._png.width,
|
|
1996
|
+
height: this._png.height,
|
|
1997
|
+
channels: this._png.channels,
|
|
1998
|
+
depth: this._png.depth,
|
|
1999
|
+
});
|
|
2000
|
+
}
|
|
2001
|
+
else {
|
|
2002
|
+
throw new Error(`Interlace method ${this._interlaceMethod} not supported`);
|
|
2003
|
+
}
|
|
2004
|
+
if (this._hasPalette) {
|
|
2005
|
+
this._png.palette = this._palette;
|
|
2006
|
+
}
|
|
2007
|
+
if (this._hasTransparency) {
|
|
2008
|
+
this._png.transparency = this._transparency;
|
|
2009
|
+
}
|
|
2010
|
+
}
|
|
2011
|
+
pushDataToFrame() {
|
|
2012
|
+
// Finalize the current stream
|
|
2013
|
+
this._inflator.push(new Uint8Array(0), true); // This triggers final=true in callback
|
|
2014
|
+
const result = this._inflatorResult;
|
|
2015
|
+
const lastFrame = this._frames.at(-1);
|
|
2016
|
+
if (lastFrame) {
|
|
2017
|
+
lastFrame.data = result;
|
|
2018
|
+
}
|
|
2019
|
+
else {
|
|
2020
|
+
this._frames.push({
|
|
2021
|
+
sequenceNumber: 0,
|
|
2022
|
+
width: this._png.width,
|
|
2023
|
+
height: this._png.height,
|
|
2024
|
+
xOffset: 0,
|
|
2025
|
+
yOffset: 0,
|
|
2026
|
+
delayNumber: 0,
|
|
2027
|
+
delayDenominator: 0,
|
|
2028
|
+
disposeOp: DisposeOpType.NONE,
|
|
2029
|
+
blendOp: BlendOpType.SOURCE,
|
|
2030
|
+
data: result,
|
|
2031
|
+
});
|
|
2032
|
+
}
|
|
2033
|
+
// Create new inflator for next frame
|
|
2034
|
+
this._inflator = new Unzlib((chunk, final) => {
|
|
2035
|
+
this._chunks.push(chunk);
|
|
2036
|
+
if (final) {
|
|
2037
|
+
const totalLength = this._chunks.reduce((sum, c) => sum + c.length, 0);
|
|
2038
|
+
this._inflatorResult = new Uint8Array(totalLength);
|
|
2039
|
+
let offset = 0;
|
|
2040
|
+
for (const chunk of this._chunks) {
|
|
2041
|
+
this._inflatorResult.set(chunk, offset);
|
|
2042
|
+
offset += chunk.length;
|
|
2043
|
+
}
|
|
2044
|
+
this._chunks = [];
|
|
2045
|
+
}
|
|
2046
|
+
});
|
|
2047
|
+
this._chunks = [];
|
|
2048
|
+
this._writingDataChunks = false;
|
|
2049
|
+
}
|
|
2050
|
+
}
|
|
2051
|
+
function checkBitDepth(value) {
|
|
2052
|
+
if (value !== 1 &&
|
|
2053
|
+
value !== 2 &&
|
|
2054
|
+
value !== 4 &&
|
|
2055
|
+
value !== 8 &&
|
|
2056
|
+
value !== 16) {
|
|
2057
|
+
throw new Error(`invalid bit depth: ${value}`);
|
|
2058
|
+
}
|
|
2059
|
+
return value;
|
|
2060
|
+
}
|
|
2061
|
+
|
|
2062
|
+
function decodePng(data, options) {
|
|
2063
|
+
const decoder = new PngDecoder(data, options);
|
|
2064
|
+
return decoder.decode();
|
|
2065
|
+
}
|
|
2066
|
+
|
|
2067
|
+
/**
|
|
2068
|
+
* @fileOverview
|
|
2069
|
+
* @author Ramon Wijnands - rayman747@hotmail.com
|
|
2070
|
+
*/
|
|
2071
|
+
const textDecoder = new TextDecoder();
|
|
2072
|
+
/**
|
|
2073
|
+
* If a message was compressed as a PNG image (a compression hack since
|
|
2074
|
+
* gzipping over WebSockets * is not supported yet), this function decodes
|
|
2075
|
+
* the "image" as a Base64 string.
|
|
2076
|
+
*
|
|
2077
|
+
* @param data - An object containing the PNG data.
|
|
2078
|
+
*/
|
|
2079
|
+
function decompressPng(data) {
|
|
2080
|
+
const buffer = Uint8Array.from(atob(data), (char) => char.charCodeAt(0));
|
|
2081
|
+
const decoded = tryDecodeBuffer(buffer);
|
|
2082
|
+
try {
|
|
2083
|
+
return JSON.parse(textDecoder.decode(decoded.data));
|
|
2084
|
+
}
|
|
2085
|
+
catch (error) {
|
|
2086
|
+
throw new Error("Error parsing PNG JSON contents", { cause: error });
|
|
2087
|
+
}
|
|
2088
|
+
}
|
|
2089
|
+
function tryDecodeBuffer(buffer) {
|
|
2090
|
+
try {
|
|
2091
|
+
return decodePng(buffer);
|
|
2092
|
+
}
|
|
2093
|
+
catch (error) {
|
|
2094
|
+
throw new Error("Error decoding PNG buffer", { cause: error });
|
|
2095
|
+
}
|
|
2096
|
+
}
|
|
2097
|
+
|
|
63
2098
|
class Ros extends EventEmitter {
|
|
64
2099
|
constructor(options = {}) {
|
|
65
2100
|
super();
|
|
@@ -97,7 +2132,8 @@ class Ros extends EventEmitter {
|
|
|
97
2132
|
this.emit('error', error);
|
|
98
2133
|
};
|
|
99
2134
|
this.socket.onmessage = (event) => {
|
|
100
|
-
|
|
2135
|
+
var message = JSON.parse(typeof event === 'string' ? event : event.data);
|
|
2136
|
+
this.handlePng(message);
|
|
101
2137
|
};
|
|
102
2138
|
}
|
|
103
2139
|
catch (error) {
|
|
@@ -111,9 +2147,8 @@ class Ros extends EventEmitter {
|
|
|
111
2147
|
}
|
|
112
2148
|
this._isConnected = false;
|
|
113
2149
|
}
|
|
114
|
-
handleMessage(
|
|
2150
|
+
handleMessage(message) {
|
|
115
2151
|
try {
|
|
116
|
-
const message = JSON.parse(data);
|
|
117
2152
|
if (message.op === 'publish') {
|
|
118
2153
|
// 发布消息到对应的 topic
|
|
119
2154
|
this.emit(message.topic, message.msg);
|
|
@@ -136,6 +2171,14 @@ class Ros extends EventEmitter {
|
|
|
136
2171
|
console.error('Error parsing message:', error);
|
|
137
2172
|
}
|
|
138
2173
|
}
|
|
2174
|
+
handlePng(message) {
|
|
2175
|
+
if (message.op === 'png') {
|
|
2176
|
+
this.handleMessage(decompressPng(message.data));
|
|
2177
|
+
}
|
|
2178
|
+
else {
|
|
2179
|
+
this.handleMessage(message);
|
|
2180
|
+
}
|
|
2181
|
+
}
|
|
139
2182
|
callOnConnection(message) {
|
|
140
2183
|
const messageStr = JSON.stringify(message);
|
|
141
2184
|
if (this._isConnected && this.socket) {
|
|
@@ -341,7 +2384,8 @@ class EnhancedRos extends EventEmitter {
|
|
|
341
2384
|
};
|
|
342
2385
|
this.socket.onmessage = (event) => {
|
|
343
2386
|
this.lastServerMessageAtMs = Date.now();
|
|
344
|
-
|
|
2387
|
+
var message = JSON.parse(typeof event === 'string' ? event : event.data);
|
|
2388
|
+
this.handlePng(message);
|
|
345
2389
|
};
|
|
346
2390
|
}
|
|
347
2391
|
catch (error) {
|
|
@@ -468,9 +2512,8 @@ class EnhancedRos extends EventEmitter {
|
|
|
468
2512
|
this.socket.send(messageStr);
|
|
469
2513
|
}
|
|
470
2514
|
/** 解析并分发服务端消息 */
|
|
471
|
-
handleMessage(
|
|
2515
|
+
handleMessage(message) {
|
|
472
2516
|
try {
|
|
473
|
-
const message = JSON.parse(data);
|
|
474
2517
|
if (message.op === 'publish') {
|
|
475
2518
|
// 普通话题消息
|
|
476
2519
|
this.emit(message.topic, message.msg);
|
|
@@ -497,6 +2540,14 @@ class EnhancedRos extends EventEmitter {
|
|
|
497
2540
|
console.error('Error parsing message:', error);
|
|
498
2541
|
}
|
|
499
2542
|
}
|
|
2543
|
+
handlePng(message) {
|
|
2544
|
+
if (message.op === 'png') {
|
|
2545
|
+
this.handleMessage(decompressPng(message.data));
|
|
2546
|
+
}
|
|
2547
|
+
else {
|
|
2548
|
+
this.handleMessage(message);
|
|
2549
|
+
}
|
|
2550
|
+
}
|
|
500
2551
|
}
|
|
501
2552
|
|
|
502
2553
|
class Topic extends EventEmitter {
|