cross-image 0.2.2 → 0.2.3

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 (36) hide show
  1. package/README.md +128 -7
  2. package/esm/src/formats/jpeg.d.ts +12 -1
  3. package/esm/src/formats/jpeg.js +633 -4
  4. package/esm/src/formats/png_base.d.ts +8 -0
  5. package/esm/src/formats/png_base.js +176 -3
  6. package/esm/src/formats/tiff.d.ts +6 -1
  7. package/esm/src/formats/tiff.js +31 -0
  8. package/esm/src/formats/webp.d.ts +9 -2
  9. package/esm/src/formats/webp.js +211 -62
  10. package/esm/src/image.d.ts +51 -0
  11. package/esm/src/image.js +225 -5
  12. package/esm/src/types.d.ts +41 -1
  13. package/esm/src/utils/image_processing.d.ts +55 -0
  14. package/esm/src/utils/image_processing.js +210 -0
  15. package/esm/src/utils/metadata/xmp.d.ts +52 -0
  16. package/esm/src/utils/metadata/xmp.js +325 -0
  17. package/esm/src/utils/resize.d.ts +4 -0
  18. package/esm/src/utils/resize.js +74 -0
  19. package/package.json +1 -1
  20. package/script/src/formats/jpeg.d.ts +12 -1
  21. package/script/src/formats/jpeg.js +633 -4
  22. package/script/src/formats/png_base.d.ts +8 -0
  23. package/script/src/formats/png_base.js +176 -3
  24. package/script/src/formats/tiff.d.ts +6 -1
  25. package/script/src/formats/tiff.js +31 -0
  26. package/script/src/formats/webp.d.ts +9 -2
  27. package/script/src/formats/webp.js +211 -62
  28. package/script/src/image.d.ts +51 -0
  29. package/script/src/image.js +223 -3
  30. package/script/src/types.d.ts +41 -1
  31. package/script/src/utils/image_processing.d.ts +55 -0
  32. package/script/src/utils/image_processing.js +216 -0
  33. package/script/src/utils/metadata/xmp.d.ts +52 -0
  34. package/script/src/utils/metadata/xmp.js +333 -0
  35. package/script/src/utils/resize.d.ts +4 -0
  36. package/script/src/utils/resize.js +75 -0
