cross-image 0.1.5 → 0.2.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (72) hide show
  1. package/README.md +36 -18
  2. package/esm/mod.d.ts +30 -4
  3. package/esm/mod.js +30 -4
  4. package/esm/src/formats/apng.d.ts +50 -0
  5. package/esm/src/formats/apng.js +364 -0
  6. package/esm/src/formats/bmp.d.ts +0 -6
  7. package/esm/src/formats/bmp.js +24 -47
  8. package/esm/src/formats/dng.d.ts +27 -0
  9. package/esm/src/formats/dng.js +191 -0
  10. package/esm/src/formats/gif.d.ts +0 -2
  11. package/esm/src/formats/gif.js +10 -16
  12. package/esm/src/formats/ico.d.ts +41 -0
  13. package/esm/src/formats/ico.js +214 -0
  14. package/esm/src/formats/pam.d.ts +43 -0
  15. package/esm/src/formats/pam.js +177 -0
  16. package/esm/src/formats/pcx.d.ts +13 -0
  17. package/esm/src/formats/pcx.js +204 -0
  18. package/esm/src/formats/png.d.ts +2 -21
  19. package/esm/src/formats/png.js +5 -429
  20. package/esm/src/formats/png_base.d.ts +108 -0
  21. package/esm/src/formats/png_base.js +487 -0
  22. package/esm/src/formats/tiff.d.ts +7 -7
  23. package/esm/src/formats/webp.d.ts +0 -1
  24. package/esm/src/formats/webp.js +4 -7
  25. package/esm/src/image.d.ts +99 -0
  26. package/esm/src/image.js +204 -2
  27. package/esm/src/utils/byte_utils.d.ts +30 -0
  28. package/esm/src/utils/byte_utils.js +50 -0
  29. package/esm/src/utils/gif_encoder.d.ts +3 -2
  30. package/esm/src/utils/gif_encoder.js +115 -48
  31. package/esm/src/utils/image_processing.d.ts +91 -0
  32. package/esm/src/utils/image_processing.js +231 -0
  33. package/esm/src/utils/webp_decoder.js +47 -12
  34. package/esm/src/utils/webp_encoder.js +97 -39
  35. package/package.json +4 -1
  36. package/script/mod.d.ts +30 -4
  37. package/script/mod.js +36 -6
  38. package/script/src/formats/apng.d.ts +50 -0
  39. package/script/src/formats/apng.js +368 -0
  40. package/script/src/formats/bmp.d.ts +0 -6
  41. package/script/src/formats/bmp.js +24 -47
  42. package/script/src/formats/dng.d.ts +27 -0
  43. package/script/src/formats/dng.js +195 -0
  44. package/script/src/formats/gif.d.ts +0 -2
  45. package/script/src/formats/gif.js +10 -16
  46. package/script/src/formats/ico.d.ts +41 -0
  47. package/script/src/formats/ico.js +218 -0
  48. package/script/src/formats/pam.d.ts +43 -0
  49. package/script/src/formats/pam.js +181 -0
  50. package/script/src/formats/pcx.d.ts +13 -0
  51. package/script/src/formats/pcx.js +208 -0
  52. package/script/src/formats/png.d.ts +2 -21
  53. package/script/src/formats/png.js +5 -429
  54. package/script/src/formats/png_base.d.ts +108 -0
  55. package/script/src/formats/png_base.js +491 -0
  56. package/script/src/formats/tiff.d.ts +7 -7
  57. package/script/src/formats/webp.d.ts +0 -1
  58. package/script/src/formats/webp.js +4 -7
  59. package/script/src/image.d.ts +99 -0
  60. package/script/src/image.js +204 -2
  61. package/script/src/utils/byte_utils.d.ts +30 -0
  62. package/script/src/utils/byte_utils.js +58 -0
  63. package/script/src/utils/gif_encoder.d.ts +3 -2
  64. package/script/src/utils/gif_encoder.js +115 -48
  65. package/script/src/utils/image_processing.d.ts +91 -0
  66. package/script/src/utils/image_processing.js +242 -0
  67. package/script/src/utils/webp_decoder.js +47 -12
  68. package/script/src/utils/webp_encoder.js +97 -39
  69. package/esm/src/formats/raw.d.ts +0 -40
  70. package/esm/src/formats/raw.js +0 -118
  71. package/script/src/formats/raw.d.ts +0 -40
  72. package/script/src/formats/raw.js +0 -122
