cross-image 0.1.2

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.
Files changed (125) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +606 -0
  3. package/esm/mod.d.ts +33 -0
  4. package/esm/mod.d.ts.map +1 -0
  5. package/esm/mod.js +31 -0
  6. package/esm/package.json +3 -0
  7. package/esm/src/formats/ascii.d.ts +27 -0
  8. package/esm/src/formats/ascii.d.ts.map +1 -0
  9. package/esm/src/formats/ascii.js +172 -0
  10. package/esm/src/formats/bmp.d.ts +19 -0
  11. package/esm/src/formats/bmp.d.ts.map +1 -0
  12. package/esm/src/formats/bmp.js +174 -0
  13. package/esm/src/formats/gif.d.ts +40 -0
  14. package/esm/src/formats/gif.d.ts.map +1 -0
  15. package/esm/src/formats/gif.js +385 -0
  16. package/esm/src/formats/jpeg.d.ts +18 -0
  17. package/esm/src/formats/jpeg.d.ts.map +1 -0
  18. package/esm/src/formats/jpeg.js +414 -0
  19. package/esm/src/formats/png.d.ts +33 -0
  20. package/esm/src/formats/png.d.ts.map +1 -0
  21. package/esm/src/formats/png.js +544 -0
  22. package/esm/src/formats/raw.d.ts +23 -0
  23. package/esm/src/formats/raw.d.ts.map +1 -0
  24. package/esm/src/formats/raw.js +98 -0
  25. package/esm/src/formats/tiff.d.ts +58 -0
  26. package/esm/src/formats/tiff.d.ts.map +1 -0
  27. package/esm/src/formats/tiff.js +791 -0
  28. package/esm/src/formats/webp.d.ts +22 -0
  29. package/esm/src/formats/webp.d.ts.map +1 -0
  30. package/esm/src/formats/webp.js +403 -0
  31. package/esm/src/image.d.ts +124 -0
  32. package/esm/src/image.d.ts.map +1 -0
  33. package/esm/src/image.js +320 -0
  34. package/esm/src/types.d.ts +167 -0
  35. package/esm/src/types.d.ts.map +1 -0
  36. package/esm/src/types.js +1 -0
  37. package/esm/src/utils/gif_decoder.d.ts +42 -0
  38. package/esm/src/utils/gif_decoder.d.ts.map +1 -0
  39. package/esm/src/utils/gif_decoder.js +374 -0
  40. package/esm/src/utils/gif_encoder.d.ts +29 -0
  41. package/esm/src/utils/gif_encoder.d.ts.map +1 -0
  42. package/esm/src/utils/gif_encoder.js +226 -0
  43. package/esm/src/utils/jpeg_decoder.d.ts +39 -0
  44. package/esm/src/utils/jpeg_decoder.d.ts.map +1 -0
  45. package/esm/src/utils/jpeg_decoder.js +580 -0
  46. package/esm/src/utils/jpeg_encoder.d.ts +33 -0
  47. package/esm/src/utils/jpeg_encoder.d.ts.map +1 -0
  48. package/esm/src/utils/jpeg_encoder.js +1017 -0
  49. package/esm/src/utils/lzw.d.ts +43 -0
  50. package/esm/src/utils/lzw.d.ts.map +1 -0
  51. package/esm/src/utils/lzw.js +309 -0
  52. package/esm/src/utils/resize.d.ts +9 -0
  53. package/esm/src/utils/resize.d.ts.map +1 -0
  54. package/esm/src/utils/resize.js +52 -0
  55. package/esm/src/utils/tiff_lzw.d.ts +44 -0
  56. package/esm/src/utils/tiff_lzw.d.ts.map +1 -0
  57. package/esm/src/utils/tiff_lzw.js +306 -0
  58. package/esm/src/utils/webp_decoder.d.ts +39 -0
  59. package/esm/src/utils/webp_decoder.d.ts.map +1 -0
  60. package/esm/src/utils/webp_decoder.js +493 -0
  61. package/esm/src/utils/webp_encoder.d.ts +72 -0
  62. package/esm/src/utils/webp_encoder.d.ts.map +1 -0
  63. package/esm/src/utils/webp_encoder.js +627 -0
  64. package/package.json +41 -0
  65. package/script/mod.d.ts +33 -0
  66. package/script/mod.d.ts.map +1 -0
  67. package/script/mod.js +43 -0
  68. package/script/package.json +3 -0
  69. package/script/src/formats/ascii.d.ts +27 -0
  70. package/script/src/formats/ascii.d.ts.map +1 -0
  71. package/script/src/formats/ascii.js +176 -0
  72. package/script/src/formats/bmp.d.ts +19 -0
  73. package/script/src/formats/bmp.d.ts.map +1 -0
  74. package/script/src/formats/bmp.js +178 -0
  75. package/script/src/formats/gif.d.ts +40 -0
  76. package/script/src/formats/gif.d.ts.map +1 -0
  77. package/script/src/formats/gif.js +389 -0
  78. package/script/src/formats/jpeg.d.ts +18 -0
  79. package/script/src/formats/jpeg.d.ts.map +1 -0
  80. package/script/src/formats/jpeg.js +451 -0
  81. package/script/src/formats/png.d.ts +33 -0
  82. package/script/src/formats/png.d.ts.map +1 -0
  83. package/script/src/formats/png.js +548 -0
  84. package/script/src/formats/raw.d.ts +23 -0
  85. package/script/src/formats/raw.d.ts.map +1 -0
  86. package/script/src/formats/raw.js +102 -0
  87. package/script/src/formats/tiff.d.ts +58 -0
  88. package/script/src/formats/tiff.d.ts.map +1 -0
  89. package/script/src/formats/tiff.js +795 -0
  90. package/script/src/formats/webp.d.ts +22 -0
  91. package/script/src/formats/webp.d.ts.map +1 -0
  92. package/script/src/formats/webp.js +440 -0
  93. package/script/src/image.d.ts +124 -0
  94. package/script/src/image.d.ts.map +1 -0
  95. package/script/src/image.js +324 -0
  96. package/script/src/types.d.ts +167 -0
  97. package/script/src/types.d.ts.map +1 -0
  98. package/script/src/types.js +2 -0
  99. package/script/src/utils/gif_decoder.d.ts +42 -0
  100. package/script/src/utils/gif_decoder.d.ts.map +1 -0
  101. package/script/src/utils/gif_decoder.js +378 -0
  102. package/script/src/utils/gif_encoder.d.ts +29 -0
  103. package/script/src/utils/gif_encoder.d.ts.map +1 -0
  104. package/script/src/utils/gif_encoder.js +230 -0
  105. package/script/src/utils/jpeg_decoder.d.ts +39 -0
  106. package/script/src/utils/jpeg_decoder.d.ts.map +1 -0
  107. package/script/src/utils/jpeg_decoder.js +584 -0
  108. package/script/src/utils/jpeg_encoder.d.ts +33 -0
  109. package/script/src/utils/jpeg_encoder.d.ts.map +1 -0
  110. package/script/src/utils/jpeg_encoder.js +1021 -0
  111. package/script/src/utils/lzw.d.ts +43 -0
  112. package/script/src/utils/lzw.d.ts.map +1 -0
  113. package/script/src/utils/lzw.js +314 -0
  114. package/script/src/utils/resize.d.ts +9 -0
  115. package/script/src/utils/resize.d.ts.map +1 -0
  116. package/script/src/utils/resize.js +56 -0
  117. package/script/src/utils/tiff_lzw.d.ts +44 -0
  118. package/script/src/utils/tiff_lzw.d.ts.map +1 -0
  119. package/script/src/utils/tiff_lzw.js +311 -0
  120. package/script/src/utils/webp_decoder.d.ts +39 -0
  121. package/script/src/utils/webp_decoder.d.ts.map +1 -0
  122. package/script/src/utils/webp_decoder.js +497 -0
  123. package/script/src/utils/webp_encoder.d.ts +72 -0
  124. package/script/src/utils/webp_encoder.d.ts.map +1 -0
  125. package/script/src/utils/webp_encoder.js +631 -0
