cross-image 0.2.2 → 0.2.4

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 (88) hide show
  1. package/README.md +333 -168
  2. package/esm/mod.d.ts +2 -0
  3. package/esm/mod.js +2 -0
  4. package/esm/src/formats/apng.d.ts +13 -1
  5. package/esm/src/formats/apng.js +97 -0
  6. package/esm/src/formats/ascii.d.ts +11 -1
  7. package/esm/src/formats/ascii.js +24 -0
  8. package/esm/src/formats/avif.d.ts +96 -0
  9. package/esm/src/formats/avif.js +607 -0
  10. package/esm/src/formats/bmp.d.ts +11 -1
  11. package/esm/src/formats/bmp.js +73 -0
  12. package/esm/src/formats/dng.d.ts +13 -1
  13. package/esm/src/formats/dng.js +26 -4
  14. package/esm/src/formats/gif.d.ts +15 -2
  15. package/esm/src/formats/gif.js +146 -4
  16. package/esm/src/formats/heic.d.ts +96 -0
  17. package/esm/src/formats/heic.js +608 -0
  18. package/esm/src/formats/ico.d.ts +11 -1
  19. package/esm/src/formats/ico.js +28 -0
  20. package/esm/src/formats/jpeg.d.ts +19 -1
  21. package/esm/src/formats/jpeg.js +709 -4
  22. package/esm/src/formats/pam.d.ts +11 -1
  23. package/esm/src/formats/pam.js +66 -0
  24. package/esm/src/formats/pcx.d.ts +11 -1
  25. package/esm/src/formats/pcx.js +45 -0
  26. package/esm/src/formats/png.d.ts +13 -1
  27. package/esm/src/formats/png.js +87 -0
  28. package/esm/src/formats/png_base.d.ts +8 -0
  29. package/esm/src/formats/png_base.js +176 -3
  30. package/esm/src/formats/ppm.d.ts +11 -1
  31. package/esm/src/formats/ppm.js +34 -0
  32. package/esm/src/formats/tiff.d.ts +13 -1
  33. package/esm/src/formats/tiff.js +165 -0
  34. package/esm/src/formats/webp.d.ts +16 -2
  35. package/esm/src/formats/webp.js +303 -62
  36. package/esm/src/image.d.ts +60 -0
  37. package/esm/src/image.js +253 -5
  38. package/esm/src/types.d.ts +59 -1
  39. package/esm/src/utils/image_processing.d.ts +55 -0
  40. package/esm/src/utils/image_processing.js +210 -0
  41. package/esm/src/utils/metadata/xmp.d.ts +52 -0
  42. package/esm/src/utils/metadata/xmp.js +325 -0
  43. package/esm/src/utils/resize.d.ts +4 -0
  44. package/esm/src/utils/resize.js +74 -0
  45. package/package.json +18 -1
  46. package/script/mod.d.ts +2 -0
  47. package/script/mod.js +5 -1
  48. package/script/src/formats/apng.d.ts +13 -1
  49. package/script/src/formats/apng.js +97 -0
  50. package/script/src/formats/ascii.d.ts +11 -1
  51. package/script/src/formats/ascii.js +24 -0
  52. package/script/src/formats/avif.d.ts +96 -0
  53. package/script/src/formats/avif.js +611 -0
  54. package/script/src/formats/bmp.d.ts +11 -1
  55. package/script/src/formats/bmp.js +73 -0
  56. package/script/src/formats/dng.d.ts +13 -1
  57. package/script/src/formats/dng.js +26 -4
  58. package/script/src/formats/gif.d.ts +15 -2
  59. package/script/src/formats/gif.js +146 -4
  60. package/script/src/formats/heic.d.ts +96 -0
  61. package/script/src/formats/heic.js +612 -0
  62. package/script/src/formats/ico.d.ts +11 -1
  63. package/script/src/formats/ico.js +28 -0
  64. package/script/src/formats/jpeg.d.ts +19 -1
  65. package/script/src/formats/jpeg.js +709 -4
  66. package/script/src/formats/pam.d.ts +11 -1
  67. package/script/src/formats/pam.js +66 -0
  68. package/script/src/formats/pcx.d.ts +11 -1
  69. package/script/src/formats/pcx.js +45 -0
  70. package/script/src/formats/png.d.ts +13 -1
  71. package/script/src/formats/png.js +87 -0
  72. package/script/src/formats/png_base.d.ts +8 -0
  73. package/script/src/formats/png_base.js +176 -3
  74. package/script/src/formats/ppm.d.ts +11 -1
  75. package/script/src/formats/ppm.js +34 -0
  76. package/script/src/formats/tiff.d.ts +13 -1
  77. package/script/src/formats/tiff.js +165 -0
  78. package/script/src/formats/webp.d.ts +16 -2
  79. package/script/src/formats/webp.js +303 -62
  80. package/script/src/image.d.ts +60 -0
  81. package/script/src/image.js +251 -3
  82. package/script/src/types.d.ts +59 -1
  83. package/script/src/utils/image_processing.d.ts +55 -0
  84. package/script/src/utils/image_processing.js +216 -0
  85. package/script/src/utils/metadata/xmp.d.ts +52 -0
  86. package/script/src/utils/metadata/xmp.js +333 -0
  87. package/script/src/utils/resize.d.ts +4 -0
  88. package/script/src/utils/resize.js +75 -0
