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