@@ -0,0 +1,325 @@
1
+ /**
2
+ * XMP (Extensible Metadata Platform) parsing and writing utilities
3
+ *
4
+ * This module provides utilities for reading and writing XMP metadata in image files.
5
+ * It supports Dublin Core, EXIF, and Photoshop namespaces.
6
+ */
7
+ /**
8
+ * XMP namespace URIs
9
+ */
10
+ export const XMP_NAMESPACES = {
11
+ RDF: "http://www.w3.org/1999/02/22-rdf-syntax-ns#",
12
+ DC: "http://purl.org/dc/elements/1.1/",
13
+ XMP: "http://ns.adobe.com/xap/1.0/",
14
+ EXIF: "http://ns.adobe.com/exif/1.0/",
15
+ TIFF: "http://ns.adobe.com/tiff/1.0/",
16
+ PHOTOSHOP: "http://ns.adobe.com/photoshop/1.0/",
17
+ XMP_RIGHTS: "http://ns.adobe.com/xap/1.0/rights/",
18
+ };
19
+ /**
20
+ * Supported XMP fields and their mappings
21
+ */
22
+ export const XMP_FIELD_MAPPINGS = [
23
+ // Dublin Core
24
+ { xmpPath: "dc:title", metadataKey: "title", namespace: XMP_NAMESPACES.DC },
25
+ {
26
+ xmpPath: "dc:description",
27
+ metadataKey: "description",
28
+ namespace: XMP_NAMESPACES.DC,
29
+ },
30
+ {
31
+ xmpPath: "dc:creator",
32
+ metadataKey: "author",
33
+ namespace: XMP_NAMESPACES.DC,
34
+ },
35
+ {
36
+ xmpPath: "dc:rights",
37
+ metadataKey: "copyright",
38
+ namespace: XMP_NAMESPACES.DC,
39
+ },
40
+ // EXIF namespace
41
+ {
42
+ xmpPath: "exif:DateTimeOriginal",
43
+ metadataKey: "creationDate",
44
+ namespace: XMP_NAMESPACES.EXIF,
45
+ },
46
+ {
47
+ xmpPath: "exif:ISOSpeedRatings",
48
+ metadataKey: "iso",
49
+ namespace: XMP_NAMESPACES.EXIF,
50
+ },
51
+ {
52
+ xmpPath: "exif:ExposureTime",
53
+ metadataKey: "exposureTime",
54
+ namespace: XMP_NAMESPACES.EXIF,
55
+ },
56
+ {
57
+ xmpPath: "exif:FNumber",
58
+ metadataKey: "fNumber",
59
+ namespace: XMP_NAMESPACES.EXIF,
60
+ },
61
+ {
62
+ xmpPath: "exif:FocalLength",
63
+ metadataKey: "focalLength",
64
+ namespace: XMP_NAMESPACES.EXIF,
65
+ },
66
+ {
67
+ xmpPath: "exif:Flash",
68
+ metadataKey: "flash",
69
+ namespace: XMP_NAMESPACES.EXIF,
70
+ },
71
+ {
72
+ xmpPath: "exif:WhiteBalance",
73
+ metadataKey: "whiteBalance",
74
+ namespace: XMP_NAMESPACES.EXIF,
75
+ },
76
+ {
77
+ xmpPath: "exif:UserComment",
78
+ metadataKey: "userComment",
79
+ namespace: XMP_NAMESPACES.EXIF,
80
+ },
81
+ // TIFF namespace
82
+ {
83
+ xmpPath: "tiff:Make",
84
+ metadataKey: "cameraMake",
85
+ namespace: XMP_NAMESPACES.TIFF,
86
+ },
87
+ {
88
+ xmpPath: "tiff:Model",
89
+ metadataKey: "cameraModel",
90
+ namespace: XMP_NAMESPACES.TIFF,
91
+ },
92
+ {
93
+ xmpPath: "tiff:Orientation",
94
+ metadataKey: "orientation",
95
+ namespace: XMP_NAMESPACES.TIFF,
96
+ },
97
+ {
98
+ xmpPath: "tiff:Software",
99
+ metadataKey: "software",
100
+ namespace: XMP_NAMESPACES.TIFF,
101
+ },
102
+ // Photoshop namespace
103
+ {
104
+ xmpPath: "photoshop:Credit",
105
+ metadataKey: "author",
106
+ namespace: XMP_NAMESPACES.PHOTOSHOP,
107
+ },
108
+ ];
109
+ /**
110
+ * Escape XML special characters
111
+ */
112
+ export function escapeXML(str) {
113
+ return str
114
+ .replace(/&/g, "&")
115
+ .replace(/</g, "&lt;")
116
+ .replace(/>/g, "&gt;")
117
+ .replace(/"/g, "&quot;")
118
+ .replace(/'/g, "&apos;");
119
+ }
120
+ /**
121
+ * Unescape XML special characters
122
+ */
123
+ export function unescapeXML(str) {
124
+ return str
125
+ .replace(/&lt;/g, "<")
126
+ .replace(/&gt;/g, ">")
127
+ .replace(/&quot;/g, '"')
128
+ .replace(/&apos;/g, "'")
129
+ .replace(/&amp;/g, "&");
130
+ }
131
+ /**
132
+ * Parse XMP metadata from XML string
133
+ */
134
+ export function parseXMP(xmpStr) {
135
+ const metadata = {};
136
+ try {
137
+ // Simple regex-based parsing for common fields
138
+ // Dublin Core - title (with dotall flag 's')
139
+ const titleMatch = xmpStr.match(/<dc:title[\s\S]*?<rdf:li[^>]*>([^<]+)<\/rdf:li>/);
140
+ if (titleMatch && titleMatch[1].trim()) {
141
+ metadata.title = unescapeXML(titleMatch[1].trim());
142
+ }
143
+ // Dublin Core - description
144
+ const descMatch = xmpStr.match(/<dc:description[\s\S]*?<rdf:li[^>]*>([^<]+)<\/rdf:li>/);
145
+ if (descMatch && descMatch[1].trim()) {
146
+ metadata.description = unescapeXML(descMatch[1].trim());
147
+ }
148
+ // Dublin Core - creator
149
+ const creatorMatch = xmpStr.match(/<dc:creator[\s\S]*?<rdf:li[^>]*>([^<]+)<\/rdf:li>/);
150
+ if (creatorMatch && creatorMatch[1].trim()) {
151
+ metadata.author = unescapeXML(creatorMatch[1].trim());
152
+ }
153
+ // Dublin Core - rights
154
+ const rightsMatch = xmpStr.match(/<dc:rights[\s\S]*?<rdf:li[^>]*>([^<]+)<\/rdf:li>/);
155
+ if (rightsMatch && rightsMatch[1].trim()) {
156
+ metadata.copyright = unescapeXML(rightsMatch[1].trim());
157
+ }
158
+ // EXIF - DateTimeOriginal
159
+ const dateMatch = xmpStr.match(/<exif:DateTimeOriginal>([^<]+)<\/exif:DateTimeOriginal>/);
160
+ if (dateMatch) {
161
+ try {
162
+ metadata.creationDate = new Date(dateMatch[1]);
163
+ }
164
+ catch (_e) {
165
+ // Ignore date parse errors
166
+ }
167
+ }
168
+ // TIFF - Make and Model
169
+ const makeMatch = xmpStr.match(/<tiff:Make>([^<]+)<\/tiff:Make>/);
170
+ if (makeMatch) {
171
+ metadata.cameraMake = unescapeXML(makeMatch[1]);
172
+ }
173
+ const modelMatch = xmpStr.match(/<tiff:Model>([^<]+)<\/tiff:Model>/);
174
+ if (modelMatch) {
175
+ metadata.cameraModel = unescapeXML(modelMatch[1]);
176
+ }
177
+ const softwareMatch = xmpStr.match(/<tiff:Software>([^<]+)<\/tiff:Software>/);
178
+ if (softwareMatch) {
179
+ metadata.software = unescapeXML(softwareMatch[1]);
180
+ }
181
+ const orientationMatch = xmpStr.match(/<tiff:Orientation>([^<]+)<\/tiff:Orientation>/);
182
+ if (orientationMatch) {
183
+ metadata.orientation = parseInt(orientationMatch[1]);
184
+ }
185
+ // EXIF - Camera settings
186
+ const isoMatch = xmpStr.match(/<exif:ISOSpeedRatings>(?:<rdf:Seq[^>]*><rdf:li>)?([^<]+)/);
187
+ if (isoMatch) {
188
+ metadata.iso = parseInt(isoMatch[1]);
189
+ }
190
+ const exposureMatch = xmpStr.match(/<exif:ExposureTime>([^<]+)<\/exif:ExposureTime>/);
191
+ if (exposureMatch) {
192
+ // Handle rational format (e.g., "1/250")
193
+ if (exposureMatch[1].includes("/")) {
194
+ const [num, den] = exposureMatch[1].split("/").map(Number);
195
+ metadata.exposureTime = num / den;
196
+ }
197
+ else {
198
+ metadata.exposureTime = parseFloat(exposureMatch[1]);
199
+ }
200
+ }
201
+ const fNumberMatch = xmpStr.match(/<exif:FNumber>([^<]+)<\/exif:FNumber>/);
202
+ if (fNumberMatch) {
203
+ metadata.fNumber = parseFloat(fNumberMatch[1]);
204
+ }
205
+ const focalLengthMatch = xmpStr.match(/<exif:FocalLength>([^<]+)<\/exif:FocalLength>/);
206
+ if (focalLengthMatch) {
207
+ metadata.focalLength = parseFloat(focalLengthMatch[1]);
208
+ }
209
+ }
210
+ catch (_e) {
211
+ // Ignore XMP parsing errors
212
+ }
213
+ return metadata;
214
+ }
215
+ /**
216
+ * Create XMP packet from metadata
217
+ */
218
+ export function createXMP(metadata) {
219
+ const parts = [];
220
+ parts.push('<?xpacket begin="\ufeff" id="W5M0MpCehiHzreSzNTczkc9d"?>');
221
+ parts.push('<x:xmpmeta xmlns:x="adobe:ns:meta/" x:xmptk="@cross/image">');
222
+ parts.push('<rdf:RDF xmlns:rdf="' + XMP_NAMESPACES.RDF + '">');
223
+ parts.push('<rdf:Description rdf:about=""');
224
+ parts.push(' xmlns:dc="' + XMP_NAMESPACES.DC + '"');
225
+ parts.push(' xmlns:xmp="' + XMP_NAMESPACES.XMP + '"');
226
+ parts.push(' xmlns:exif="' + XMP_NAMESPACES.EXIF + '"');
227
+ parts.push(' xmlns:tiff="' + XMP_NAMESPACES.TIFF + '"');
228
+ parts.push(' xmlns:photoshop="' + XMP_NAMESPACES.PHOTOSHOP + '"');
229
+ parts.push(' xmlns:xmpRights="' + XMP_NAMESPACES.XMP_RIGHTS + '">');
230
+ // Dublin Core - title
231
+ if (metadata.title) {
232
+ parts.push(" <dc:title>");
233
+ parts.push(" <rdf:Alt>");
234
+ parts.push(' <rdf:li xml:lang="x-default">' + escapeXML(metadata.title) +
235
+ "</rdf:li>");
236
+ parts.push(" </rdf:Alt>");
237
+ parts.push(" </dc:title>");
238
+ }
239
+ // Dublin Core - description
240
+ if (metadata.description) {
241
+ parts.push(" <dc:description>");
242
+ parts.push(" <rdf:Alt>");
243
+ parts.push(' <rdf:li xml:lang="x-default">' + escapeXML(metadata.description) +
244
+ "</rdf:li>");
245
+ parts.push(" </rdf:Alt>");
246
+ parts.push(" </dc:description>");
247
+ }
248
+ // Dublin Core - creator
249
+ if (metadata.author) {
250
+ parts.push(" <dc:creator>");
251
+ parts.push(" <rdf:Seq>");
252
+ parts.push(" <rdf:li>" + escapeXML(metadata.author) + "</rdf:li>");
253
+ parts.push(" </rdf:Seq>");
254
+ parts.push(" </dc:creator>");
255
+ }
256
+ // Dublin Core - rights
257
+ if (metadata.copyright) {
258
+ parts.push(" <dc:rights>");
259
+ parts.push(" <rdf:Alt>");
260
+ parts.push(' <rdf:li xml:lang="x-default">' + escapeXML(metadata.copyright) +
261
+ "</rdf:li>");
262
+ parts.push(" </rdf:Alt>");
263
+ parts.push(" </dc:rights>");
264
+ }
265
+ // EXIF - DateTimeOriginal
266
+ if (metadata.creationDate) {
267
+ const isoDate = metadata.creationDate.toISOString();
268
+ parts.push(" <exif:DateTimeOriginal>" + isoDate + "</exif:DateTimeOriginal>");
269
+ }
270
+ // TIFF - Make and Model
271
+ if (metadata.cameraMake) {
272
+ parts.push(" <tiff:Make>" + escapeXML(metadata.cameraMake) + "</tiff:Make>");
273
+ }
274
+ if (metadata.cameraModel) {
275
+ parts.push(" <tiff:Model>" + escapeXML(metadata.cameraModel) + "</tiff:Model>");
276
+ }
277
+ if (metadata.software) {
278
+ parts.push(" <tiff:Software>" + escapeXML(metadata.software) + "</tiff:Software>");
279
+ }
280
+ if (metadata.orientation !== undefined) {
281
+ parts.push(" <tiff:Orientation>" + metadata.orientation + "</tiff:Orientation>");
282
+ }
283
+ // EXIF - Camera settings
284
+ if (metadata.iso !== undefined) {
285
+ parts.push(" <exif:ISOSpeedRatings>");
286
+ parts.push(" <rdf:Seq>");
287
+ parts.push(" <rdf:li>" + metadata.iso + "</rdf:li>");
288
+ parts.push(" </rdf:Seq>");
289
+ parts.push(" </exif:ISOSpeedRatings>");
290
+ }
291
+ if (metadata.exposureTime !== undefined) {
292
+ parts.push(" <exif:ExposureTime>" + metadata.exposureTime + "</exif:ExposureTime>");
293
+ }
294
+ if (metadata.fNumber !== undefined) {
295
+ parts.push(" <exif:FNumber>" + metadata.fNumber + "</exif:FNumber>");
296
+ }
297
+ if (metadata.focalLength !== undefined) {
298
+ parts.push(" <exif:FocalLength>" + metadata.focalLength + "</exif:FocalLength>");
299
+ }
300
+ parts.push("</rdf:Description>");
301
+ parts.push("</rdf:RDF>");
302
+ parts.push("</x:xmpmeta>");
303
+ parts.push('<?xpacket end="w"?>');
304
+ return parts.join("\n");
305
+ }
306
+ /**
307
+ * Get list of supported XMP metadata fields
308
+ */
309
+ export function getSupportedXMPFields() {
310
+ return [
311
+ "title",
312
+ "description",
313
+ "author",
314
+ "copyright",
315
+ "creationDate",
316
+ "cameraMake",
317
+ "cameraModel",
318
+ "orientation",
319
+ "software",
320
+ "iso",
321
+ "exposureTime",
322
+ "fNumber",
323
+ "focalLength",
324
+ ];
325
+ }
@@ -6,4 +6,8 @@ export declare function resizeBilinear(src: Uint8Array, srcWidth: number, srcHei
6
6
  * Nearest neighbor resize
7
7
  */
8
8
  export declare function resizeNearest(src: Uint8Array, srcWidth: number, srcHeight: number, dstWidth: number, dstHeight: number): Uint8Array;
9
+ /**
10
+ * Bicubic interpolation resize (Catmull-Rom)
11
+ */
12
+ export declare function resizeBicubic(src: Uint8Array, srcWidth: number, srcHeight: number, dstWidth: number, dstHeight: number): Uint8Array;
9
13
  //# sourceMappingURL=resize.d.ts.map
@@ -50,3 +50,77 @@ export function resizeNearest(src, srcWidth, srcHeight, dstWidth, dstHeight) {
50
50
  }
51
51
  return dst;
52
52
  }
53
+ /**
54
+ * Cubic interpolation kernel (Catmull-Rom)
55
+ *
56
+ * The Catmull-Rom spline provides smooth interpolation through control points.
57
+ * It's a special case of the cubic Hermite spline with specific tangent values.
58
+ *
59
+ * @param x Distance from the sample point
60
+ * @returns Weight for the sample at distance x
61
+ */
62
+ function cubicKernel(x) {
63
+ const absX = Math.abs(x);
64
+ if (absX <= 1) {
65
+ return 1.5 * absX * absX * absX - 2.5 * absX * absX + 1;
66
+ }
67
+ else if (absX < 2) {
68
+ return -0.5 * absX * absX * absX + 2.5 * absX * absX - 4 * absX + 2;
69
+ }
70
+ return 0;
71
+ }
72
+ /**
73
+ * Get pixel value with bounds checking
74
+ *
75
+ * Clamps coordinates to valid image bounds to prevent out-of-bounds access.
76
+ * This ensures that edge pixels are repeated when sampling outside the image.
77
+ *
78
+ * @param src Source image data
79
+ * @param x X coordinate (may be outside bounds)
80
+ * @param y Y coordinate (may be outside bounds)
81
+ * @param width Image width
82
+ * @param height Image height
83
+ * @param channel Channel index (0=R, 1=G, 2=B, 3=A)
84
+ * @returns Pixel value at the clamped coordinates
85
+ */
86
+ function getPixel(src, x, y, width, height, channel) {
87
+ const clampedX = Math.max(0, Math.min(width - 1, x));
88
+ const clampedY = Math.max(0, Math.min(height - 1, y));
89
+ return src[(clampedY * width + clampedX) * 4 + channel];
90
+ }
91
+ /**
92
+ * Bicubic interpolation resize (Catmull-Rom)
93
+ */
94
+ export function resizeBicubic(src, srcWidth, srcHeight, dstWidth, dstHeight) {
95
+ const dst = new Uint8Array(dstWidth * dstHeight * 4);
96
+ const xRatio = srcWidth / dstWidth;
97
+ const yRatio = srcHeight / dstHeight;
98
+ for (let y = 0; y < dstHeight; y++) {
99
+ for (let x = 0; x < dstWidth; x++) {
100
+ const srcX = x * xRatio;
101
+ const srcY = y * yRatio;
102
+ const x0 = Math.floor(srcX);
103
+ const y0 = Math.floor(srcY);
104
+ const dx = srcX - x0;
105
+ const dy = srcY - y0;
106
+ // Process each channel separately
107
+ for (let c = 0; c < 4; c++) {
108
+ let value = 0;
109
+ // Sample 4x4 neighborhood
110
+ for (let j = -1; j <= 2; j++) {
111
+ for (let i = -1; i <= 2; i++) {
112
+ const px = x0 + i;
113
+ const py = y0 + j;
114
+ const pixelValue = getPixel(src, px, py, srcWidth, srcHeight, c);
115
+ const weight = cubicKernel(i - dx) * cubicKernel(j - dy);
116
+ value += pixelValue * weight;
117
+ }
118
+ }
119
+ // Clamp to valid range
120
+ const dstIdx = (y * dstWidth + x) * 4 + c;
121
+ dst[dstIdx] = Math.max(0, Math.min(255, Math.round(value)));
122
+ }
123
+ }
124
+ }
125
+ return dst;
126
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "cross-image",
3
- "version": "0.2.2",
3
+ "version": "0.2.3",
4
4
  "description": "A pure JavaScript, dependency-free, cross-runtime image processing library for Deno, Node.js, and Bun.",
5
5
  "keywords": [
6
6
  "image",
@@ -1,4 +1,4 @@
1
- import type { ImageData, ImageFormat } from "../types.js";
1
+ import type { ImageData, ImageFormat, ImageMetadata } from "../types.js";
2
2
  /**
3
3
  * JPEG format handler
4
4
  * Implements a basic JPEG decoder and encoder
@@ -30,6 +30,17 @@ export declare class JPEGFormat implements ImageFormat {
30
30
  private decodeUsingRuntime;
31
31
  private parseJFIF;
32
32
  private parseEXIF;
33
+ private parseExifSubIFD;
34
+ private parseGPSIFD;
35
+ private readRational;
33
36
  private createEXIFData;
37
+ private createGPSIFD;
38
+ private createExifSubIFD;
39
+ private toRational;
40
+ private writeRational;
41
+ /**
42
+ * Get the list of metadata fields supported by JPEG format
43
+ */
44
+ getSupportedMetadata(): Array<keyof ImageMetadata>;
34
45
  }
35
46
  //# sourceMappingURL=jpeg.d.ts.map