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,440 @@
1
+ "use strict";
2
+ var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
3
+ if (k2 === undefined) k2 = k;
4
+ var desc = Object.getOwnPropertyDescriptor(m, k);
5
+ if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
6
+ desc = { enumerable: true, get: function() { return m[k]; } };
7
+ }
8
+ Object.defineProperty(o, k2, desc);
9
+ }) : (function(o, m, k, k2) {
10
+ if (k2 === undefined) k2 = k;
11
+ o[k2] = m[k];
12
+ }));
13
+ var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
14
+ Object.defineProperty(o, "default", { enumerable: true, value: v });
15
+ }) : function(o, v) {
16
+ o["default"] = v;
17
+ });
18
+ var __importStar = (this && this.__importStar) || (function () {
19
+ var ownKeys = function(o) {
20
+ ownKeys = Object.getOwnPropertyNames || function (o) {
21
+ var ar = [];
22
+ for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
23
+ return ar;
24
+ };
25
+ return ownKeys(o);
26
+ };
27
+ return function (mod) {
28
+ if (mod && mod.__esModule) return mod;
29
+ var result = {};
30
+ if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
31
+ __setModuleDefault(result, mod);
32
+ return result;
33
+ };
34
+ })();
35
+ Object.defineProperty(exports, "__esModule", { value: true });
36
+ exports.WebPFormat = void 0;
37
+ // Default quality for WebP encoding when not specified
38
+ const DEFAULT_WEBP_QUALITY = 90;
39
+ /**
40
+ * WebP format handler
41
+ * Implements a basic WebP decoder and encoder
42
+ */
43
+ class WebPFormat {
44
+ constructor() {
45
+ Object.defineProperty(this, "name", {
46
+ enumerable: true,
47
+ configurable: true,
48
+ writable: true,
49
+ value: "webp"
50
+ });
51
+ Object.defineProperty(this, "mimeType", {
52
+ enumerable: true,
53
+ configurable: true,
54
+ writable: true,
55
+ value: "image/webp"
56
+ });
57
+ }
58
+ canDecode(data) {
59
+ // WebP signature: "RIFF" + size + "WEBP"
60
+ return data.length >= 12 &&
61
+ data[0] === 0x52 && data[1] === 0x49 && // "RI"
62
+ data[2] === 0x46 && data[3] === 0x46 && // "FF"
63
+ data[8] === 0x57 && data[9] === 0x45 && // "WE"
64
+ data[10] === 0x42 && data[11] === 0x50; // "BP"
65
+ }
66
+ async decode(data) {
67
+ if (!this.canDecode(data)) {
68
+ throw new Error("Invalid WebP signature");
69
+ }
70
+ // Parse WebP structure
71
+ let pos = 12; // Skip RIFF header
72
+ let width = 0;
73
+ let height = 0;
74
+ const metadata = {};
75
+ // Read all chunks to extract metadata
76
+ while (pos + 8 <= data.length) {
77
+ const chunkType = String.fromCharCode(data[pos], data[pos + 1], data[pos + 2], data[pos + 3]);
78
+ const chunkSize = this.readUint32LE(data, pos + 4);
79
+ pos += 8;
80
+ // Stop if we've gone past the end
81
+ if (pos + chunkSize > data.length)
82
+ break;
83
+ const chunkData = data.slice(pos, pos + chunkSize);
84
+ if (chunkType === "VP8 ") {
85
+ // Lossy format - extract dimensions
86
+ if (chunkData.length >= 10) {
87
+ const frameTag = chunkData[0] | (chunkData[1] << 8) |
88
+ (chunkData[2] << 16);
89
+ const keyFrame = (frameTag & 1) === 0;
90
+ if (keyFrame && chunkData[3] === 0x9d && chunkData[4] === 0x01 &&
91
+ chunkData[5] === 0x2a) {
92
+ width = chunkData[6] | ((chunkData[7] & 0x3f) << 8);
93
+ height = chunkData[8] | ((chunkData[9] & 0x3f) << 8);
94
+ }
95
+ }
96
+ }
97
+ else if (chunkType === "VP8L") {
98
+ // Lossless format - extract dimensions
99
+ if (chunkData.length >= 5 && chunkData[0] === 0x2f) {
100
+ const bits = this.readUint32LE(chunkData, 1);
101
+ width = (bits & 0x3fff) + 1;
102
+ height = ((bits >> 14) & 0x3fff) + 1;
103
+ }
104
+ }
105
+ else if (chunkType === "VP8X") {
106
+ // Extended format - extract dimensions
107
+ if (chunkData.length >= 10) {
108
+ width = this.readUint24LE(chunkData, 4) + 1;
109
+ height = this.readUint24LE(chunkData, 7) + 1;
110
+ }
111
+ }
112
+ else if (chunkType === "EXIF") {
113
+ // EXIF metadata chunk
114
+ this.parseEXIF(chunkData, metadata);
115
+ }
116
+ else if (chunkType === "XMP ") {
117
+ // XMP metadata chunk
118
+ this.parseXMP(chunkData, metadata);
119
+ }
120
+ pos += chunkSize;
121
+ // Chunks are padded to even length
122
+ if (chunkSize % 2 === 1)
123
+ pos++;
124
+ }
125
+ if (width === 0 || height === 0) {
126
+ throw new Error("Could not determine WebP dimensions");
127
+ }
128
+ // For a pure JS implementation, we'd need to implement full WebP decoding
129
+ // which is very complex. Instead, we'll use the browser/runtime's decoder.
130
+ const rgba = await this.decodeUsingRuntime(data, width, height);
131
+ return {
132
+ width,
133
+ height,
134
+ data: rgba,
135
+ metadata: Object.keys(metadata).length > 0 ? metadata : undefined,
136
+ };
137
+ }
138
+ async encode(imageData, options) {
139
+ const { width, height, data, metadata } = imageData;
140
+ const quality = options?.quality ?? DEFAULT_WEBP_QUALITY;
141
+ const forceLossless = options?.lossless ?? false;
142
+ // Determine if we should use lossless encoding
143
+ // Use lossless if: quality is 100, or lossless flag is set
144
+ const useLossless = quality === 100 || forceLossless;
145
+ // Try to use runtime encoding if available (better quality and compression)
146
+ if (typeof OffscreenCanvas !== "undefined") {
147
+ try {
148
+ const canvas = new OffscreenCanvas(width, height);
149
+ const ctx = canvas.getContext("2d");
150
+ if (ctx) {
151
+ const imgData = ctx.createImageData(width, height);
152
+ const imgDataData = new Uint8ClampedArray(data);
153
+ imgData.data.set(imgDataData);
154
+ ctx.putImageData(imgData, 0, 0);
155
+ const blob = await canvas.convertToBlob({
156
+ type: "image/webp",
157
+ quality: quality / 100, // Convert 1-100 to 0-1
158
+ });
159
+ const arrayBuffer = await blob.arrayBuffer();
160
+ const encoded = new Uint8Array(arrayBuffer);
161
+ // Inject metadata if present
162
+ if (metadata && Object.keys(metadata).length > 0) {
163
+ const injected = this.injectMetadata(encoded, metadata);
164
+ return injected;
165
+ }
166
+ return encoded;
167
+ }
168
+ }
169
+ catch (_error) {
170
+ // Fall through to pure JS encoder
171
+ }
172
+ }
173
+ // Fallback to pure JavaScript encoder
174
+ // VP8L (lossless) encoder with optional quality-based quantization
175
+ const { WebPEncoder } = await Promise.resolve().then(() => __importStar(require("../utils/webp_encoder.js")));
176
+ const encoder = new WebPEncoder(width, height, data);
177
+ const encoded = encoder.encode(useLossless ? 100 : quality);
178
+ // Inject metadata if present
179
+ if (metadata && Object.keys(metadata).length > 0) {
180
+ return this.injectMetadata(encoded, metadata);
181
+ }
182
+ return encoded;
183
+ }
184
+ readUint32LE(data, offset) {
185
+ return data[offset] | (data[offset + 1] << 8) |
186
+ (data[offset + 2] << 16) | (data[offset + 3] << 24);
187
+ }
188
+ readUint24LE(data, offset) {
189
+ return data[offset] | (data[offset + 1] << 8) | (data[offset + 2] << 16);
190
+ }
191
+ async decodeUsingRuntime(data, _width, _height) {
192
+ // Try to use ImageDecoder API if available (Deno, modern browsers)
193
+ if (typeof ImageDecoder !== "undefined") {
194
+ try {
195
+ const decoder = new ImageDecoder({ data, type: "image/webp" });
196
+ const result = await decoder.decode();
197
+ const bitmap = result.image;
198
+ // Create a canvas to extract pixel data
199
+ const canvas = new OffscreenCanvas(bitmap.displayWidth, bitmap.displayHeight);
200
+ const ctx = canvas.getContext("2d");
201
+ if (!ctx)
202
+ throw new Error("Could not get canvas context");
203
+ ctx.drawImage(bitmap, 0, 0);
204
+ const imageData = ctx.getImageData(0, 0, canvas.width, canvas.height);
205
+ bitmap.close();
206
+ return new Uint8Array(imageData.data.buffer);
207
+ }
208
+ catch (error) {
209
+ // ImageDecoder API failed, fall through to pure JS decoder
210
+ console.warn("WebP decoding with ImageDecoder failed, using pure JS decoder:", error);
211
+ }
212
+ }
213
+ // Fallback to pure JavaScript decoder (VP8L lossless only)
214
+ try {
215
+ const { WebPDecoder } = await Promise.resolve().then(() => __importStar(require("../utils/webp_decoder.js")));
216
+ const decoder = new WebPDecoder(data);
217
+ const result = decoder.decode();
218
+ return result.data;
219
+ }
220
+ catch (error) {
221
+ throw new Error(`WebP decoding failed: ${error}`);
222
+ }
223
+ }
224
+ // Metadata parsing and injection methods
225
+ parseEXIF(data, metadata) {
226
+ // EXIF data parsing (similar to JPEG/PNG EXIF parsing)
227
+ if (data.length < 8)
228
+ return;
229
+ try {
230
+ const byteOrder = String.fromCharCode(data[0], data[1]);
231
+ const littleEndian = byteOrder === "II";
232
+ const ifd0Offset = littleEndian
233
+ ? data[4] | (data[5] << 8) | (data[6] << 16) | (data[7] << 24)
234
+ : (data[4] << 24) | (data[5] << 16) | (data[6] << 8) | data[7];
235
+ if (ifd0Offset + 2 > data.length)
236
+ return;
237
+ const numEntries = littleEndian
238
+ ? data[ifd0Offset] | (data[ifd0Offset + 1] << 8)
239
+ : (data[ifd0Offset] << 8) | data[ifd0Offset + 1];
240
+ // Parse basic EXIF tags (simplified version)
241
+ for (let i = 0; i < numEntries && i < 50; i++) {
242
+ const entryOffset = ifd0Offset + 2 + i * 12;
243
+ if (entryOffset + 12 > data.length)
244
+ break;
245
+ const tag = littleEndian
246
+ ? data[entryOffset] | (data[entryOffset + 1] << 8)
247
+ : (data[entryOffset] << 8) | data[entryOffset + 1];
248
+ // DateTime tag
249
+ if (tag === 0x0132) {
250
+ const valueOffset = littleEndian
251
+ ? data[entryOffset + 8] | (data[entryOffset + 9] << 8) |
252
+ (data[entryOffset + 10] << 16) | (data[entryOffset + 11] << 24)
253
+ : (data[entryOffset + 8] << 24) | (data[entryOffset + 9] << 16) |
254
+ (data[entryOffset + 10] << 8) | data[entryOffset + 11];
255
+ if (valueOffset < data.length) {
256
+ const endIndex = data.indexOf(0, valueOffset);
257
+ if (endIndex > valueOffset) {
258
+ const dateStr = new TextDecoder().decode(data.slice(valueOffset, endIndex));
259
+ const match = dateStr.match(/^(\d{4}):(\d{2}):(\d{2}) (\d{2}):(\d{2}):(\d{2})$/);
260
+ if (match) {
261
+ metadata.creationDate = new Date(parseInt(match[1]), parseInt(match[2]) - 1, parseInt(match[3]), parseInt(match[4]), parseInt(match[5]), parseInt(match[6]));
262
+ }
263
+ }
264
+ }
265
+ }
266
+ }
267
+ }
268
+ catch (_e) {
269
+ // Ignore EXIF parsing errors
270
+ }
271
+ }
272
+ parseXMP(data, metadata) {
273
+ // XMP is XML-based metadata - simple parsing for common fields
274
+ try {
275
+ const xmpStr = new TextDecoder().decode(data);
276
+ // Extract title
277
+ const titleMatch = xmpStr.match(/<dc:title[^>]*>([^<]+)<\/dc:title>/);
278
+ if (titleMatch)
279
+ metadata.title = titleMatch[1].trim();
280
+ // Extract description
281
+ const descMatch = xmpStr.match(/<dc:description[^>]*>([^<]+)<\/dc:description>/);
282
+ if (descMatch)
283
+ metadata.description = descMatch[1].trim();
284
+ // Extract creator/author
285
+ const creatorMatch = xmpStr.match(/<dc:creator[^>]*>([^<]+)<\/dc:creator>/);
286
+ if (creatorMatch)
287
+ metadata.author = creatorMatch[1].trim();
288
+ // Extract rights/copyright
289
+ const rightsMatch = xmpStr.match(/<dc:rights[^>]*>([^<]+)<\/dc:rights>/);
290
+ if (rightsMatch)
291
+ metadata.copyright = rightsMatch[1].trim();
292
+ }
293
+ catch (_e) {
294
+ // Ignore XMP parsing errors
295
+ }
296
+ }
297
+ injectMetadata(webpData, metadata) {
298
+ // WebP files are RIFF containers: RIFF + size + WEBP + chunks
299
+ // We need to inject EXIF and/or XMP chunks before the image data
300
+ const chunks = [];
301
+ // Copy RIFF header (12 bytes)
302
+ chunks.push(webpData.slice(0, 12));
303
+ // Create metadata chunks
304
+ const metadataChunks = [];
305
+ // Create EXIF chunk if we have date or other EXIF data
306
+ if (metadata.creationDate) {
307
+ const exifData = this.createEXIFChunk(metadata);
308
+ if (exifData) {
309
+ metadataChunks.push(exifData);
310
+ }
311
+ }
312
+ // Create XMP chunk if we have text metadata
313
+ if (metadata.title || metadata.description || metadata.author ||
314
+ metadata.copyright) {
315
+ const xmpData = this.createXMPChunk(metadata);
316
+ if (xmpData) {
317
+ metadataChunks.push(xmpData);
318
+ }
319
+ }
320
+ // Copy original chunks (skip header)
321
+ let pos = 12;
322
+ while (pos + 8 <= webpData.length) {
323
+ const chunkType = String.fromCharCode(webpData[pos], webpData[pos + 1], webpData[pos + 2], webpData[pos + 3]);
324
+ const chunkSize = this.readUint32LE(webpData, pos + 4);
325
+ // Don't copy existing EXIF/XMP chunks (we'll add new ones)
326
+ if (chunkType !== "EXIF" && chunkType !== "XMP ") {
327
+ const chunkEnd = pos + 8 + chunkSize + (chunkSize % 2);
328
+ chunks.push(webpData.slice(pos, chunkEnd));
329
+ }
330
+ pos += 8 + chunkSize;
331
+ if (chunkSize % 2 === 1)
332
+ pos++; // Padding
333
+ }
334
+ // Insert metadata chunks after VP8/VP8L/VP8X chunk
335
+ const result = [chunks[0]]; // RIFF header
336
+ if (chunks.length > 1) {
337
+ result.push(chunks[1]); // First chunk (VP8/VP8L/VP8X)
338
+ }
339
+ result.push(...metadataChunks);
340
+ for (let i = 2; i < chunks.length; i++) {
341
+ result.push(chunks[i]);
342
+ }
343
+ // Recalculate total size
344
+ const totalSize = result.reduce((sum, chunk) => sum + chunk.length, 0) - 8;
345
+ const finalData = new Uint8Array(totalSize + 8);
346
+ // Write RIFF header with updated size
347
+ finalData.set(new TextEncoder().encode("RIFF"), 0);
348
+ finalData[4] = totalSize & 0xff;
349
+ finalData[5] = (totalSize >> 8) & 0xff;
350
+ finalData[6] = (totalSize >> 16) & 0xff;
351
+ finalData[7] = (totalSize >> 24) & 0xff;
352
+ finalData.set(new TextEncoder().encode("WEBP"), 8);
353
+ // Copy all chunks
354
+ let offset = 12;
355
+ for (let i = 1; i < result.length; i++) {
356
+ finalData.set(result[i], offset);
357
+ offset += result[i].length;
358
+ }
359
+ return finalData;
360
+ }
361
+ createEXIFChunk(metadata) {
362
+ if (!metadata.creationDate)
363
+ return null;
364
+ const exifData = [];
365
+ // Byte order marker (little endian)
366
+ exifData.push(0x49, 0x49); // "II"
367
+ exifData.push(0x2a, 0x00); // 42
368
+ // IFD0 offset
369
+ exifData.push(0x08, 0x00, 0x00, 0x00);
370
+ // Number of entries
371
+ exifData.push(0x01, 0x00);
372
+ // DateTime entry
373
+ const date = metadata.creationDate;
374
+ 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`;
375
+ const dateBytes = new TextEncoder().encode(dateStr);
376
+ // Tag 0x0132, Type 2 (ASCII), Count, Offset
377
+ exifData.push(0x32, 0x01, 0x02, 0x00);
378
+ exifData.push(dateBytes.length & 0xff, (dateBytes.length >> 8) & 0xff, (dateBytes.length >> 16) & 0xff, (dateBytes.length >> 24) & 0xff);
379
+ exifData.push(0x12, 0x00, 0x00, 0x00); // Offset to data
380
+ // Next IFD
381
+ exifData.push(0x00, 0x00, 0x00, 0x00);
382
+ // Date string data
383
+ for (const byte of dateBytes) {
384
+ exifData.push(byte);
385
+ }
386
+ // Create chunk header
387
+ const chunkData = new Uint8Array(exifData);
388
+ const chunk = new Uint8Array(8 + chunkData.length);
389
+ chunk.set(new TextEncoder().encode("EXIF"), 0);
390
+ chunk[4] = chunkData.length & 0xff;
391
+ chunk[5] = (chunkData.length >> 8) & 0xff;
392
+ chunk[6] = (chunkData.length >> 16) & 0xff;
393
+ chunk[7] = (chunkData.length >> 24) & 0xff;
394
+ chunk.set(chunkData, 8);
395
+ return chunk;
396
+ }
397
+ createXMPChunk(metadata) {
398
+ const xmpParts = [];
399
+ xmpParts.push('<?xpacket begin="" id="W5M0MpCehiHzreSzNTczkc9d"?>');
400
+ xmpParts.push('<x:xmpmeta xmlns:x="adobe:ns:meta/">');
401
+ xmpParts.push('<rdf:RDF xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#">');
402
+ xmpParts.push('<rdf:Description xmlns:dc="http://purl.org/dc/elements/1.1/">');
403
+ if (metadata.title) {
404
+ xmpParts.push(`<dc:title>${this.escapeXML(metadata.title)}</dc:title>`);
405
+ }
406
+ if (metadata.description) {
407
+ xmpParts.push(`<dc:description>${this.escapeXML(metadata.description)}</dc:description>`);
408
+ }
409
+ if (metadata.author) {
410
+ xmpParts.push(`<dc:creator>${this.escapeXML(metadata.author)}</dc:creator>`);
411
+ }
412
+ if (metadata.copyright) {
413
+ xmpParts.push(`<dc:rights>${this.escapeXML(metadata.copyright)}</dc:rights>`);
414
+ }
415
+ xmpParts.push("</rdf:Description>");
416
+ xmpParts.push("</rdf:RDF>");
417
+ xmpParts.push("</x:xmpmeta>");
418
+ xmpParts.push('<?xpacket end="w"?>');
419
+ const xmpStr = xmpParts.join("\n");
420
+ const xmpData = new TextEncoder().encode(xmpStr);
421
+ // Create chunk
422
+ const chunk = new Uint8Array(8 + xmpData.length);
423
+ chunk.set(new TextEncoder().encode("XMP "), 0);
424
+ chunk[4] = xmpData.length & 0xff;
425
+ chunk[5] = (xmpData.length >> 8) & 0xff;
426
+ chunk[6] = (xmpData.length >> 16) & 0xff;
427
+ chunk[7] = (xmpData.length >> 24) & 0xff;
428
+ chunk.set(xmpData, 8);
429
+ return chunk;
430
+ }
431
+ escapeXML(str) {
432
+ return str
433
+ .replace(/&/g, "&amp;")
434
+ .replace(/</g, "&lt;")
435
+ .replace(/>/g, "&gt;")
436
+ .replace(/"/g, "&quot;")
437
+ .replace(/'/g, "&apos;");
438
+ }
439
+ }
440
+ exports.WebPFormat = WebPFormat;
@@ -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"}