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,584 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* Basic baseline JPEG decoder implementation
|
|
4
|
+
* Supports baseline DCT JPEG images (the most common format)
|
|
5
|
+
*
|
|
6
|
+
* This is a simplified implementation that handles common JPEG files.
|
|
7
|
+
* For complex or non-standard JPEGs, the ImageDecoder API fallback is preferred.
|
|
8
|
+
*/
|
|
9
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
10
|
+
exports.JPEGDecoder = void 0;
|
|
11
|
+
// JPEG markers
|
|
12
|
+
const EOI = 0xFFD9; // End of Image
|
|
13
|
+
const SOS = 0xFFDA; // Start of Scan
|
|
14
|
+
const DQT = 0xFFDB; // Define Quantization Table
|
|
15
|
+
const DHT = 0xFFC4; // Define Huffman Table
|
|
16
|
+
const SOF0 = 0xFFC0; // Start of Frame (Baseline DCT)
|
|
17
|
+
const SOF2 = 0xFFC2; // Start of Frame (Progressive DCT)
|
|
18
|
+
const DRI = 0xFFDD; // Define Restart Interval
|
|
19
|
+
// Zigzag order for DCT coefficients
|
|
20
|
+
const ZIGZAG = [
|
|
21
|
+
0,
|
|
22
|
+
1,
|
|
23
|
+
8,
|
|
24
|
+
16,
|
|
25
|
+
9,
|
|
26
|
+
2,
|
|
27
|
+
3,
|
|
28
|
+
10,
|
|
29
|
+
17,
|
|
30
|
+
24,
|
|
31
|
+
32,
|
|
32
|
+
25,
|
|
33
|
+
18,
|
|
34
|
+
11,
|
|
35
|
+
4,
|
|
36
|
+
5,
|
|
37
|
+
12,
|
|
38
|
+
19,
|
|
39
|
+
26,
|
|
40
|
+
33,
|
|
41
|
+
40,
|
|
42
|
+
48,
|
|
43
|
+
41,
|
|
44
|
+
34,
|
|
45
|
+
27,
|
|
46
|
+
20,
|
|
47
|
+
13,
|
|
48
|
+
6,
|
|
49
|
+
7,
|
|
50
|
+
14,
|
|
51
|
+
21,
|
|
52
|
+
28,
|
|
53
|
+
35,
|
|
54
|
+
42,
|
|
55
|
+
49,
|
|
56
|
+
56,
|
|
57
|
+
57,
|
|
58
|
+
50,
|
|
59
|
+
43,
|
|
60
|
+
36,
|
|
61
|
+
29,
|
|
62
|
+
22,
|
|
63
|
+
15,
|
|
64
|
+
23,
|
|
65
|
+
30,
|
|
66
|
+
37,
|
|
67
|
+
44,
|
|
68
|
+
51,
|
|
69
|
+
58,
|
|
70
|
+
59,
|
|
71
|
+
52,
|
|
72
|
+
45,
|
|
73
|
+
38,
|
|
74
|
+
31,
|
|
75
|
+
39,
|
|
76
|
+
46,
|
|
77
|
+
53,
|
|
78
|
+
60,
|
|
79
|
+
61,
|
|
80
|
+
54,
|
|
81
|
+
47,
|
|
82
|
+
55,
|
|
83
|
+
62,
|
|
84
|
+
63,
|
|
85
|
+
];
|
|
86
|
+
class JPEGDecoder {
|
|
87
|
+
constructor(data) {
|
|
88
|
+
Object.defineProperty(this, "data", {
|
|
89
|
+
enumerable: true,
|
|
90
|
+
configurable: true,
|
|
91
|
+
writable: true,
|
|
92
|
+
value: void 0
|
|
93
|
+
});
|
|
94
|
+
Object.defineProperty(this, "pos", {
|
|
95
|
+
enumerable: true,
|
|
96
|
+
configurable: true,
|
|
97
|
+
writable: true,
|
|
98
|
+
value: 0
|
|
99
|
+
});
|
|
100
|
+
Object.defineProperty(this, "width", {
|
|
101
|
+
enumerable: true,
|
|
102
|
+
configurable: true,
|
|
103
|
+
writable: true,
|
|
104
|
+
value: 0
|
|
105
|
+
});
|
|
106
|
+
Object.defineProperty(this, "height", {
|
|
107
|
+
enumerable: true,
|
|
108
|
+
configurable: true,
|
|
109
|
+
writable: true,
|
|
110
|
+
value: 0
|
|
111
|
+
});
|
|
112
|
+
Object.defineProperty(this, "components", {
|
|
113
|
+
enumerable: true,
|
|
114
|
+
configurable: true,
|
|
115
|
+
writable: true,
|
|
116
|
+
value: []
|
|
117
|
+
});
|
|
118
|
+
Object.defineProperty(this, "qTables", {
|
|
119
|
+
enumerable: true,
|
|
120
|
+
configurable: true,
|
|
121
|
+
writable: true,
|
|
122
|
+
value: []
|
|
123
|
+
});
|
|
124
|
+
Object.defineProperty(this, "dcTables", {
|
|
125
|
+
enumerable: true,
|
|
126
|
+
configurable: true,
|
|
127
|
+
writable: true,
|
|
128
|
+
value: []
|
|
129
|
+
});
|
|
130
|
+
Object.defineProperty(this, "acTables", {
|
|
131
|
+
enumerable: true,
|
|
132
|
+
configurable: true,
|
|
133
|
+
writable: true,
|
|
134
|
+
value: []
|
|
135
|
+
});
|
|
136
|
+
Object.defineProperty(this, "restartInterval", {
|
|
137
|
+
enumerable: true,
|
|
138
|
+
configurable: true,
|
|
139
|
+
writable: true,
|
|
140
|
+
value: 0
|
|
141
|
+
});
|
|
142
|
+
Object.defineProperty(this, "bitBuffer", {
|
|
143
|
+
enumerable: true,
|
|
144
|
+
configurable: true,
|
|
145
|
+
writable: true,
|
|
146
|
+
value: 0
|
|
147
|
+
});
|
|
148
|
+
Object.defineProperty(this, "bitCount", {
|
|
149
|
+
enumerable: true,
|
|
150
|
+
configurable: true,
|
|
151
|
+
writable: true,
|
|
152
|
+
value: 0
|
|
153
|
+
});
|
|
154
|
+
this.data = data;
|
|
155
|
+
}
|
|
156
|
+
decode() {
|
|
157
|
+
// Verify JPEG signature
|
|
158
|
+
if (this.data.length < 2 || this.data[0] !== 0xFF || this.data[1] !== 0xD8) {
|
|
159
|
+
throw new Error("Invalid JPEG signature");
|
|
160
|
+
}
|
|
161
|
+
this.pos = 2;
|
|
162
|
+
// Parse markers
|
|
163
|
+
while (this.pos < this.data.length) {
|
|
164
|
+
const marker = this.readMarker();
|
|
165
|
+
if (marker === EOI) {
|
|
166
|
+
break;
|
|
167
|
+
}
|
|
168
|
+
else if (marker === SOS) {
|
|
169
|
+
this.parseSOS();
|
|
170
|
+
this.decodeScan();
|
|
171
|
+
break; // Stop after first scan for baseline JPEG
|
|
172
|
+
}
|
|
173
|
+
else if (marker === DQT) {
|
|
174
|
+
this.parseDQT();
|
|
175
|
+
}
|
|
176
|
+
else if (marker === DHT) {
|
|
177
|
+
this.parseDHT();
|
|
178
|
+
}
|
|
179
|
+
else if (marker === SOF0) {
|
|
180
|
+
this.parseSOF();
|
|
181
|
+
}
|
|
182
|
+
else if (marker === SOF2) {
|
|
183
|
+
throw new Error("Progressive JPEG not supported in pure JS decoder");
|
|
184
|
+
}
|
|
185
|
+
else if (marker === DRI) {
|
|
186
|
+
this.parseDRI();
|
|
187
|
+
}
|
|
188
|
+
else if (marker >= 0xFFE0 && marker <= 0xFFEF) {
|
|
189
|
+
// Skip APP markers
|
|
190
|
+
this.skipSegment();
|
|
191
|
+
}
|
|
192
|
+
else if (marker >= 0xFFC0 && marker <= 0xFFCF) {
|
|
193
|
+
// Other SOF markers
|
|
194
|
+
if (marker !== 0xFFC4 && marker !== 0xFFC8 && marker !== 0xFFCC) {
|
|
195
|
+
throw new Error(`Unsupported JPEG type: marker 0x${marker.toString(16)}`);
|
|
196
|
+
}
|
|
197
|
+
}
|
|
198
|
+
else {
|
|
199
|
+
// Skip unknown markers
|
|
200
|
+
if (this.pos < this.data.length) {
|
|
201
|
+
this.skipSegment();
|
|
202
|
+
}
|
|
203
|
+
}
|
|
204
|
+
}
|
|
205
|
+
if (this.width === 0 || this.height === 0) {
|
|
206
|
+
throw new Error("Failed to decode JPEG: invalid dimensions");
|
|
207
|
+
}
|
|
208
|
+
// Convert YCbCr to RGB
|
|
209
|
+
return this.convertToRGB();
|
|
210
|
+
}
|
|
211
|
+
readMarker() {
|
|
212
|
+
while (this.pos < this.data.length && this.data[this.pos] !== 0xFF) {
|
|
213
|
+
this.pos++;
|
|
214
|
+
}
|
|
215
|
+
if (this.pos >= this.data.length - 1) {
|
|
216
|
+
return EOI;
|
|
217
|
+
}
|
|
218
|
+
const byte1 = this.data[this.pos++];
|
|
219
|
+
let byte2 = this.data[this.pos++];
|
|
220
|
+
// Skip padding 0xFF bytes
|
|
221
|
+
while (byte2 === 0xFF && this.pos < this.data.length) {
|
|
222
|
+
byte2 = this.data[this.pos++];
|
|
223
|
+
}
|
|
224
|
+
return (byte1 << 8) | byte2;
|
|
225
|
+
}
|
|
226
|
+
readUint16() {
|
|
227
|
+
const value = (this.data[this.pos] << 8) | this.data[this.pos + 1];
|
|
228
|
+
this.pos += 2;
|
|
229
|
+
return value;
|
|
230
|
+
}
|
|
231
|
+
skipSegment() {
|
|
232
|
+
const length = this.readUint16();
|
|
233
|
+
this.pos += length - 2;
|
|
234
|
+
}
|
|
235
|
+
parseDQT() {
|
|
236
|
+
let length = this.readUint16() - 2;
|
|
237
|
+
while (length > 0) {
|
|
238
|
+
const info = this.data[this.pos++];
|
|
239
|
+
const tableId = info & 0x0F;
|
|
240
|
+
const precision = (info >> 4) & 0x0F;
|
|
241
|
+
if (precision !== 0) {
|
|
242
|
+
throw new Error("16-bit quantization tables not supported");
|
|
243
|
+
}
|
|
244
|
+
const table = new Array(64);
|
|
245
|
+
for (let i = 0; i < 64; i++) {
|
|
246
|
+
table[ZIGZAG[i]] = this.data[this.pos++];
|
|
247
|
+
}
|
|
248
|
+
this.qTables[tableId] = table;
|
|
249
|
+
length -= 65;
|
|
250
|
+
}
|
|
251
|
+
}
|
|
252
|
+
parseDHT() {
|
|
253
|
+
let length = this.readUint16() - 2;
|
|
254
|
+
while (length > 0) {
|
|
255
|
+
const info = this.data[this.pos++];
|
|
256
|
+
const tableId = info & 0x0F;
|
|
257
|
+
const tableClass = (info >> 4) & 0x0F;
|
|
258
|
+
const bits = new Array(16);
|
|
259
|
+
let numSymbols = 0;
|
|
260
|
+
for (let i = 0; i < 16; i++) {
|
|
261
|
+
bits[i] = this.data[this.pos++];
|
|
262
|
+
numSymbols += bits[i];
|
|
263
|
+
}
|
|
264
|
+
const huffVal = new Array(numSymbols);
|
|
265
|
+
for (let i = 0; i < numSymbols; i++) {
|
|
266
|
+
huffVal[i] = this.data[this.pos++];
|
|
267
|
+
}
|
|
268
|
+
const table = this.buildHuffmanTable(bits, huffVal);
|
|
269
|
+
if (tableClass === 0) {
|
|
270
|
+
this.dcTables[tableId] = table;
|
|
271
|
+
}
|
|
272
|
+
else {
|
|
273
|
+
this.acTables[tableId] = table;
|
|
274
|
+
}
|
|
275
|
+
length -= 17 + numSymbols;
|
|
276
|
+
}
|
|
277
|
+
}
|
|
278
|
+
buildHuffmanTable(bits, huffVal) {
|
|
279
|
+
const maxCode = new Array(16).fill(-1);
|
|
280
|
+
const minCode = new Array(16).fill(-1);
|
|
281
|
+
const valPtr = new Array(16).fill(-1);
|
|
282
|
+
const codes = new Map();
|
|
283
|
+
let code = 0;
|
|
284
|
+
let valIndex = 0;
|
|
285
|
+
for (let i = 0; i < 16; i++) {
|
|
286
|
+
if (bits[i] > 0) {
|
|
287
|
+
minCode[i] = code;
|
|
288
|
+
valPtr[i] = valIndex;
|
|
289
|
+
for (let j = 0; j < bits[i]; j++) {
|
|
290
|
+
codes.set((i << 16) | code, huffVal[valIndex]);
|
|
291
|
+
code++;
|
|
292
|
+
valIndex++;
|
|
293
|
+
}
|
|
294
|
+
maxCode[i] = code - 1;
|
|
295
|
+
code <<= 1;
|
|
296
|
+
}
|
|
297
|
+
else {
|
|
298
|
+
code <<= 1;
|
|
299
|
+
}
|
|
300
|
+
}
|
|
301
|
+
return { codes, maxCode, minCode, valPtr, huffVal };
|
|
302
|
+
}
|
|
303
|
+
parseSOF() {
|
|
304
|
+
const _length = this.readUint16();
|
|
305
|
+
const precision = this.data[this.pos++];
|
|
306
|
+
if (precision !== 8) {
|
|
307
|
+
throw new Error(`Unsupported precision: ${precision}`);
|
|
308
|
+
}
|
|
309
|
+
this.height = this.readUint16();
|
|
310
|
+
this.width = this.readUint16();
|
|
311
|
+
const numComponents = this.data[this.pos++];
|
|
312
|
+
if (numComponents !== 1 && numComponents !== 3) {
|
|
313
|
+
throw new Error(`Unsupported number of components: ${numComponents}`);
|
|
314
|
+
}
|
|
315
|
+
this.components = [];
|
|
316
|
+
for (let i = 0; i < numComponents; i++) {
|
|
317
|
+
const id = this.data[this.pos++];
|
|
318
|
+
const samplingFactor = this.data[this.pos++];
|
|
319
|
+
const qTable = this.data[this.pos++];
|
|
320
|
+
this.components.push({
|
|
321
|
+
id,
|
|
322
|
+
h: (samplingFactor >> 4) & 0x0F,
|
|
323
|
+
v: samplingFactor & 0x0F,
|
|
324
|
+
qTable,
|
|
325
|
+
dcTable: 0,
|
|
326
|
+
acTable: 0,
|
|
327
|
+
pred: 0,
|
|
328
|
+
blocks: [],
|
|
329
|
+
});
|
|
330
|
+
}
|
|
331
|
+
}
|
|
332
|
+
parseSOS() {
|
|
333
|
+
const _length = this.readUint16();
|
|
334
|
+
const numComponents = this.data[this.pos++];
|
|
335
|
+
for (let i = 0; i < numComponents; i++) {
|
|
336
|
+
const id = this.data[this.pos++];
|
|
337
|
+
const tables = this.data[this.pos++];
|
|
338
|
+
const component = this.components.find((c) => c.id === id);
|
|
339
|
+
if (component) {
|
|
340
|
+
component.dcTable = (tables >> 4) & 0x0F;
|
|
341
|
+
component.acTable = tables & 0x0F;
|
|
342
|
+
}
|
|
343
|
+
}
|
|
344
|
+
this.pos += 3; // Skip spectral selection and successive approximation
|
|
345
|
+
}
|
|
346
|
+
parseDRI() {
|
|
347
|
+
const _length = this.readUint16();
|
|
348
|
+
this.restartInterval = this.readUint16();
|
|
349
|
+
}
|
|
350
|
+
decodeScan() {
|
|
351
|
+
// Calculate MCU dimensions
|
|
352
|
+
const maxH = Math.max(...this.components.map((c) => c.h));
|
|
353
|
+
const maxV = Math.max(...this.components.map((c) => c.v));
|
|
354
|
+
const mcuWidth = Math.ceil(this.width / (8 * maxH));
|
|
355
|
+
const mcuHeight = Math.ceil(this.height / (8 * maxV));
|
|
356
|
+
// Initialize bit buffer
|
|
357
|
+
this.bitBuffer = 0;
|
|
358
|
+
this.bitCount = 0;
|
|
359
|
+
// Initialize blocks for each component
|
|
360
|
+
for (const component of this.components) {
|
|
361
|
+
const blocksAcross = Math.ceil(this.width * component.h / (8 * maxH));
|
|
362
|
+
const blocksDown = Math.ceil(this.height * component.v / (8 * maxV));
|
|
363
|
+
component.blocks = Array(blocksDown).fill(null).map(() => Array(blocksAcross).fill(null).map(() => new Array(64).fill(0)));
|
|
364
|
+
}
|
|
365
|
+
// Decode MCUs
|
|
366
|
+
try {
|
|
367
|
+
for (let mcuY = 0; mcuY < mcuHeight; mcuY++) {
|
|
368
|
+
for (let mcuX = 0; mcuX < mcuWidth; mcuX++) {
|
|
369
|
+
// Decode all components in this MCU
|
|
370
|
+
for (const component of this.components) {
|
|
371
|
+
for (let v = 0; v < component.v; v++) {
|
|
372
|
+
for (let h = 0; h < component.h; h++) {
|
|
373
|
+
const blockY = mcuY * component.v + v;
|
|
374
|
+
const blockX = mcuX * component.h + h;
|
|
375
|
+
if (blockY < component.blocks.length &&
|
|
376
|
+
blockX < component.blocks[0].length) {
|
|
377
|
+
this.decodeBlock(component, blockY, blockX);
|
|
378
|
+
}
|
|
379
|
+
}
|
|
380
|
+
}
|
|
381
|
+
}
|
|
382
|
+
}
|
|
383
|
+
}
|
|
384
|
+
}
|
|
385
|
+
catch (e) {
|
|
386
|
+
// If we run into issues during decoding, we may still have partial data
|
|
387
|
+
console.warn("JPEG decode warning:", e);
|
|
388
|
+
}
|
|
389
|
+
}
|
|
390
|
+
decodeBlock(component, blockY, blockX) {
|
|
391
|
+
const block = component.blocks[blockY][blockX];
|
|
392
|
+
// Decode DC coefficient
|
|
393
|
+
const dcTable = this.dcTables[component.dcTable];
|
|
394
|
+
if (!dcTable) {
|
|
395
|
+
throw new Error(`Missing DC table ${component.dcTable}`);
|
|
396
|
+
}
|
|
397
|
+
const dcLen = this.decodeHuffman(dcTable);
|
|
398
|
+
const dcDiff = dcLen > 0 ? this.receiveBits(dcLen) : 0;
|
|
399
|
+
component.pred += dcDiff;
|
|
400
|
+
block[0] = component.pred * this.qTables[component.qTable][0];
|
|
401
|
+
// Decode AC coefficients
|
|
402
|
+
const acTable = this.acTables[component.acTable];
|
|
403
|
+
if (!acTable) {
|
|
404
|
+
throw new Error(`Missing AC table ${component.acTable}`);
|
|
405
|
+
}
|
|
406
|
+
let k = 1;
|
|
407
|
+
while (k < 64) {
|
|
408
|
+
const rs = this.decodeHuffman(acTable);
|
|
409
|
+
const r = (rs >> 4) & 0x0F;
|
|
410
|
+
const s = rs & 0x0F;
|
|
411
|
+
if (s === 0) {
|
|
412
|
+
if (r === 15) {
|
|
413
|
+
k += 16;
|
|
414
|
+
}
|
|
415
|
+
else {
|
|
416
|
+
break; // EOB
|
|
417
|
+
}
|
|
418
|
+
}
|
|
419
|
+
else {
|
|
420
|
+
k += r;
|
|
421
|
+
if (k >= 64)
|
|
422
|
+
break;
|
|
423
|
+
block[ZIGZAG[k]] = this.receiveBits(s) *
|
|
424
|
+
this.qTables[component.qTable][ZIGZAG[k]];
|
|
425
|
+
k++;
|
|
426
|
+
}
|
|
427
|
+
}
|
|
428
|
+
// Perform IDCT
|
|
429
|
+
this.idct(block);
|
|
430
|
+
}
|
|
431
|
+
decodeHuffman(table) {
|
|
432
|
+
let code = 0;
|
|
433
|
+
for (let len = 0; len < 16; len++) {
|
|
434
|
+
code = (code << 1) | this.readBit();
|
|
435
|
+
if (table.minCode[len] !== -1 && code <= table.maxCode[len]) {
|
|
436
|
+
const index = table.valPtr[len] + (code - table.minCode[len]);
|
|
437
|
+
return table.huffVal[index];
|
|
438
|
+
}
|
|
439
|
+
}
|
|
440
|
+
throw new Error("Invalid Huffman code");
|
|
441
|
+
}
|
|
442
|
+
readBit() {
|
|
443
|
+
if (this.bitCount === 0) {
|
|
444
|
+
let byte = this.data[this.pos++];
|
|
445
|
+
// Handle byte stuffing (0xFF 0x00)
|
|
446
|
+
if (byte === 0xFF) {
|
|
447
|
+
const nextByte = this.data[this.pos];
|
|
448
|
+
if (nextByte === 0x00) {
|
|
449
|
+
this.pos++;
|
|
450
|
+
}
|
|
451
|
+
else if (nextByte >= 0xD0 && nextByte <= 0xD7) {
|
|
452
|
+
// Restart marker - reset DC predictors
|
|
453
|
+
this.pos++;
|
|
454
|
+
for (const component of this.components) {
|
|
455
|
+
component.pred = 0;
|
|
456
|
+
}
|
|
457
|
+
byte = this.data[this.pos++];
|
|
458
|
+
}
|
|
459
|
+
}
|
|
460
|
+
this.bitBuffer = byte;
|
|
461
|
+
this.bitCount = 8;
|
|
462
|
+
}
|
|
463
|
+
this.bitCount--;
|
|
464
|
+
return (this.bitBuffer >> this.bitCount) & 1;
|
|
465
|
+
}
|
|
466
|
+
receiveBits(n) {
|
|
467
|
+
let value = 0;
|
|
468
|
+
for (let i = 0; i < n; i++) {
|
|
469
|
+
value = (value << 1) | this.readBit();
|
|
470
|
+
}
|
|
471
|
+
// Convert from magnitude representation
|
|
472
|
+
if (value < (1 << (n - 1))) {
|
|
473
|
+
value = value - (1 << n) + 1;
|
|
474
|
+
}
|
|
475
|
+
return value;
|
|
476
|
+
}
|
|
477
|
+
idct(block) {
|
|
478
|
+
// Simplified 2D IDCT
|
|
479
|
+
// This is a basic implementation - not optimized
|
|
480
|
+
const temp = new Array(64);
|
|
481
|
+
// 1D IDCT on rows
|
|
482
|
+
for (let i = 0; i < 8; i++) {
|
|
483
|
+
const offset = i * 8;
|
|
484
|
+
for (let j = 0; j < 8; j++) {
|
|
485
|
+
let sum = 0;
|
|
486
|
+
for (let k = 0; k < 8; k++) {
|
|
487
|
+
const c = k === 0 ? 1 / Math.sqrt(2) : 1;
|
|
488
|
+
sum += c * block[offset + k] *
|
|
489
|
+
Math.cos((2 * j + 1) * k * Math.PI / 16);
|
|
490
|
+
}
|
|
491
|
+
temp[offset + j] = sum / 2;
|
|
492
|
+
}
|
|
493
|
+
}
|
|
494
|
+
// 1D IDCT on columns
|
|
495
|
+
for (let j = 0; j < 8; j++) {
|
|
496
|
+
for (let i = 0; i < 8; i++) {
|
|
497
|
+
let sum = 0;
|
|
498
|
+
for (let k = 0; k < 8; k++) {
|
|
499
|
+
const c = k === 0 ? 1 / Math.sqrt(2) : 1;
|
|
500
|
+
sum += c * temp[k * 8 + j] * Math.cos((2 * i + 1) * k * Math.PI / 16);
|
|
501
|
+
}
|
|
502
|
+
// Level shift and clamp
|
|
503
|
+
block[i * 8 + j] = Math.max(0, Math.min(255, Math.round(sum / 2 + 128)));
|
|
504
|
+
}
|
|
505
|
+
}
|
|
506
|
+
}
|
|
507
|
+
convertToRGB() {
|
|
508
|
+
const rgba = new Uint8Array(this.width * this.height * 4);
|
|
509
|
+
if (this.components.length === 1) {
|
|
510
|
+
// Grayscale
|
|
511
|
+
const y = this.components[0];
|
|
512
|
+
for (let row = 0; row < this.height; row++) {
|
|
513
|
+
for (let col = 0; col < this.width; col++) {
|
|
514
|
+
const blockRow = Math.floor(row / 8);
|
|
515
|
+
const blockCol = Math.floor(col / 8);
|
|
516
|
+
const blockY = row % 8;
|
|
517
|
+
const blockX = col % 8;
|
|
518
|
+
if (blockRow < y.blocks.length && blockCol < y.blocks[0].length) {
|
|
519
|
+
const value = y.blocks[blockRow][blockCol][blockY * 8 + blockX];
|
|
520
|
+
const offset = (row * this.width + col) * 4;
|
|
521
|
+
rgba[offset] = value;
|
|
522
|
+
rgba[offset + 1] = value;
|
|
523
|
+
rgba[offset + 2] = value;
|
|
524
|
+
rgba[offset + 3] = 255;
|
|
525
|
+
}
|
|
526
|
+
}
|
|
527
|
+
}
|
|
528
|
+
}
|
|
529
|
+
else {
|
|
530
|
+
// YCbCr to RGB
|
|
531
|
+
const [y, cb, cr] = this.components;
|
|
532
|
+
const maxH = Math.max(...this.components.map((c) => c.h));
|
|
533
|
+
const maxV = Math.max(...this.components.map((c) => c.v));
|
|
534
|
+
for (let row = 0; row < this.height; row++) {
|
|
535
|
+
for (let col = 0; col < this.width; col++) {
|
|
536
|
+
// Y component
|
|
537
|
+
const yBlockRow = Math.floor(row / 8);
|
|
538
|
+
const yBlockCol = Math.floor(col / 8);
|
|
539
|
+
const yBlockY = row % 8;
|
|
540
|
+
const yBlockX = col % 8;
|
|
541
|
+
let yVal = 0;
|
|
542
|
+
if (yBlockRow < y.blocks.length && yBlockCol < y.blocks[0].length) {
|
|
543
|
+
yVal = y.blocks[yBlockRow][yBlockCol][yBlockY * 8 + yBlockX];
|
|
544
|
+
}
|
|
545
|
+
// Cb and Cr components (may be subsampled)
|
|
546
|
+
// Scale pixel position by subsampling factor, then get block and within-block positions
|
|
547
|
+
const cbRow = Math.floor(row * cb.v / maxV);
|
|
548
|
+
const cbCol = Math.floor(col * cb.h / maxH);
|
|
549
|
+
const cbBlockRow = Math.floor(cbRow / 8);
|
|
550
|
+
const cbBlockCol = Math.floor(cbCol / 8);
|
|
551
|
+
const cbBlockY = cbRow % 8;
|
|
552
|
+
const cbBlockX = cbCol % 8;
|
|
553
|
+
let cbVal = 0;
|
|
554
|
+
if (cbBlockRow < cb.blocks.length && cbBlockCol < cb.blocks[0].length) {
|
|
555
|
+
cbVal = cb.blocks[cbBlockRow][cbBlockCol][cbBlockY * 8 + cbBlockX] -
|
|
556
|
+
128;
|
|
557
|
+
}
|
|
558
|
+
const crRow = Math.floor(row * cr.v / maxV);
|
|
559
|
+
const crCol = Math.floor(col * cr.h / maxH);
|
|
560
|
+
const crBlockRow = Math.floor(crRow / 8);
|
|
561
|
+
const crBlockCol = Math.floor(crCol / 8);
|
|
562
|
+
const crBlockY = crRow % 8;
|
|
563
|
+
const crBlockX = crCol % 8;
|
|
564
|
+
let crVal = 0;
|
|
565
|
+
if (crBlockRow < cr.blocks.length && crBlockCol < cr.blocks[0].length) {
|
|
566
|
+
crVal = cr.blocks[crBlockRow][crBlockCol][crBlockY * 8 + crBlockX] -
|
|
567
|
+
128;
|
|
568
|
+
}
|
|
569
|
+
// YCbCr to RGB conversion
|
|
570
|
+
const r = Math.max(0, Math.min(255, Math.round(yVal + 1.402 * crVal)));
|
|
571
|
+
const g = Math.max(0, Math.min(255, Math.round(yVal - 0.344136 * cbVal - 0.714136 * crVal)));
|
|
572
|
+
const b = Math.max(0, Math.min(255, Math.round(yVal + 1.772 * cbVal)));
|
|
573
|
+
const offset = (row * this.width + col) * 4;
|
|
574
|
+
rgba[offset] = r;
|
|
575
|
+
rgba[offset + 1] = g;
|
|
576
|
+
rgba[offset + 2] = b;
|
|
577
|
+
rgba[offset + 3] = 255;
|
|
578
|
+
}
|
|
579
|
+
}
|
|
580
|
+
}
|
|
581
|
+
return rgba;
|
|
582
|
+
}
|
|
583
|
+
}
|
|
584
|
+
exports.JPEGDecoder = JPEGDecoder;
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Basic baseline JPEG encoder implementation
|
|
3
|
+
* Produces valid baseline DCT JPEG images
|
|
4
|
+
*
|
|
5
|
+
* This is a simplified implementation focusing on correctness over performance.
|
|
6
|
+
* For production use with better quality/size, the OffscreenCanvas API is preferred.
|
|
7
|
+
*/
|
|
8
|
+
export declare class JPEGEncoder {
|
|
9
|
+
private quality;
|
|
10
|
+
private luminanceQuantTable;
|
|
11
|
+
private chrominanceQuantTable;
|
|
12
|
+
private dcLuminanceHuffman;
|
|
13
|
+
private acLuminanceHuffman;
|
|
14
|
+
private dcChrominanceHuffman;
|
|
15
|
+
private acChrominanceHuffman;
|
|
16
|
+
constructor(quality?: number);
|
|
17
|
+
private initQuantizationTables;
|
|
18
|
+
private initHuffmanTables;
|
|
19
|
+
private buildHuffmanTable;
|
|
20
|
+
encode(width: number, height: number, rgba: Uint8Array, dpiX?: number, dpiY?: number): Uint8Array;
|
|
21
|
+
private writeAPP0;
|
|
22
|
+
private writeDQT;
|
|
23
|
+
private writeSOF0;
|
|
24
|
+
private writeDHT;
|
|
25
|
+
private writeHuffmanTable;
|
|
26
|
+
private writeSOS;
|
|
27
|
+
private encodeScan;
|
|
28
|
+
private encodeBlock;
|
|
29
|
+
private forwardDCT;
|
|
30
|
+
private encodeDC;
|
|
31
|
+
private encodeAC;
|
|
32
|
+
}
|
|
33
|
+
//# sourceMappingURL=jpeg_encoder.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"jpeg_encoder.d.ts","sourceRoot":"","sources":["../../../src/src/utils/jpeg_encoder.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAgrBH,qBAAa,WAAW;IACtB,OAAO,CAAC,OAAO,CAAS;IACxB,OAAO,CAAC,mBAAmB,CAAgB;IAC3C,OAAO,CAAC,qBAAqB,CAAgB;IAC7C,OAAO,CAAC,kBAAkB,CAAgB;IAC1C,OAAO,CAAC,kBAAkB,CAAgB;IAC1C,OAAO,CAAC,oBAAoB,CAAgB;IAC5C,OAAO,CAAC,oBAAoB,CAAgB;gBAEhC,OAAO,GAAE,MAAW;IAMhC,OAAO,CAAC,sBAAsB;IAqB9B,OAAO,CAAC,iBAAiB;IAmBzB,OAAO,CAAC,iBAAiB;IAqBzB,MAAM,CACJ,KAAK,EAAE,MAAM,EACb,MAAM,EAAE,MAAM,EACd,IAAI,EAAE,UAAU,EAChB,IAAI,SAAK,EACT,IAAI,SAAK,GACR,UAAU;IAiCb,OAAO,CAAC,SAAS;IAWjB,OAAO,CAAC,QAAQ;IAkBhB,OAAO,CAAC,SAAS;IAwBjB,OAAO,CAAC,QAAQ;IAkChB,OAAO,CAAC,iBAAiB;IA8BzB,OAAO,CAAC,QAAQ;IAsBhB,OAAO,CAAC,UAAU;IA2ElB,OAAO,CAAC,WAAW;IA4BnB,OAAO,CAAC,UAAU;IA8BlB,OAAO,CAAC,QAAQ;IAsBhB,OAAO,CAAC,QAAQ;CAqCjB"}
|