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