@@ -0,0 +1,491 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.PNGBase = void 0;
4
+ // Constants for unit conversions
5
+ const INCHES_PER_METER = 39.3701;
6
+ /**
7
+ * Base class for PNG and APNG format handlers
8
+ * Contains shared utility methods for PNG chunk manipulation and metadata parsing
9
+ */
10
+ class PNGBase {
11
+ /**
12
+ * Read a 32-bit unsigned integer (big-endian)
13
+ */
14
+ readUint32(data, offset) {
15
+ return (data[offset] << 24) | (data[offset + 1] << 16) |
16
+ (data[offset + 2] << 8) | data[offset + 3];
17
+ }
18
+ /**
19
+ * Read a 16-bit unsigned integer (big-endian)
20
+ */
21
+ readUint16(data, offset) {
22
+ return (data[offset] << 8) | data[offset + 1];
23
+ }
24
+ /**
25
+ * Write a 32-bit unsigned integer (big-endian)
26
+ */
27
+ writeUint32(data, offset, value) {
28
+ data[offset] = (value >>> 24) & 0xff;
29
+ data[offset + 1] = (value >>> 16) & 0xff;
30
+ data[offset + 2] = (value >>> 8) & 0xff;
31
+ data[offset + 3] = value & 0xff;
32
+ }
33
+ /**
34
+ * Write a 16-bit unsigned integer (big-endian)
35
+ */
36
+ writeUint16(data, offset, value) {
37
+ data[offset] = (value >>> 8) & 0xff;
38
+ data[offset + 1] = value & 0xff;
39
+ }
40
+ /**
41
+ * Decompress PNG data using deflate
42
+ */
43
+ async inflate(data) {
44
+ const stream = new Response(data).body
45
+ .pipeThrough(new DecompressionStream("deflate"));
46
+ const decompressed = await new Response(stream).arrayBuffer();
47
+ return new Uint8Array(decompressed);
48
+ }
49
+ /**
50
+ * Compress PNG data using deflate
51
+ */
52
+ async deflate(data) {
53
+ const stream = new Response(data).body
54
+ .pipeThrough(new CompressionStream("deflate"));
55
+ const compressed = await new Response(stream).arrayBuffer();
56
+ return new Uint8Array(compressed);
57
+ }
58
+ /**
59
+ * Unfilter PNG scanlines and convert to RGBA
60
+ */
61
+ unfilterAndConvert(data, width, height, bitDepth, colorType) {
62
+ const rgba = new Uint8Array(width * height * 4);
63
+ const bytesPerPixel = this.getBytesPerPixel(colorType, bitDepth);
64
+ const scanlineLength = width * bytesPerPixel;
65
+ let dataPos = 0;
66
+ const scanlines = [];
67
+ for (let y = 0; y < height; y++) {
68
+ const filterType = data[dataPos++];
69
+ const scanline = new Uint8Array(scanlineLength);
70
+ for (let x = 0; x < scanlineLength; x++) {
71
+ scanline[x] = data[dataPos++];
72
+ }
73
+ this.unfilterScanline(scanline, y > 0 ? scanlines[y - 1] : null, filterType, bytesPerPixel);
74
+ scanlines.push(scanline);
75
+ // Convert to RGBA
76
+ for (let x = 0; x < width; x++) {
77
+ const outIdx = (y * width + x) * 4;
78
+ if (colorType === 6) { // RGBA
79
+ rgba[outIdx] = scanline[x * 4];
80
+ rgba[outIdx + 1] = scanline[x * 4 + 1];
81
+ rgba[outIdx + 2] = scanline[x * 4 + 2];
82
+ rgba[outIdx + 3] = scanline[x * 4 + 3];
83
+ }
84
+ else if (colorType === 2) { // RGB
85
+ rgba[outIdx] = scanline[x * 3];
86
+ rgba[outIdx + 1] = scanline[x * 3 + 1];
87
+ rgba[outIdx + 2] = scanline[x * 3 + 2];
88
+ rgba[outIdx + 3] = 255;
89
+ }
90
+ else if (colorType === 0) { // Grayscale
91
+ const gray = scanline[x];
92
+ rgba[outIdx] = gray;
93
+ rgba[outIdx + 1] = gray;
94
+ rgba[outIdx + 2] = gray;
95
+ rgba[outIdx + 3] = 255;
96
+ }
97
+ else {
98
+ throw new Error(`Unsupported PNG color type: ${colorType}`);
99
+ }
100
+ }
101
+ }
102
+ return rgba;
103
+ }
104
+ /**
105
+ * Unfilter a single PNG scanline
106
+ */
107
+ unfilterScanline(scanline, prevLine, filterType, bytesPerPixel) {
108
+ for (let x = 0; x < scanline.length; x++) {
109
+ const left = x >= bytesPerPixel ? scanline[x - bytesPerPixel] : 0;
110
+ const above = prevLine ? prevLine[x] : 0;
111
+ const upperLeft = (x >= bytesPerPixel && prevLine)
112
+ ? prevLine[x - bytesPerPixel]
113
+ : 0;
114
+ switch (filterType) {
115
+ case 0: // None
116
+ break;
117
+ case 1: // Sub
118
+ scanline[x] = (scanline[x] + left) & 0xff;
119
+ break;
120
+ case 2: // Up
121
+ scanline[x] = (scanline[x] + above) & 0xff;
122
+ break;
123
+ case 3: // Average
124
+ scanline[x] = (scanline[x] + Math.floor((left + above) / 2)) & 0xff;
125
+ break;
126
+ case 4: // Paeth
127
+ scanline[x] =
128
+ (scanline[x] + this.paethPredictor(left, above, upperLeft)) & 0xff;
129
+ break;
130
+ }
131
+ }
132
+ }
133
+ /**
134
+ * Paeth predictor for PNG filtering
135
+ */
136
+ paethPredictor(a, b, c) {
137
+ const p = a + b - c;
138
+ const pa = Math.abs(p - a);
139
+ const pb = Math.abs(p - b);
140
+ const pc = Math.abs(p - c);
141
+ if (pa <= pb && pa <= pc)
142
+ return a;
143
+ if (pb <= pc)
144
+ return b;
145
+ return c;
146
+ }
147
+ /**
148
+ * Filter PNG data for encoding (using filter type 0 - None)
149
+ */
150
+ filterData(data, width, height) {
151
+ // Use filter type 0 (None) for simplicity
152
+ const filtered = new Uint8Array(height * (1 + width * 4));
153
+ let pos = 0;
154
+ for (let y = 0; y < height; y++) {
155
+ filtered[pos++] = 0; // Filter type: None
156
+ for (let x = 0; x < width * 4; x++) {
157
+ filtered[pos++] = data[y * width * 4 + x];
158
+ }
159
+ }
160
+ return filtered;
161
+ }
162
+ /**
163
+ * Get bytes per pixel for a given color type and bit depth
164
+ */
165
+ getBytesPerPixel(colorType, bitDepth) {
166
+ const bitsPerPixel = this.getBitsPerPixel(colorType, bitDepth);
167
+ return Math.ceil(bitsPerPixel / 8);
168
+ }
169
+ /**
170
+ * Get bits per pixel for a given color type and bit depth
171
+ */
172
+ getBitsPerPixel(colorType, bitDepth) {
173
+ switch (colorType) {
174
+ case 0: // Grayscale
175
+ return bitDepth;
176
+ case 2: // RGB
177
+ return bitDepth * 3;
178
+ case 3: // Palette
179
+ return bitDepth;
180
+ case 4: // Grayscale + Alpha
181
+ return bitDepth * 2;
182
+ case 6: // RGBA
183
+ return bitDepth * 4;
184
+ default:
185
+ throw new Error(`Unknown color type: ${colorType}`);
186
+ }
187
+ }
188
+ /**
189
+ * Create a PNG chunk with length, type, data, and CRC
190
+ */
191
+ createChunk(type, data) {
192
+ const chunk = new Uint8Array(12 + data.length);
193
+ this.writeUint32(chunk, 0, data.length);
194
+ chunk[4] = type.charCodeAt(0);
195
+ chunk[5] = type.charCodeAt(1);
196
+ chunk[6] = type.charCodeAt(2);
197
+ chunk[7] = type.charCodeAt(3);
198
+ chunk.set(data, 8);
199
+ const crc = this.crc32(chunk.slice(4, 8 + data.length));
200
+ this.writeUint32(chunk, 8 + data.length, crc);
201
+ return chunk;
202
+ }
203
+ /**
204
+ * Calculate CRC32 checksum
205
+ */
206
+ crc32(data) {
207
+ let crc = 0xffffffff;
208
+ for (let i = 0; i < data.length; i++) {
209
+ crc ^= data[i];
210
+ for (let j = 0; j < 8; j++) {
211
+ crc = (crc & 1) ? (crc >>> 1) ^ 0xedb88320 : crc >>> 1;
212
+ }
213
+ }
214
+ return (crc ^ 0xffffffff) >>> 0;
215
+ }
216
+ // Metadata parsing methods
217
+ /**
218
+ * Parse pHYs (physical pixel dimensions) chunk
219
+ */
220
+ parsePhysChunk(data, metadata, width, height) {
221
+ if (data.length < 9)
222
+ return;
223
+ const pixelsPerUnitX = this.readUint32(data, 0);
224
+ const pixelsPerUnitY = this.readUint32(data, 4);
225
+ const unit = data[8];
226
+ if (unit === 1 && pixelsPerUnitX > 0 && pixelsPerUnitY > 0) {
227
+ metadata.dpiX = Math.round(pixelsPerUnitX / INCHES_PER_METER);
228
+ metadata.dpiY = Math.round(pixelsPerUnitY / INCHES_PER_METER);
229
+ metadata.physicalWidth = width / metadata.dpiX;
230
+ metadata.physicalHeight = height / metadata.dpiY;
231
+ }
232
+ }
233
+ /**
234
+ * Parse tEXt (text) chunk
235
+ */
236
+ parseTextChunk(data, metadata) {
237
+ const nullIndex = data.indexOf(0);
238
+ if (nullIndex === -1)
239
+ return;
240
+ const keyword = new TextDecoder().decode(data.slice(0, nullIndex));
241
+ const text = new TextDecoder().decode(data.slice(nullIndex + 1));
242
+ switch (keyword.toLowerCase()) {
243
+ case "title":
244
+ metadata.title = text;
245
+ break;
246
+ case "author":
247
+ metadata.author = text;
248
+ break;
249
+ case "description":
250
+ metadata.description = text;
251
+ break;
252
+ case "copyright":
253
+ metadata.copyright = text;
254
+ break;
255
+ default:
256
+ if (!metadata.custom)
257
+ metadata.custom = {};
258
+ metadata.custom[keyword] = text;
259
+ }
260
+ }
261
+ /**
262
+ * Parse iTXt (international text) chunk
263
+ */
264
+ parseITxtChunk(data, metadata) {
265
+ let pos = 0;
266
+ const nullIndex = data.indexOf(0, pos);
267
+ if (nullIndex === -1 || pos >= data.length)
268
+ return;
269
+ const keyword = new TextDecoder().decode(data.slice(pos, nullIndex));
270
+ pos = nullIndex + 1;
271
+ if (pos + 2 > data.length)
272
+ return;
273
+ pos += 2; // Skip compression flag and method
274
+ const languageNullIndex = data.indexOf(0, pos);
275
+ if (languageNullIndex === -1 || pos >= data.length)
276
+ return;
277
+ pos = languageNullIndex + 1;
278
+ const translatedKeywordNullIndex = data.indexOf(0, pos);
279
+ if (translatedKeywordNullIndex === -1 || pos >= data.length)
280
+ return;
281
+ pos = translatedKeywordNullIndex + 1;
282
+ if (pos >= data.length)
283
+ return;
284
+ const text = new TextDecoder("utf-8").decode(data.slice(pos));
285
+ switch (keyword.toLowerCase()) {
286
+ case "title":
287
+ metadata.title = text;
288
+ break;
289
+ case "author":
290
+ metadata.author = text;
291
+ break;
292
+ case "description":
293
+ metadata.description = text;
294
+ break;
295
+ case "copyright":
296
+ metadata.copyright = text;
297
+ break;
298
+ default:
299
+ if (!metadata.custom)
300
+ metadata.custom = {};
301
+ metadata.custom[keyword] = text;
302
+ }
303
+ }
304
+ /**
305
+ * Parse eXIf (EXIF) chunk
306
+ */
307
+ parseExifChunk(data, metadata) {
308
+ if (data.length < 8)
309
+ return;
310
+ try {
311
+ const byteOrder = String.fromCharCode(data[0], data[1]);
312
+ const littleEndian = byteOrder === "II";
313
+ const ifd0Offset = littleEndian
314
+ ? data[4] | (data[5] << 8) | (data[6] << 16) | (data[7] << 24)
315
+ : (data[4] << 24) | (data[5] << 16) | (data[6] << 8) | data[7];
316
+ if (ifd0Offset + 2 > data.length)
317
+ return;
318
+ const numEntries = littleEndian
319
+ ? data[ifd0Offset] | (data[ifd0Offset + 1] << 8)
320
+ : (data[ifd0Offset] << 8) | data[ifd0Offset + 1];
321
+ for (let i = 0; i < numEntries; i++) {
322
+ const entryOffset = ifd0Offset + 2 + i * 12;
323
+ if (entryOffset + 12 > data.length)
324
+ break;
325
+ const tag = littleEndian
326
+ ? data[entryOffset] | (data[entryOffset + 1] << 8)
327
+ : (data[entryOffset] << 8) | data[entryOffset + 1];
328
+ if (tag === 0x0132) {
329
+ const valueOffset = littleEndian
330
+ ? data[entryOffset + 8] | (data[entryOffset + 9] << 8) |
331
+ (data[entryOffset + 10] << 16) | (data[entryOffset + 11] << 24)
332
+ : (data[entryOffset + 8] << 24) | (data[entryOffset + 9] << 16) |
333
+ (data[entryOffset + 10] << 8) | data[entryOffset + 11];
334
+ if (valueOffset < data.length) {
335
+ const nullIndex = data.indexOf(0, valueOffset);
336
+ if (nullIndex > valueOffset) {
337
+ const dateStr = new TextDecoder().decode(data.slice(valueOffset, nullIndex));
338
+ const match = dateStr.match(/^(\d{4}):(\d{2}):(\d{2}) (\d{2}):(\d{2}):(\d{2})$/);
339
+ if (match) {
340
+ metadata.creationDate = new Date(parseInt(match[1]), parseInt(match[2]) - 1, parseInt(match[3]), parseInt(match[4]), parseInt(match[5]), parseInt(match[6]));
341
+ }
342
+ }
343
+ }
344
+ }
345
+ }
346
+ }
347
+ catch (_e) {
348
+ // Ignore EXIF parsing errors
349
+ }
350
+ }
351
+ /**
352
+ * Create pHYs (physical pixel dimensions) chunk
353
+ */
354
+ createPhysChunk(metadata) {
355
+ const chunk = new Uint8Array(9);
356
+ const dpiX = metadata.dpiX ?? 72;
357
+ const dpiY = metadata.dpiY ?? 72;
358
+ const pixelsPerMeterX = Math.round(dpiX * INCHES_PER_METER);
359
+ const pixelsPerMeterY = Math.round(dpiY * INCHES_PER_METER);
360
+ this.writeUint32(chunk, 0, pixelsPerMeterX);
361
+ this.writeUint32(chunk, 4, pixelsPerMeterY);
362
+ chunk[8] = 1;
363
+ return chunk;
364
+ }
365
+ /**
366
+ * Create tEXt (text) chunk
367
+ */
368
+ createTextChunk(keyword, text) {
369
+ const keywordBytes = new TextEncoder().encode(keyword);
370
+ const textBytes = new TextEncoder().encode(text);
371
+ const chunk = new Uint8Array(keywordBytes.length + 1 + textBytes.length);
372
+ chunk.set(keywordBytes, 0);
373
+ chunk[keywordBytes.length] = 0;
374
+ chunk.set(textBytes, keywordBytes.length + 1);
375
+ return chunk;
376
+ }
377
+ /**
378
+ * Create eXIf (EXIF) chunk
379
+ */
380
+ createExifChunk(metadata) {
381
+ const entries = [];
382
+ if (metadata.creationDate) {
383
+ const date = metadata.creationDate;
384
+ const dateStr = `${date.getFullYear()}:${String(date.getMonth() + 1).padStart(2, "0")}:${String(date.getDate()).padStart(2, "0")} ${String(date.getHours()).padStart(2, "0")}:${String(date.getMinutes()).padStart(2, "0")}:${String(date.getSeconds()).padStart(2, "0")}\0`;
385
+ entries.push({
386
+ tag: 0x0132,
387
+ type: 2,
388
+ value: new TextEncoder().encode(dateStr),
389
+ });
390
+ }
391
+ if (entries.length === 0)
392
+ return null;
393
+ const exif = [];
394
+ exif.push(0x49, 0x49); // "II"
395
+ exif.push(0x2a, 0x00); // 42
396
+ exif.push(0x08, 0x00, 0x00, 0x00);
397
+ exif.push(entries.length & 0xff, (entries.length >> 8) & 0xff);
398
+ let dataOffset = 8 + 2 + entries.length * 12 + 4;
399
+ for (const entry of entries) {
400
+ exif.push(entry.tag & 0xff, (entry.tag >> 8) & 0xff);
401
+ exif.push(entry.type & 0xff, (entry.type >> 8) & 0xff);
402
+ const count = entry.value.length;
403
+ exif.push(count & 0xff, (count >> 8) & 0xff, (count >> 16) & 0xff, (count >> 24) & 0xff);
404
+ if (entry.value.length <= 4) {
405
+ for (let i = 0; i < 4; i++) {
406
+ exif.push(i < entry.value.length ? entry.value[i] : 0);
407
+ }
408
+ }
409
+ else {
410
+ exif.push(dataOffset & 0xff, (dataOffset >> 8) & 0xff, (dataOffset >> 16) & 0xff, (dataOffset >> 24) & 0xff);
411
+ dataOffset += entry.value.length;
412
+ }
413
+ }
414
+ exif.push(0x00, 0x00, 0x00, 0x00);
415
+ for (const entry of entries) {
416
+ if (entry.value.length > 4) {
417
+ for (const byte of entry.value) {
418
+ exif.push(byte);
419
+ }
420
+ }
421
+ }
422
+ return new Uint8Array(exif);
423
+ }
424
+ /**
425
+ * Concatenate multiple byte arrays into a single Uint8Array
426
+ */
427
+ concatenateChunks(chunks) {
428
+ const totalLength = chunks.reduce((sum, chunk) => sum + chunk.data.length, 0);
429
+ const result = new Uint8Array(totalLength);
430
+ let offset = 0;
431
+ for (const chunk of chunks) {
432
+ result.set(chunk.data, offset);
433
+ offset += chunk.data.length;
434
+ }
435
+ return result;
436
+ }
437
+ /**
438
+ * Concatenate multiple Uint8Arrays into a single Uint8Array
439
+ */
440
+ concatenateArrays(arrays) {
441
+ const totalLength = arrays.reduce((sum, arr) => sum + arr.length, 0);
442
+ const result = new Uint8Array(totalLength);
443
+ let offset = 0;
444
+ for (const arr of arrays) {
445
+ result.set(arr, offset);
446
+ offset += arr.length;
447
+ }
448
+ return result;
449
+ }
450
+ /**
451
+ * Add metadata chunks to the chunks array
452
+ * Shared method to avoid duplication between PNG and APNG encoding
453
+ */
454
+ addMetadataChunks(chunks, metadata) {
455
+ if (!metadata)
456
+ return;
457
+ // Add pHYs chunk for DPI information
458
+ if (metadata.dpiX !== undefined || metadata.dpiY !== undefined) {
459
+ const physChunk = this.createPhysChunk(metadata);
460
+ chunks.push(this.createChunk("pHYs", physChunk));
461
+ }
462
+ // Add tEXt chunks for standard metadata
463
+ if (metadata.title !== undefined) {
464
+ chunks.push(this.createChunk("tEXt", this.createTextChunk("Title", metadata.title)));
465
+ }
466
+ if (metadata.author !== undefined) {
467
+ chunks.push(this.createChunk("tEXt", this.createTextChunk("Author", metadata.author)));
468
+ }
469
+ if (metadata.description !== undefined) {
470
+ chunks.push(this.createChunk("tEXt", this.createTextChunk("Description", metadata.description)));
471
+ }
472
+ if (metadata.copyright !== undefined) {
473
+ chunks.push(this.createChunk("tEXt", this.createTextChunk("Copyright", metadata.copyright)));
474
+ }
475
+ // Add custom metadata fields
476
+ if (metadata.custom) {
477
+ for (const [key, value] of Object.entries(metadata.custom)) {
478
+ chunks.push(this.createChunk("tEXt", this.createTextChunk(key, String(value))));
479
+ }
480
+ }
481
+ // Add EXIF chunk for GPS data and creation date
482
+ if (metadata.latitude !== undefined || metadata.longitude !== undefined ||
483
+ metadata.creationDate !== undefined) {
484
+ const exifChunk = this.createExifChunk(metadata);
485
+ if (exifChunk) {
486
+ chunks.push(this.createChunk("eXIf", exifChunk));
487
+ }
488
+ }
489
+ }
490
+ }
491
+ exports.PNGBase = PNGBase;
@@ -15,9 +15,9 @@ export interface TIFFEncodeOptions {
15
15
  */
16
16
  export declare class TIFFFormat implements ImageFormat {
17
17
  /** Format name identifier */
18
- readonly name = "tiff";
18
+ readonly name: string;
19
19
  /** MIME type for TIFF images */
20
- readonly mimeType = "image/tiff";
20
+ readonly mimeType: string;
21
21
  /**
22
22
  * Check if this format supports multiple frames (pages)
23
23
  * @returns true for TIFF format
@@ -52,11 +52,11 @@ export declare class TIFFFormat implements ImageFormat {
52
52
  * Extract metadata from an IFD
53
53
  */
54
54
  private extractMetadataFromIFD;
55
- private readUint16;
56
- private readUint32;
57
- private writeUint16LE;
58
- private writeUint32LE;
59
- private writeIFDEntry;
55
+ protected readUint16(data: Uint8Array, offset: number, isLittleEndian: boolean): number;
56
+ protected readUint32(data: Uint8Array, offset: number, isLittleEndian: boolean): number;
57
+ protected writeUint16LE(result: number[], value: number): void;
58
+ protected writeUint32LE(result: number[], value: number): void;
59
+ protected writeIFDEntry(result: number[], tag: number, type: number, count: number, valueOrOffset: number): void;
60
60
  private getIFDValue;
61
61
  private decodeUsingRuntime;
62
62
  private readString;
@@ -27,7 +27,6 @@ export declare class WebPFormat implements ImageFormat {
27
27
  * @returns Encoded WebP image bytes
28
28
  */
29
29
  encode(imageData: ImageData, options?: WebPEncodeOptions): Promise<Uint8Array>;
30
- private readUint32LE;
31
30
  private readUint24LE;
32
31
  private decodeUsingRuntime;
33
32
  private parseEXIF;
@@ -35,6 +35,7 @@ var __importStar = (this && this.__importStar) || (function () {
35
35
  Object.defineProperty(exports, "__esModule", { value: true });
36
36
  exports.WebPFormat = void 0;
37
37
  const security_js_1 = require("../utils/security.js");
38
+ const byte_utils_js_1 = require("../utils/byte_utils.js");
38
39
  // Default quality for WebP encoding when not specified
39
40
  const DEFAULT_WEBP_QUALITY = 90;
40
41
  /**
@@ -88,7 +89,7 @@ class WebPFormat {
88
89
  // Read all chunks to extract metadata
89
90
  while (pos + 8 <= data.length) {
90
91
  const chunkType = String.fromCharCode(data[pos], data[pos + 1], data[pos + 2], data[pos + 3]);
91
- const chunkSize = this.readUint32LE(data, pos + 4);
92
+ const chunkSize = (0, byte_utils_js_1.readUint32LE)(data, pos + 4);
92
93
  pos += 8;
93
94
  // Stop if we've gone past the end
94
95
  if (pos + chunkSize > data.length)
@@ -110,7 +111,7 @@ class WebPFormat {
110
111
  else if (chunkType === "VP8L") {
111
112
  // Lossless format - extract dimensions
112
113
  if (chunkData.length >= 5 && chunkData[0] === 0x2f) {
113
- const bits = this.readUint32LE(chunkData, 1);
114
+ const bits = (0, byte_utils_js_1.readUint32LE)(chunkData, 1);
114
115
  width = (bits & 0x3fff) + 1;
115
116
  height = ((bits >> 14) & 0x3fff) + 1;
116
117
  }
@@ -202,10 +203,6 @@ class WebPFormat {
202
203
  }
203
204
  return encoded;
204
205
  }
205
- readUint32LE(data, offset) {
206
- return data[offset] | (data[offset + 1] << 8) |
207
- (data[offset + 2] << 16) | (data[offset + 3] << 24);
208
- }
209
206
  readUint24LE(data, offset) {
210
207
  return data[offset] | (data[offset + 1] << 8) | (data[offset + 2] << 16);
211
208
  }
@@ -342,7 +339,7 @@ class WebPFormat {
342
339
  let pos = 12;
343
340
  while (pos + 8 <= webpData.length) {
344
341
  const chunkType = String.fromCharCode(webpData[pos], webpData[pos + 1], webpData[pos + 2], webpData[pos + 3]);
345
- const chunkSize = this.readUint32LE(webpData, pos + 4);
342
+ const chunkSize = (0, byte_utils_js_1.readUint32LE)(webpData, pos + 4);
346
343
  // Don't copy existing EXIF/XMP chunks (we'll add new ones)
347
344
  if (chunkType !== "EXIF" && chunkType !== "XMP ") {
348
345
  const chunkEnd = pos + 8 + chunkSize + (chunkSize % 2);
@@ -127,6 +127,17 @@ export declare class Image {
127
127
  * @returns Image instance
128
128
  */
129
129
  static fromRGBA(width: number, height: number, data: Uint8Array): Image;
130
+ /**
131
+ * Create a blank image with the specified dimensions and color
132
+ * @param width Image width
133
+ * @param height Image height
134
+ * @param r Red component (0-255, default: 0)
135
+ * @param g Green component (0-255, default: 0)
136
+ * @param b Blue component (0-255, default: 0)
137
+ * @param a Alpha component (0-255, default: 255)
138
+ * @returns Image instance
139
+ */
140
+ static create(width: number, height: number, r?: number, g?: number, b?: number, a?: number): Image;
130
141
  /**
131
142
  * Resize the image
132
143
  * @param options Resize options
@@ -153,5 +164,93 @@ export declare class Image {
153
164
  * @returns New image instance with copied data and metadata
154
165
  */
155
166
  clone(): Image;
167
+ /**
168
+ * Composite another image on top of this image at the specified position
169
+ * @param overlay Image to place on top
170
+ * @param x X position (can be negative)
171
+ * @param y Y position (can be negative)
172
+ * @param opacity Opacity of overlay (0-1, default: 1)
173
+ * @returns This image instance for chaining
174
+ */
175
+ composite(overlay: Image, x: number, y: number, opacity?: number): this;
176
+ /**
177
+ * Adjust brightness of the image
178
+ * @param amount Brightness adjustment (-1 to 1, where 0 is no change)
179
+ * @returns This image instance for chaining
180
+ */
181
+ brightness(amount: number): this;
182
+ /**
183
+ * Adjust contrast of the image
184
+ * @param amount Contrast adjustment (-1 to 1, where 0 is no change)
185
+ * @returns This image instance for chaining
186
+ */
187
+ contrast(amount: number): this;
188
+ /**
189
+ * Adjust exposure of the image
190
+ * @param amount Exposure adjustment in stops (-3 to 3, where 0 is no change)
191
+ * @returns This image instance for chaining
192
+ */
193
+ exposure(amount: number): this;
194
+ /**
195
+ * Adjust saturation of the image
196
+ * @param amount Saturation adjustment (-1 to 1, where 0 is no change)
197
+ * @returns This image instance for chaining
198
+ */
199
+ saturation(amount: number): this;
200
+ /**
201
+ * Invert colors of the image
202
+ * @returns This image instance for chaining
203
+ */
204
+ invert(): this;
205
+ /**
206
+ * Convert the image to grayscale
207
+ * @returns This image instance for chaining
208
+ */
209
+ grayscale(): this;
210
+ /**
211
+ * Fill a rectangular region with a color
212
+ * @param x Starting X position
213
+ * @param y Starting Y position
214
+ * @param width Width of the fill region
215
+ * @param height Height of the fill region
216
+ * @param r Red component (0-255)
217
+ * @param g Green component (0-255)
218
+ * @param b Blue component (0-255)
219
+ * @param a Alpha component (0-255, default: 255)
220
+ * @returns This image instance for chaining
221
+ */
222
+ fillRect(x: number, y: number, width: number, height: number, r: number, g: number, b: number, a?: number): this;
223
+ /**
224
+ * Crop the image to a rectangular region
225
+ * @param x Starting X position
226
+ * @param y Starting Y position
227
+ * @param width Width of the crop region
228
+ * @param height Height of the crop region
229
+ * @returns This image instance for chaining
230
+ */
231
+ crop(x: number, y: number, width: number, height: number): this;
232
+ /**
233
+ * Get the pixel color at the specified position
234
+ * @param x X position
235
+ * @param y Y position
236
+ * @returns Object with r, g, b, a components (0-255) or undefined if out of bounds
237
+ */
238
+ getPixel(x: number, y: number): {
239
+ r: number;
240
+ g: number;
241
+ b: number;
242
+ a: number;
243
+ } | undefined;
244
+ /**
245
+ * Set the pixel color at the specified position
246
+ * @param x X position
247
+ * @param y Y position
248
+ * @param r Red component (0-255)
249
+ * @param g Green component (0-255)
250
+ * @param b Blue component (0-255)
251
+ * @param a Alpha component (0-255, default: 255)
252
+ * @returns This image instance for chaining
253
+ */
254
+ setPixel(x: number, y: number, r: number, g: number, b: number, a?: number): this;
156
255
  }
157
256
  //# sourceMappingURL=image.d.ts.map