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,414 @@
1
+ // Constants for unit conversions
2
+ const CM_PER_INCH = 2.54;
3
+ /**
4
+ * JPEG format handler
5
+ * Implements a basic JPEG decoder and encoder
6
+ */
7
+ export class JPEGFormat {
8
+ constructor() {
9
+ Object.defineProperty(this, "name", {
10
+ enumerable: true,
11
+ configurable: true,
12
+ writable: true,
13
+ value: "jpeg"
14
+ });
15
+ Object.defineProperty(this, "mimeType", {
16
+ enumerable: true,
17
+ configurable: true,
18
+ writable: true,
19
+ value: "image/jpeg"
20
+ });
21
+ }
22
+ canDecode(data) {
23
+ // JPEG signature: FF D8 FF
24
+ return data.length >= 3 &&
25
+ data[0] === 0xff && data[1] === 0xd8 && data[2] === 0xff;
26
+ }
27
+ async decode(data) {
28
+ if (!this.canDecode(data)) {
29
+ throw new Error("Invalid JPEG signature");
30
+ }
31
+ // Parse JPEG structure to get dimensions and metadata
32
+ let pos = 2; // Skip initial FF D8
33
+ let width = 0;
34
+ let height = 0;
35
+ const metadata = {};
36
+ while (pos < data.length - 1) {
37
+ if (data[pos] !== 0xff) {
38
+ pos++;
39
+ continue;
40
+ }
41
+ const marker = data[pos + 1];
42
+ pos += 2;
43
+ // SOF markers (Start of Frame)
44
+ if (marker >= 0xc0 && marker <= 0xcf &&
45
+ marker !== 0xc4 && marker !== 0xc8 && marker !== 0xcc) {
46
+ const _length = (data[pos] << 8) | data[pos + 1];
47
+ // precision at pos+2
48
+ height = (data[pos + 3] << 8) | data[pos + 4];
49
+ width = (data[pos + 5] << 8) | data[pos + 6];
50
+ break;
51
+ }
52
+ // APP0 marker (JFIF)
53
+ if (marker === 0xe0) {
54
+ const length = (data[pos] << 8) | data[pos + 1];
55
+ const appData = data.slice(pos + 2, pos + length);
56
+ this.parseJFIF(appData, metadata, width, height);
57
+ pos += length;
58
+ continue;
59
+ }
60
+ // APP1 marker (EXIF)
61
+ if (marker === 0xe1) {
62
+ const length = (data[pos] << 8) | data[pos + 1];
63
+ const appData = data.slice(pos + 2, pos + length);
64
+ this.parseEXIF(appData, metadata);
65
+ pos += length;
66
+ continue;
67
+ }
68
+ // Skip other markers
69
+ if (marker === 0xd9 || marker === 0xda)
70
+ break; // EOI or SOS
71
+ if (marker >= 0xd0 && marker <= 0xd8)
72
+ continue; // RST markers have no length
73
+ if (marker === 0x01)
74
+ continue; // TEM has no length
75
+ const length = (data[pos] << 8) | data[pos + 1];
76
+ pos += length;
77
+ }
78
+ if (width === 0 || height === 0) {
79
+ throw new Error("Could not determine JPEG dimensions");
80
+ }
81
+ // For a pure JS implementation, we'd need to implement full JPEG decoding
82
+ // which is very complex. Instead, we'll use the browser/runtime's decoder.
83
+ const rgba = await this.decodeUsingRuntime(data, width, height);
84
+ return {
85
+ width,
86
+ height,
87
+ data: rgba,
88
+ metadata: Object.keys(metadata).length > 0 ? metadata : undefined,
89
+ };
90
+ }
91
+ async encode(imageData) {
92
+ const { width, height, data, metadata } = imageData;
93
+ // Try to use runtime encoding if available (better quality)
94
+ if (typeof OffscreenCanvas !== "undefined") {
95
+ try {
96
+ const canvas = new OffscreenCanvas(width, height);
97
+ const ctx = canvas.getContext("2d");
98
+ if (ctx) {
99
+ const imgData = ctx.createImageData(width, height);
100
+ const imgDataData = new Uint8ClampedArray(data);
101
+ imgData.data.set(imgDataData);
102
+ ctx.putImageData(imgData, 0, 0);
103
+ const blob = await canvas.convertToBlob({
104
+ type: "image/jpeg",
105
+ quality: 0.9,
106
+ });
107
+ const arrayBuffer = await blob.arrayBuffer();
108
+ const encoded = new Uint8Array(arrayBuffer);
109
+ // If we have metadata, we need to inject it into the JPEG
110
+ if (metadata && Object.keys(metadata).length > 0) {
111
+ return this.injectMetadata(encoded, metadata);
112
+ }
113
+ return encoded;
114
+ }
115
+ }
116
+ catch (_error) {
117
+ // Fall through to pure JS encoder
118
+ }
119
+ }
120
+ // Fallback to pure JavaScript encoder
121
+ const { JPEGEncoder } = await import("../utils/jpeg_encoder.js");
122
+ const dpiX = metadata?.dpiX ?? 72;
123
+ const dpiY = metadata?.dpiY ?? 72;
124
+ const encoder = new JPEGEncoder(85); // Quality 85
125
+ const encoded = encoder.encode(width, height, data, dpiX, dpiY);
126
+ // Add EXIF metadata if present
127
+ if (metadata && Object.keys(metadata).length > 0) {
128
+ return this.injectMetadata(encoded, metadata);
129
+ }
130
+ return encoded;
131
+ }
132
+ injectMetadata(encoded, metadata) {
133
+ // Find the position after SOI and APP0 to inject APP1 (EXIF)
134
+ let pos = 2; // After SOI (0xFF 0xD8)
135
+ // Skip APP0 if present
136
+ if (pos + 2 < encoded.length && encoded[pos] === 0xff &&
137
+ encoded[pos + 1] === 0xe0) {
138
+ const length = (encoded[pos + 2] << 8) | encoded[pos + 3];
139
+ pos += length + 2;
140
+ }
141
+ // Create EXIF data
142
+ const exifData = this.createEXIFData(metadata);
143
+ if (exifData.length === 0) {
144
+ return encoded;
145
+ }
146
+ // Create APP1 marker with EXIF data
147
+ // APP1 structure: FF E1 [length 2 bytes] "Exif\0\0" [exif data]
148
+ const app1Length = 2 + 6 + exifData.length; // length field + "Exif\0\0" + data
149
+ const app1 = new Uint8Array(2 + 2 + 6 + exifData.length); // marker + length + "Exif\0\0" + data
150
+ app1[0] = 0xff;
151
+ app1[1] = 0xe1; // APP1 marker
152
+ app1[2] = (app1Length >> 8) & 0xff;
153
+ app1[3] = app1Length & 0xff;
154
+ app1[4] = 0x45; // 'E'
155
+ app1[5] = 0x78; // 'x'
156
+ app1[6] = 0x69; // 'i'
157
+ app1[7] = 0x66; // 'f'
158
+ app1[8] = 0x00;
159
+ app1[9] = 0x00;
160
+ app1.set(exifData, 10);
161
+ // Inject APP1 into the JPEG
162
+ const result = new Uint8Array(encoded.length + app1.length);
163
+ result.set(encoded.slice(0, pos), 0);
164
+ result.set(app1, pos);
165
+ result.set(encoded.slice(pos), pos + app1.length);
166
+ return result;
167
+ }
168
+ async decodeUsingRuntime(data, _width, _height) {
169
+ // Try to use ImageDecoder API if available (Deno, modern browsers)
170
+ if (typeof ImageDecoder !== "undefined") {
171
+ try {
172
+ const decoder = new ImageDecoder({ data, type: "image/jpeg" });
173
+ const result = await decoder.decode();
174
+ const bitmap = result.image;
175
+ // Create a canvas to extract pixel data
176
+ const canvas = new OffscreenCanvas(bitmap.displayWidth, bitmap.displayHeight);
177
+ const ctx = canvas.getContext("2d");
178
+ if (!ctx)
179
+ throw new Error("Could not get canvas context");
180
+ ctx.drawImage(bitmap, 0, 0);
181
+ const imageData = ctx.getImageData(0, 0, canvas.width, canvas.height);
182
+ bitmap.close();
183
+ return new Uint8Array(imageData.data.buffer);
184
+ }
185
+ catch (error) {
186
+ // ImageDecoder API failed, fall through to pure JS decoder
187
+ console.warn("JPEG decoding with ImageDecoder failed, using pure JS decoder:", error);
188
+ }
189
+ }
190
+ // Fallback to pure JavaScript decoder
191
+ try {
192
+ const { JPEGDecoder } = await import("../utils/jpeg_decoder.js");
193
+ const decoder = new JPEGDecoder(data);
194
+ return decoder.decode();
195
+ }
196
+ catch (error) {
197
+ throw new Error(`JPEG decoding failed: ${error}`);
198
+ }
199
+ }
200
+ // Metadata parsing and creation methods
201
+ parseJFIF(data, metadata, width, height) {
202
+ // JFIF format: "JFIF\0" version units xDensity yDensity
203
+ if (data.length < 14)
204
+ return;
205
+ // Check for JFIF identifier
206
+ if (data[0] !== 0x4a || data[1] !== 0x46 || data[2] !== 0x49 ||
207
+ data[3] !== 0x46 || data[4] !== 0x00)
208
+ return;
209
+ const units = data[7]; // 0=no units, 1=dpi, 2=dpcm
210
+ const xDensity = (data[8] << 8) | data[9];
211
+ const yDensity = (data[10] << 8) | data[11];
212
+ if (units === 1 && xDensity > 0 && yDensity > 0) {
213
+ // Units are DPI
214
+ metadata.dpiX = xDensity;
215
+ metadata.dpiY = yDensity;
216
+ metadata.physicalWidth = width / xDensity;
217
+ metadata.physicalHeight = height / yDensity;
218
+ }
219
+ else if (units === 2 && xDensity > 0 && yDensity > 0) {
220
+ // Units are dots per cm, convert to DPI
221
+ metadata.dpiX = Math.round(xDensity * CM_PER_INCH);
222
+ metadata.dpiY = Math.round(yDensity * CM_PER_INCH);
223
+ metadata.physicalWidth = width / metadata.dpiX;
224
+ metadata.physicalHeight = height / metadata.dpiY;
225
+ }
226
+ }
227
+ parseEXIF(data, metadata) {
228
+ // Check for EXIF identifier
229
+ if (data.length < 6 || data[0] !== 0x45 || data[1] !== 0x78 ||
230
+ data[2] !== 0x69 || data[3] !== 0x66 || data[4] !== 0x00 ||
231
+ data[5] !== 0x00)
232
+ return;
233
+ // Skip "Exif\0\0" header
234
+ const exifData = data.slice(6);
235
+ if (exifData.length < 8)
236
+ return;
237
+ try {
238
+ const byteOrder = String.fromCharCode(exifData[0], exifData[1]);
239
+ const littleEndian = byteOrder === "II";
240
+ // Read IFD0 offset
241
+ const ifd0Offset = littleEndian
242
+ ? exifData[4] | (exifData[5] << 8) | (exifData[6] << 16) |
243
+ (exifData[7] << 24)
244
+ : (exifData[4] << 24) | (exifData[5] << 16) | (exifData[6] << 8) |
245
+ exifData[7];
246
+ if (ifd0Offset + 2 > exifData.length)
247
+ return;
248
+ // Read number of entries with bounds check
249
+ const numEntries = littleEndian
250
+ ? exifData[ifd0Offset] | (exifData[ifd0Offset + 1] << 8)
251
+ : (exifData[ifd0Offset] << 8) | exifData[ifd0Offset + 1];
252
+ // Parse entries
253
+ for (let i = 0; i < numEntries; i++) {
254
+ const entryOffset = ifd0Offset + 2 + i * 12;
255
+ if (entryOffset + 12 > exifData.length)
256
+ break;
257
+ const tag = littleEndian
258
+ ? exifData[entryOffset] | (exifData[entryOffset + 1] << 8)
259
+ : (exifData[entryOffset] << 8) | exifData[entryOffset + 1];
260
+ // DateTime tag (0x0132)
261
+ if (tag === 0x0132) {
262
+ const valueOffset = littleEndian
263
+ ? exifData[entryOffset + 8] | (exifData[entryOffset + 9] << 8) |
264
+ (exifData[entryOffset + 10] << 16) |
265
+ (exifData[entryOffset + 11] << 24)
266
+ : (exifData[entryOffset + 8] << 24) |
267
+ (exifData[entryOffset + 9] << 16) |
268
+ (exifData[entryOffset + 10] << 8) | exifData[entryOffset + 11];
269
+ if (valueOffset < exifData.length) {
270
+ const endIndex = exifData.indexOf(0, valueOffset);
271
+ if (endIndex > valueOffset) {
272
+ const dateStr = new TextDecoder().decode(exifData.slice(valueOffset, endIndex));
273
+ const match = dateStr.match(/^(\d{4}):(\d{2}):(\d{2}) (\d{2}):(\d{2}):(\d{2})$/);
274
+ if (match) {
275
+ metadata.creationDate = new Date(parseInt(match[1]), parseInt(match[2]) - 1, parseInt(match[3]), parseInt(match[4]), parseInt(match[5]), parseInt(match[6]));
276
+ }
277
+ }
278
+ }
279
+ }
280
+ // ImageDescription tag (0x010E)
281
+ if (tag === 0x010e) {
282
+ const valueOffset = littleEndian
283
+ ? exifData[entryOffset + 8] | (exifData[entryOffset + 9] << 8) |
284
+ (exifData[entryOffset + 10] << 16) |
285
+ (exifData[entryOffset + 11] << 24)
286
+ : (exifData[entryOffset + 8] << 24) |
287
+ (exifData[entryOffset + 9] << 16) |
288
+ (exifData[entryOffset + 10] << 8) | exifData[entryOffset + 11];
289
+ if (valueOffset < exifData.length) {
290
+ const endIndex = exifData.indexOf(0, valueOffset);
291
+ if (endIndex > valueOffset) {
292
+ metadata.description = new TextDecoder().decode(exifData.slice(valueOffset, endIndex));
293
+ }
294
+ }
295
+ }
296
+ // Artist tag (0x013B)
297
+ if (tag === 0x013b) {
298
+ const valueOffset = littleEndian
299
+ ? exifData[entryOffset + 8] | (exifData[entryOffset + 9] << 8) |
300
+ (exifData[entryOffset + 10] << 16) |
301
+ (exifData[entryOffset + 11] << 24)
302
+ : (exifData[entryOffset + 8] << 24) |
303
+ (exifData[entryOffset + 9] << 16) |
304
+ (exifData[entryOffset + 10] << 8) | exifData[entryOffset + 11];
305
+ if (valueOffset < exifData.length) {
306
+ const endIndex = exifData.indexOf(0, valueOffset);
307
+ if (endIndex > valueOffset) {
308
+ metadata.author = new TextDecoder().decode(exifData.slice(valueOffset, endIndex));
309
+ }
310
+ }
311
+ }
312
+ // Copyright tag (0x8298)
313
+ if (tag === 0x8298) {
314
+ const valueOffset = littleEndian
315
+ ? exifData[entryOffset + 8] | (exifData[entryOffset + 9] << 8) |
316
+ (exifData[entryOffset + 10] << 16) |
317
+ (exifData[entryOffset + 11] << 24)
318
+ : (exifData[entryOffset + 8] << 24) |
319
+ (exifData[entryOffset + 9] << 16) |
320
+ (exifData[entryOffset + 10] << 8) | exifData[entryOffset + 11];
321
+ if (valueOffset < exifData.length) {
322
+ const endIndex = exifData.indexOf(0, valueOffset);
323
+ if (endIndex > valueOffset) {
324
+ metadata.copyright = new TextDecoder().decode(exifData.slice(valueOffset, endIndex));
325
+ }
326
+ }
327
+ }
328
+ }
329
+ }
330
+ catch (_e) {
331
+ // Ignore EXIF parsing errors
332
+ }
333
+ }
334
+ createEXIFData(metadata) {
335
+ const entries = [];
336
+ // Add DateTime if available
337
+ if (metadata.creationDate) {
338
+ const date = metadata.creationDate;
339
+ 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`;
340
+ entries.push({
341
+ tag: 0x0132,
342
+ type: 2, // ASCII
343
+ value: new TextEncoder().encode(dateStr),
344
+ });
345
+ }
346
+ // Add ImageDescription
347
+ if (metadata.description) {
348
+ entries.push({
349
+ tag: 0x010e,
350
+ type: 2,
351
+ value: new TextEncoder().encode(metadata.description + "\0"),
352
+ });
353
+ }
354
+ // Add Artist
355
+ if (metadata.author) {
356
+ entries.push({
357
+ tag: 0x013b,
358
+ type: 2,
359
+ value: new TextEncoder().encode(metadata.author + "\0"),
360
+ });
361
+ }
362
+ // Add Copyright
363
+ if (metadata.copyright) {
364
+ entries.push({
365
+ tag: 0x8298,
366
+ type: 2,
367
+ value: new TextEncoder().encode(metadata.copyright + "\0"),
368
+ });
369
+ }
370
+ if (entries.length === 0)
371
+ return [];
372
+ // Build EXIF structure
373
+ const exif = [];
374
+ // Byte order marker (little endian)
375
+ exif.push(0x49, 0x49); // "II"
376
+ exif.push(0x2a, 0x00); // 42
377
+ // Offset to IFD0 (8 bytes from start)
378
+ exif.push(0x08, 0x00, 0x00, 0x00);
379
+ // Number of entries
380
+ exif.push(entries.length & 0xff, (entries.length >> 8) & 0xff);
381
+ // Calculate data offset
382
+ let dataOffset = 8 + 2 + entries.length * 12 + 4;
383
+ for (const entry of entries) {
384
+ // Tag
385
+ exif.push(entry.tag & 0xff, (entry.tag >> 8) & 0xff);
386
+ // Type
387
+ exif.push(entry.type & 0xff, (entry.type >> 8) & 0xff);
388
+ // Count
389
+ const count = entry.value.length;
390
+ exif.push(count & 0xff, (count >> 8) & 0xff, (count >> 16) & 0xff, (count >> 24) & 0xff);
391
+ // Value/Offset
392
+ if (entry.value.length <= 4) {
393
+ for (let i = 0; i < 4; i++) {
394
+ exif.push(i < entry.value.length ? entry.value[i] : 0);
395
+ }
396
+ }
397
+ else {
398
+ exif.push(dataOffset & 0xff, (dataOffset >> 8) & 0xff, (dataOffset >> 16) & 0xff, (dataOffset >> 24) & 0xff);
399
+ dataOffset += entry.value.length;
400
+ }
401
+ }
402
+ // Next IFD offset (0 = no more IFDs)
403
+ exif.push(0x00, 0x00, 0x00, 0x00);
404
+ // Append data for entries that didn't fit in value field
405
+ for (const entry of entries) {
406
+ if (entry.value.length > 4) {
407
+ for (const byte of entry.value) {
408
+ exif.push(byte);
409
+ }
410
+ }
411
+ }
412
+ return exif;
413
+ }
414
+ }
@@ -0,0 +1,33 @@
1
+ import type { ImageData, ImageFormat } from "../types.js";
2
+ /**
3
+ * PNG format handler
4
+ * Implements a pure JavaScript PNG decoder and encoder
5
+ */
6
+ export declare class PNGFormat implements ImageFormat {
7
+ readonly name = "png";
8
+ readonly mimeType = "image/png";
9
+ canDecode(data: Uint8Array): boolean;
10
+ decode(data: Uint8Array): Promise<ImageData>;
11
+ encode(imageData: ImageData): Promise<Uint8Array>;
12
+ private readUint32;
13
+ private writeUint32;
14
+ private concatenateChunks;
15
+ private inflate;
16
+ private deflate;
17
+ private unfilterAndConvert;
18
+ private unfilterScanline;
19
+ private paethPredictor;
20
+ private filterData;
21
+ private getBytesPerPixel;
22
+ private getBitsPerPixel;
23
+ private createChunk;
24
+ private crc32;
25
+ private parsePhysChunk;
26
+ private parseTextChunk;
27
+ private parseITxtChunk;
28
+ private parseExifChunk;
29
+ private createPhysChunk;
30
+ private createTextChunk;
31
+ private createExifChunk;
32
+ }
33
+ //# sourceMappingURL=png.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"png.d.ts","sourceRoot":"","sources":["../../../src/src/formats/png.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,SAAS,EAAE,WAAW,EAAiB,MAAM,aAAa,CAAC;AAKzE;;;GAGG;AACH,qBAAa,SAAU,YAAW,WAAW;IAC3C,QAAQ,CAAC,IAAI,SAAS;IACtB,QAAQ,CAAC,QAAQ,eAAe;IAEhC,SAAS,CAAC,IAAI,EAAE,UAAU,GAAG,OAAO;IAS9B,MAAM,CAAC,IAAI,EAAE,UAAU,GAAG,OAAO,CAAC,SAAS,CAAC;IA+E5C,MAAM,CAAC,SAAS,EAAE,SAAS,GAAG,OAAO,CAAC,UAAU,CAAC;IAuGvD,OAAO,CAAC,UAAU;IAKlB,OAAO,CAAC,WAAW;IAOnB,OAAO,CAAC,iBAAiB;YAgBX,OAAO;YAQP,OAAO;IAQrB,OAAO,CAAC,kBAAkB;IA0D1B,OAAO,CAAC,gBAAgB;IAiCxB,OAAO,CAAC,cAAc;IAWtB,OAAO,CAAC,UAAU;IAmBlB,OAAO,CAAC,gBAAgB;IAKxB,OAAO,CAAC,eAAe;IAiBvB,OAAO,CAAC,WAAW;IAanB,OAAO,CAAC,KAAK;IAab,OAAO,CAAC,cAAc;IAqBtB,OAAO,CAAC,cAAc;IA6BtB,OAAO,CAAC,cAAc;IA4CtB,OAAO,CAAC,cAAc;IAmEtB,OAAO,CAAC,eAAe;IAkBvB,OAAO,CAAC,eAAe;IAYvB,OAAO,CAAC,eAAe;CAkFxB"}