@twin.org/image 0.0.1-next.1

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.
@@ -0,0 +1,1896 @@
1
+ import { Guards, GeneralError, Is, Compression } from '@twin.org/core';
2
+
3
+ // Copyright 2024 IOTA Stiftung.
4
+ // SPDX-License-Identifier: Apache-2.0.
5
+ /* eslint-disable no-bitwise */
6
+ /**
7
+ * Class to represent a color.
8
+ */
9
+ class Color {
10
+ /**
11
+ * Runtime name for the class.
12
+ * @internal
13
+ */
14
+ static _CLASS_NAME = "Color";
15
+ /**
16
+ * @internal
17
+ */
18
+ _alpha;
19
+ /**
20
+ * @internal
21
+ */
22
+ _red;
23
+ /**
24
+ * @internal
25
+ */
26
+ _green;
27
+ /**
28
+ * @internal
29
+ */
30
+ _blue;
31
+ /**
32
+ * Create a new instance of color.
33
+ * @param alpha The alpha element of the color.
34
+ * @param red The red element of the color.
35
+ * @param green The green element of the color.
36
+ * @param blue The blue element of the color.
37
+ */
38
+ constructor(alpha, red, green, blue) {
39
+ Guards.number(Color._CLASS_NAME, "alpha", alpha);
40
+ Guards.number(Color._CLASS_NAME, "red", red);
41
+ Guards.number(Color._CLASS_NAME, "green", green);
42
+ Guards.number(Color._CLASS_NAME, "blue", blue);
43
+ if (alpha < 0 || alpha > 255) {
44
+ throw new GeneralError(Color._CLASS_NAME, "range", {
45
+ prop: "alpha",
46
+ value: alpha
47
+ });
48
+ }
49
+ if (red < 0 || red > 255) {
50
+ throw new GeneralError(Color._CLASS_NAME, "range", {
51
+ prop: "red",
52
+ value: red
53
+ });
54
+ }
55
+ if (green < 0 || green > 255) {
56
+ throw new GeneralError(Color._CLASS_NAME, "range", {
57
+ prop: "green",
58
+ value: green
59
+ });
60
+ }
61
+ if (blue < 0 || blue > 255) {
62
+ throw new GeneralError(Color._CLASS_NAME, "range", {
63
+ prop: "blue",
64
+ value: blue
65
+ });
66
+ }
67
+ this._alpha = alpha;
68
+ this._red = red;
69
+ this._green = green;
70
+ this._blue = blue;
71
+ }
72
+ /**
73
+ * Construct a color from a hex string.
74
+ * @param hex The hex string to parse.
75
+ * @returns The color.
76
+ * @throws Error if the format is incorrect.
77
+ */
78
+ static fromHex(hex) {
79
+ Guards.stringValue(Color._CLASS_NAME, "hex", hex);
80
+ let alpha;
81
+ let red;
82
+ let green;
83
+ let blue;
84
+ if (/^#[\dA-Fa-f]{3}$/.test(hex)) {
85
+ // #RGB
86
+ alpha = "0xFF";
87
+ red = hex.slice(1, 2).repeat(2);
88
+ green = hex.slice(2, 3).repeat(2);
89
+ blue = hex.slice(3, 4).repeat(2);
90
+ }
91
+ else if (/^#[\dA-Fa-f]{4}$/.test(hex)) {
92
+ // #ARGB
93
+ alpha = hex.slice(1, 2).repeat(2);
94
+ red = hex.slice(2, 3).repeat(2);
95
+ green = hex.slice(3, 4).repeat(2);
96
+ blue = hex.slice(4, 5).repeat(2);
97
+ }
98
+ else if (/^#[\dA-Fa-f]{6}$/.test(hex)) {
99
+ // #RRGGBB
100
+ alpha = "0xFF";
101
+ red = hex.slice(1, 3);
102
+ green = hex.slice(3, 5);
103
+ blue = hex.slice(5, 7);
104
+ }
105
+ else if (/^#[\dA-Fa-f]{8}$/.test(hex)) {
106
+ // #AARRGGBB
107
+ alpha = hex.slice(1, 3);
108
+ red = hex.slice(3, 5);
109
+ green = hex.slice(5, 7);
110
+ blue = hex.slice(7, 9);
111
+ }
112
+ else {
113
+ throw new GeneralError(Color._CLASS_NAME, "hex");
114
+ }
115
+ return new Color(Number.parseInt(alpha, 16), Number.parseInt(red, 16), Number.parseInt(green, 16), Number.parseInt(blue, 16));
116
+ }
117
+ /**
118
+ * Coerce an unknown type to a color.
119
+ * @param value The value to try and convert.
120
+ * @returns The color if one can be created.
121
+ */
122
+ static coerce(value) {
123
+ if (Is.object(value) &&
124
+ Is.number(value._alpha) &&
125
+ Is.number(value._red) &&
126
+ Is.number(value._green) &&
127
+ Is.number(value._blue)) {
128
+ return new Color(value._alpha, value._red, value._green, value._blue);
129
+ }
130
+ else if (Is.stringValue(value) && value.startsWith("#")) {
131
+ try {
132
+ return Color.fromHex(value);
133
+ }
134
+ catch { }
135
+ }
136
+ }
137
+ /**
138
+ * Get the alpha element.
139
+ * @returns The alpha element.
140
+ */
141
+ alpha() {
142
+ return this._alpha;
143
+ }
144
+ /**
145
+ * Get the red element.
146
+ * @returns The red element.
147
+ */
148
+ red() {
149
+ return this._red;
150
+ }
151
+ /**
152
+ * Get the green element.
153
+ * @returns The green element.
154
+ */
155
+ green() {
156
+ return this._green;
157
+ }
158
+ /**
159
+ * Get the blue element.
160
+ * @returns The blue element.
161
+ */
162
+ blue() {
163
+ return this._blue;
164
+ }
165
+ /**
166
+ * Get color as argb.
167
+ * @returns The color as argb.
168
+ */
169
+ argb() {
170
+ return ((this._alpha << 24) | (this._red << 16) | (this._green << 8) | this._blue) >>> 0;
171
+ }
172
+ /**
173
+ * Get color as rgba.
174
+ * @returns The color as rgba.
175
+ */
176
+ rgba() {
177
+ return ((this._red << 24) | (this._green << 16) | (this._blue << 8) | this._alpha) >>> 0;
178
+ }
179
+ /**
180
+ * Get color as rgb text.
181
+ * @returns The color as rgb.
182
+ */
183
+ rgbText() {
184
+ return `rgb(${this._red},${this._green},${this._blue})`;
185
+ }
186
+ /**
187
+ * Get color as rgba text.
188
+ * @returns The color as rgba.
189
+ */
190
+ rgbaText() {
191
+ return `rgba(${this._red},${this._green},${this._blue},${Math.round((this._alpha / 255) * 100) / 100})`;
192
+ }
193
+ /**
194
+ * Get color as hex no alpha.
195
+ * @returns The color as hex with no alpha component.
196
+ */
197
+ hex() {
198
+ const red = `00${this._red.toString(16)}`.slice(-2);
199
+ const green = `00${this._green.toString(16)}`.slice(-2);
200
+ const blue = `00${this._blue.toString(16)}`.slice(-2);
201
+ return `#${red}${green}${blue}`.toUpperCase();
202
+ }
203
+ /**
204
+ * Get color as hex with alpha.
205
+ * @returns The color as hex with with alpha component.
206
+ */
207
+ hexWithAlpha() {
208
+ const alpha = `00${this._alpha.toString(16)}`.slice(-2);
209
+ const red = `00${this._red.toString(16)}`.slice(-2);
210
+ const green = `00${this._green.toString(16)}`.slice(-2);
211
+ const blue = `00${this._blue.toString(16)}`.slice(-2);
212
+ return `#${alpha}${red}${green}${blue}`.toUpperCase();
213
+ }
214
+ }
215
+
216
+ // Copyright 2024 IOTA Stiftung.
217
+ // SPDX-License-Identifier: Apache-2.0.
218
+ /* eslint-disable no-bitwise */
219
+ /* eslint-disable no-mixed-operators */
220
+ /* eslint-disable array-bracket-newline */
221
+ /**
222
+ * JPEG Encoder.
223
+ * Based on JPEG encoder ported to JavaScript and optimized by Andreas Ritter.
224
+ */
225
+ class JpegEncoder {
226
+ /**
227
+ * Runtime name for the class.
228
+ * @internal
229
+ */
230
+ static _CLASS_NAME = "JpegEncoder";
231
+ /**
232
+ * @internal
233
+ */
234
+ static _STD_DC_LUMINANCE_NR_CODES = [
235
+ 0, 0, 1, 5, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0
236
+ ];
237
+ /**
238
+ * @internal
239
+ */
240
+ static _STD_DC_LUMINANCE_VALUES = [
241
+ 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11
242
+ ];
243
+ /**
244
+ * @internal
245
+ */
246
+ static _STD_AC_LUMINANCE_NR_CODES = [
247
+ 0, 0, 2, 1, 3, 3, 2, 4, 3, 5, 5, 4, 4, 0, 0, 1, 0x7d
248
+ ];
249
+ /**
250
+ * @internal
251
+ */
252
+ static _STD_AC_LUMINANCE_VALUES = [
253
+ 0x01, 0x02, 0x03, 0x00, 0x04, 0x11, 0x05, 0x12, 0x21, 0x31, 0x41, 0x06, 0x13, 0x51, 0x61, 0x07,
254
+ 0x22, 0x71, 0x14, 0x32, 0x81, 0x91, 0xa1, 0x08, 0x23, 0x42, 0xb1, 0xc1, 0x15, 0x52, 0xd1, 0xf0,
255
+ 0x24, 0x33, 0x62, 0x72, 0x82, 0x09, 0x0a, 0x16, 0x17, 0x18, 0x19, 0x1a, 0x25, 0x26, 0x27, 0x28,
256
+ 0x29, 0x2a, 0x34, 0x35, 0x36, 0x37, 0x38, 0x39, 0x3a, 0x43, 0x44, 0x45, 0x46, 0x47, 0x48, 0x49,
257
+ 0x4a, 0x53, 0x54, 0x55, 0x56, 0x57, 0x58, 0x59, 0x5a, 0x63, 0x64, 0x65, 0x66, 0x67, 0x68, 0x69,
258
+ 0x6a, 0x73, 0x74, 0x75, 0x76, 0x77, 0x78, 0x79, 0x7a, 0x83, 0x84, 0x85, 0x86, 0x87, 0x88, 0x89,
259
+ 0x8a, 0x92, 0x93, 0x94, 0x95, 0x96, 0x97, 0x98, 0x99, 0x9a, 0xa2, 0xa3, 0xa4, 0xa5, 0xa6, 0xa7,
260
+ 0xa8, 0xa9, 0xaa, 0xb2, 0xb3, 0xb4, 0xb5, 0xb6, 0xb7, 0xb8, 0xb9, 0xba, 0xc2, 0xc3, 0xc4, 0xc5,
261
+ 0xc6, 0xc7, 0xc8, 0xc9, 0xca, 0xd2, 0xd3, 0xd4, 0xd5, 0xd6, 0xd7, 0xd8, 0xd9, 0xda, 0xe1, 0xe2,
262
+ 0xe3, 0xe4, 0xe5, 0xe6, 0xe7, 0xe8, 0xe9, 0xea, 0xf1, 0xf2, 0xf3, 0xf4, 0xf5, 0xf6, 0xf7, 0xf8,
263
+ 0xf9, 0xfa
264
+ ];
265
+ /**
266
+ * @internal
267
+ */
268
+ static _STD_DC_CHROMINANCE_NR_CODES = [
269
+ 0, 0, 3, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0
270
+ ];
271
+ /**
272
+ * @internal
273
+ */
274
+ static _STD_DC_CHROMINANCE_VALUES = [
275
+ 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11
276
+ ];
277
+ /**
278
+ * @internal
279
+ */
280
+ static _STD_AC_CHROMINANCE_NR_CODES = [
281
+ 0, 0, 2, 1, 2, 4, 4, 3, 4, 7, 5, 4, 4, 0, 1, 2, 0x77
282
+ ];
283
+ /**
284
+ * @internal
285
+ */
286
+ static _STD_AC_CHROMINANCE_VALUES = [
287
+ 0x00, 0x01, 0x02, 0x03, 0x11, 0x04, 0x05, 0x21, 0x31, 0x06, 0x12, 0x41, 0x51, 0x07, 0x61, 0x71,
288
+ 0x13, 0x22, 0x32, 0x81, 0x08, 0x14, 0x42, 0x91, 0xa1, 0xb1, 0xc1, 0x09, 0x23, 0x33, 0x52, 0xf0,
289
+ 0x15, 0x62, 0x72, 0xd1, 0x0a, 0x16, 0x24, 0x34, 0xe1, 0x25, 0xf1, 0x17, 0x18, 0x19, 0x1a, 0x26,
290
+ 0x27, 0x28, 0x29, 0x2a, 0x35, 0x36, 0x37, 0x38, 0x39, 0x3a, 0x43, 0x44, 0x45, 0x46, 0x47, 0x48,
291
+ 0x49, 0x4a, 0x53, 0x54, 0x55, 0x56, 0x57, 0x58, 0x59, 0x5a, 0x63, 0x64, 0x65, 0x66, 0x67, 0x68,
292
+ 0x69, 0x6a, 0x73, 0x74, 0x75, 0x76, 0x77, 0x78, 0x79, 0x7a, 0x82, 0x83, 0x84, 0x85, 0x86, 0x87,
293
+ 0x88, 0x89, 0x8a, 0x92, 0x93, 0x94, 0x95, 0x96, 0x97, 0x98, 0x99, 0x9a, 0xa2, 0xa3, 0xa4, 0xa5,
294
+ 0xa6, 0xa7, 0xa8, 0xa9, 0xaa, 0xb2, 0xb3, 0xb4, 0xb5, 0xb6, 0xb7, 0xb8, 0xb9, 0xba, 0xc2, 0xc3,
295
+ 0xc4, 0xc5, 0xc6, 0xc7, 0xc8, 0xc9, 0xca, 0xd2, 0xd3, 0xd4, 0xd5, 0xd6, 0xd7, 0xd8, 0xd9, 0xda,
296
+ 0xe2, 0xe3, 0xe4, 0xe5, 0xe6, 0xe7, 0xe8, 0xe9, 0xea, 0xf2, 0xf3, 0xf4, 0xf5, 0xf6, 0xf7, 0xf8,
297
+ 0xf9, 0xfa
298
+ ];
299
+ /** @internal */
300
+ static _SIG_ZAG = [
301
+ 0, 1, 5, 6, 14, 15, 27, 28, 2, 4, 7, 13, 16, 26, 29, 42, 3, 8, 12, 17, 25, 30, 41, 43, 9, 11,
302
+ 18, 24, 31, 40, 44, 53, 10, 19, 23, 32, 39, 45, 52, 54, 20, 22, 33, 38, 46, 51, 55, 60, 21, 34,
303
+ 37, 47, 50, 56, 59, 61, 35, 36, 48, 49, 57, 58, 62, 63
304
+ ];
305
+ /**
306
+ * @internal
307
+ */
308
+ _yTable;
309
+ /**
310
+ * @internal
311
+ */
312
+ _uvTable;
313
+ /**
314
+ * @internal
315
+ */
316
+ _fdTblY;
317
+ /**
318
+ * @internal
319
+ */
320
+ _fdTblUV;
321
+ /**
322
+ * @internal
323
+ */
324
+ _ydcHashTable;
325
+ /**
326
+ * @internal
327
+ */
328
+ _uVdcHashTable;
329
+ /**
330
+ * @internal
331
+ */
332
+ _yacHashTable;
333
+ /**
334
+ * @internal
335
+ */
336
+ _uVacHashTable;
337
+ /**
338
+ * @internal
339
+ */
340
+ _bitCode;
341
+ /**
342
+ * @internal
343
+ */
344
+ _category;
345
+ /**
346
+ * @internal
347
+ */
348
+ _outputFDctQuant;
349
+ /**
350
+ * @internal
351
+ */
352
+ _du;
353
+ /**
354
+ * @internal
355
+ */
356
+ _byteOut;
357
+ /**
358
+ * @internal
359
+ */
360
+ _byteNew;
361
+ /**
362
+ * @internal
363
+ */
364
+ _bytePos;
365
+ /**
366
+ * @internal
367
+ */
368
+ _ydu;
369
+ /**
370
+ * @internal
371
+ */
372
+ _udu;
373
+ /**
374
+ * @internal
375
+ */
376
+ _vdu;
377
+ /**
378
+ * @internal
379
+ */
380
+ _rgbYuvTable;
381
+ /**
382
+ * Create a new instance of JpegEncoder.
383
+ */
384
+ constructor() {
385
+ this._yTable = Array.from({ length: 64 });
386
+ this._uvTable = Array.from({ length: 64 });
387
+ this._fdTblY = Array.from({ length: 64 });
388
+ this._fdTblUV = Array.from({ length: 64 });
389
+ this._bitCode = Array.from({ length: 65535 });
390
+ this._category = Array.from({ length: 65535 });
391
+ this._outputFDctQuant = Array.from({ length: 64 });
392
+ this._du = Array.from({ length: 64 });
393
+ this._byteOut = [];
394
+ this._byteNew = 0;
395
+ this._bytePos = 7;
396
+ this._ydu = Array.from({ length: 64 });
397
+ this._udu = Array.from({ length: 64 });
398
+ this._vdu = Array.from({ length: 64 });
399
+ this._rgbYuvTable = Array.from({ length: 2048 });
400
+ this.initHuffmanTbl();
401
+ this.initCategoryNumber();
402
+ this.initRGBYUVTable();
403
+ }
404
+ /**
405
+ * Encode the image with the given quality.
406
+ * @param width The width of the image to encode.
407
+ * @param height The height of the image to encode.
408
+ * @param imageData The data for the image.
409
+ * @param quality The quality to encode the image at.
410
+ * @returns The data for the encoded image.
411
+ */
412
+ encode(width, height, imageData, quality) {
413
+ this.setQuality(quality);
414
+ // Initialize bit writer
415
+ this._byteOut = [];
416
+ this._byteNew = 0;
417
+ this._bytePos = 7;
418
+ // Add JPEG headers
419
+ this.writeWord(0xffd8); // SOI
420
+ this.writeAPP0();
421
+ this.writeDQT();
422
+ this.writeSOF0(width, height);
423
+ this.writeDHT();
424
+ this.writeSOS();
425
+ // Encode 8x8 macro blocks
426
+ let DCY = 0;
427
+ let DCU = 0;
428
+ let DCV = 0;
429
+ this._byteNew = 0;
430
+ this._bytePos = 7;
431
+ const quadWidth = width * 4;
432
+ let x;
433
+ let y = 0;
434
+ let r;
435
+ let g;
436
+ let b;
437
+ let start;
438
+ let p;
439
+ let col;
440
+ let row;
441
+ let pos;
442
+ while (y < height) {
443
+ x = 0;
444
+ while (x < quadWidth) {
445
+ start = quadWidth * y + x;
446
+ p = start;
447
+ col = -1;
448
+ row = 0;
449
+ for (pos = 0; pos < 64; pos++) {
450
+ row = pos >> 3; // /8
451
+ col = (pos & 7) * 4; // %8
452
+ p = start + row * quadWidth + col;
453
+ if (y + row >= height) {
454
+ // padding bottom
455
+ p -= quadWidth * (y + 1 + row - height);
456
+ }
457
+ if (x + col >= quadWidth) {
458
+ // padding right
459
+ p -= x + col - quadWidth + 4;
460
+ }
461
+ r = imageData[p++];
462
+ g = imageData[p++];
463
+ b = imageData[p++];
464
+ // use lookup table (slightly faster)
465
+ this._ydu[pos] =
466
+ ((this._rgbYuvTable[r] +
467
+ this._rgbYuvTable[Math.trunc(g + 256)] +
468
+ this._rgbYuvTable[Math.trunc(b + 512)]) >>
469
+ 16) -
470
+ 128;
471
+ this._udu[pos] =
472
+ ((this._rgbYuvTable[Math.trunc(r + 768)] +
473
+ this._rgbYuvTable[Math.trunc(g + 1024)] +
474
+ this._rgbYuvTable[Math.trunc(b + 1280)]) >>
475
+ 16) -
476
+ 128;
477
+ this._vdu[pos] =
478
+ ((this._rgbYuvTable[Math.trunc(r + 1280)] +
479
+ this._rgbYuvTable[Math.trunc(g + 1536)] +
480
+ this._rgbYuvTable[Math.trunc(b + 1792)]) >>
481
+ 16) -
482
+ 128;
483
+ }
484
+ if (this._ydcHashTable && this._yacHashTable) {
485
+ DCY = this.processDU(this._ydu, this._fdTblY, DCY, this._ydcHashTable, this._yacHashTable);
486
+ }
487
+ if (this._uVdcHashTable && this._uVacHashTable) {
488
+ DCU = this.processDU(this._udu, this._fdTblUV, DCU, this._uVdcHashTable, this._uVacHashTable);
489
+ }
490
+ if (this._uVdcHashTable && this._uVacHashTable) {
491
+ DCV = this.processDU(this._vdu, this._fdTblUV, DCV, this._uVdcHashTable, this._uVacHashTable);
492
+ }
493
+ x += 32;
494
+ }
495
+ y += 8;
496
+ }
497
+ // Do the bit alignment of the EOI marker
498
+ if (this._bytePos >= 0) {
499
+ const fillBits = [];
500
+ fillBits[1] = this._bytePos + 1;
501
+ fillBits[0] = (1 << (this._bytePos + 1)) - 1;
502
+ this.writeBits(fillBits);
503
+ }
504
+ this.writeWord(0xffd9); // EOI
505
+ return new Uint8Array(this._byteOut);
506
+ }
507
+ /**
508
+ * @internal
509
+ */
510
+ setQuality(quality) {
511
+ if (quality <= 0 || quality > 100) {
512
+ throw new GeneralError(JpegEncoder._CLASS_NAME, "invalidQuality", { value: quality });
513
+ }
514
+ let sf = 0;
515
+ if (quality < 50) {
516
+ sf = Math.floor(5000 / quality);
517
+ }
518
+ else {
519
+ sf = Math.floor(200 - quality * 2);
520
+ }
521
+ this.initQuantTables(sf);
522
+ }
523
+ /**
524
+ * @internal
525
+ */
526
+ initQuantTables(sf) {
527
+ const YQT = [
528
+ 16, 11, 10, 16, 24, 40, 51, 61, 12, 12, 14, 19, 26, 58, 60, 55, 14, 13, 16, 24, 40, 57, 69,
529
+ 56, 14, 17, 22, 29, 51, 87, 80, 62, 18, 22, 37, 56, 68, 109, 103, 77, 24, 35, 55, 64, 81, 104,
530
+ 113, 92, 49, 64, 78, 87, 103, 121, 120, 101, 72, 92, 95, 98, 112, 100, 103, 99
531
+ ];
532
+ for (let i = 0; i < 64; i++) {
533
+ let t = Math.floor((YQT[i] * sf + 50) / 100);
534
+ if (t < 1) {
535
+ t = 1;
536
+ }
537
+ else if (t > 255) {
538
+ t = 255;
539
+ }
540
+ this._yTable[JpegEncoder._SIG_ZAG[i]] = t;
541
+ }
542
+ const UVQT = [
543
+ 17, 18, 24, 47, 99, 99, 99, 99, 18, 21, 26, 66, 99, 99, 99, 99, 24, 26, 56, 99, 99, 99, 99,
544
+ 99, 47, 66, 99, 99, 99, 99, 99, 99, 99, 99, 99, 99, 99, 99, 99, 99, 99, 99, 99, 99, 99, 99,
545
+ 99, 99, 99, 99, 99, 99, 99, 99, 99, 99, 99, 99, 99, 99, 99, 99, 99, 99
546
+ ];
547
+ for (let j = 0; j < 64; j++) {
548
+ let u = Math.floor((UVQT[j] * sf + 50) / 100);
549
+ if (u < 1) {
550
+ u = 1;
551
+ }
552
+ else if (u > 255) {
553
+ u = 255;
554
+ }
555
+ this._uvTable[JpegEncoder._SIG_ZAG[j]] = u;
556
+ }
557
+ const aAsf = [1, 1.387039845, 1.306562965, 1.175875602, 1, 0.785694958, 0.5411961, 0.275899379];
558
+ let k = 0;
559
+ for (let row = 0; row < 8; row++) {
560
+ for (let col = 0; col < 8; col++) {
561
+ this._fdTblY[k] = 1 / (this._yTable[JpegEncoder._SIG_ZAG[k]] * aAsf[row] * aAsf[col] * 8);
562
+ this._fdTblUV[k] = 1 / (this._uvTable[JpegEncoder._SIG_ZAG[k]] * aAsf[row] * aAsf[col] * 8);
563
+ k++;
564
+ }
565
+ }
566
+ }
567
+ /**
568
+ * @internal
569
+ */
570
+ computeHuffmanTbl(nrCodes, stdTable) {
571
+ let codeValue = 0;
572
+ let posInTable = 0;
573
+ const HT = [];
574
+ for (let k = 1; k <= 16; k++) {
575
+ for (let j = 1; j <= nrCodes[k]; j++) {
576
+ HT[stdTable[posInTable]] = [];
577
+ HT[stdTable[posInTable]][0] = codeValue;
578
+ HT[stdTable[posInTable]][1] = k;
579
+ posInTable++;
580
+ codeValue++;
581
+ }
582
+ codeValue *= 2;
583
+ }
584
+ return HT;
585
+ }
586
+ /**
587
+ * @internal
588
+ */
589
+ initHuffmanTbl() {
590
+ this._ydcHashTable = this.computeHuffmanTbl(JpegEncoder._STD_DC_LUMINANCE_NR_CODES, JpegEncoder._STD_DC_LUMINANCE_VALUES);
591
+ this._uVdcHashTable = this.computeHuffmanTbl(JpegEncoder._STD_DC_CHROMINANCE_NR_CODES, JpegEncoder._STD_DC_CHROMINANCE_VALUES);
592
+ this._yacHashTable = this.computeHuffmanTbl(JpegEncoder._STD_AC_LUMINANCE_NR_CODES, JpegEncoder._STD_AC_LUMINANCE_VALUES);
593
+ this._uVacHashTable = this.computeHuffmanTbl(JpegEncoder._STD_AC_CHROMINANCE_NR_CODES, JpegEncoder._STD_AC_CHROMINANCE_VALUES);
594
+ }
595
+ /**
596
+ * @internal
597
+ */
598
+ initCategoryNumber() {
599
+ let nrLower = 1;
600
+ let nrUpper = 2;
601
+ for (let cat = 1; cat <= 15; cat++) {
602
+ // Positive numbers
603
+ for (let nr = nrLower; nr < nrUpper; nr++) {
604
+ this._category[32767 + nr] = cat;
605
+ this._bitCode[32767 + nr] = [];
606
+ this._bitCode[32767 + nr][1] = cat;
607
+ this._bitCode[32767 + nr][0] = nr;
608
+ }
609
+ // Negative numbers
610
+ for (let nrNeg = -(nrUpper - 1); nrNeg <= -nrLower; nrNeg++) {
611
+ this._category[32767 + nrNeg] = cat;
612
+ this._bitCode[32767 + nrNeg] = [];
613
+ this._bitCode[32767 + nrNeg][1] = cat;
614
+ this._bitCode[32767 + nrNeg][0] = nrUpper - 1 + nrNeg;
615
+ }
616
+ nrLower <<= 1;
617
+ nrUpper <<= 1;
618
+ }
619
+ }
620
+ /**
621
+ * @internal
622
+ */
623
+ initRGBYUVTable() {
624
+ for (let i = 0; i < 256; i++) {
625
+ this._rgbYuvTable[i] = 19595 * i;
626
+ this._rgbYuvTable[Math.trunc(i + 256)] = 38470 * i;
627
+ this._rgbYuvTable[Math.trunc(i + 512)] = 7471 * i + 0x8000;
628
+ this._rgbYuvTable[Math.trunc(i + 768)] = -11059 * i;
629
+ this._rgbYuvTable[Math.trunc(i + 1024)] = -21709 * i;
630
+ this._rgbYuvTable[Math.trunc(i + 1280)] = 32768 * i + 0x807fff;
631
+ this._rgbYuvTable[Math.trunc(i + 1536)] = -27439 * i;
632
+ this._rgbYuvTable[Math.trunc(i + 1792)] = -5329 * i;
633
+ }
634
+ }
635
+ /**
636
+ * @internal
637
+ */
638
+ writeBits(bs) {
639
+ const value = bs[0];
640
+ let posVal = bs[1] - 1;
641
+ while (posVal >= 0) {
642
+ if (value & (1 << posVal)) {
643
+ this._byteNew |= 1 << this._bytePos;
644
+ }
645
+ posVal--;
646
+ this._bytePos--;
647
+ if (this._bytePos < 0) {
648
+ if (this._byteNew === 0xff) {
649
+ this.writeByte(0xff);
650
+ this.writeByte(0);
651
+ }
652
+ else {
653
+ this.writeByte(this._byteNew);
654
+ }
655
+ this._bytePos = 7;
656
+ this._byteNew = 0;
657
+ }
658
+ }
659
+ }
660
+ /**
661
+ * @internal
662
+ */
663
+ writeByte(value) {
664
+ this._byteOut.push(value);
665
+ }
666
+ /**
667
+ * @internal
668
+ */
669
+ writeWord(value) {
670
+ this.writeByte((value >> 8) & 0xff);
671
+ this.writeByte(value & 0xff);
672
+ }
673
+ /**
674
+ * @internal
675
+ */
676
+ fDCTQuant(data, fdTbl) {
677
+ let d0;
678
+ let d1;
679
+ let d2;
680
+ let d3;
681
+ let d4;
682
+ let d5;
683
+ let d6;
684
+ let d7;
685
+ /* Pass 1: process rows. */
686
+ let dataOff = 0;
687
+ let i;
688
+ const I8 = 8;
689
+ const I64 = 64;
690
+ for (i = 0; i < I8; ++i) {
691
+ d0 = data[dataOff];
692
+ d1 = data[dataOff + 1];
693
+ d2 = data[dataOff + 2];
694
+ d3 = data[dataOff + 3];
695
+ d4 = data[dataOff + 4];
696
+ d5 = data[dataOff + 5];
697
+ d6 = data[dataOff + 6];
698
+ d7 = data[dataOff + 7];
699
+ const tmp0 = d0 + d7;
700
+ const tmp7 = d0 - d7;
701
+ const tmp1 = d1 + d6;
702
+ const tmp6 = d1 - d6;
703
+ const tmp2 = d2 + d5;
704
+ const tmp5 = d2 - d5;
705
+ const tmp3 = d3 + d4;
706
+ const tmp4 = d3 - d4;
707
+ /* Even part */
708
+ let tmp10 = tmp0 + tmp3; /* phase 2 */
709
+ const tmp13 = tmp0 - tmp3;
710
+ let tmp11 = tmp1 + tmp2;
711
+ let tmp12 = tmp1 - tmp2;
712
+ data[dataOff] = tmp10 + tmp11; /* phase 3 */
713
+ data[dataOff + 4] = tmp10 - tmp11;
714
+ const z1 = (tmp12 + tmp13) * 0.707106781; /* c4 */
715
+ data[dataOff + 2] = tmp13 + z1; /* phase 5 */
716
+ data[dataOff + 6] = tmp13 - z1;
717
+ /* Odd part */
718
+ tmp10 = tmp4 + tmp5; /* phase 2 */
719
+ tmp11 = tmp5 + tmp6;
720
+ tmp12 = tmp6 + tmp7;
721
+ /* The rotator is modified from fig 4-8 to avoid extra negations. */
722
+ const z5 = (tmp10 - tmp12) * 0.382683433; /* c6 */
723
+ const z2 = 0.5411961 * tmp10 + z5; /* c2-c6 */
724
+ const z4 = 1.306562965 * tmp12 + z5; /* c2+c6 */
725
+ const z3 = tmp11 * 0.707106781; /* c4 */
726
+ const z11 = tmp7 + z3; /* phase 5 */
727
+ const z13 = tmp7 - z3;
728
+ data[dataOff + 5] = z13 + z2; /* phase 6 */
729
+ data[dataOff + 3] = z13 - z2;
730
+ data[dataOff + 1] = z11 + z4;
731
+ data[dataOff + 7] = z11 - z4;
732
+ dataOff += 8; /* advance pointer to next row */
733
+ }
734
+ /* Pass 2: process columns. */
735
+ dataOff = 0;
736
+ for (i = 0; i < I8; ++i) {
737
+ d0 = data[dataOff];
738
+ d1 = data[dataOff + 8];
739
+ d2 = data[dataOff + 16];
740
+ d3 = data[dataOff + 24];
741
+ d4 = data[dataOff + 32];
742
+ d5 = data[dataOff + 40];
743
+ d6 = data[dataOff + 48];
744
+ d7 = data[dataOff + 56];
745
+ const tmp0p2 = d0 + d7;
746
+ const tmp7p2 = d0 - d7;
747
+ const tmp1p2 = d1 + d6;
748
+ const tmp6p2 = d1 - d6;
749
+ const tmp2p2 = d2 + d5;
750
+ const tmp5p2 = d2 - d5;
751
+ const tmp3p2 = d3 + d4;
752
+ const tmp4p2 = d3 - d4;
753
+ /* Even part */
754
+ let tmp10p2 = tmp0p2 + tmp3p2; /* phase 2 */
755
+ const tmp13p2 = tmp0p2 - tmp3p2;
756
+ let tmp11p2 = tmp1p2 + tmp2p2;
757
+ let tmp12p2 = tmp1p2 - tmp2p2;
758
+ data[dataOff] = tmp10p2 + tmp11p2; /* phase 3 */
759
+ data[dataOff + 32] = tmp10p2 - tmp11p2;
760
+ const z1p2 = (tmp12p2 + tmp13p2) * 0.707106781; /* c4 */
761
+ data[dataOff + 16] = tmp13p2 + z1p2; /* phase 5 */
762
+ data[dataOff + 48] = tmp13p2 - z1p2;
763
+ /* Odd part */
764
+ tmp10p2 = tmp4p2 + tmp5p2; /* phase 2 */
765
+ tmp11p2 = tmp5p2 + tmp6p2;
766
+ tmp12p2 = tmp6p2 + tmp7p2;
767
+ /* The rotator is modified from fig 4-8 to avoid extra negations. */
768
+ const z5p2 = (tmp10p2 - tmp12p2) * 0.382683433; /* c6 */
769
+ const z2p2 = 0.5411961 * tmp10p2 + z5p2; /* c2-c6 */
770
+ const z4p2 = 1.306562965 * tmp12p2 + z5p2; /* c2+c6 */
771
+ const z3p2 = tmp11p2 * 0.707106781; /* c4 */
772
+ const z11p2 = tmp7p2 + z3p2; /* phase 5 */
773
+ const z13p2 = tmp7p2 - z3p2;
774
+ data[dataOff + 40] = z13p2 + z2p2; /* phase 6 */
775
+ data[dataOff + 24] = z13p2 - z2p2;
776
+ data[dataOff + 8] = z11p2 + z4p2;
777
+ data[dataOff + 56] = z11p2 - z4p2;
778
+ dataOff++; /* advance pointer to next column */
779
+ }
780
+ // Quantize/descale the coefficients
781
+ let fDCTQuant;
782
+ for (i = 0; i < I64; ++i) {
783
+ // Apply the quantization and scaling factor & Round to nearest integer
784
+ fDCTQuant = data[i] * fdTbl[i];
785
+ this._outputFDctQuant[i] =
786
+ fDCTQuant > 0 ? Math.trunc(fDCTQuant + 0.5) : Math.trunc(fDCTQuant - 0.5);
787
+ }
788
+ return this._outputFDctQuant;
789
+ }
790
+ /**
791
+ * @internal
792
+ */
793
+ writeAPP0() {
794
+ this.writeWord(0xffe0); // marker
795
+ this.writeWord(16); // length
796
+ this.writeByte(0x4a); // J
797
+ this.writeByte(0x46); // F
798
+ this.writeByte(0x49); // I
799
+ this.writeByte(0x46); // F
800
+ // cspell:disable-next-line
801
+ this.writeByte(0); // = "JFIF",'\0'
802
+ this.writeByte(1); // version hi
803
+ this.writeByte(1); // version lo
804
+ this.writeByte(0); // xy units
805
+ this.writeWord(1); // x density
806
+ this.writeWord(1); // y density
807
+ this.writeByte(0); // thumb n width
808
+ this.writeByte(0); // thumb n height
809
+ }
810
+ /**
811
+ * @internal
812
+ */
813
+ writeSOF0(width, height) {
814
+ this.writeWord(0xffc0); // marker
815
+ this.writeWord(17); // length, true color YUV JPG
816
+ this.writeByte(8); // precision
817
+ this.writeWord(height);
818
+ this.writeWord(width);
819
+ this.writeByte(3); // nr of components
820
+ this.writeByte(1); // IdY
821
+ this.writeByte(0x11); // HVY
822
+ this.writeByte(0); // QTY
823
+ this.writeByte(2); // IdU
824
+ this.writeByte(0x11); // HVU
825
+ this.writeByte(1); // QTU
826
+ this.writeByte(3); // IdV
827
+ this.writeByte(0x11); // HVV
828
+ this.writeByte(1); // QTV
829
+ }
830
+ /**
831
+ * @internal
832
+ */
833
+ writeDQT() {
834
+ this.writeWord(0xffdb); // marker
835
+ this.writeWord(132); // length
836
+ this.writeByte(0);
837
+ for (let i = 0; i < 64; i++) {
838
+ this.writeByte(this._yTable[i]);
839
+ }
840
+ this.writeByte(1);
841
+ for (let j = 0; j < 64; j++) {
842
+ this.writeByte(this._uvTable[j]);
843
+ }
844
+ }
845
+ /**
846
+ * @internal
847
+ */
848
+ writeDHT() {
849
+ this.writeWord(0xffc4); // marker
850
+ this.writeWord(0x01a2); // length
851
+ this.writeByte(0); // HTYDc info
852
+ for (let i = 0; i < 16; i++) {
853
+ this.writeByte(JpegEncoder._STD_DC_LUMINANCE_NR_CODES[i + 1]);
854
+ }
855
+ for (let j = 0; j <= 11; j++) {
856
+ this.writeByte(JpegEncoder._STD_DC_LUMINANCE_VALUES[j]);
857
+ }
858
+ this.writeByte(0x10); // HTYAc info
859
+ for (let k = 0; k < 16; k++) {
860
+ this.writeByte(JpegEncoder._STD_AC_LUMINANCE_NR_CODES[k + 1]);
861
+ }
862
+ for (let l = 0; l <= 161; l++) {
863
+ this.writeByte(JpegEncoder._STD_AC_LUMINANCE_VALUES[l]);
864
+ }
865
+ this.writeByte(1); // HTUDc info
866
+ for (let m = 0; m < 16; m++) {
867
+ this.writeByte(JpegEncoder._STD_DC_CHROMINANCE_NR_CODES[m + 1]);
868
+ }
869
+ for (let n = 0; n <= 11; n++) {
870
+ this.writeByte(JpegEncoder._STD_DC_CHROMINANCE_VALUES[n]);
871
+ }
872
+ this.writeByte(0x11); // HTUAc info
873
+ for (let o = 0; o < 16; o++) {
874
+ this.writeByte(JpegEncoder._STD_AC_CHROMINANCE_NR_CODES[o + 1]);
875
+ }
876
+ for (let p = 0; p <= 161; p++) {
877
+ this.writeByte(JpegEncoder._STD_AC_CHROMINANCE_VALUES[p]);
878
+ }
879
+ }
880
+ /**
881
+ * @internal
882
+ */
883
+ writeSOS() {
884
+ this.writeWord(0xffda); // marker
885
+ this.writeWord(12); // length
886
+ this.writeByte(3); // nr of components
887
+ this.writeByte(1); // IdY
888
+ this.writeByte(0); // HTY
889
+ this.writeByte(2); // IdU
890
+ this.writeByte(0x11); // HTU
891
+ this.writeByte(3); // IdV
892
+ this.writeByte(0x11); // HTV
893
+ this.writeByte(0); // Ss
894
+ this.writeByte(0x3f); // Se
895
+ this.writeByte(0); // Bf
896
+ }
897
+ /**
898
+ * @internal
899
+ */
900
+ processDU(CDU, fdTbl, passedDC, HTDc, HTAc) {
901
+ let DC = passedDC;
902
+ const EOB = HTAc[0x00];
903
+ const m16zeroes = HTAc[0xf0];
904
+ let pos;
905
+ const I16 = 16;
906
+ const I63 = 63;
907
+ const I64 = 64;
908
+ const DU_DCT = this.fDCTQuant(CDU, fdTbl);
909
+ // ZigZag reorder
910
+ for (let j = 0; j < I64; ++j) {
911
+ this._du[JpegEncoder._SIG_ZAG[j]] = DU_DCT[j];
912
+ }
913
+ const diff = this._du[0] - DC;
914
+ DC = this._du[0];
915
+ // Encode DC
916
+ if (diff === 0) {
917
+ this.writeBits(HTDc[0]); // Diff might be 0
918
+ }
919
+ else {
920
+ pos = 32767 + diff;
921
+ this.writeBits(HTDc[this._category[pos]]);
922
+ this.writeBits(this._bitCode[pos]);
923
+ }
924
+ // Encode ACs
925
+ let end0pos = 63; // was const... which is crazy
926
+ for (; end0pos > 0 && this._du[end0pos] === 0; end0pos--) { }
927
+ // end0pos = first element in reverse order !=0
928
+ if (end0pos === 0) {
929
+ this.writeBits(EOB);
930
+ return DC;
931
+ }
932
+ let i = 1;
933
+ let lng;
934
+ while (i <= end0pos) {
935
+ const startPos = i;
936
+ for (; this._du[i] === 0 && i <= end0pos; ++i) { }
937
+ let nrZeroes = i - startPos;
938
+ if (nrZeroes >= I16) {
939
+ lng = nrZeroes >> 4;
940
+ for (let nrMarker = 1; nrMarker <= lng; ++nrMarker) {
941
+ this.writeBits(m16zeroes);
942
+ }
943
+ nrZeroes &= 0xf;
944
+ }
945
+ pos = 32767 + this._du[i];
946
+ this.writeBits(HTAc[(nrZeroes << 4) + this._category[pos]]);
947
+ this.writeBits(this._bitCode[pos]);
948
+ i++;
949
+ }
950
+ if (end0pos !== I63) {
951
+ this.writeBits(EOB);
952
+ }
953
+ return DC;
954
+ }
955
+ }
956
+
957
+ // Copyright 2024 IOTA Stiftung.
958
+ // SPDX-License-Identifier: Apache-2.0.
959
+ /* eslint-disable no-bitwise */
960
+ /* eslint-disable no-mixed-operators */
961
+ /* eslint-disable no-continue */
962
+ /* eslint-disable unicorn/prefer-math-trunc */
963
+ /**
964
+ * PNG Encoder.
965
+ * Based on https://github.com/photopea/UPNG.js.
966
+ */
967
+ class PngEncoder {
968
+ /**
969
+ * Encode the image frames to png.
970
+ * @param buffers The frame buffers to encode.
971
+ * @param w The image width.
972
+ * @param h The image height.
973
+ * @returns The data for the image.
974
+ */
975
+ async encode(buffers, w, h) {
976
+ const ps = 0;
977
+ const forbidPlte = false;
978
+ const data = new Uint8Array(buffers[0].byteLength * buffers.length + 100);
979
+ const wr = [0x89, 0x50, 0x4e, 0x47, 0x0d, 0x0a, 0x1a, 0x0a];
980
+ for (let i = 0; i < 8; i++) {
981
+ data[i] = wr[i];
982
+ }
983
+ let offset = 8;
984
+ const nImg = await this.compressPNG(buffers, w, h, ps, forbidPlte);
985
+ this.writeUint(data, offset, 13);
986
+ offset += 4;
987
+ // cspell:disable-next-line
988
+ this.writeASCII(data, offset, "IHDR");
989
+ offset += 4;
990
+ this.writeUint(data, offset, w);
991
+ offset += 4;
992
+ this.writeUint(data, offset, h);
993
+ offset += 4;
994
+ data[offset] = nImg.depth;
995
+ offset++;
996
+ data[offset] = nImg.cType;
997
+ offset++;
998
+ data[offset] = 0; // compress
999
+ offset++;
1000
+ data[offset] = 0; // filter
1001
+ offset++;
1002
+ data[offset] = 0; // interlace
1003
+ offset++;
1004
+ this.writeUint(data, offset, this.crc(data, offset - 17, 17));
1005
+ offset += 4; // crc
1006
+ // 9 bytes to say, that it is sRGB
1007
+ this.writeUint(data, offset, 1);
1008
+ offset += 4;
1009
+ this.writeASCII(data, offset, "sRGB");
1010
+ offset += 4;
1011
+ data[offset] = 1;
1012
+ offset++;
1013
+ this.writeUint(data, offset, this.crc(data, offset - 5, 5));
1014
+ offset += 4; // crc
1015
+ const anim = buffers.length > 1;
1016
+ if (anim) {
1017
+ this.writeUint(data, offset, 8);
1018
+ offset += 4;
1019
+ this.writeASCII(data, offset, "acTL");
1020
+ offset += 4;
1021
+ this.writeUint(data, offset, buffers.length);
1022
+ offset += 4;
1023
+ this.writeUint(data, offset, 0);
1024
+ offset += 4;
1025
+ this.writeUint(data, offset, this.crc(data, offset - 12, 12));
1026
+ offset += 4; // crc
1027
+ }
1028
+ if (nImg.cType === 3) {
1029
+ const dl = nImg.plte.length;
1030
+ this.writeUint(data, offset, dl * 3);
1031
+ offset += 4;
1032
+ this.writeASCII(data, offset, "PLTE");
1033
+ offset += 4;
1034
+ for (let i = 0; i < dl; i++) {
1035
+ const ti = i * 3;
1036
+ const c = nImg.plte[i];
1037
+ const r = c & 255;
1038
+ const g = (c >> 8) & 255;
1039
+ const b = (c >> 16) & 255;
1040
+ data[offset + ti + 0] = r;
1041
+ data[offset + ti + 1] = g;
1042
+ data[offset + ti + 2] = b;
1043
+ }
1044
+ offset += dl * 3;
1045
+ this.writeUint(data, offset, this.crc(data, offset - dl * 3 - 4, dl * 3 + 4));
1046
+ offset += 4; // crc
1047
+ if (nImg.gotAlpha) {
1048
+ this.writeUint(data, offset, dl);
1049
+ offset += 4;
1050
+ this.writeASCII(data, offset, "tRNS");
1051
+ offset += 4;
1052
+ for (let i = 0; i < dl; i++) {
1053
+ data[offset + i] = (nImg.plte[i] >> 24) & 255;
1054
+ }
1055
+ offset += dl;
1056
+ this.writeUint(data, offset, this.crc(data, offset - dl - 4, dl + 4));
1057
+ offset += 4; // crc
1058
+ }
1059
+ }
1060
+ let fi = 0;
1061
+ for (let j = 0; j < nImg.frames.length; j++) {
1062
+ const fr = nImg.frames[j];
1063
+ if (anim) {
1064
+ this.writeUint(data, offset, 26);
1065
+ offset += 4;
1066
+ this.writeASCII(data, offset, "fcTL");
1067
+ offset += 4;
1068
+ this.writeUint(data, offset, fi++);
1069
+ offset += 4;
1070
+ this.writeUint(data, offset, fr.rect.width);
1071
+ offset += 4;
1072
+ this.writeUint(data, offset, fr.rect.height);
1073
+ offset += 4;
1074
+ this.writeUint(data, offset, fr.rect.x);
1075
+ offset += 4;
1076
+ this.writeUint(data, offset, fr.rect.y);
1077
+ offset += 4;
1078
+ this.writeUshort(data, offset, 0);
1079
+ offset += 2;
1080
+ this.writeUshort(data, offset, 1000);
1081
+ offset += 2;
1082
+ data[offset] = fr.dispose;
1083
+ offset++; // dispose
1084
+ data[offset] = fr.blend;
1085
+ offset++; // blend
1086
+ this.writeUint(data, offset, this.crc(data, offset - 30, 30));
1087
+ offset += 4; // crc
1088
+ }
1089
+ const imgD = fr.cImg;
1090
+ const dl = imgD?.length ?? 0;
1091
+ this.writeUint(data, offset, dl + (j === 0 ? 0 : 4));
1092
+ offset += 4;
1093
+ const iOff = offset;
1094
+ // cspell:disable-next-line
1095
+ this.writeASCII(data, offset, j === 0 ? "IDAT" : "fdAT");
1096
+ offset += 4;
1097
+ if (j !== 0) {
1098
+ this.writeUint(data, offset, fi++);
1099
+ offset += 4;
1100
+ }
1101
+ if (imgD) {
1102
+ for (let i = 0; i < dl; i++) {
1103
+ data[offset + i] = imgD[i];
1104
+ }
1105
+ }
1106
+ offset += dl;
1107
+ this.writeUint(data, offset, this.crc(data, iOff, offset - iOff));
1108
+ offset += 4; // crc
1109
+ }
1110
+ this.writeUint(data, offset, 0);
1111
+ offset += 4;
1112
+ // cspell:disable-next-line
1113
+ this.writeASCII(data, offset, "IEND");
1114
+ offset += 4;
1115
+ this.writeUint(data, offset, this.crc(data, offset - 4, 4));
1116
+ offset += 4; // crc
1117
+ return new Uint8Array(data.buffer.slice(0, offset));
1118
+ }
1119
+ /**
1120
+ * @internal
1121
+ */
1122
+ async compressPNG(buffers, w, h, ps, forbidPlte) {
1123
+ const out = this.compress(buffers, w, h, ps, 0, forbidPlte);
1124
+ for (let i = 0; i < buffers.length; i++) {
1125
+ const frm = out.frames[i];
1126
+ const nw = frm.rect.width;
1127
+ const nh = frm.rect.height;
1128
+ const bpl = frm.bpl;
1129
+ const bpp = frm.bpp;
1130
+ const fData = new Uint8Array(nw * bpl + nh);
1131
+ frm.cImg = await this.filterZero(frm.img, nh, bpp, bpl, fData);
1132
+ }
1133
+ return out;
1134
+ }
1135
+ /**
1136
+ * @internal
1137
+ */
1138
+ compress(inBuffers, w, h, inPs, forGIF, forbidPlte) {
1139
+ let cType = 6;
1140
+ let depth = 8;
1141
+ let bpp = 4;
1142
+ let alphaAnd = 255;
1143
+ let ps = inPs;
1144
+ let buffers = inBuffers;
1145
+ for (let j = 0; j < buffers.length; j++) {
1146
+ // when not quantized, other frames can contain colors, that are not in an initial frame
1147
+ const img = new Uint8Array(buffers[j]);
1148
+ const iLen = img.length;
1149
+ for (let i = 0; i < iLen; i += 4) {
1150
+ alphaAnd &= img[i + 3];
1151
+ }
1152
+ }
1153
+ let gotAlpha = alphaAnd !== 255;
1154
+ const cMap = {};
1155
+ const pLte = [];
1156
+ if (buffers.length !== 0) {
1157
+ cMap[0] = 0;
1158
+ pLte.push(0);
1159
+ if (ps !== 0) {
1160
+ ps--;
1161
+ }
1162
+ }
1163
+ if (ps !== 0) {
1164
+ const qRes = this.quantize(buffers, ps, forGIF);
1165
+ buffers = qRes.buffers;
1166
+ for (let i = 0; i < qRes.plte.length; i++) {
1167
+ const c = qRes.plte[i].est?.rgba ?? 0;
1168
+ if (!cMap[c]) {
1169
+ cMap[c] = pLte.length;
1170
+ pLte.push(c);
1171
+ }
1172
+ }
1173
+ }
1174
+ else {
1175
+ // what if ps==0, but there are <=256 colors? we still need to detect, if the palette could be used
1176
+ for (let j = 0; j < buffers.length; j++) {
1177
+ // when not quantized, other frames can contain colors, that are not in an initial frame
1178
+ const img32 = new Uint32Array(buffers[j]);
1179
+ const iLen = img32.length;
1180
+ for (let i = 0; i < iLen; i++) {
1181
+ const c = img32[i];
1182
+ if ((i < w || (c !== img32[i - 1] && c !== img32[i - w])) && !cMap[c]) {
1183
+ cMap[c] = pLte.length;
1184
+ pLte.push(c);
1185
+ if (pLte.length >= 300) {
1186
+ break;
1187
+ }
1188
+ }
1189
+ }
1190
+ }
1191
+ }
1192
+ const brute = gotAlpha ? forGIF : false; // brute : frames can only be copied, not "blended"
1193
+ const cc = pLte.length;
1194
+ if (cc <= 256 && !forbidPlte) {
1195
+ if (cc <= 2) {
1196
+ depth = 1;
1197
+ }
1198
+ else if (cc <= 4) {
1199
+ depth = 2;
1200
+ }
1201
+ else if (cc <= 16) {
1202
+ depth = 4;
1203
+ }
1204
+ else {
1205
+ depth = 8;
1206
+ }
1207
+ if (forGIF) {
1208
+ depth = 8;
1209
+ }
1210
+ gotAlpha = true;
1211
+ }
1212
+ const frames = [];
1213
+ for (let j = 0; j < buffers.length; j++) {
1214
+ let cImg = new Uint8Array(buffers[j]);
1215
+ let cImg32 = new Uint32Array(cImg.buffer);
1216
+ let nx = 0;
1217
+ let ny = 0;
1218
+ let nw = w;
1219
+ let nh = h;
1220
+ let blend = 0;
1221
+ if (j !== 0 && !brute) {
1222
+ const tLim = forGIF || j === 1 || frames[frames.length - 2].dispose === 2 ? 1 : 2;
1223
+ let tStp = 0;
1224
+ let tArea = 1e9;
1225
+ for (let it = 0; it < tLim; it++) {
1226
+ const p32 = new Uint32Array(buffers[j - 1 - it]);
1227
+ let mix = w;
1228
+ let miy = h;
1229
+ let max = -1;
1230
+ let may = -1;
1231
+ for (let y = 0; y < h; y++) {
1232
+ for (let x = 0; x < w; x++) {
1233
+ const i = y * w + x;
1234
+ if (cImg32[i] !== p32[i]) {
1235
+ if (x < mix) {
1236
+ mix = x;
1237
+ }
1238
+ if (x > max) {
1239
+ max = x;
1240
+ }
1241
+ if (y < miy) {
1242
+ miy = y;
1243
+ }
1244
+ if (y > may) {
1245
+ may = y;
1246
+ }
1247
+ }
1248
+ }
1249
+ }
1250
+ const sArea = max === -1 ? 1 : (max - mix + 1) * (may - miy + 1);
1251
+ if (sArea < tArea) {
1252
+ tArea = sArea;
1253
+ tStp = it;
1254
+ if (max === -1) {
1255
+ nx = 0;
1256
+ ny = 0;
1257
+ nw = 1;
1258
+ nh = 1;
1259
+ }
1260
+ else {
1261
+ nx = mix;
1262
+ ny = miy;
1263
+ nw = max - mix + 1;
1264
+ nh = may - miy + 1;
1265
+ }
1266
+ }
1267
+ }
1268
+ const pImg = new Uint8Array(buffers[j - 1 - tStp]);
1269
+ if (tStp === 1) {
1270
+ frames[frames.length - 1].dispose = 2;
1271
+ }
1272
+ const nImg = new Uint8Array(nw * nh * 4);
1273
+ this.copyTile(pImg, w, h, nImg, nw, nh, -nx, -ny, 0);
1274
+ if (this.copyTile(cImg, w, h, nImg, nw, nh, -nx, -ny, 3)) {
1275
+ this.copyTile(cImg, w, h, nImg, nw, nh, -nx, -ny, 2);
1276
+ blend = 1;
1277
+ }
1278
+ else {
1279
+ this.copyTile(cImg, w, h, nImg, nw, nh, -nx, -ny, 0);
1280
+ blend = 0;
1281
+ }
1282
+ cImg = nImg;
1283
+ cImg32 = new Uint32Array(cImg.buffer);
1284
+ }
1285
+ let bpl = 4 * nw;
1286
+ if (cc <= 256 && !forbidPlte) {
1287
+ bpl = Math.ceil((depth * nw) / 8);
1288
+ const nImg = new Uint8Array(bpl * nh);
1289
+ for (let y = 0; y < nh; y++) {
1290
+ const i = y * bpl;
1291
+ const ii = y * nw;
1292
+ if (depth === 8) {
1293
+ for (let x = 0; x < nw; x++) {
1294
+ nImg[i + x] = cMap[cImg32[ii + x]];
1295
+ }
1296
+ }
1297
+ else if (depth === 4) {
1298
+ for (let x = 0; x < nw; x++) {
1299
+ nImg[i + (x >> 1)] |= cMap[cImg32[ii + x]] << (4 - (x & 1) * 4);
1300
+ }
1301
+ }
1302
+ else if (depth === 2) {
1303
+ for (let x = 0; x < nw; x++) {
1304
+ nImg[i + (x >> 2)] |= cMap[cImg32[ii + x]] << (6 - (x & 3) * 2);
1305
+ }
1306
+ }
1307
+ else if (depth === 1) {
1308
+ for (let x = 0; x < nw; x++) {
1309
+ nImg[i + (x >> 3)] |= cMap[cImg32[ii + x]] << (7 - (x & 7) * 1);
1310
+ }
1311
+ }
1312
+ }
1313
+ cImg = nImg;
1314
+ cType = 3;
1315
+ bpp = 1;
1316
+ }
1317
+ else if (!gotAlpha && buffers.length === 1) {
1318
+ // some next "reduced" frames may contain alpha for blending
1319
+ const nImg = new Uint8Array(nw * nh * 3);
1320
+ const area = nw * nh;
1321
+ for (let i = 0; i < area; i++) {
1322
+ const ti = i * 3;
1323
+ const qi = i * 4;
1324
+ nImg[ti] = cImg[qi];
1325
+ nImg[ti + 1] = cImg[qi + 1];
1326
+ nImg[ti + 2] = cImg[qi + 2];
1327
+ }
1328
+ cImg = nImg;
1329
+ cType = 2;
1330
+ bpp = 3;
1331
+ bpl = 3 * nw;
1332
+ }
1333
+ frames.push({
1334
+ rect: {
1335
+ x: nx,
1336
+ y: ny,
1337
+ width: nw,
1338
+ height: nh
1339
+ },
1340
+ img: cImg,
1341
+ bpl,
1342
+ bpp,
1343
+ blend,
1344
+ dispose: brute ? 1 : 0
1345
+ });
1346
+ }
1347
+ return { cType, depth, plte: pLte, gotAlpha, frames };
1348
+ }
1349
+ /**
1350
+ * @internal
1351
+ */
1352
+ async filterZero(img, h, bpp, bpl, data) {
1353
+ const fls = [];
1354
+ for (let t = 0; t < 5; t++) {
1355
+ if (h * bpl > 500000 && (t === 2 || t === 3 || t === 4)) {
1356
+ continue;
1357
+ }
1358
+ for (let y = 0; y < h; y++) {
1359
+ this.filterLine(data, img, y, bpl, bpp, t);
1360
+ }
1361
+ const deflated = await Compression.compress(data, "deflate");
1362
+ fls.push(deflated);
1363
+ if (bpp === 1) {
1364
+ break;
1365
+ }
1366
+ }
1367
+ let ti = 0;
1368
+ let tSize = 1e9;
1369
+ for (let i = 0; i < fls.length; i++) {
1370
+ if (fls[i].length < tSize) {
1371
+ ti = i;
1372
+ tSize = fls[i].length;
1373
+ }
1374
+ }
1375
+ return fls[ti];
1376
+ }
1377
+ /**
1378
+ * @internal
1379
+ */
1380
+ filterLine(data, img, y, bpl, bpp, type) {
1381
+ const i = y * bpl;
1382
+ let di = i + y;
1383
+ data[di] = type;
1384
+ di++;
1385
+ if (type === 0) {
1386
+ for (let x = 0; x < bpl; x++) {
1387
+ data[di + x] = img[i + x];
1388
+ }
1389
+ }
1390
+ else if (type === 1) {
1391
+ for (let x = 0; x < bpp; x++) {
1392
+ data[di + x] = img[i + x];
1393
+ }
1394
+ for (let x = bpp; x < bpl; x++) {
1395
+ data[di + x] = (img[i + x] - img[i + x - bpp] + 256) & 255;
1396
+ }
1397
+ }
1398
+ else if (y === 0) {
1399
+ for (let x = 0; x < bpp; x++) {
1400
+ data[di + x] = img[i + x];
1401
+ }
1402
+ if (type === 2) {
1403
+ for (let x = bpp; x < bpl; x++) {
1404
+ data[di + x] = img[i + x];
1405
+ }
1406
+ }
1407
+ if (type === 3) {
1408
+ for (let x = bpp; x < bpl; x++) {
1409
+ data[di + x] = (img[i + x] - (img[i + x - bpp] >> 1) + 256) & 255;
1410
+ }
1411
+ }
1412
+ if (type === 4) {
1413
+ for (let x = bpp; x < bpl; x++) {
1414
+ data[di + x] = (img[i + x] - this.paeth(img[i + x - bpp], 0, 0) + 256) & 255;
1415
+ }
1416
+ }
1417
+ }
1418
+ else {
1419
+ if (type === 2) {
1420
+ for (let x = 0; x < bpl; x++) {
1421
+ data[di + x] = (img[i + x] + 256 - img[i + x - bpl]) & 255;
1422
+ }
1423
+ }
1424
+ if (type === 3) {
1425
+ for (let x = 0; x < bpp; x++) {
1426
+ data[di + x] = (img[i + x] + 256 - (img[i + x - bpl] >> 1)) & 255;
1427
+ }
1428
+ for (let x = bpp; x < bpl; x++) {
1429
+ data[di + x] = (img[i + x] + 256 - ((img[i + x - bpl] + img[i + x - bpp]) >> 1)) & 255;
1430
+ }
1431
+ }
1432
+ if (type === 4) {
1433
+ for (let x = 0; x < bpp; x++) {
1434
+ data[di + x] = (img[i + x] + 256 - this.paeth(0, img[i + x - bpl], 0)) & 255;
1435
+ }
1436
+ for (let x = bpp; x < bpl; x++) {
1437
+ data[di + x] =
1438
+ (img[i + x] +
1439
+ 256 -
1440
+ this.paeth(img[i + x - bpp], img[i + x - bpl], img[i + x - bpp - bpl])) &
1441
+ 255;
1442
+ }
1443
+ }
1444
+ }
1445
+ }
1446
+ /**
1447
+ * @internal
1448
+ */
1449
+ paeth(a, b, c) {
1450
+ const p = a + b - c;
1451
+ const pa = Math.abs(p - a);
1452
+ const pb = Math.abs(p - b);
1453
+ const pc = Math.abs(p - c);
1454
+ if (pa <= pb && pa <= pc) {
1455
+ return a;
1456
+ }
1457
+ if (pb <= pc) {
1458
+ return b;
1459
+ }
1460
+ return c;
1461
+ }
1462
+ /**
1463
+ * @internal
1464
+ */
1465
+ writeASCII(data, p, s) {
1466
+ for (let i = 0; i < s.length; i++) {
1467
+ data[p + i] = s.charCodeAt(i);
1468
+ }
1469
+ }
1470
+ /**
1471
+ * @internal
1472
+ */
1473
+ writeUint(buff, p, n) {
1474
+ buff[p] = (n >> 24) & 255;
1475
+ buff[p + 1] = (n >> 16) & 255;
1476
+ buff[p + 2] = (n >> 8) & 255;
1477
+ buff[p + 3] = n & 255;
1478
+ }
1479
+ /**
1480
+ * @internal
1481
+ */
1482
+ writeUshort(buff, p, n) {
1483
+ buff[p] = (n >> 8) & 255;
1484
+ buff[p + 1] = n & 255;
1485
+ }
1486
+ /**
1487
+ * @internal
1488
+ */
1489
+ copyTile(sb, sw, sh, tb, tw, th, xOffset, yOffset, mode) {
1490
+ const w = Math.min(sw, tw);
1491
+ const h = Math.min(sh, th);
1492
+ let si = 0;
1493
+ let ti = 0;
1494
+ for (let y = 0; y < h; y++) {
1495
+ for (let x = 0; x < w; x++) {
1496
+ if (xOffset >= 0 && yOffset >= 0) {
1497
+ si = (y * sw + x) << 2;
1498
+ ti = ((yOffset + y) * tw + xOffset + x) << 2;
1499
+ }
1500
+ else {
1501
+ si = ((-yOffset + y) * sw - xOffset + x) << 2;
1502
+ ti = (y * tw + x) << 2;
1503
+ }
1504
+ if (mode === 0) {
1505
+ tb[ti] = sb[si];
1506
+ tb[ti + 1] = sb[si + 1];
1507
+ tb[ti + 2] = sb[si + 2];
1508
+ tb[ti + 3] = sb[si + 3];
1509
+ }
1510
+ else if (mode === 1) {
1511
+ const fa = sb[si + 3] * (1 / 255);
1512
+ const fr = sb[si] * fa;
1513
+ const fg = sb[si + 1] * fa;
1514
+ const fb = sb[si + 2] * fa;
1515
+ const ba = tb[ti + 3] * (1 / 255);
1516
+ const br = tb[ti] * ba;
1517
+ const bg = tb[ti + 1] * ba;
1518
+ const bb = tb[ti + 2] * ba;
1519
+ const ifa = 1 - fa;
1520
+ const oa = fa + ba * ifa;
1521
+ const ioa = oa === 0 ? 0 : 1 / oa;
1522
+ tb[ti + 3] = 255 * oa;
1523
+ tb[ti + 0] = (fr + br * ifa) * ioa;
1524
+ tb[ti + 1] = (fg + bg * ifa) * ioa;
1525
+ tb[ti + 2] = (fb + bb * ifa) * ioa;
1526
+ }
1527
+ else if (mode === 2) {
1528
+ // copy only differences, otherwise zero
1529
+ const fa = sb[si + 3];
1530
+ const fr = sb[si];
1531
+ const fg = sb[si + 1];
1532
+ const fb = sb[si + 2];
1533
+ const ba = tb[ti + 3];
1534
+ const br = tb[ti];
1535
+ const bg = tb[ti + 1];
1536
+ const bb = tb[ti + 2];
1537
+ if (fa === ba && fr === br && fg === bg && fb === bb) {
1538
+ tb[ti] = 0;
1539
+ tb[ti + 1] = 0;
1540
+ tb[ti + 2] = 0;
1541
+ tb[ti + 3] = 0;
1542
+ }
1543
+ else {
1544
+ tb[ti] = fr;
1545
+ tb[ti + 1] = fg;
1546
+ tb[ti + 2] = fb;
1547
+ tb[ti + 3] = fa;
1548
+ }
1549
+ }
1550
+ else if (mode === 3) {
1551
+ // check if can be blended
1552
+ const fa = sb[si + 3];
1553
+ const fr = sb[si];
1554
+ const fg = sb[si + 1];
1555
+ const fb = sb[si + 2];
1556
+ const ba = tb[ti + 3];
1557
+ const br = tb[ti];
1558
+ const bg = tb[ti + 1];
1559
+ const bb = tb[ti + 2];
1560
+ if (fa === ba && fr === br && fg === bg && fb === bb) {
1561
+ continue;
1562
+ }
1563
+ if (fa < 220 && ba > 20) {
1564
+ return false;
1565
+ }
1566
+ }
1567
+ }
1568
+ }
1569
+ return true;
1570
+ }
1571
+ /**
1572
+ * @internal
1573
+ */
1574
+ crc(b, o, l) {
1575
+ return this.crcUpdate(0xffffffff, b, o, l) ^ 0xffffffff;
1576
+ }
1577
+ /**
1578
+ * @internal
1579
+ */
1580
+ crcUpdate(c, buf, off, len) {
1581
+ let localC = c;
1582
+ const crcTable = this.crcTable();
1583
+ for (let i = 0; i < len; i++) {
1584
+ localC = crcTable[(localC ^ buf[off + i]) & 0xff] ^ (localC >>> 8);
1585
+ }
1586
+ return localC;
1587
+ }
1588
+ /**
1589
+ * @internal
1590
+ */
1591
+ crcTable() {
1592
+ const tab = new Uint32Array(256);
1593
+ for (let n = 0; n < 256; n++) {
1594
+ let c = n;
1595
+ for (let k = 0; k < 8; k++) {
1596
+ if (c & 1) {
1597
+ c = 0xedb88320 ^ (c >>> 1);
1598
+ }
1599
+ else {
1600
+ c >>>= 1;
1601
+ }
1602
+ }
1603
+ tab[n] = c;
1604
+ }
1605
+ return tab;
1606
+ }
1607
+ /**
1608
+ * @internal
1609
+ */
1610
+ quantize(buffers, ps, roundAlpha) {
1611
+ const imgs = [];
1612
+ let total = 0;
1613
+ for (let i = 0; i < buffers.length; i++) {
1614
+ imgs.push(this.alphaMul(new Uint8Array(buffers[i]), roundAlpha));
1615
+ total += buffers[i].byteLength;
1616
+ }
1617
+ const nImg = new Uint8Array(total);
1618
+ const nImg32 = new Uint32Array(nImg.buffer);
1619
+ let nOff = 0;
1620
+ for (let i = 0; i < imgs.length; i++) {
1621
+ const img = imgs[i];
1622
+ const il = img.length;
1623
+ for (let j = 0; j < il; j++) {
1624
+ nImg[nOff + j] = img[j];
1625
+ }
1626
+ nOff += il;
1627
+ }
1628
+ const root = {
1629
+ i0: 0,
1630
+ i1: nImg.length,
1631
+ bst: null,
1632
+ est: null,
1633
+ tDst: 0,
1634
+ left: null,
1635
+ right: null
1636
+ };
1637
+ root.bst = this.quantizeStats(nImg, root.i0, root.i1);
1638
+ root.est = this.quantizeEStats(root.bst);
1639
+ const leafs = [root];
1640
+ while (leafs.length < ps) {
1641
+ let maxL = 0;
1642
+ let mi = 0;
1643
+ for (let i = 0; i < leafs.length; i++) {
1644
+ const est = leafs[i].est;
1645
+ if (est && est.L > maxL) {
1646
+ maxL = est.L;
1647
+ mi = i;
1648
+ }
1649
+ }
1650
+ if (maxL < 1e-3) {
1651
+ break;
1652
+ }
1653
+ const node = leafs[mi];
1654
+ const s0 = this.quantizeSplitPixels(nImg, nImg32, node.i0, node.i1, node.est?.e ?? [], node.est?.eMq255 ?? 0);
1655
+ const ln = {
1656
+ i0: node.i0,
1657
+ i1: s0,
1658
+ bst: null,
1659
+ est: null,
1660
+ tDst: 0,
1661
+ left: null,
1662
+ right: null
1663
+ };
1664
+ ln.bst = this.quantizeStats(nImg, ln.i0, ln.i1);
1665
+ ln.est = this.quantizeEStats(ln.bst);
1666
+ const rn = {
1667
+ i0: s0,
1668
+ i1: node.i1,
1669
+ bst: null,
1670
+ est: null,
1671
+ tDst: 0,
1672
+ left: null,
1673
+ right: null
1674
+ };
1675
+ rn.bst = {
1676
+ R: [],
1677
+ m: [],
1678
+ N: (node.bst?.N ?? 0) - ln.bst.N
1679
+ };
1680
+ for (let i = 0; i < 16; i++) {
1681
+ rn.bst.R[i] = (node.bst?.R[i] ?? 0) - ln.bst.R[i];
1682
+ }
1683
+ for (let i = 0; i < 4; i++) {
1684
+ rn.bst.m[i] = (node.bst?.m[i] ?? 0) - ln.bst.m[i];
1685
+ }
1686
+ rn.est = this.quantizeEStats(rn.bst);
1687
+ node.left = ln;
1688
+ node.right = rn;
1689
+ leafs[mi] = ln;
1690
+ leafs.push(rn);
1691
+ }
1692
+ leafs.sort((a, b) => (b.bst?.N ?? 0) - (a.bst?.N ?? 0));
1693
+ const outBuffers = [];
1694
+ for (let ii = 0; ii < imgs.length; ii++) {
1695
+ const sb = new Uint8Array(imgs[ii]);
1696
+ const tb = new Uint32Array(imgs[ii]);
1697
+ const len = sb.length;
1698
+ for (let i = 0; i < len; i += 4) {
1699
+ const r = sb[i] * (1 / 255);
1700
+ const g = sb[i + 1] * (1 / 255);
1701
+ const b = sb[i + 2] * (1 / 255);
1702
+ const a = sb[i + 3] * (1 / 255);
1703
+ let nd = root;
1704
+ while (nd?.left) {
1705
+ // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
1706
+ nd = this.quantizePlaneDst(nd.est, r, g, b, a) <= 0 ? nd.left : nd.right;
1707
+ }
1708
+ tb[i >> 2] = nd?.est?.rgba ?? 0;
1709
+ }
1710
+ outBuffers[ii] = tb.buffer;
1711
+ }
1712
+ return { buffers: outBuffers, plte: leafs };
1713
+ }
1714
+ /**
1715
+ * @internal
1716
+ */
1717
+ quantizeStats(nImg, i0, i1) {
1718
+ const R = [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0];
1719
+ const m = [0, 0, 0, 0];
1720
+ const N = (i1 - i0) >> 2;
1721
+ for (let i = i0; i < i1; i += 4) {
1722
+ const r = nImg[i] * (1 / 255);
1723
+ const g = nImg[i + 1] * (1 / 255);
1724
+ const b = nImg[i + 2] * (1 / 255);
1725
+ const a = nImg[i + 3] * (1 / 255);
1726
+ m[0] += r;
1727
+ m[1] += g;
1728
+ m[2] += b;
1729
+ m[3] += a;
1730
+ R[0] += r * r;
1731
+ R[1] += r * g;
1732
+ R[2] += r * b;
1733
+ R[3] += r * a;
1734
+ R[5] += g * g;
1735
+ R[6] += g * b;
1736
+ R[7] += g * a;
1737
+ R[10] += b * b;
1738
+ R[11] += b * a;
1739
+ R[15] += a * a;
1740
+ }
1741
+ R[4] = R[1];
1742
+ R[8] = R[2];
1743
+ R[12] = R[3];
1744
+ R[9] = R[6];
1745
+ R[13] = R[7];
1746
+ R[14] = R[11];
1747
+ return { R, m, N };
1748
+ }
1749
+ /**
1750
+ * @internal
1751
+ */
1752
+ quantizeEStats(stats) {
1753
+ const R = stats.R;
1754
+ const m = stats.m;
1755
+ const N = stats.N;
1756
+ const m0 = m[0];
1757
+ const m1 = m[1];
1758
+ const m2 = m[2];
1759
+ const m3 = m[3];
1760
+ const iN = N === 0 ? 0 : 1 / N;
1761
+ const rj = [
1762
+ R[0] - m0 * m0 * iN,
1763
+ R[1] - m0 * m1 * iN,
1764
+ R[2] - m0 * m2 * iN,
1765
+ R[3] - m0 * m3 * iN,
1766
+ R[4] - m1 * m0 * iN,
1767
+ R[5] - m1 * m1 * iN,
1768
+ R[6] - m1 * m2 * iN,
1769
+ R[7] - m1 * m3 * iN,
1770
+ R[8] - m2 * m0 * iN,
1771
+ R[9] - m2 * m1 * iN,
1772
+ R[10] - m2 * m2 * iN,
1773
+ R[11] - m2 * m3 * iN,
1774
+ R[12] - m3 * m0 * iN,
1775
+ R[13] - m3 * m1 * iN,
1776
+ R[14] - m3 * m2 * iN,
1777
+ R[15] - m3 * m3 * iN
1778
+ ];
1779
+ const A = rj;
1780
+ let b = [0.5, 0.5, 0.5, 0.5];
1781
+ let mi = 0;
1782
+ let tmi = 0;
1783
+ if (N !== 0) {
1784
+ for (let i = 0; i < 10; i++) {
1785
+ b = this.m4MultiplyVec(A, b);
1786
+ tmi = Math.sqrt(this.m4Dot(b, b));
1787
+ b = this.m4Sml(1 / tmi, b);
1788
+ if (Math.abs(tmi - mi) < 1e-9) {
1789
+ break;
1790
+ }
1791
+ mi = tmi;
1792
+ }
1793
+ }
1794
+ const q = [m0 * iN, m1 * iN, m2 * iN, m3 * iN];
1795
+ const eMq255 = this.m4Dot(this.m4Sml(255, q), b);
1796
+ const ia = q[3] < 0.001 ? 0 : 1 / q[3];
1797
+ return {
1798
+ Cov: rj,
1799
+ q,
1800
+ e: b,
1801
+ L: mi,
1802
+ eMq255,
1803
+ eMq: this.m4Dot(b, q),
1804
+ rgba: ((Math.round(255 * q[3]) << 24) |
1805
+ (Math.round(255 * q[2] * ia) << 16) |
1806
+ (Math.round(255 * q[1] * ia) << 8) |
1807
+ (Math.round(255 * q[0] * ia) << 0)) >>>
1808
+ 0
1809
+ };
1810
+ }
1811
+ /**
1812
+ * @internal
1813
+ */
1814
+ quantizePlaneDst(est, r, g, b, a) {
1815
+ const e = est.e;
1816
+ return e[0] * r + e[1] * g + e[2] * b + e[3] * a - est.eMq;
1817
+ }
1818
+ /**
1819
+ * @internal
1820
+ */
1821
+ quantizeSplitPixels(nImg, nImg32, i0in, i1in, e, eMq) {
1822
+ let i1 = i1in - 4;
1823
+ let i0 = i0in;
1824
+ while (i0 < i1) {
1825
+ while (this.quantizeVecDot(nImg, i0, e) <= eMq) {
1826
+ i0 += 4;
1827
+ }
1828
+ while (this.quantizeVecDot(nImg, i1, e) > eMq) {
1829
+ i1 -= 4;
1830
+ }
1831
+ if (i0 >= i1) {
1832
+ break;
1833
+ }
1834
+ const t = nImg32[i0 >> 2];
1835
+ nImg32[i0 >> 2] = nImg32[i1 >> 2];
1836
+ nImg32[i1 >> 2] = t;
1837
+ i0 += 4;
1838
+ i1 -= 4;
1839
+ }
1840
+ while (this.quantizeVecDot(nImg, i0, e) > eMq) {
1841
+ i0 -= 4;
1842
+ }
1843
+ return i0 + 4;
1844
+ }
1845
+ /**
1846
+ * @internal
1847
+ */
1848
+ quantizeVecDot(nImg, i, e) {
1849
+ return nImg[i] * e[0] + nImg[i + 1] * e[1] + nImg[i + 2] * e[2] + nImg[i + 3] * e[3];
1850
+ }
1851
+ /**
1852
+ * @internal
1853
+ */
1854
+ m4MultiplyVec(m, v) {
1855
+ return [
1856
+ m[0] * v[0] + m[1] * v[1] + m[2] * v[2] + m[3] * v[3],
1857
+ m[4] * v[0] + m[5] * v[1] + m[6] * v[2] + m[7] * v[3],
1858
+ m[8] * v[0] + m[9] * v[1] + m[10] * v[2] + m[11] * v[3],
1859
+ m[12] * v[0] + m[13] * v[1] + m[14] * v[2] + m[15] * v[3]
1860
+ ];
1861
+ }
1862
+ /**
1863
+ * @internal
1864
+ */
1865
+ m4Dot(x, y) {
1866
+ return x[0] * y[0] + x[1] * y[1] + x[2] * y[2] + x[3] * y[3];
1867
+ }
1868
+ /**
1869
+ * @internal
1870
+ */
1871
+ m4Sml(a, y) {
1872
+ return [a * y[0], a * y[1], a * y[2], a * y[3]];
1873
+ }
1874
+ /**
1875
+ * @internal
1876
+ */
1877
+ alphaMul(img, roundA) {
1878
+ const nImg = new Uint8Array(img.length);
1879
+ const area = img.length >> 2;
1880
+ for (let i = 0; i < area; i++) {
1881
+ const qi = i << 2;
1882
+ let ia = img[qi + 3];
1883
+ if (roundA) {
1884
+ ia = ia < 128 ? 0 : 255;
1885
+ }
1886
+ const a = ia * (1 / 255);
1887
+ nImg[qi + 0] = img[qi + 0] * a;
1888
+ nImg[qi + 1] = img[qi + 1] * a;
1889
+ nImg[qi + 2] = img[qi + 2] * a;
1890
+ nImg[qi + 3] = ia;
1891
+ }
1892
+ return nImg;
1893
+ }
1894
+ }
1895
+
1896
+ export { Color, JpegEncoder, PngEncoder };