@@ -0,0 +1,493 @@
1
+ /**
2
+ * WebP VP8L (Lossless) decoder implementation
3
+ *
4
+ * This module implements a pure JavaScript decoder for WebP lossless (VP8L) format.
5
+ * It supports:
6
+ * - Huffman coding (canonical Huffman codes)
7
+ * - LZ77 backward references for compression
8
+ * - Color cache for repeated colors
9
+ * - Simple and complex Huffman code tables
10
+ *
11
+ * Current limitations:
12
+ * - Does not support transforms (predictor, color, subtract green, color indexing)
13
+ * - Does not support meta Huffman codes
14
+ * - Does not support lossy WebP (VP8) format
15
+ *
16
+ * For images with transforms or lossy compression, the decoder will fall back
17
+ * to the runtime's ImageDecoder API if available.
18
+ *
19
+ * @see https://developers.google.com/speed/webp/docs/riff_container
20
+ * @see https://developers.google.com/speed/webp/docs/webp_lossless_bitstream_specification
21
+ */
22
+ // Helper to read little-endian values
23
+ function readUint24LE(data, offset) {
24
+ return data[offset] | (data[offset + 1] << 8) | (data[offset + 2] << 16);
25
+ }
26
+ function readUint32LE(data, offset) {
27
+ return data[offset] | (data[offset + 1] << 8) |
28
+ (data[offset + 2] << 16) | (data[offset + 3] << 24);
29
+ }
30
+ // Huffman code table
31
+ class HuffmanTable {
32
+ constructor() {
33
+ Object.defineProperty(this, "root", {
34
+ enumerable: true,
35
+ configurable: true,
36
+ writable: true,
37
+ value: void 0
38
+ });
39
+ Object.defineProperty(this, "singleSymbol", {
40
+ enumerable: true,
41
+ configurable: true,
42
+ writable: true,
43
+ value: void 0
44
+ }); // For tables with only one symbol
45
+ this.root = {};
46
+ }
47
+ addCode(symbol, code, codeLength) {
48
+ // Handle single symbol case (code length 0)
49
+ if (codeLength === 0) {
50
+ this.singleSymbol = symbol;
51
+ return;
52
+ }
53
+ let node = this.root;
54
+ for (let i = codeLength - 1; i >= 0; i--) {
55
+ const bit = (code >> i) & 1;
56
+ if (bit === 0) {
57
+ if (!node.left)
58
+ node.left = {};
59
+ node = node.left;
60
+ }
61
+ else {
62
+ if (!node.right)
63
+ node.right = {};
64
+ node = node.right;
65
+ }
66
+ }
67
+ node.symbol = symbol;
68
+ }
69
+ readSymbol(reader) {
70
+ // Handle single symbol case
71
+ if (this.singleSymbol !== undefined) {
72
+ return this.singleSymbol;
73
+ }
74
+ let node = this.root;
75
+ while (node.symbol === undefined) {
76
+ const bit = reader.readBits(1);
77
+ node = bit === 0 ? node.left : node.right;
78
+ if (!node) {
79
+ throw new Error("Invalid Huffman code");
80
+ }
81
+ }
82
+ return node.symbol;
83
+ }
84
+ }
85
+ class BitReader {
86
+ constructor(data, offset) {
87
+ Object.defineProperty(this, "data", {
88
+ enumerable: true,
89
+ configurable: true,
90
+ writable: true,
91
+ value: void 0
92
+ });
93
+ Object.defineProperty(this, "pos", {
94
+ enumerable: true,
95
+ configurable: true,
96
+ writable: true,
97
+ value: void 0
98
+ });
99
+ Object.defineProperty(this, "bitPos", {
100
+ enumerable: true,
101
+ configurable: true,
102
+ writable: true,
103
+ value: void 0
104
+ });
105
+ Object.defineProperty(this, "value", {
106
+ enumerable: true,
107
+ configurable: true,
108
+ writable: true,
109
+ value: void 0
110
+ });
111
+ this.data = data;
112
+ this.pos = offset;
113
+ this.bitPos = 0;
114
+ this.value = 0;
115
+ }
116
+ readBits(numBits) {
117
+ let result = 0;
118
+ for (let i = 0; i < numBits; i++) {
119
+ if (this.bitPos === 0) {
120
+ if (this.pos >= this.data.length) {
121
+ throw new Error("Unexpected end of data");
122
+ }
123
+ this.value = this.data[this.pos++];
124
+ this.bitPos = 8;
125
+ }
126
+ result |= ((this.value >> (this.bitPos - 1)) & 1) << i;
127
+ this.bitPos--;
128
+ }
129
+ return result;
130
+ }
131
+ getPosition() {
132
+ return this.pos;
133
+ }
134
+ // Read bytes aligned to byte boundary
135
+ readBytes(count) {
136
+ // Align to byte boundary
137
+ if (this.bitPos !== 0 && this.bitPos !== 8) {
138
+ this.bitPos = 0;
139
+ }
140
+ if (this.pos + count > this.data.length) {
141
+ throw new Error("Unexpected end of data");
142
+ }
143
+ const result = this.data.slice(this.pos, this.pos + count);
144
+ this.pos += count;
145
+ return result;
146
+ }
147
+ }
148
+ export class WebPDecoder {
149
+ constructor(data) {
150
+ Object.defineProperty(this, "data", {
151
+ enumerable: true,
152
+ configurable: true,
153
+ writable: true,
154
+ value: void 0
155
+ });
156
+ this.data = data;
157
+ }
158
+ decode() {
159
+ // Verify WebP signature
160
+ if (this.data.length < 12 ||
161
+ this.data[0] !== 0x52 || this.data[1] !== 0x49 || // "RI"
162
+ this.data[2] !== 0x46 || this.data[3] !== 0x46 || // "FF"
163
+ this.data[8] !== 0x57 || this.data[9] !== 0x45 || // "WE"
164
+ this.data[10] !== 0x42 || this.data[11] !== 0x50 // "BP"
165
+ ) {
166
+ throw new Error("Invalid WebP signature");
167
+ }
168
+ let pos = 12; // Skip RIFF header
169
+ let width = 0;
170
+ let height = 0;
171
+ let imageData = null;
172
+ // Parse chunks
173
+ while (pos + 8 <= this.data.length) {
174
+ const chunkType = String.fromCharCode(this.data[pos], this.data[pos + 1], this.data[pos + 2], this.data[pos + 3]);
175
+ const chunkSize = readUint32LE(this.data, pos + 4);
176
+ pos += 8;
177
+ if (pos + chunkSize > this.data.length)
178
+ break;
179
+ const chunkData = this.data.slice(pos, pos + chunkSize);
180
+ if (chunkType === "VP8L") {
181
+ // Lossless format - we can decode this
182
+ const result = this.decodeVP8L(chunkData);
183
+ width = result.width;
184
+ height = result.height;
185
+ imageData = result.data;
186
+ break; // Stop after decoding image data
187
+ }
188
+ else if (chunkType === "VP8 ") {
189
+ // Lossy format - not supported in pure JS decoder
190
+ throw new Error("WebP lossy (VP8) format not supported in pure JS decoder");
191
+ }
192
+ else if (chunkType === "VP8X") {
193
+ // Extended format header
194
+ if (chunkData.length >= 10) {
195
+ width = readUint24LE(chunkData, 4) + 1;
196
+ height = readUint24LE(chunkData, 7) + 1;
197
+ }
198
+ }
199
+ pos += chunkSize;
200
+ // Chunks are padded to even length
201
+ if (chunkSize % 2 === 1)
202
+ pos++;
203
+ }
204
+ if (!imageData || width === 0 || height === 0) {
205
+ throw new Error("Failed to decode WebP: no valid image data found");
206
+ }
207
+ return { width, height, data: imageData };
208
+ }
209
+ decodeVP8L(data) {
210
+ // VP8L signature
211
+ if (data[0] !== 0x2f) {
212
+ throw new Error("Invalid VP8L signature");
213
+ }
214
+ const bits = readUint32LE(data, 1);
215
+ const width = (bits & 0x3fff) + 1;
216
+ const height = ((bits >> 14) & 0x3fff) + 1;
217
+ const alphaUsed = (bits >> 28) & 1;
218
+ const versionNumber = (bits >> 29) & 7;
219
+ if (versionNumber !== 0) {
220
+ throw new Error(`Unsupported VP8L version: ${versionNumber}`);
221
+ }
222
+ // Create bit reader starting after header
223
+ const reader = new BitReader(data, 5);
224
+ // Decode image data
225
+ // This is a simplified decoder that handles basic lossless WebP
226
+ try {
227
+ const rgba = this.decodeImageData(reader, width, height, alphaUsed);
228
+ return { width, height, data: rgba };
229
+ }
230
+ catch (error) {
231
+ throw new Error(`VP8L decoding failed: ${error}`);
232
+ }
233
+ }
234
+ decodeImageData(reader, width, height, alphaUsed) {
235
+ // Read transform info
236
+ const useTransforms = reader.readBits(1);
237
+ if (useTransforms) {
238
+ // For simplicity, we don't support transforms in this basic decoder
239
+ // Transforms include: predictor, color, subtract green, color indexing
240
+ throw new Error("WebP transforms not supported in basic decoder");
241
+ }
242
+ // Read color cache info
243
+ const useColorCache = reader.readBits(1) === 1;
244
+ let colorCacheBits = 0;
245
+ let colorCacheSize = 0;
246
+ if (useColorCache) {
247
+ colorCacheBits = reader.readBits(4);
248
+ if (colorCacheBits < 1 || colorCacheBits > 11) {
249
+ throw new Error("Invalid color cache bits");
250
+ }
251
+ colorCacheSize = 1 << colorCacheBits;
252
+ }
253
+ // Read Huffman codes
254
+ const huffmanTables = this.readHuffmanCodes(reader, useColorCache, colorCacheBits);
255
+ // Decode the image using Huffman codes
256
+ const pixelData = new Uint8Array(width * height * 4);
257
+ let pixelIndex = 0;
258
+ const numPixels = width * height;
259
+ // Color cache for repeated colors
260
+ const colorCache = new Uint32Array(colorCacheSize);
261
+ for (let i = 0; i < numPixels;) {
262
+ // Read green channel (which determines the code type)
263
+ const green = huffmanTables.green.readSymbol(reader);
264
+ if (green < 256) {
265
+ // Literal pixel
266
+ const red = huffmanTables.red.readSymbol(reader);
267
+ const blue = huffmanTables.blue.readSymbol(reader);
268
+ const alpha = alphaUsed !== 0
269
+ ? huffmanTables.alpha.readSymbol(reader)
270
+ : 255;
271
+ pixelData[pixelIndex++] = red;
272
+ pixelData[pixelIndex++] = green;
273
+ pixelData[pixelIndex++] = blue;
274
+ pixelData[pixelIndex++] = alpha;
275
+ // Add to color cache if enabled
276
+ if (useColorCache) {
277
+ const color = (alpha << 24) | (blue << 16) | (green << 8) | red;
278
+ colorCache[i % colorCacheSize] = color;
279
+ }
280
+ i++;
281
+ }
282
+ else if (green < 256 + 24) {
283
+ // Backward reference (LZ77)
284
+ const lengthSymbol = green - 256;
285
+ const length = this.getLength(lengthSymbol, reader);
286
+ const distancePrefix = huffmanTables.distance.readSymbol(reader);
287
+ const distance = this.getDistance(distancePrefix, reader);
288
+ // Copy pixels from earlier in the stream
289
+ const srcIndex = pixelIndex - distance * 4;
290
+ if (srcIndex < 0) {
291
+ throw new Error("Invalid backward reference");
292
+ }
293
+ for (let j = 0; j < length; j++) {
294
+ pixelData[pixelIndex++] = pixelData[srcIndex + j * 4];
295
+ pixelData[pixelIndex++] = pixelData[srcIndex + j * 4 + 1];
296
+ pixelData[pixelIndex++] = pixelData[srcIndex + j * 4 + 2];
297
+ pixelData[pixelIndex++] = pixelData[srcIndex + j * 4 + 3];
298
+ // Add to color cache
299
+ if (useColorCache) {
300
+ const color = (pixelData[pixelIndex - 1] << 24) |
301
+ (pixelData[pixelIndex - 2] << 16) |
302
+ (pixelData[pixelIndex - 3] << 8) |
303
+ pixelData[pixelIndex - 4];
304
+ colorCache[(i + j) % colorCacheSize] = color;
305
+ }
306
+ }
307
+ i += length;
308
+ }
309
+ else {
310
+ // Color cache reference
311
+ const cacheIndex = green - 256 - 24;
312
+ if (cacheIndex >= colorCacheSize) {
313
+ throw new Error("Invalid color cache index");
314
+ }
315
+ const color = colorCache[cacheIndex];
316
+ pixelData[pixelIndex++] = color & 0xff; // R
317
+ pixelData[pixelIndex++] = (color >> 8) & 0xff; // G
318
+ pixelData[pixelIndex++] = (color >> 16) & 0xff; // B
319
+ pixelData[pixelIndex++] = (color >> 24) & 0xff; // A
320
+ i++;
321
+ }
322
+ }
323
+ return pixelData;
324
+ }
325
+ readHuffmanCodes(reader, useColorCache, colorCacheBits) {
326
+ // Read meta Huffman codes
327
+ const useMetaHuffman = reader.readBits(1);
328
+ if (useMetaHuffman) {
329
+ // Meta Huffman is more complex - for now throw error
330
+ throw new Error("Meta Huffman codes not yet supported");
331
+ }
332
+ // Read the main Huffman codes
333
+ // There are 5 Huffman code groups: green, red, blue, alpha, distance
334
+ // But we read 4 + optional distance code
335
+ const numCodeGroups = reader.readBits(4) + 4;
336
+ const tables = {
337
+ green: new HuffmanTable(),
338
+ red: new HuffmanTable(),
339
+ blue: new HuffmanTable(),
340
+ alpha: new HuffmanTable(),
341
+ distance: new HuffmanTable(),
342
+ };
343
+ const tableArray = [
344
+ tables.green,
345
+ tables.red,
346
+ tables.blue,
347
+ tables.alpha,
348
+ tables.distance,
349
+ ];
350
+ for (let i = 0; i < numCodeGroups && i < 5; i++) {
351
+ this.readHuffmanCode(reader, tableArray[i], useColorCache, colorCacheBits, i === 0);
352
+ }
353
+ return tables;
354
+ }
355
+ readHuffmanCode(reader, table, useColorCache, colorCacheBits, isGreen) {
356
+ const simple = reader.readBits(1);
357
+ if (simple) {
358
+ // Simple code - directly specify 1 or 2 symbols
359
+ const numSymbols = reader.readBits(1) + 1;
360
+ const isFirstEightBits = reader.readBits(1);
361
+ const symbols = [];
362
+ for (let i = 0; i < numSymbols; i++) {
363
+ const symbolBits = isFirstEightBits
364
+ ? 1 + reader.readBits(7)
365
+ : reader.readBits(8);
366
+ symbols.push(symbolBits);
367
+ }
368
+ // Build simple Huffman table
369
+ if (numSymbols === 1) {
370
+ // Single symbol - 0 bits needed
371
+ table.addCode(symbols[0], 0, 0);
372
+ }
373
+ else {
374
+ // Two symbols - 1 bit each
375
+ table.addCode(symbols[0], 0, 1);
376
+ table.addCode(symbols[1], 1, 1);
377
+ }
378
+ }
379
+ else {
380
+ // Complex code - read code lengths
381
+ const maxSymbol = isGreen
382
+ ? (256 + 24 + (useColorCache ? (1 << colorCacheBits) : 0))
383
+ : 256;
384
+ const codeLengths = this.readCodeLengths(reader, maxSymbol);
385
+ this.buildHuffmanTable(table, codeLengths);
386
+ }
387
+ }
388
+ readCodeLengths(reader, maxSymbol) {
389
+ // Read code length codes (used to encode the actual code lengths)
390
+ const numCodeLengthCodes = reader.readBits(4) + 4;
391
+ const codeLengthCodeLengths = new Array(19).fill(0);
392
+ // Code length code order
393
+ const codeLengthCodeOrder = [
394
+ 17,
395
+ 18,
396
+ 0,
397
+ 1,
398
+ 2,
399
+ 3,
400
+ 4,
401
+ 5,
402
+ 16,
403
+ 6,
404
+ 7,
405
+ 8,
406
+ 9,
407
+ 10,
408
+ 11,
409
+ 12,
410
+ 13,
411
+ 14,
412
+ 15,
413
+ ];
414
+ for (let i = 0; i < numCodeLengthCodes; i++) {
415
+ codeLengthCodeLengths[codeLengthCodeOrder[i]] = reader.readBits(3);
416
+ }
417
+ // Build code length Huffman table
418
+ const codeLengthTable = new HuffmanTable();
419
+ this.buildHuffmanTable(codeLengthTable, codeLengthCodeLengths);
420
+ // Read actual code lengths
421
+ const codeLengths = new Array(maxSymbol).fill(0);
422
+ let i = 0;
423
+ while (i < maxSymbol) {
424
+ const code = codeLengthTable.readSymbol(reader);
425
+ if (code < 16) {
426
+ // Literal code length
427
+ codeLengths[i++] = code;
428
+ }
429
+ else if (code === 16) {
430
+ // Repeat previous code length 3-6 times
431
+ const repeatCount = reader.readBits(2) + 3;
432
+ const prevLength = i > 0 ? codeLengths[i - 1] : 0;
433
+ for (let j = 0; j < repeatCount && i < maxSymbol; j++) {
434
+ codeLengths[i++] = prevLength;
435
+ }
436
+ }
437
+ else if (code === 17) {
438
+ // Repeat 0 for 3-10 times
439
+ const repeatCount = reader.readBits(3) + 3;
440
+ i += repeatCount;
441
+ }
442
+ else if (code === 18) {
443
+ // Repeat 0 for 11-138 times
444
+ const repeatCount = reader.readBits(7) + 11;
445
+ i += repeatCount;
446
+ }
447
+ }
448
+ return codeLengths;
449
+ }
450
+ buildHuffmanTable(table, codeLengths) {
451
+ // Build canonical Huffman codes
452
+ const maxCodeLength = Math.max(...codeLengths);
453
+ const lengthCounts = new Array(maxCodeLength + 1).fill(0);
454
+ for (const length of codeLengths) {
455
+ if (length > 0) {
456
+ lengthCounts[length]++;
457
+ }
458
+ }
459
+ // Generate codes
460
+ let code = 0;
461
+ const nextCode = new Array(maxCodeLength + 1).fill(0);
462
+ for (let i = 1; i <= maxCodeLength; i++) {
463
+ code = (code + lengthCounts[i - 1]) << 1;
464
+ nextCode[i] = code;
465
+ }
466
+ // Assign codes to symbols
467
+ for (let symbol = 0; symbol < codeLengths.length; symbol++) {
468
+ const length = codeLengths[symbol];
469
+ if (length > 0) {
470
+ table.addCode(symbol, nextCode[length], length);
471
+ nextCode[length]++;
472
+ }
473
+ }
474
+ }
475
+ getLength(symbol, reader) {
476
+ // Length encoding for backward references
477
+ if (symbol < 4) {
478
+ return symbol + 1;
479
+ }
480
+ const extraBits = (symbol - 2) >> 1;
481
+ const base = ((2 + (symbol & 1)) << extraBits) + 1;
482
+ return base + reader.readBits(extraBits);
483
+ }
484
+ getDistance(symbol, reader) {
485
+ // Distance encoding for backward references
486
+ if (symbol < 4) {
487
+ return symbol + 1;
488
+ }
489
+ const extraBits = (symbol - 2) >> 1;
490
+ const base = ((2 + (symbol & 1)) << extraBits) + 1;
491
+ return base + reader.readBits(extraBits);
492
+ }
493
+ }
@@ -0,0 +1,72 @@
1
+ /**
2
+ * WebP VP8L (Lossless) encoder implementation with quality-based quantization
3
+ *
4
+ * This module implements a pure JavaScript encoder for WebP lossless (VP8L) format.
5
+ * It supports:
6
+ * - Lossless encoding (quality=100) with Huffman coding
7
+ * - Lossy encoding (quality<100) using color quantization while still using VP8L format
8
+ * - Simple Huffman coding (1-2 symbols per channel)
9
+ * - Complex Huffman coding for channels with many unique values (3+ symbols)
10
+ * - Literal pixel encoding (no transforms applied)
11
+ *
12
+ * Current limitations:
13
+ * - Does not use transforms (predictor, color, subtract green, color indexing)
14
+ * - Does not use LZ77 backward references (planned for future)
15
+ * - Does not use color cache (planned for future)
16
+ * - Lossy mode uses simple quantization, not true VP8 lossy encoding
17
+ * - Intended as a fallback when OffscreenCanvas is not available
18
+ *
19
+ * This encoder produces valid WebP lossless files with optional quality-based
20
+ * color quantization for lossy compression. For true VP8 lossy encoding with
21
+ * better compression, use the runtime's OffscreenCanvas API when available.
22
+ *
23
+ * @see https://developers.google.com/speed/webp/docs/riff_container
24
+ * @see https://developers.google.com/speed/webp/docs/webp_lossless_bitstream_specification
25
+ */
26
+ export declare class WebPEncoder {
27
+ private width;
28
+ private height;
29
+ private data;
30
+ private quality;
31
+ constructor(width: number, height: number, rgba: Uint8Array);
32
+ encode(quality?: number): Uint8Array;
33
+ private encodeVP8L;
34
+ private hasAlphaChannel;
35
+ /**
36
+ * Quantize image data based on quality setting
37
+ * Quality 100 = no quantization (lossless)
38
+ * Quality 1-99 = quantize colors to reduce bit depth
39
+ * This creates a "lossy" effect while still using VP8L format
40
+ */
41
+ private quantizeImageData;
42
+ private encodeImageData;
43
+ /**
44
+ * Write Huffman code for a channel (either simple or complex)
45
+ * Returns the Huffman codes for encoding pixels
46
+ */
47
+ private writeHuffmanCode;
48
+ /**
49
+ * Write a symbol using its Huffman code
50
+ */
51
+ private writeSymbol;
52
+ private writeSimpleHuffmanCode;
53
+ /**
54
+ * Calculate optimal code lengths for symbols using standard Huffman algorithm
55
+ * Returns an array where index is the symbol and value is the code length
56
+ */
57
+ private calculateCodeLengths;
58
+ /**
59
+ * Build canonical Huffman codes from code lengths
60
+ * Returns a map from symbol to {code, length}
61
+ */
62
+ private buildCanonicalCodes;
63
+ /**
64
+ * RLE encode code lengths using special codes 16, 17, 18
65
+ */
66
+ private rleEncodeCodeLengths;
67
+ /**
68
+ * Write complex Huffman code using code lengths
69
+ */
70
+ private writeComplexHuffmanCode;
71
+ }
72
+ //# sourceMappingURL=webp_encoder.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"webp_encoder.d.ts","sourceRoot":"","sources":["../../../src/src/utils/webp_encoder.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;GAwBG;AA2DH,qBAAa,WAAW;IACtB,OAAO,CAAC,KAAK,CAAS;IACtB,OAAO,CAAC,MAAM,CAAS;IACvB,OAAO,CAAC,IAAI,CAAa;IACzB,OAAO,CAAC,OAAO,CAAS;gBAEZ,KAAK,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,EAAE,IAAI,EAAE,UAAU;IAO3D,MAAM,CAAC,OAAO,GAAE,MAAY,GAAG,UAAU;IAmDzC,OAAO,CAAC,UAAU;IAyBlB,OAAO,CAAC,eAAe;IAUvB;;;;;OAKG;IACH,OAAO,CAAC,iBAAiB;IAwCzB,OAAO,CAAC,eAAe;IA8EvB;;;OAGG;IACH,OAAO,CAAC,gBAAgB;IA4BxB;;OAEG;IACH,OAAO,CAAC,WAAW;IAoBnB,OAAO,CAAC,sBAAsB;IA4B9B;;;OAGG;IACH,OAAO,CAAC,oBAAoB;IAoF5B;;;OAGG;IACH,OAAO,CAAC,mBAAmB;IAyC3B;;OAEG;IACH,OAAO,CAAC,oBAAoB;IA8D5B;;OAEG;IACH,OAAO,CAAC,uBAAuB;CA6GhC"}