@@ -131,6 +131,99 @@ export function adjustSaturation(data, amount) {
131
131
  }
132
132
  return result;
133
133
  }
134
+ /**
135
+ * Convert RGB to HSL color space
136
+ * @param r Red component (0-255)
137
+ * @param g Green component (0-255)
138
+ * @param b Blue component (0-255)
139
+ * @returns HSL values: [h (0-360), s (0-1), l (0-1)]
140
+ */
141
+ function rgbToHsl(r, g, b) {
142
+ r /= 255;
143
+ g /= 255;
144
+ b /= 255;
145
+ const max = Math.max(r, g, b);
146
+ const min = Math.min(r, g, b);
147
+ const delta = max - min;
148
+ let h = 0;
149
+ let s = 0;
150
+ const l = (max + min) / 2;
151
+ if (delta !== 0) {
152
+ s = l > 0.5 ? delta / (2 - max - min) : delta / (max + min);
153
+ if (max === r) {
154
+ h = ((g - b) / delta + (g < b ? 6 : 0)) / 6;
155
+ }
156
+ else if (max === g) {
157
+ h = ((b - r) / delta + 2) / 6;
158
+ }
159
+ else {
160
+ h = ((r - g) / delta + 4) / 6;
161
+ }
162
+ }
163
+ return [h * 360, s, l];
164
+ }
165
+ /**
166
+ * Convert HSL to RGB color space
167
+ * @param h Hue (0-360)
168
+ * @param s Saturation (0-1)
169
+ * @param l Lightness (0-1)
170
+ * @returns RGB values: [r (0-255), g (0-255), b (0-255)]
171
+ */
172
+ function hslToRgb(h, s, l) {
173
+ h = h / 360;
174
+ let r, g, b;
175
+ if (s === 0) {
176
+ r = g = b = l; // Achromatic
177
+ }
178
+ else {
179
+ const hue2rgb = (p, q, t) => {
180
+ if (t < 0)
181
+ t += 1;
182
+ if (t > 1)
183
+ t -= 1;
184
+ if (t < 1 / 6)
185
+ return p + (q - p) * 6 * t;
186
+ if (t < 1 / 2)
187
+ return q;
188
+ if (t < 2 / 3)
189
+ return p + (q - p) * (2 / 3 - t) * 6;
190
+ return p;
191
+ };
192
+ const q = l < 0.5 ? l * (1 + s) : l + s - l * s;
193
+ const p = 2 * l - q;
194
+ r = hue2rgb(p, q, h + 1 / 3);
195
+ g = hue2rgb(p, q, h);
196
+ b = hue2rgb(p, q, h - 1 / 3);
197
+ }
198
+ return [Math.round(r * 255), Math.round(g * 255), Math.round(b * 255)];
199
+ }
200
+ /**
201
+ * Adjust hue of an image by rotating the hue wheel
202
+ * @param data Image data (RGBA)
203
+ * @param degrees Hue rotation in degrees (any value accepted, wraps at 360)
204
+ * @returns New image data with adjusted hue
205
+ */
206
+ export function adjustHue(data, degrees) {
207
+ const result = new Uint8Array(data.length);
208
+ // Normalize rotation to -180 to 180 range
209
+ const rotation = ((degrees % 360) + 360) % 360;
210
+ for (let i = 0; i < data.length; i += 4) {
211
+ const r = data[i];
212
+ const g = data[i + 1];
213
+ const b = data[i + 2];
214
+ // Convert to HSL
215
+ const [h, s, l] = rgbToHsl(r, g, b);
216
+ // Rotate hue
217
+ const newH = (h + rotation) % 360;
218
+ // Convert back to RGB
219
+ const [newR, newG, newB] = hslToRgb(newH, s, l);
220
+ result[i] = newR;
221
+ result[i + 1] = newG;
222
+ result[i + 2] = newB;
223
+ result[i + 3] = data[i + 3]; // Preserve alpha
224
+ }
225
+ return result;
226
+ }
134
227
  /**
135
228
  * Invert colors of an image
136
229
  * @param data Image data (RGBA)
@@ -459,3 +552,120 @@ export function medianFilter(data, width, height, radius = 1) {
459
552
  }
460
553
  return result;
461
554
  }
555
+ /**
556
+ * Rotate image 90 degrees clockwise
557
+ * @param data Image data (RGBA)
558
+ * @param width Image width
559
+ * @param height Image height
560
+ * @returns Rotated image data with swapped dimensions
561
+ */
562
+ export function rotate90(data, width, height) {
563
+ const result = new Uint8Array(data.length);
564
+ const newWidth = height;
565
+ const newHeight = width;
566
+ for (let y = 0; y < height; y++) {
567
+ for (let x = 0; x < width; x++) {
568
+ const srcIdx = (y * width + x) * 4;
569
+ const dstX = height - 1 - y;
570
+ const dstY = x;
571
+ const dstIdx = (dstY * newWidth + dstX) * 4;
572
+ result[dstIdx] = data[srcIdx];
573
+ result[dstIdx + 1] = data[srcIdx + 1];
574
+ result[dstIdx + 2] = data[srcIdx + 2];
575
+ result[dstIdx + 3] = data[srcIdx + 3];
576
+ }
577
+ }
578
+ return { data: result, width: newWidth, height: newHeight };
579
+ }
580
+ /**
581
+ * Rotate image 180 degrees
582
+ * @param data Image data (RGBA)
583
+ * @param width Image width
584
+ * @param height Image height
585
+ * @returns Rotated image data with same dimensions
586
+ */
587
+ export function rotate180(data, width, height) {
588
+ const result = new Uint8Array(data.length);
589
+ for (let y = 0; y < height; y++) {
590
+ for (let x = 0; x < width; x++) {
591
+ const srcIdx = (y * width + x) * 4;
592
+ const dstX = width - 1 - x;
593
+ const dstY = height - 1 - y;
594
+ const dstIdx = (dstY * width + dstX) * 4;
595
+ result[dstIdx] = data[srcIdx];
596
+ result[dstIdx + 1] = data[srcIdx + 1];
597
+ result[dstIdx + 2] = data[srcIdx + 2];
598
+ result[dstIdx + 3] = data[srcIdx + 3];
599
+ }
600
+ }
601
+ return result;
602
+ }
603
+ /**
604
+ * Rotate image 270 degrees clockwise (or 90 degrees counter-clockwise)
605
+ * @param data Image data (RGBA)
606
+ * @param width Image width
607
+ * @param height Image height
608
+ * @returns Rotated image data with swapped dimensions
609
+ */
610
+ export function rotate270(data, width, height) {
611
+ const result = new Uint8Array(data.length);
612
+ const newWidth = height;
613
+ const newHeight = width;
614
+ for (let y = 0; y < height; y++) {
615
+ for (let x = 0; x < width; x++) {
616
+ const srcIdx = (y * width + x) * 4;
617
+ const dstX = y;
618
+ const dstY = width - 1 - x;
619
+ const dstIdx = (dstY * newWidth + dstX) * 4;
620
+ result[dstIdx] = data[srcIdx];
621
+ result[dstIdx + 1] = data[srcIdx + 1];
622
+ result[dstIdx + 2] = data[srcIdx + 2];
623
+ result[dstIdx + 3] = data[srcIdx + 3];
624
+ }
625
+ }
626
+ return { data: result, width: newWidth, height: newHeight };
627
+ }
628
+ /**
629
+ * Flip image horizontally (mirror)
630
+ * @param data Image data (RGBA)
631
+ * @param width Image width
632
+ * @param height Image height
633
+ * @returns Flipped image data
634
+ */
635
+ export function flipHorizontal(data, width, height) {
636
+ const result = new Uint8Array(data.length);
637
+ for (let y = 0; y < height; y++) {
638
+ for (let x = 0; x < width; x++) {
639
+ const srcIdx = (y * width + x) * 4;
640
+ const dstX = width - 1 - x;
641
+ const dstIdx = (y * width + dstX) * 4;
642
+ result[dstIdx] = data[srcIdx];
643
+ result[dstIdx + 1] = data[srcIdx + 1];
644
+ result[dstIdx + 2] = data[srcIdx + 2];
645
+ result[dstIdx + 3] = data[srcIdx + 3];
646
+ }
647
+ }
648
+ return result;
649
+ }
650
+ /**
651
+ * Flip image vertically
652
+ * @param data Image data (RGBA)
653
+ * @param width Image width
654
+ * @param height Image height
655
+ * @returns Flipped image data
656
+ */
657
+ export function flipVertical(data, width, height) {
658
+ const result = new Uint8Array(data.length);
659
+ for (let y = 0; y < height; y++) {
660
+ for (let x = 0; x < width; x++) {
661
+ const srcIdx = (y * width + x) * 4;
662
+ const dstY = height - 1 - y;
663
+ const dstIdx = (dstY * width + x) * 4;
664
+ result[dstIdx] = data[srcIdx];
665
+ result[dstIdx + 1] = data[srcIdx + 1];
666
+ result[dstIdx + 2] = data[srcIdx + 2];
667
+ result[dstIdx + 3] = data[srcIdx + 3];
668
+ }
669
+ }
670
+ return result;
671
+ }
@@ -0,0 +1,52 @@
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
+ import type { ImageMetadata } from "../../types.js";
8
+ /**
9
+ * XMP namespace URIs
10
+ */
11
+ export declare const XMP_NAMESPACES: {
12
+ readonly RDF: "http://www.w3.org/1999/02/22-rdf-syntax-ns#";
13
+ readonly DC: "http://purl.org/dc/elements/1.1/";
14
+ readonly XMP: "http://ns.adobe.com/xap/1.0/";
15
+ readonly EXIF: "http://ns.adobe.com/exif/1.0/";
16
+ readonly TIFF: "http://ns.adobe.com/tiff/1.0/";
17
+ readonly PHOTOSHOP: "http://ns.adobe.com/photoshop/1.0/";
18
+ readonly XMP_RIGHTS: "http://ns.adobe.com/xap/1.0/rights/";
19
+ };
20
+ /**
21
+ * XMP Dublin Core field mapping to ImageMetadata
22
+ */
23
+ export interface XMPFieldMapping {
24
+ xmpPath: string;
25
+ metadataKey: keyof ImageMetadata;
26
+ namespace: string;
27
+ }
28
+ /**
29
+ * Supported XMP fields and their mappings
30
+ */
31
+ export declare const XMP_FIELD_MAPPINGS: XMPFieldMapping[];
32
+ /**
33
+ * Escape XML special characters
34
+ */
35
+ export declare function escapeXML(str: string): string;
36
+ /**
37
+ * Unescape XML special characters
38
+ */
39
+ export declare function unescapeXML(str: string): string;
40
+ /**
41
+ * Parse XMP metadata from XML string
42
+ */
43
+ export declare function parseXMP(xmpStr: string): Partial<ImageMetadata>;
44
+ /**
45
+ * Create XMP packet from metadata
46
+ */
47
+ export declare function createXMP(metadata: Partial<ImageMetadata>): string;
48
+ /**
49
+ * Get list of supported XMP metadata fields
50
+ */
51
+ export declare function getSupportedXMPFields(): Array<keyof ImageMetadata>;
52
+ //# sourceMappingURL=xmp.d.ts.map
@@ -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, "&amp;")
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
+ }