cross-image 0.2.4 → 0.4.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +21 -21
- package/README.md +507 -333
- package/esm/mod.d.ts +4 -4
- package/esm/mod.js +2 -2
- package/esm/src/formats/apng.d.ts +5 -5
- package/esm/src/formats/apng.js +7 -9
- package/esm/src/formats/ascii.d.ts +3 -3
- package/esm/src/formats/ascii.js +1 -1
- package/esm/src/formats/avif.d.ts +3 -3
- package/esm/src/formats/avif.js +7 -7
- package/esm/src/formats/bmp.d.ts +3 -3
- package/esm/src/formats/bmp.js +2 -2
- package/esm/src/formats/dng.d.ts +1 -1
- package/esm/src/formats/dng.js +1 -1
- package/esm/src/formats/gif.d.ts +4 -4
- package/esm/src/formats/gif.js +14 -10
- package/esm/src/formats/heic.d.ts +3 -3
- package/esm/src/formats/heic.js +7 -7
- package/esm/src/formats/ico.d.ts +3 -3
- package/esm/src/formats/ico.js +4 -4
- package/esm/src/formats/jpeg.d.ts +3 -3
- package/esm/src/formats/jpeg.js +23 -11
- package/esm/src/formats/pam.d.ts +3 -3
- package/esm/src/formats/pam.js +2 -2
- package/esm/src/formats/pcx.d.ts +3 -3
- package/esm/src/formats/pcx.js +2 -2
- package/esm/src/formats/png.d.ts +3 -3
- package/esm/src/formats/png.js +2 -2
- package/esm/src/formats/png_base.js +2 -5
- package/esm/src/formats/ppm.d.ts +3 -3
- package/esm/src/formats/ppm.js +2 -2
- package/esm/src/formats/tiff.d.ts +7 -18
- package/esm/src/formats/tiff.js +86 -21
- package/esm/src/formats/webp.d.ts +3 -3
- package/esm/src/formats/webp.js +11 -8
- package/esm/src/image.d.ts +11 -3
- package/esm/src/image.js +37 -21
- package/esm/src/types.d.ts +56 -4
- package/esm/src/utils/gif_decoder.d.ts +4 -1
- package/esm/src/utils/gif_decoder.js +91 -65
- package/esm/src/utils/image_processing.js +144 -70
- package/esm/src/utils/jpeg_decoder.d.ts +17 -4
- package/esm/src/utils/jpeg_decoder.js +448 -83
- package/esm/src/utils/jpeg_encoder.d.ts +15 -1
- package/esm/src/utils/jpeg_encoder.js +263 -24
- package/esm/src/utils/resize.js +51 -20
- package/esm/src/utils/tiff_deflate.d.ts +18 -0
- package/esm/src/utils/tiff_deflate.js +27 -0
- package/esm/src/utils/tiff_packbits.d.ts +24 -0
- package/esm/src/utils/tiff_packbits.js +90 -0
- package/esm/src/utils/webp_decoder.d.ts +3 -1
- package/esm/src/utils/webp_decoder.js +144 -63
- package/esm/src/utils/webp_encoder.js +5 -11
- package/package.json +1 -1
- package/script/mod.d.ts +4 -4
- package/script/mod.js +2 -2
- package/script/src/formats/apng.d.ts +5 -5
- package/script/src/formats/apng.js +7 -9
- package/script/src/formats/ascii.d.ts +3 -3
- package/script/src/formats/ascii.js +1 -1
- package/script/src/formats/avif.d.ts +3 -3
- package/script/src/formats/avif.js +7 -7
- package/script/src/formats/bmp.d.ts +3 -3
- package/script/src/formats/bmp.js +2 -2
- package/script/src/formats/dng.d.ts +1 -1
- package/script/src/formats/dng.js +1 -1
- package/script/src/formats/gif.d.ts +4 -4
- package/script/src/formats/gif.js +14 -10
- package/script/src/formats/heic.d.ts +3 -3
- package/script/src/formats/heic.js +7 -7
- package/script/src/formats/ico.d.ts +3 -3
- package/script/src/formats/ico.js +4 -4
- package/script/src/formats/jpeg.d.ts +3 -3
- package/script/src/formats/jpeg.js +23 -11
- package/script/src/formats/pam.d.ts +3 -3
- package/script/src/formats/pam.js +2 -2
- package/script/src/formats/pcx.d.ts +3 -3
- package/script/src/formats/pcx.js +2 -2
- package/script/src/formats/png.d.ts +3 -3
- package/script/src/formats/png.js +2 -2
- package/script/src/formats/png_base.js +2 -5
- package/script/src/formats/ppm.d.ts +3 -3
- package/script/src/formats/ppm.js +2 -2
- package/script/src/formats/tiff.d.ts +7 -18
- package/script/src/formats/tiff.js +86 -21
- package/script/src/formats/webp.d.ts +3 -3
- package/script/src/formats/webp.js +11 -8
- package/script/src/image.d.ts +11 -3
- package/script/src/image.js +36 -20
- package/script/src/types.d.ts +56 -4
- package/script/src/utils/gif_decoder.d.ts +4 -1
- package/script/src/utils/gif_decoder.js +91 -65
- package/script/src/utils/image_processing.js +144 -70
- package/script/src/utils/jpeg_decoder.d.ts +17 -4
- package/script/src/utils/jpeg_decoder.js +448 -83
- package/script/src/utils/jpeg_encoder.d.ts +15 -1
- package/script/src/utils/jpeg_encoder.js +263 -24
- package/script/src/utils/resize.js +51 -20
- package/script/src/utils/tiff_deflate.d.ts +18 -0
- package/script/src/utils/tiff_deflate.js +31 -0
- package/script/src/utils/tiff_packbits.d.ts +24 -0
- package/script/src/utils/tiff_packbits.js +94 -0
- package/script/src/utils/webp_decoder.d.ts +3 -1
- package/script/src/utils/webp_decoder.js +144 -63
- package/script/src/utils/webp_encoder.js +5 -11
|
@@ -0,0 +1,94 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* PackBits compression/decompression for TIFF
|
|
4
|
+
* PackBits is a simple run-length encoding (RLE) scheme used in TIFF images
|
|
5
|
+
* Compression code: 32773
|
|
6
|
+
*
|
|
7
|
+
* Format:
|
|
8
|
+
* - Header byte n:
|
|
9
|
+
* - If n >= 0 and n <= 127: Copy the next n+1 bytes literally
|
|
10
|
+
* - If n >= -127 and n <= -1: Repeat the next byte -n+1 times
|
|
11
|
+
* - If n = -128: No operation (skip)
|
|
12
|
+
*/
|
|
13
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
14
|
+
exports.packBitsCompress = packBitsCompress;
|
|
15
|
+
exports.packBitsDecompress = packBitsDecompress;
|
|
16
|
+
/**
|
|
17
|
+
* Compress data using PackBits RLE
|
|
18
|
+
* @param data Uncompressed data
|
|
19
|
+
* @returns Compressed data
|
|
20
|
+
*/
|
|
21
|
+
function packBitsCompress(data) {
|
|
22
|
+
const result = [];
|
|
23
|
+
let i = 0;
|
|
24
|
+
while (i < data.length) {
|
|
25
|
+
// Look for runs (repeated bytes)
|
|
26
|
+
let runLength = 1;
|
|
27
|
+
while (i + runLength < data.length &&
|
|
28
|
+
data[i + runLength] === data[i] &&
|
|
29
|
+
runLength < 128) {
|
|
30
|
+
runLength++;
|
|
31
|
+
}
|
|
32
|
+
// If we have a run of 2 or more, encode it as a run
|
|
33
|
+
if (runLength >= 2) {
|
|
34
|
+
result.push(-(runLength - 1) & 0xff); // Two's complement representation
|
|
35
|
+
result.push(data[i]);
|
|
36
|
+
i += runLength;
|
|
37
|
+
}
|
|
38
|
+
else {
|
|
39
|
+
// Look for literals (non-repeating bytes)
|
|
40
|
+
const literalStart = i;
|
|
41
|
+
let literalLength = 1;
|
|
42
|
+
while (i + literalLength < data.length &&
|
|
43
|
+
literalLength < 128) {
|
|
44
|
+
// Check if we're starting a run
|
|
45
|
+
if (i + literalLength + 1 < data.length &&
|
|
46
|
+
data[i + literalLength] === data[i + literalLength + 1]) {
|
|
47
|
+
// We found a run, stop the literal sequence here
|
|
48
|
+
break;
|
|
49
|
+
}
|
|
50
|
+
literalLength++;
|
|
51
|
+
}
|
|
52
|
+
result.push(literalLength - 1);
|
|
53
|
+
for (let j = 0; j < literalLength; j++) {
|
|
54
|
+
result.push(data[literalStart + j]);
|
|
55
|
+
}
|
|
56
|
+
i += literalLength;
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
return new Uint8Array(result);
|
|
60
|
+
}
|
|
61
|
+
/**
|
|
62
|
+
* Decompress PackBits RLE data
|
|
63
|
+
* @param data Compressed data
|
|
64
|
+
* @returns Decompressed data
|
|
65
|
+
*/
|
|
66
|
+
function packBitsDecompress(data) {
|
|
67
|
+
const result = [];
|
|
68
|
+
let i = 0;
|
|
69
|
+
while (i < data.length) {
|
|
70
|
+
const header = data[i++];
|
|
71
|
+
if (header === 128) {
|
|
72
|
+
// No operation, skip
|
|
73
|
+
continue;
|
|
74
|
+
}
|
|
75
|
+
else if (header < 128) {
|
|
76
|
+
// Literal run: copy next (header + 1) bytes
|
|
77
|
+
const count = header + 1;
|
|
78
|
+
for (let j = 0; j < count && i < data.length; j++) {
|
|
79
|
+
result.push(data[i++]);
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
else {
|
|
83
|
+
// Repeated run: repeat next byte (257 - header) times
|
|
84
|
+
const count = 257 - header;
|
|
85
|
+
if (i < data.length) {
|
|
86
|
+
const value = data[i++];
|
|
87
|
+
for (let j = 0; j < count; j++) {
|
|
88
|
+
result.push(value);
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
return new Uint8Array(result);
|
|
94
|
+
}
|
|
@@ -19,9 +19,11 @@
|
|
|
19
19
|
* @see https://developers.google.com/speed/webp/docs/riff_container
|
|
20
20
|
* @see https://developers.google.com/speed/webp/docs/webp_lossless_bitstream_specification
|
|
21
21
|
*/
|
|
22
|
+
import type { ImageDecoderOptions } from "../types.js";
|
|
22
23
|
export declare class WebPDecoder {
|
|
23
24
|
private data;
|
|
24
|
-
|
|
25
|
+
private options;
|
|
26
|
+
constructor(data: Uint8Array, settings?: ImageDecoderOptions);
|
|
25
27
|
decode(): {
|
|
26
28
|
width: number;
|
|
27
29
|
height: number;
|
|
@@ -96,7 +96,6 @@ class HuffmanTable {
|
|
|
96
96
|
const bit = reader.readBits(1);
|
|
97
97
|
node = bit === 0 ? node.left : node.right;
|
|
98
98
|
if (!node) {
|
|
99
|
-
// console.log("Invalid Huffman code - walked off tree");
|
|
100
99
|
throw new Error("Invalid Huffman code");
|
|
101
100
|
}
|
|
102
101
|
}
|
|
@@ -167,14 +166,24 @@ class BitReader {
|
|
|
167
166
|
}
|
|
168
167
|
}
|
|
169
168
|
class WebPDecoder {
|
|
170
|
-
constructor(data) {
|
|
169
|
+
constructor(data, settings = {}) {
|
|
171
170
|
Object.defineProperty(this, "data", {
|
|
172
171
|
enumerable: true,
|
|
173
172
|
configurable: true,
|
|
174
173
|
writable: true,
|
|
175
174
|
value: void 0
|
|
176
175
|
});
|
|
176
|
+
Object.defineProperty(this, "options", {
|
|
177
|
+
enumerable: true,
|
|
178
|
+
configurable: true,
|
|
179
|
+
writable: true,
|
|
180
|
+
value: void 0
|
|
181
|
+
});
|
|
177
182
|
this.data = data;
|
|
183
|
+
this.options = {
|
|
184
|
+
tolerantDecoding: settings.tolerantDecoding ?? true,
|
|
185
|
+
onWarning: settings.onWarning,
|
|
186
|
+
};
|
|
178
187
|
}
|
|
179
188
|
decode() {
|
|
180
189
|
// Verify WebP signature
|
|
@@ -281,66 +290,142 @@ class WebPDecoder {
|
|
|
281
290
|
const numPixels = width * height;
|
|
282
291
|
// Color cache for repeated colors
|
|
283
292
|
const colorCache = new Uint32Array(colorCacheSize);
|
|
284
|
-
|
|
285
|
-
//
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
293
|
+
if (this.options.tolerantDecoding) {
|
|
294
|
+
// Tolerant mode: continue decoding even if errors occur
|
|
295
|
+
try {
|
|
296
|
+
for (let i = 0; i < numPixels;) {
|
|
297
|
+
// Read green channel (which determines the code type)
|
|
298
|
+
const green = huffmanTables.green.readSymbol(reader);
|
|
299
|
+
if (green < 256) {
|
|
300
|
+
// Literal pixel
|
|
301
|
+
const red = huffmanTables.red.readSymbol(reader);
|
|
302
|
+
const blue = huffmanTables.blue.readSymbol(reader);
|
|
303
|
+
const alpha = alphaUsed !== 0 ? huffmanTables.alpha.readSymbol(reader) : 255;
|
|
304
|
+
pixelData[pixelIndex++] = red;
|
|
305
|
+
pixelData[pixelIndex++] = green;
|
|
306
|
+
pixelData[pixelIndex++] = blue;
|
|
307
|
+
pixelData[pixelIndex++] = alpha;
|
|
308
|
+
// Add to color cache if enabled
|
|
309
|
+
if (useColorCache) {
|
|
310
|
+
const color = (alpha << 24) | (blue << 16) | (green << 8) | red;
|
|
311
|
+
colorCache[i % colorCacheSize] = color;
|
|
312
|
+
}
|
|
313
|
+
i++;
|
|
314
|
+
}
|
|
315
|
+
else if (green < 256 + 24) {
|
|
316
|
+
// Backward reference (LZ77)
|
|
317
|
+
const lengthSymbol = green - 256;
|
|
318
|
+
const length = this.getLength(lengthSymbol, reader);
|
|
319
|
+
const distancePrefix = huffmanTables.distance.readSymbol(reader);
|
|
320
|
+
const distance = this.getDistance(distancePrefix, reader);
|
|
321
|
+
// Copy pixels from earlier in the stream
|
|
322
|
+
const srcIndex = pixelIndex - distance * 4;
|
|
323
|
+
if (srcIndex < 0) {
|
|
324
|
+
throw new Error("Invalid backward reference");
|
|
325
|
+
}
|
|
326
|
+
for (let j = 0; j < length; j++) {
|
|
327
|
+
pixelData[pixelIndex++] = pixelData[srcIndex + j * 4];
|
|
328
|
+
pixelData[pixelIndex++] = pixelData[srcIndex + j * 4 + 1];
|
|
329
|
+
pixelData[pixelIndex++] = pixelData[srcIndex + j * 4 + 2];
|
|
330
|
+
pixelData[pixelIndex++] = pixelData[srcIndex + j * 4 + 3];
|
|
331
|
+
// Add to color cache
|
|
332
|
+
if (useColorCache) {
|
|
333
|
+
const color = (pixelData[pixelIndex - 1] << 24) |
|
|
334
|
+
(pixelData[pixelIndex - 2] << 16) |
|
|
335
|
+
(pixelData[pixelIndex - 3] << 8) |
|
|
336
|
+
pixelData[pixelIndex - 4];
|
|
337
|
+
colorCache[(i + j) % colorCacheSize] = color;
|
|
338
|
+
}
|
|
339
|
+
}
|
|
340
|
+
i += length;
|
|
341
|
+
}
|
|
342
|
+
else {
|
|
343
|
+
// Color cache reference
|
|
344
|
+
const cacheIndex = green - 256 - 24;
|
|
345
|
+
if (cacheIndex >= colorCacheSize) {
|
|
346
|
+
throw new Error("Invalid color cache index");
|
|
347
|
+
}
|
|
348
|
+
const color = colorCache[cacheIndex];
|
|
349
|
+
pixelData[pixelIndex++] = color & 0xff; // R
|
|
350
|
+
pixelData[pixelIndex++] = (color >> 8) & 0xff; // G
|
|
351
|
+
pixelData[pixelIndex++] = (color >> 16) & 0xff; // B
|
|
352
|
+
pixelData[pixelIndex++] = (color >> 24) & 0xff; // A
|
|
353
|
+
i++;
|
|
354
|
+
}
|
|
302
355
|
}
|
|
303
|
-
i++;
|
|
304
356
|
}
|
|
305
|
-
|
|
306
|
-
//
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
if (srcIndex < 0) {
|
|
314
|
-
throw new Error("Invalid backward reference");
|
|
357
|
+
catch (e) {
|
|
358
|
+
// Tolerant decoding: fill remaining pixels with gray (128, 128, 128, 255)
|
|
359
|
+
this.options.onWarning?.(`WebP VP8L: Partial decode at pixel ${pixelIndex / 4}/${numPixels}`, e);
|
|
360
|
+
while (pixelIndex < pixelData.length) {
|
|
361
|
+
pixelData[pixelIndex++] = 128; // R
|
|
362
|
+
pixelData[pixelIndex++] = 128; // G
|
|
363
|
+
pixelData[pixelIndex++] = 128; // B
|
|
364
|
+
pixelData[pixelIndex++] = 255; // A
|
|
315
365
|
}
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
366
|
+
}
|
|
367
|
+
}
|
|
368
|
+
else {
|
|
369
|
+
// Non-tolerant mode: throw on first error
|
|
370
|
+
for (let i = 0; i < numPixels;) {
|
|
371
|
+
// Read green channel (which determines the code type)
|
|
372
|
+
const green = huffmanTables.green.readSymbol(reader);
|
|
373
|
+
if (green < 256) {
|
|
374
|
+
// Literal pixel
|
|
375
|
+
const red = huffmanTables.red.readSymbol(reader);
|
|
376
|
+
const blue = huffmanTables.blue.readSymbol(reader);
|
|
377
|
+
const alpha = alphaUsed !== 0 ? huffmanTables.alpha.readSymbol(reader) : 255;
|
|
378
|
+
pixelData[pixelIndex++] = red;
|
|
379
|
+
pixelData[pixelIndex++] = green;
|
|
380
|
+
pixelData[pixelIndex++] = blue;
|
|
381
|
+
pixelData[pixelIndex++] = alpha;
|
|
382
|
+
// Add to color cache if enabled
|
|
322
383
|
if (useColorCache) {
|
|
323
|
-
const color = (
|
|
324
|
-
|
|
325
|
-
(pixelData[pixelIndex - 3] << 8) |
|
|
326
|
-
pixelData[pixelIndex - 4];
|
|
327
|
-
colorCache[(i + j) % colorCacheSize] = color;
|
|
384
|
+
const color = (alpha << 24) | (blue << 16) | (green << 8) | red;
|
|
385
|
+
colorCache[i % colorCacheSize] = color;
|
|
328
386
|
}
|
|
387
|
+
i++;
|
|
329
388
|
}
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
389
|
+
else if (green < 256 + 24) {
|
|
390
|
+
// Backward reference (LZ77)
|
|
391
|
+
const lengthSymbol = green - 256;
|
|
392
|
+
const length = this.getLength(lengthSymbol, reader);
|
|
393
|
+
const distancePrefix = huffmanTables.distance.readSymbol(reader);
|
|
394
|
+
const distance = this.getDistance(distancePrefix, reader);
|
|
395
|
+
// Copy pixels from earlier in the stream
|
|
396
|
+
const srcIndex = pixelIndex - distance * 4;
|
|
397
|
+
if (srcIndex < 0) {
|
|
398
|
+
throw new Error("Invalid backward reference");
|
|
399
|
+
}
|
|
400
|
+
for (let j = 0; j < length; j++) {
|
|
401
|
+
pixelData[pixelIndex++] = pixelData[srcIndex + j * 4];
|
|
402
|
+
pixelData[pixelIndex++] = pixelData[srcIndex + j * 4 + 1];
|
|
403
|
+
pixelData[pixelIndex++] = pixelData[srcIndex + j * 4 + 2];
|
|
404
|
+
pixelData[pixelIndex++] = pixelData[srcIndex + j * 4 + 3];
|
|
405
|
+
// Add to color cache
|
|
406
|
+
if (useColorCache) {
|
|
407
|
+
const color = (pixelData[pixelIndex - 1] << 24) |
|
|
408
|
+
(pixelData[pixelIndex - 2] << 16) |
|
|
409
|
+
(pixelData[pixelIndex - 3] << 8) |
|
|
410
|
+
pixelData[pixelIndex - 4];
|
|
411
|
+
colorCache[(i + j) % colorCacheSize] = color;
|
|
412
|
+
}
|
|
413
|
+
}
|
|
414
|
+
i += length;
|
|
415
|
+
}
|
|
416
|
+
else {
|
|
417
|
+
// Color cache reference
|
|
418
|
+
const cacheIndex = green - 256 - 24;
|
|
419
|
+
if (cacheIndex >= colorCacheSize) {
|
|
420
|
+
throw new Error("Invalid color cache index");
|
|
421
|
+
}
|
|
422
|
+
const color = colorCache[cacheIndex];
|
|
423
|
+
pixelData[pixelIndex++] = color & 0xff; // R
|
|
424
|
+
pixelData[pixelIndex++] = (color >> 8) & 0xff; // G
|
|
425
|
+
pixelData[pixelIndex++] = (color >> 16) & 0xff; // B
|
|
426
|
+
pixelData[pixelIndex++] = (color >> 24) & 0xff; // A
|
|
427
|
+
i++;
|
|
337
428
|
}
|
|
338
|
-
const color = colorCache[cacheIndex];
|
|
339
|
-
pixelData[pixelIndex++] = color & 0xff; // R
|
|
340
|
-
pixelData[pixelIndex++] = (color >> 8) & 0xff; // G
|
|
341
|
-
pixelData[pixelIndex++] = (color >> 16) & 0xff; // B
|
|
342
|
-
pixelData[pixelIndex++] = (color >> 24) & 0xff; // A
|
|
343
|
-
i++;
|
|
344
429
|
}
|
|
345
430
|
}
|
|
346
431
|
return pixelData;
|
|
@@ -381,9 +466,7 @@ class WebPDecoder {
|
|
|
381
466
|
const isFirstEightBits = reader.readBits(1);
|
|
382
467
|
const symbols = [];
|
|
383
468
|
for (let i = 0; i < numSymbols; i++) {
|
|
384
|
-
const symbolBits = isFirstEightBits
|
|
385
|
-
? reader.readBits(8)
|
|
386
|
-
: reader.readBits(1);
|
|
469
|
+
const symbolBits = isFirstEightBits ? reader.readBits(8) : reader.readBits(1);
|
|
387
470
|
symbols.push(symbolBits);
|
|
388
471
|
}
|
|
389
472
|
// Build simple Huffman table
|
|
@@ -399,9 +482,7 @@ class WebPDecoder {
|
|
|
399
482
|
}
|
|
400
483
|
else {
|
|
401
484
|
// Complex code - read code lengths
|
|
402
|
-
const maxSymbol = isGreen
|
|
403
|
-
? (256 + 24 + (useColorCache ? (1 << colorCacheBits) : 0))
|
|
404
|
-
: 256;
|
|
485
|
+
const maxSymbol = isGreen ? (256 + 24 + (useColorCache ? (1 << colorCacheBits) : 0)) : 256;
|
|
405
486
|
const codeLengths = this.readCodeLengths(reader, maxSymbol);
|
|
406
487
|
this.buildHuffmanTable(table, codeLengths);
|
|
407
488
|
}
|
|
@@ -409,7 +490,7 @@ class WebPDecoder {
|
|
|
409
490
|
readCodeLengths(reader, maxSymbol) {
|
|
410
491
|
// Read code length codes (used to encode the actual code lengths)
|
|
411
492
|
const numCodeLengthCodes = reader.readBits(4) + 4;
|
|
412
|
-
const codeLengthCodeLengths = new
|
|
493
|
+
const codeLengthCodeLengths = new Uint8Array(19);
|
|
413
494
|
// Code length code order
|
|
414
495
|
const codeLengthCodeOrder = [
|
|
415
496
|
17,
|
|
@@ -445,7 +526,7 @@ class WebPDecoder {
|
|
|
445
526
|
const codeLengthTable = new HuffmanTable();
|
|
446
527
|
this.buildHuffmanTable(codeLengthTable, codeLengthCodeLengths);
|
|
447
528
|
// Read actual code lengths
|
|
448
|
-
const codeLengths = new
|
|
529
|
+
const codeLengths = new Uint8Array(maxSymbol);
|
|
449
530
|
let i = 0;
|
|
450
531
|
while (i < maxSymbol) {
|
|
451
532
|
const code = codeLengthTable.readSymbol(reader);
|
|
@@ -372,7 +372,7 @@ class WebPEncoder {
|
|
|
372
372
|
* Returns an array where index is the symbol and value is the code length
|
|
373
373
|
*/
|
|
374
374
|
calculateCodeLengths(frequencies, maxSymbol, maxCodeLength = 15) {
|
|
375
|
-
const codeLengths = new
|
|
375
|
+
const codeLengths = new Uint8Array(maxSymbol);
|
|
376
376
|
// Get symbols with non-zero frequencies
|
|
377
377
|
const symbols = Array.from(frequencies.keys()).sort((a, b) => a - b);
|
|
378
378
|
if (symbols.length === 0)
|
|
@@ -380,7 +380,6 @@ class WebPEncoder {
|
|
|
380
380
|
// For a single symbol, use code length 1
|
|
381
381
|
// (Canonical Huffman codes require length >= 1)
|
|
382
382
|
if (symbols.length === 1) {
|
|
383
|
-
// console.log(`Single symbol ${symbols[0]}, forcing length 1`);
|
|
384
383
|
codeLengths[symbols[0]] = 1;
|
|
385
384
|
return codeLengths;
|
|
386
385
|
}
|
|
@@ -427,7 +426,6 @@ class WebPEncoder {
|
|
|
427
426
|
// If tree is too deep, flatten frequencies and rebuild
|
|
428
427
|
let attempts = 0;
|
|
429
428
|
while (maxDepth > maxCodeLength && attempts < 5) {
|
|
430
|
-
// console.log(`Tree too deep (${maxDepth} > ${maxCodeLength}), flattening...`);
|
|
431
429
|
attempts++;
|
|
432
430
|
// Add bias to frequencies to flatten the tree
|
|
433
431
|
// Increase bias with each attempt
|
|
@@ -442,11 +440,8 @@ class WebPEncoder {
|
|
|
442
440
|
maxDepth = 0;
|
|
443
441
|
checkDepth(root, 0);
|
|
444
442
|
}
|
|
445
|
-
|
|
446
|
-
|
|
447
|
-
// Force hard limit by sorting and assigning lengths?
|
|
448
|
-
// For now, let's just see if this is happening.
|
|
449
|
-
}
|
|
443
|
+
// If tree depth couldn't be reduced, encoding may fail but we'll try anyway
|
|
444
|
+
// This is an internal limitation that doesn't affect most images
|
|
450
445
|
// Calculate code lengths by traversing tree (iterative to avoid deep recursion)
|
|
451
446
|
const stack = [{
|
|
452
447
|
node: root,
|
|
@@ -488,7 +483,7 @@ class WebPEncoder {
|
|
|
488
483
|
}
|
|
489
484
|
}
|
|
490
485
|
// Count symbols at each length
|
|
491
|
-
const lengthCounts = new
|
|
486
|
+
const lengthCounts = new Uint32Array(maxLength + 1);
|
|
492
487
|
for (let i = 0; i < codeLengths.length; i++) {
|
|
493
488
|
if (codeLengths[i] > 0) {
|
|
494
489
|
lengthCounts[codeLengths[i]]++;
|
|
@@ -496,7 +491,7 @@ class WebPEncoder {
|
|
|
496
491
|
}
|
|
497
492
|
// Generate first code for each length
|
|
498
493
|
let code = 0;
|
|
499
|
-
const nextCode = new
|
|
494
|
+
const nextCode = new Uint32Array(maxLength + 1);
|
|
500
495
|
for (let len = 1; len <= maxLength; len++) {
|
|
501
496
|
code = (code + lengthCounts[len - 1]) << 1;
|
|
502
497
|
nextCode[len] = code;
|
|
@@ -628,7 +623,6 @@ class WebPEncoder {
|
|
|
628
623
|
}
|
|
629
624
|
numCodeLengthCodes = Math.max(4, numCodeLengthCodes);
|
|
630
625
|
// Write number of code length codes
|
|
631
|
-
// console.log(`Complex Huffman: numCodeLengthCodes=${numCodeLengthCodes}, rleEncoded.length=${rleEncoded.length}`);
|
|
632
626
|
writer.writeBits(numCodeLengthCodes - 4, 4);
|
|
633
627
|
// Write code length code lengths
|
|
634
628
|
for (let i = 0; i < numCodeLengthCodes; i++) {
|