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,22 @@
1
+ import type { ImageData, ImageFormat, WebPEncodeOptions } from "../types.js";
2
+ /**
3
+ * WebP format handler
4
+ * Implements a basic WebP decoder and encoder
5
+ */
6
+ export declare class WebPFormat implements ImageFormat {
7
+ readonly name = "webp";
8
+ readonly mimeType = "image/webp";
9
+ canDecode(data: Uint8Array): boolean;
10
+ decode(data: Uint8Array): Promise<ImageData>;
11
+ encode(imageData: ImageData, options?: WebPEncodeOptions): Promise<Uint8Array>;
12
+ private readUint32LE;
13
+ private readUint24LE;
14
+ private decodeUsingRuntime;
15
+ private parseEXIF;
16
+ private parseXMP;
17
+ private injectMetadata;
18
+ private createEXIFChunk;
19
+ private createXMPChunk;
20
+ private escapeXML;
21
+ }
22
+ //# sourceMappingURL=webp.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"webp.d.ts","sourceRoot":"","sources":["../../../src/src/formats/webp.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EACV,SAAS,EACT,WAAW,EAEX,iBAAiB,EAClB,MAAM,aAAa,CAAC;AAKrB;;;GAGG;AACH,qBAAa,UAAW,YAAW,WAAW;IAC5C,QAAQ,CAAC,IAAI,UAAU;IACvB,QAAQ,CAAC,QAAQ,gBAAgB;IAEjC,SAAS,CAAC,IAAI,EAAE,UAAU,GAAG,OAAO;IAS9B,MAAM,CAAC,IAAI,EAAE,UAAU,GAAG,OAAO,CAAC,SAAS,CAAC;IAmF5C,MAAM,CACV,SAAS,EAAE,SAAS,EACpB,OAAO,CAAC,EAAE,iBAAiB,GAC1B,OAAO,CAAC,UAAU,CAAC;IAsDtB,OAAO,CAAC,YAAY;IAKpB,OAAO,CAAC,YAAY;YAIN,kBAAkB;IAiDhC,OAAO,CAAC,SAAS;IA+DjB,OAAO,CAAC,QAAQ;IA6BhB,OAAO,CAAC,cAAc;IAuFtB,OAAO,CAAC,eAAe;IAyDvB,OAAO,CAAC,cAAc;IAoDtB,OAAO,CAAC,SAAS;CAQlB"}
@@ -0,0 +1,403 @@
1
+ // Default quality for WebP encoding when not specified
2
+ const DEFAULT_WEBP_QUALITY = 90;
3
+ /**
4
+ * WebP format handler
5
+ * Implements a basic WebP decoder and encoder
6
+ */
7
+ export class WebPFormat {
8
+ constructor() {
9
+ Object.defineProperty(this, "name", {
10
+ enumerable: true,
11
+ configurable: true,
12
+ writable: true,
13
+ value: "webp"
14
+ });
15
+ Object.defineProperty(this, "mimeType", {
16
+ enumerable: true,
17
+ configurable: true,
18
+ writable: true,
19
+ value: "image/webp"
20
+ });
21
+ }
22
+ canDecode(data) {
23
+ // WebP signature: "RIFF" + size + "WEBP"
24
+ return data.length >= 12 &&
25
+ data[0] === 0x52 && data[1] === 0x49 && // "RI"
26
+ data[2] === 0x46 && data[3] === 0x46 && // "FF"
27
+ data[8] === 0x57 && data[9] === 0x45 && // "WE"
28
+ data[10] === 0x42 && data[11] === 0x50; // "BP"
29
+ }
30
+ async decode(data) {
31
+ if (!this.canDecode(data)) {
32
+ throw new Error("Invalid WebP signature");
33
+ }
34
+ // Parse WebP structure
35
+ let pos = 12; // Skip RIFF header
36
+ let width = 0;
37
+ let height = 0;
38
+ const metadata = {};
39
+ // Read all chunks to extract metadata
40
+ while (pos + 8 <= data.length) {
41
+ const chunkType = String.fromCharCode(data[pos], data[pos + 1], data[pos + 2], data[pos + 3]);
42
+ const chunkSize = this.readUint32LE(data, pos + 4);
43
+ pos += 8;
44
+ // Stop if we've gone past the end
45
+ if (pos + chunkSize > data.length)
46
+ break;
47
+ const chunkData = data.slice(pos, pos + chunkSize);
48
+ if (chunkType === "VP8 ") {
49
+ // Lossy format - extract dimensions
50
+ if (chunkData.length >= 10) {
51
+ const frameTag = chunkData[0] | (chunkData[1] << 8) |
52
+ (chunkData[2] << 16);
53
+ const keyFrame = (frameTag & 1) === 0;
54
+ if (keyFrame && chunkData[3] === 0x9d && chunkData[4] === 0x01 &&
55
+ chunkData[5] === 0x2a) {
56
+ width = chunkData[6] | ((chunkData[7] & 0x3f) << 8);
57
+ height = chunkData[8] | ((chunkData[9] & 0x3f) << 8);
58
+ }
59
+ }
60
+ }
61
+ else if (chunkType === "VP8L") {
62
+ // Lossless format - extract dimensions
63
+ if (chunkData.length >= 5 && chunkData[0] === 0x2f) {
64
+ const bits = this.readUint32LE(chunkData, 1);
65
+ width = (bits & 0x3fff) + 1;
66
+ height = ((bits >> 14) & 0x3fff) + 1;
67
+ }
68
+ }
69
+ else if (chunkType === "VP8X") {
70
+ // Extended format - extract dimensions
71
+ if (chunkData.length >= 10) {
72
+ width = this.readUint24LE(chunkData, 4) + 1;
73
+ height = this.readUint24LE(chunkData, 7) + 1;
74
+ }
75
+ }
76
+ else if (chunkType === "EXIF") {
77
+ // EXIF metadata chunk
78
+ this.parseEXIF(chunkData, metadata);
79
+ }
80
+ else if (chunkType === "XMP ") {
81
+ // XMP metadata chunk
82
+ this.parseXMP(chunkData, metadata);
83
+ }
84
+ pos += chunkSize;
85
+ // Chunks are padded to even length
86
+ if (chunkSize % 2 === 1)
87
+ pos++;
88
+ }
89
+ if (width === 0 || height === 0) {
90
+ throw new Error("Could not determine WebP dimensions");
91
+ }
92
+ // For a pure JS implementation, we'd need to implement full WebP decoding
93
+ // which is very complex. Instead, we'll use the browser/runtime's decoder.
94
+ const rgba = await this.decodeUsingRuntime(data, width, height);
95
+ return {
96
+ width,
97
+ height,
98
+ data: rgba,
99
+ metadata: Object.keys(metadata).length > 0 ? metadata : undefined,
100
+ };
101
+ }
102
+ async encode(imageData, options) {
103
+ const { width, height, data, metadata } = imageData;
104
+ const quality = options?.quality ?? DEFAULT_WEBP_QUALITY;
105
+ const forceLossless = options?.lossless ?? false;
106
+ // Determine if we should use lossless encoding
107
+ // Use lossless if: quality is 100, or lossless flag is set
108
+ const useLossless = quality === 100 || forceLossless;
109
+ // Try to use runtime encoding if available (better quality and compression)
110
+ if (typeof OffscreenCanvas !== "undefined") {
111
+ try {
112
+ const canvas = new OffscreenCanvas(width, height);
113
+ const ctx = canvas.getContext("2d");
114
+ if (ctx) {
115
+ const imgData = ctx.createImageData(width, height);
116
+ const imgDataData = new Uint8ClampedArray(data);
117
+ imgData.data.set(imgDataData);
118
+ ctx.putImageData(imgData, 0, 0);
119
+ const blob = await canvas.convertToBlob({
120
+ type: "image/webp",
121
+ quality: quality / 100, // Convert 1-100 to 0-1
122
+ });
123
+ const arrayBuffer = await blob.arrayBuffer();
124
+ const encoded = new Uint8Array(arrayBuffer);
125
+ // Inject metadata if present
126
+ if (metadata && Object.keys(metadata).length > 0) {
127
+ const injected = this.injectMetadata(encoded, metadata);
128
+ return injected;
129
+ }
130
+ return encoded;
131
+ }
132
+ }
133
+ catch (_error) {
134
+ // Fall through to pure JS encoder
135
+ }
136
+ }
137
+ // Fallback to pure JavaScript encoder
138
+ // VP8L (lossless) encoder with optional quality-based quantization
139
+ const { WebPEncoder } = await import("../utils/webp_encoder.js");
140
+ const encoder = new WebPEncoder(width, height, data);
141
+ const encoded = encoder.encode(useLossless ? 100 : quality);
142
+ // Inject metadata if present
143
+ if (metadata && Object.keys(metadata).length > 0) {
144
+ return this.injectMetadata(encoded, metadata);
145
+ }
146
+ return encoded;
147
+ }
148
+ readUint32LE(data, offset) {
149
+ return data[offset] | (data[offset + 1] << 8) |
150
+ (data[offset + 2] << 16) | (data[offset + 3] << 24);
151
+ }
152
+ readUint24LE(data, offset) {
153
+ return data[offset] | (data[offset + 1] << 8) | (data[offset + 2] << 16);
154
+ }
155
+ async decodeUsingRuntime(data, _width, _height) {
156
+ // Try to use ImageDecoder API if available (Deno, modern browsers)
157
+ if (typeof ImageDecoder !== "undefined") {
158
+ try {
159
+ const decoder = new ImageDecoder({ data, type: "image/webp" });
160
+ const result = await decoder.decode();
161
+ const bitmap = result.image;
162
+ // Create a canvas to extract pixel data
163
+ const canvas = new OffscreenCanvas(bitmap.displayWidth, bitmap.displayHeight);
164
+ const ctx = canvas.getContext("2d");
165
+ if (!ctx)
166
+ throw new Error("Could not get canvas context");
167
+ ctx.drawImage(bitmap, 0, 0);
168
+ const imageData = ctx.getImageData(0, 0, canvas.width, canvas.height);
169
+ bitmap.close();
170
+ return new Uint8Array(imageData.data.buffer);
171
+ }
172
+ catch (error) {
173
+ // ImageDecoder API failed, fall through to pure JS decoder
174
+ console.warn("WebP decoding with ImageDecoder failed, using pure JS decoder:", error);
175
+ }
176
+ }
177
+ // Fallback to pure JavaScript decoder (VP8L lossless only)
178
+ try {
179
+ const { WebPDecoder } = await import("../utils/webp_decoder.js");
180
+ const decoder = new WebPDecoder(data);
181
+ const result = decoder.decode();
182
+ return result.data;
183
+ }
184
+ catch (error) {
185
+ throw new Error(`WebP decoding failed: ${error}`);
186
+ }
187
+ }
188
+ // Metadata parsing and injection methods
189
+ parseEXIF(data, metadata) {
190
+ // EXIF data parsing (similar to JPEG/PNG EXIF parsing)
191
+ if (data.length < 8)
192
+ return;
193
+ try {
194
+ const byteOrder = String.fromCharCode(data[0], data[1]);
195
+ const littleEndian = byteOrder === "II";
196
+ const ifd0Offset = littleEndian
197
+ ? data[4] | (data[5] << 8) | (data[6] << 16) | (data[7] << 24)
198
+ : (data[4] << 24) | (data[5] << 16) | (data[6] << 8) | data[7];
199
+ if (ifd0Offset + 2 > data.length)
200
+ return;
201
+ const numEntries = littleEndian
202
+ ? data[ifd0Offset] | (data[ifd0Offset + 1] << 8)
203
+ : (data[ifd0Offset] << 8) | data[ifd0Offset + 1];
204
+ // Parse basic EXIF tags (simplified version)
205
+ for (let i = 0; i < numEntries && i < 50; i++) {
206
+ const entryOffset = ifd0Offset + 2 + i * 12;
207
+ if (entryOffset + 12 > data.length)
208
+ break;
209
+ const tag = littleEndian
210
+ ? data[entryOffset] | (data[entryOffset + 1] << 8)
211
+ : (data[entryOffset] << 8) | data[entryOffset + 1];
212
+ // DateTime tag
213
+ if (tag === 0x0132) {
214
+ const valueOffset = littleEndian
215
+ ? data[entryOffset + 8] | (data[entryOffset + 9] << 8) |
216
+ (data[entryOffset + 10] << 16) | (data[entryOffset + 11] << 24)
217
+ : (data[entryOffset + 8] << 24) | (data[entryOffset + 9] << 16) |
218
+ (data[entryOffset + 10] << 8) | data[entryOffset + 11];
219
+ if (valueOffset < data.length) {
220
+ const endIndex = data.indexOf(0, valueOffset);
221
+ if (endIndex > valueOffset) {
222
+ const dateStr = new TextDecoder().decode(data.slice(valueOffset, endIndex));
223
+ const match = dateStr.match(/^(\d{4}):(\d{2}):(\d{2}) (\d{2}):(\d{2}):(\d{2})$/);
224
+ if (match) {
225
+ metadata.creationDate = new Date(parseInt(match[1]), parseInt(match[2]) - 1, parseInt(match[3]), parseInt(match[4]), parseInt(match[5]), parseInt(match[6]));
226
+ }
227
+ }
228
+ }
229
+ }
230
+ }
231
+ }
232
+ catch (_e) {
233
+ // Ignore EXIF parsing errors
234
+ }
235
+ }
236
+ parseXMP(data, metadata) {
237
+ // XMP is XML-based metadata - simple parsing for common fields
238
+ try {
239
+ const xmpStr = new TextDecoder().decode(data);
240
+ // Extract title
241
+ const titleMatch = xmpStr.match(/<dc:title[^>]*>([^<]+)<\/dc:title>/);
242
+ if (titleMatch)
243
+ metadata.title = titleMatch[1].trim();
244
+ // Extract description
245
+ const descMatch = xmpStr.match(/<dc:description[^>]*>([^<]+)<\/dc:description>/);
246
+ if (descMatch)
247
+ metadata.description = descMatch[1].trim();
248
+ // Extract creator/author
249
+ const creatorMatch = xmpStr.match(/<dc:creator[^>]*>([^<]+)<\/dc:creator>/);
250
+ if (creatorMatch)
251
+ metadata.author = creatorMatch[1].trim();
252
+ // Extract rights/copyright
253
+ const rightsMatch = xmpStr.match(/<dc:rights[^>]*>([^<]+)<\/dc:rights>/);
254
+ if (rightsMatch)
255
+ metadata.copyright = rightsMatch[1].trim();
256
+ }
257
+ catch (_e) {
258
+ // Ignore XMP parsing errors
259
+ }
260
+ }
261
+ injectMetadata(webpData, metadata) {
262
+ // WebP files are RIFF containers: RIFF + size + WEBP + chunks
263
+ // We need to inject EXIF and/or XMP chunks before the image data
264
+ const chunks = [];
265
+ // Copy RIFF header (12 bytes)
266
+ chunks.push(webpData.slice(0, 12));
267
+ // Create metadata chunks
268
+ const metadataChunks = [];
269
+ // Create EXIF chunk if we have date or other EXIF data
270
+ if (metadata.creationDate) {
271
+ const exifData = this.createEXIFChunk(metadata);
272
+ if (exifData) {
273
+ metadataChunks.push(exifData);
274
+ }
275
+ }
276
+ // Create XMP chunk if we have text metadata
277
+ if (metadata.title || metadata.description || metadata.author ||
278
+ metadata.copyright) {
279
+ const xmpData = this.createXMPChunk(metadata);
280
+ if (xmpData) {
281
+ metadataChunks.push(xmpData);
282
+ }
283
+ }
284
+ // Copy original chunks (skip header)
285
+ let pos = 12;
286
+ while (pos + 8 <= webpData.length) {
287
+ const chunkType = String.fromCharCode(webpData[pos], webpData[pos + 1], webpData[pos + 2], webpData[pos + 3]);
288
+ const chunkSize = this.readUint32LE(webpData, pos + 4);
289
+ // Don't copy existing EXIF/XMP chunks (we'll add new ones)
290
+ if (chunkType !== "EXIF" && chunkType !== "XMP ") {
291
+ const chunkEnd = pos + 8 + chunkSize + (chunkSize % 2);
292
+ chunks.push(webpData.slice(pos, chunkEnd));
293
+ }
294
+ pos += 8 + chunkSize;
295
+ if (chunkSize % 2 === 1)
296
+ pos++; // Padding
297
+ }
298
+ // Insert metadata chunks after VP8/VP8L/VP8X chunk
299
+ const result = [chunks[0]]; // RIFF header
300
+ if (chunks.length > 1) {
301
+ result.push(chunks[1]); // First chunk (VP8/VP8L/VP8X)
302
+ }
303
+ result.push(...metadataChunks);
304
+ for (let i = 2; i < chunks.length; i++) {
305
+ result.push(chunks[i]);
306
+ }
307
+ // Recalculate total size
308
+ const totalSize = result.reduce((sum, chunk) => sum + chunk.length, 0) - 8;
309
+ const finalData = new Uint8Array(totalSize + 8);
310
+ // Write RIFF header with updated size
311
+ finalData.set(new TextEncoder().encode("RIFF"), 0);
312
+ finalData[4] = totalSize & 0xff;
313
+ finalData[5] = (totalSize >> 8) & 0xff;
314
+ finalData[6] = (totalSize >> 16) & 0xff;
315
+ finalData[7] = (totalSize >> 24) & 0xff;
316
+ finalData.set(new TextEncoder().encode("WEBP"), 8);
317
+ // Copy all chunks
318
+ let offset = 12;
319
+ for (let i = 1; i < result.length; i++) {
320
+ finalData.set(result[i], offset);
321
+ offset += result[i].length;
322
+ }
323
+ return finalData;
324
+ }
325
+ createEXIFChunk(metadata) {
326
+ if (!metadata.creationDate)
327
+ return null;
328
+ const exifData = [];
329
+ // Byte order marker (little endian)
330
+ exifData.push(0x49, 0x49); // "II"
331
+ exifData.push(0x2a, 0x00); // 42
332
+ // IFD0 offset
333
+ exifData.push(0x08, 0x00, 0x00, 0x00);
334
+ // Number of entries
335
+ exifData.push(0x01, 0x00);
336
+ // DateTime entry
337
+ const date = metadata.creationDate;
338
+ 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`;
339
+ const dateBytes = new TextEncoder().encode(dateStr);
340
+ // Tag 0x0132, Type 2 (ASCII), Count, Offset
341
+ exifData.push(0x32, 0x01, 0x02, 0x00);
342
+ exifData.push(dateBytes.length & 0xff, (dateBytes.length >> 8) & 0xff, (dateBytes.length >> 16) & 0xff, (dateBytes.length >> 24) & 0xff);
343
+ exifData.push(0x12, 0x00, 0x00, 0x00); // Offset to data
344
+ // Next IFD
345
+ exifData.push(0x00, 0x00, 0x00, 0x00);
346
+ // Date string data
347
+ for (const byte of dateBytes) {
348
+ exifData.push(byte);
349
+ }
350
+ // Create chunk header
351
+ const chunkData = new Uint8Array(exifData);
352
+ const chunk = new Uint8Array(8 + chunkData.length);
353
+ chunk.set(new TextEncoder().encode("EXIF"), 0);
354
+ chunk[4] = chunkData.length & 0xff;
355
+ chunk[5] = (chunkData.length >> 8) & 0xff;
356
+ chunk[6] = (chunkData.length >> 16) & 0xff;
357
+ chunk[7] = (chunkData.length >> 24) & 0xff;
358
+ chunk.set(chunkData, 8);
359
+ return chunk;
360
+ }
361
+ createXMPChunk(metadata) {
362
+ const xmpParts = [];
363
+ xmpParts.push('<?xpacket begin="" id="W5M0MpCehiHzreSzNTczkc9d"?>');
364
+ xmpParts.push('<x:xmpmeta xmlns:x="adobe:ns:meta/">');
365
+ xmpParts.push('<rdf:RDF xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#">');
366
+ xmpParts.push('<rdf:Description xmlns:dc="http://purl.org/dc/elements/1.1/">');
367
+ if (metadata.title) {
368
+ xmpParts.push(`<dc:title>${this.escapeXML(metadata.title)}</dc:title>`);
369
+ }
370
+ if (metadata.description) {
371
+ xmpParts.push(`<dc:description>${this.escapeXML(metadata.description)}</dc:description>`);
372
+ }
373
+ if (metadata.author) {
374
+ xmpParts.push(`<dc:creator>${this.escapeXML(metadata.author)}</dc:creator>`);
375
+ }
376
+ if (metadata.copyright) {
377
+ xmpParts.push(`<dc:rights>${this.escapeXML(metadata.copyright)}</dc:rights>`);
378
+ }
379
+ xmpParts.push("</rdf:Description>");
380
+ xmpParts.push("</rdf:RDF>");
381
+ xmpParts.push("</x:xmpmeta>");
382
+ xmpParts.push('<?xpacket end="w"?>');
383
+ const xmpStr = xmpParts.join("\n");
384
+ const xmpData = new TextEncoder().encode(xmpStr);
385
+ // Create chunk
386
+ const chunk = new Uint8Array(8 + xmpData.length);
387
+ chunk.set(new TextEncoder().encode("XMP "), 0);
388
+ chunk[4] = xmpData.length & 0xff;
389
+ chunk[5] = (xmpData.length >> 8) & 0xff;
390
+ chunk[6] = (xmpData.length >> 16) & 0xff;
391
+ chunk[7] = (xmpData.length >> 24) & 0xff;
392
+ chunk.set(xmpData, 8);
393
+ return chunk;
394
+ }
395
+ escapeXML(str) {
396
+ return str
397
+ .replace(/&/g, "&amp;")
398
+ .replace(/</g, "&lt;")
399
+ .replace(/>/g, "&gt;")
400
+ .replace(/"/g, "&quot;")
401
+ .replace(/'/g, "&apos;");
402
+ }
403
+ }
@@ -0,0 +1,124 @@
1
+ import type { ImageFormat, ImageMetadata, MultiFrameImageData, ResizeOptions } from "./types.js";
2
+ /**
3
+ * Main Image class for reading, manipulating, and saving images
4
+ */
5
+ export declare class Image {
6
+ private imageData;
7
+ private static formats;
8
+ /**
9
+ * Get the current image width
10
+ */
11
+ get width(): number;
12
+ /**
13
+ * Get the current image height
14
+ */
15
+ get height(): number;
16
+ /**
17
+ * Get the current image data
18
+ */
19
+ get data(): Uint8Array;
20
+ /**
21
+ * Get the current image metadata
22
+ */
23
+ get metadata(): ImageMetadata | undefined;
24
+ /**
25
+ * Set or update image metadata
26
+ * @param metadata Metadata to set or merge
27
+ * @param merge If true, merges with existing metadata. If false, replaces it. Default: true
28
+ */
29
+ setMetadata(metadata: ImageMetadata, merge?: boolean): this;
30
+ /**
31
+ * Get a specific metadata field
32
+ * @param key The metadata field to retrieve
33
+ * @returns The metadata value or undefined
34
+ */
35
+ getMetadataField<K extends keyof ImageMetadata>(key: K): ImageMetadata[K] | undefined;
36
+ /**
37
+ * Get position (latitude, longitude) from metadata
38
+ * @returns Object with latitude and longitude, or undefined if not available
39
+ */
40
+ getPosition(): {
41
+ latitude: number;
42
+ longitude: number;
43
+ } | undefined;
44
+ /**
45
+ * Set position (latitude, longitude) in metadata
46
+ * @param latitude GPS latitude
47
+ * @param longitude GPS longitude
48
+ */
49
+ setPosition(latitude: number, longitude: number): this;
50
+ /**
51
+ * Get physical dimensions from metadata
52
+ * @returns Object with DPI and physical dimensions, or undefined if not available
53
+ */
54
+ getDimensions(): {
55
+ dpiX?: number;
56
+ dpiY?: number;
57
+ physicalWidth?: number;
58
+ physicalHeight?: number;
59
+ } | undefined;
60
+ /**
61
+ * Set physical dimensions in metadata
62
+ * @param dpiX Dots per inch (horizontal)
63
+ * @param dpiY Dots per inch (vertical), defaults to dpiX if not provided
64
+ */
65
+ setDPI(dpiX: number, dpiY?: number): this;
66
+ /**
67
+ * Register a custom image format
68
+ * @param format Custom format implementation
69
+ */
70
+ static registerFormat(format: ImageFormat): void;
71
+ /**
72
+ * Get all registered formats
73
+ */
74
+ static getFormats(): readonly ImageFormat[];
75
+ /**
76
+ * Read an image from bytes
77
+ * @param data Raw image data
78
+ * @param format Optional format hint (e.g., "png", "jpeg", "webp")
79
+ * @returns Image instance
80
+ */
81
+ static read(data: Uint8Array, format?: string): Promise<Image>;
82
+ /**
83
+ * Read all frames from a multi-frame image (GIF animation, multi-page TIFF)
84
+ * @param data Raw image data
85
+ * @param format Optional format hint (e.g., "gif", "tiff")
86
+ * @returns MultiFrameImageData with all frames
87
+ */
88
+ static readFrames(data: Uint8Array, format?: string): Promise<MultiFrameImageData>;
89
+ /**
90
+ * Save multi-frame image data to bytes in the specified format
91
+ * @param format Format name (e.g., "gif", "tiff")
92
+ * @param imageData Multi-frame image data to save
93
+ * @param options Optional format-specific encoding options
94
+ * @returns Encoded image bytes
95
+ */
96
+ static saveFrames(format: string, imageData: MultiFrameImageData, options?: unknown): Promise<Uint8Array>;
97
+ /**
98
+ * Create an image from raw RGBA data
99
+ * @param width Image width
100
+ * @param height Image height
101
+ * @param data Raw RGBA pixel data (4 bytes per pixel)
102
+ * @returns Image instance
103
+ */
104
+ static fromRGBA(width: number, height: number, data: Uint8Array): Image;
105
+ /**
106
+ * Resize the image
107
+ * @param options Resize options
108
+ * @returns This image instance for chaining
109
+ */
110
+ resize(options: ResizeOptions): this;
111
+ /**
112
+ * Save the image to bytes in the specified format
113
+ * @param format Format name (e.g., "png", "jpeg", "webp", "ascii")
114
+ * @param options Optional format-specific encoding options
115
+ * @returns Encoded image bytes
116
+ */
117
+ save(format: string, options?: unknown): Promise<Uint8Array>;
118
+ /**
119
+ * Clone this image
120
+ * @returns New image instance with copied data and metadata
121
+ */
122
+ clone(): Image;
123
+ }
124
+ //# sourceMappingURL=image.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"image.d.ts","sourceRoot":"","sources":["../../src/src/image.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAEV,WAAW,EACX,aAAa,EACb,mBAAmB,EACnB,aAAa,EACd,MAAM,YAAY,CAAC;AAWpB;;GAEG;AACH,qBAAa,KAAK;IAChB,OAAO,CAAC,SAAS,CAA0B;IAC3C,OAAO,CAAC,MAAM,CAAC,OAAO,CASpB;IAEF;;OAEG;IACH,IAAI,KAAK,IAAI,MAAM,CAGlB;IAED;;OAEG;IACH,IAAI,MAAM,IAAI,MAAM,CAGnB;IAED;;OAEG;IACH,IAAI,IAAI,IAAI,UAAU,CAGrB;IAED;;OAEG;IACH,IAAI,QAAQ,IAAI,aAAa,GAAG,SAAS,CAGxC;IAED;;;;OAIG;IACH,WAAW,CAAC,QAAQ,EAAE,aAAa,EAAE,KAAK,UAAO,GAAG,IAAI;IAmBxD;;;;OAIG;IACH,gBAAgB,CAAC,CAAC,SAAS,MAAM,aAAa,EAC5C,GAAG,EAAE,CAAC,GACL,aAAa,CAAC,CAAC,CAAC,GAAG,SAAS;IAK/B;;;OAGG;IACH,WAAW,IAAI;QAAE,QAAQ,EAAE,MAAM,CAAC;QAAC,SAAS,EAAE,MAAM,CAAA;KAAE,GAAG,SAAS;IAWlE;;;;OAIG;IACH,WAAW,CAAC,QAAQ,EAAE,MAAM,EAAE,SAAS,EAAE,MAAM,GAAG,IAAI;IAItD;;;OAGG;IACH,aAAa,IAAI;QACf,IAAI,CAAC,EAAE,MAAM,CAAC;QACd,IAAI,CAAC,EAAE,MAAM,CAAC;QACd,aAAa,CAAC,EAAE,MAAM,CAAC;QACvB,cAAc,CAAC,EAAE,MAAM,CAAC;KACzB,GAAG,SAAS;IAgBb;;;;OAIG;IACH,MAAM,CAAC,IAAI,EAAE,MAAM,EAAE,IAAI,CAAC,EAAE,MAAM,GAAG,IAAI;IAezC;;;OAGG;IACH,MAAM,CAAC,cAAc,CAAC,MAAM,EAAE,WAAW,GAAG,IAAI;IAIhD;;OAEG;IACH,MAAM,CAAC,UAAU,IAAI,SAAS,WAAW,EAAE;IAI3C;;;;;OAKG;WACU,IAAI,CAAC,IAAI,EAAE,UAAU,EAAE,MAAM,CAAC,EAAE,MAAM,GAAG,OAAO,CAAC,KAAK,CAAC;IAuBpE;;;;;OAKG;WACU,UAAU,CACrB,IAAI,EAAE,UAAU,EAChB,MAAM,CAAC,EAAE,MAAM,GACd,OAAO,CAAC,mBAAmB,CAAC;IAqB/B;;;;;;OAMG;WACU,UAAU,CACrB,MAAM,EAAE,MAAM,EACd,SAAS,EAAE,mBAAmB,EAC9B,OAAO,CAAC,EAAE,OAAO,GAChB,OAAO,CAAC,UAAU,CAAC;IAgBtB;;;;;;OAMG;IACH,MAAM,CAAC,QAAQ,CAAC,KAAK,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,EAAE,IAAI,EAAE,UAAU,GAAG,KAAK;IAcvE;;;;OAIG;IACH,MAAM,CAAC,OAAO,EAAE,aAAa,GAAG,IAAI;IAqCpC;;;;;OAKG;IACG,IAAI,CAAC,MAAM,EAAE,MAAM,EAAE,OAAO,CAAC,EAAE,OAAO,GAAG,OAAO,CAAC,UAAU,CAAC;IAWlE;;;OAGG;IACH,KAAK,IAAI,KAAK;CAmBf"}