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.
- package/README.md +333 -168
- package/esm/mod.d.ts +2 -0
- package/esm/mod.js +2 -0
- package/esm/src/formats/apng.d.ts +13 -1
- package/esm/src/formats/apng.js +97 -0
- package/esm/src/formats/ascii.d.ts +11 -1
- package/esm/src/formats/ascii.js +24 -0
- package/esm/src/formats/avif.d.ts +96 -0
- package/esm/src/formats/avif.js +607 -0
- package/esm/src/formats/bmp.d.ts +11 -1
- package/esm/src/formats/bmp.js +73 -0
- package/esm/src/formats/dng.d.ts +13 -1
- package/esm/src/formats/dng.js +26 -4
- package/esm/src/formats/gif.d.ts +15 -2
- package/esm/src/formats/gif.js +146 -4
- package/esm/src/formats/heic.d.ts +96 -0
- package/esm/src/formats/heic.js +608 -0
- package/esm/src/formats/ico.d.ts +11 -1
- package/esm/src/formats/ico.js +28 -0
- package/esm/src/formats/jpeg.d.ts +19 -1
- package/esm/src/formats/jpeg.js +709 -4
- package/esm/src/formats/pam.d.ts +11 -1
- package/esm/src/formats/pam.js +66 -0
- package/esm/src/formats/pcx.d.ts +11 -1
- package/esm/src/formats/pcx.js +45 -0
- package/esm/src/formats/png.d.ts +13 -1
- package/esm/src/formats/png.js +87 -0
- package/esm/src/formats/png_base.d.ts +8 -0
- package/esm/src/formats/png_base.js +176 -3
- package/esm/src/formats/ppm.d.ts +11 -1
- package/esm/src/formats/ppm.js +34 -0
- package/esm/src/formats/tiff.d.ts +13 -1
- package/esm/src/formats/tiff.js +165 -0
- package/esm/src/formats/webp.d.ts +16 -2
- package/esm/src/formats/webp.js +303 -62
- package/esm/src/image.d.ts +60 -0
- package/esm/src/image.js +253 -5
- package/esm/src/types.d.ts +59 -1
- package/esm/src/utils/image_processing.d.ts +55 -0
- package/esm/src/utils/image_processing.js +210 -0
- package/esm/src/utils/metadata/xmp.d.ts +52 -0
- package/esm/src/utils/metadata/xmp.js +325 -0
- package/esm/src/utils/resize.d.ts +4 -0
- package/esm/src/utils/resize.js +74 -0
- package/package.json +18 -1
- package/script/mod.d.ts +2 -0
- package/script/mod.js +5 -1
- package/script/src/formats/apng.d.ts +13 -1
- package/script/src/formats/apng.js +97 -0
- package/script/src/formats/ascii.d.ts +11 -1
- package/script/src/formats/ascii.js +24 -0
- package/script/src/formats/avif.d.ts +96 -0
- package/script/src/formats/avif.js +611 -0
- package/script/src/formats/bmp.d.ts +11 -1
- package/script/src/formats/bmp.js +73 -0
- package/script/src/formats/dng.d.ts +13 -1
- package/script/src/formats/dng.js +26 -4
- package/script/src/formats/gif.d.ts +15 -2
- package/script/src/formats/gif.js +146 -4
- package/script/src/formats/heic.d.ts +96 -0
- package/script/src/formats/heic.js +612 -0
- package/script/src/formats/ico.d.ts +11 -1
- package/script/src/formats/ico.js +28 -0
- package/script/src/formats/jpeg.d.ts +19 -1
- package/script/src/formats/jpeg.js +709 -4
- package/script/src/formats/pam.d.ts +11 -1
- package/script/src/formats/pam.js +66 -0
- package/script/src/formats/pcx.d.ts +11 -1
- package/script/src/formats/pcx.js +45 -0
- package/script/src/formats/png.d.ts +13 -1
- package/script/src/formats/png.js +87 -0
- package/script/src/formats/png_base.d.ts +8 -0
- package/script/src/formats/png_base.js +176 -3
- package/script/src/formats/ppm.d.ts +11 -1
- package/script/src/formats/ppm.js +34 -0
- package/script/src/formats/tiff.d.ts +13 -1
- package/script/src/formats/tiff.js +165 -0
- package/script/src/formats/webp.d.ts +16 -2
- package/script/src/formats/webp.js +303 -62
- package/script/src/image.d.ts +60 -0
- package/script/src/image.js +251 -3
- package/script/src/types.d.ts +59 -1
- package/script/src/utils/image_processing.d.ts +55 -0
- package/script/src/utils/image_processing.js +216 -0
- package/script/src/utils/metadata/xmp.d.ts +52 -0
- package/script/src/utils/metadata/xmp.js +333 -0
- package/script/src/utils/resize.d.ts +4 -0
- package/script/src/utils/resize.js +75 -0
package/script/src/image.d.ts
CHANGED
|
@@ -79,6 +79,21 @@ export declare class Image {
|
|
|
79
79
|
* @returns Image instance
|
|
80
80
|
*/
|
|
81
81
|
static decode(data: Uint8Array, format?: string): Promise<Image>;
|
|
82
|
+
/**
|
|
83
|
+
* Get supported metadata fields for a specific format
|
|
84
|
+
* @param format Format name (e.g., "jpeg", "png", "webp")
|
|
85
|
+
* @returns Array of supported metadata field names, or undefined if format doesn't support metadata
|
|
86
|
+
*/
|
|
87
|
+
static getSupportedMetadata(format: string): Array<keyof ImageMetadata> | undefined;
|
|
88
|
+
/**
|
|
89
|
+
* Extract metadata from image data without fully decoding the pixel data
|
|
90
|
+
* This is useful for quickly reading EXIF, XMP, or other metadata from images
|
|
91
|
+
* that may have unsupported features or compression methods
|
|
92
|
+
* @param data Raw image data
|
|
93
|
+
* @param format Optional format hint (e.g., "png", "jpeg", "webp")
|
|
94
|
+
* @returns Metadata extracted from the image, or undefined if extraction fails or format is unsupported
|
|
95
|
+
*/
|
|
96
|
+
static extractMetadata(data: Uint8Array, format?: string): Promise<ImageMetadata | undefined>;
|
|
82
97
|
/**
|
|
83
98
|
* Read an image from bytes
|
|
84
99
|
* @deprecated Use `decode()` instead. This method will be removed in a future version.
|
|
@@ -197,6 +212,12 @@ export declare class Image {
|
|
|
197
212
|
* @returns This image instance for chaining
|
|
198
213
|
*/
|
|
199
214
|
saturation(amount: number): this;
|
|
215
|
+
/**
|
|
216
|
+
* Adjust hue of the image by rotating the color wheel
|
|
217
|
+
* @param degrees Hue rotation in degrees (any value accepted, wraps at 360)
|
|
218
|
+
* @returns This image instance for chaining
|
|
219
|
+
*/
|
|
220
|
+
hue(degrees: number): this;
|
|
200
221
|
/**
|
|
201
222
|
* Invert colors of the image
|
|
202
223
|
* @returns This image instance for chaining
|
|
@@ -282,5 +303,44 @@ export declare class Image {
|
|
|
282
303
|
* @returns This image instance for chaining
|
|
283
304
|
*/
|
|
284
305
|
setPixel(x: number, y: number, r: number, g: number, b: number, a?: number): this;
|
|
306
|
+
/**
|
|
307
|
+
* Rotate the image 90 degrees clockwise
|
|
308
|
+
* @returns This image instance for chaining
|
|
309
|
+
*/
|
|
310
|
+
rotate90(): this;
|
|
311
|
+
/**
|
|
312
|
+
* Rotate the image 180 degrees
|
|
313
|
+
* @returns This image instance for chaining
|
|
314
|
+
*/
|
|
315
|
+
rotate180(): this;
|
|
316
|
+
/**
|
|
317
|
+
* Rotate the image 270 degrees clockwise (or 90 degrees counter-clockwise)
|
|
318
|
+
* @returns This image instance for chaining
|
|
319
|
+
*/
|
|
320
|
+
rotate270(): this;
|
|
321
|
+
/**
|
|
322
|
+
* Rotate the image by the specified angle in degrees
|
|
323
|
+
* @param degrees Rotation angle in degrees (positive = clockwise, negative = counter-clockwise)
|
|
324
|
+
* @returns This image instance for chaining
|
|
325
|
+
*
|
|
326
|
+
* @example
|
|
327
|
+
* ```ts
|
|
328
|
+
* image.rotate(90); // Rotate 90° clockwise
|
|
329
|
+
* image.rotate(-90); // Rotate 90° counter-clockwise
|
|
330
|
+
* image.rotate(180); // Rotate 180°
|
|
331
|
+
* image.rotate(45); // Rotate 45° clockwise (rounded to nearest 90°)
|
|
332
|
+
* ```
|
|
333
|
+
*/
|
|
334
|
+
rotate(degrees: number): this;
|
|
335
|
+
/**
|
|
336
|
+
* Flip the image horizontally (mirror)
|
|
337
|
+
* @returns This image instance for chaining
|
|
338
|
+
*/
|
|
339
|
+
flipHorizontal(): this;
|
|
340
|
+
/**
|
|
341
|
+
* Flip the image vertically
|
|
342
|
+
* @returns This image instance for chaining
|
|
343
|
+
*/
|
|
344
|
+
flipVertical(): this;
|
|
285
345
|
}
|
|
286
346
|
//# sourceMappingURL=image.d.ts.map
|
package/script/src/image.js
CHANGED
|
@@ -16,6 +16,8 @@ const pam_js_1 = require("./formats/pam.js");
|
|
|
16
16
|
const pcx_js_1 = require("./formats/pcx.js");
|
|
17
17
|
const ppm_js_1 = require("./formats/ppm.js");
|
|
18
18
|
const ascii_js_1 = require("./formats/ascii.js");
|
|
19
|
+
const heic_js_1 = require("./formats/heic.js");
|
|
20
|
+
const avif_js_1 = require("./formats/avif.js");
|
|
19
21
|
const security_js_1 = require("./utils/security.js");
|
|
20
22
|
/**
|
|
21
23
|
* Main Image class for reading, manipulating, and saving images
|
|
@@ -184,6 +186,42 @@ class Image {
|
|
|
184
186
|
}
|
|
185
187
|
throw new Error("Unsupported or unrecognized image format");
|
|
186
188
|
}
|
|
189
|
+
/**
|
|
190
|
+
* Get supported metadata fields for a specific format
|
|
191
|
+
* @param format Format name (e.g., "jpeg", "png", "webp")
|
|
192
|
+
* @returns Array of supported metadata field names, or undefined if format doesn't support metadata
|
|
193
|
+
*/
|
|
194
|
+
static getSupportedMetadata(format) {
|
|
195
|
+
const formatHandler = Image.formats.find((f) => f.name === format.toLowerCase());
|
|
196
|
+
if (!formatHandler) {
|
|
197
|
+
throw new Error(`Unknown image format: ${format}`);
|
|
198
|
+
}
|
|
199
|
+
return formatHandler.getSupportedMetadata?.();
|
|
200
|
+
}
|
|
201
|
+
/**
|
|
202
|
+
* Extract metadata from image data without fully decoding the pixel data
|
|
203
|
+
* This is useful for quickly reading EXIF, XMP, or other metadata from images
|
|
204
|
+
* that may have unsupported features or compression methods
|
|
205
|
+
* @param data Raw image data
|
|
206
|
+
* @param format Optional format hint (e.g., "png", "jpeg", "webp")
|
|
207
|
+
* @returns Metadata extracted from the image, or undefined if extraction fails or format is unsupported
|
|
208
|
+
*/
|
|
209
|
+
static async extractMetadata(data, format) {
|
|
210
|
+
// Try specified format first
|
|
211
|
+
if (format) {
|
|
212
|
+
const handler = Image.formats.find((f) => f.name === format);
|
|
213
|
+
if (handler && handler.canDecode(data) && handler.extractMetadata) {
|
|
214
|
+
return await handler.extractMetadata(data);
|
|
215
|
+
}
|
|
216
|
+
}
|
|
217
|
+
// Auto-detect format
|
|
218
|
+
for (const handler of Image.formats) {
|
|
219
|
+
if (handler.canDecode(data) && handler.extractMetadata) {
|
|
220
|
+
return await handler.extractMetadata(data);
|
|
221
|
+
}
|
|
222
|
+
}
|
|
223
|
+
return undefined;
|
|
224
|
+
}
|
|
187
225
|
/**
|
|
188
226
|
* Read an image from bytes
|
|
189
227
|
* @deprecated Use `decode()` instead. This method will be removed in a future version.
|
|
@@ -304,19 +342,98 @@ class Image {
|
|
|
304
342
|
resize(options) {
|
|
305
343
|
if (!this.imageData)
|
|
306
344
|
throw new Error("No image loaded");
|
|
307
|
-
const { width, height, method = "bilinear" } = options;
|
|
345
|
+
const { width, height, method = "bilinear", fit = "stretch" } = options;
|
|
308
346
|
// Validate new dimensions for security (prevent integer overflow and heap exhaustion)
|
|
309
347
|
(0, security_js_1.validateImageDimensions)(width, height);
|
|
310
348
|
const { data: srcData, width: srcWidth, height: srcHeight } = this.imageData;
|
|
349
|
+
// Handle fitting modes
|
|
350
|
+
let targetWidth = width;
|
|
351
|
+
let targetHeight = height;
|
|
352
|
+
let shouldCenter = false;
|
|
353
|
+
const fitMode = fit === "contain" ? "fit" : fit === "cover" ? "fill" : fit;
|
|
354
|
+
if (fitMode === "fit" || fitMode === "fill") {
|
|
355
|
+
const srcAspect = srcWidth / srcHeight;
|
|
356
|
+
const targetAspect = width / height;
|
|
357
|
+
if (fitMode === "fit") {
|
|
358
|
+
// Fit within dimensions (letterbox)
|
|
359
|
+
if (srcAspect > targetAspect) {
|
|
360
|
+
// Source is wider - fit to width
|
|
361
|
+
targetWidth = width;
|
|
362
|
+
targetHeight = Math.round(width / srcAspect);
|
|
363
|
+
}
|
|
364
|
+
else {
|
|
365
|
+
// Source is taller - fit to height
|
|
366
|
+
targetWidth = Math.round(height * srcAspect);
|
|
367
|
+
targetHeight = height;
|
|
368
|
+
}
|
|
369
|
+
shouldCenter = true;
|
|
370
|
+
}
|
|
371
|
+
else {
|
|
372
|
+
// Fill dimensions (crop)
|
|
373
|
+
if (srcAspect > targetAspect) {
|
|
374
|
+
// Source is wider - fit to height and crop width
|
|
375
|
+
targetWidth = Math.round(height * srcAspect);
|
|
376
|
+
targetHeight = height;
|
|
377
|
+
}
|
|
378
|
+
else {
|
|
379
|
+
// Source is taller - fit to width and crop height
|
|
380
|
+
targetWidth = width;
|
|
381
|
+
targetHeight = Math.round(width / srcAspect);
|
|
382
|
+
}
|
|
383
|
+
shouldCenter = true;
|
|
384
|
+
}
|
|
385
|
+
}
|
|
386
|
+
// Perform the resize
|
|
311
387
|
let resizedData;
|
|
312
388
|
if (method === "nearest") {
|
|
313
|
-
resizedData = (0, resize_js_1.resizeNearest)(srcData, srcWidth, srcHeight,
|
|
389
|
+
resizedData = (0, resize_js_1.resizeNearest)(srcData, srcWidth, srcHeight, targetWidth, targetHeight);
|
|
390
|
+
}
|
|
391
|
+
else if (method === "bicubic") {
|
|
392
|
+
resizedData = (0, resize_js_1.resizeBicubic)(srcData, srcWidth, srcHeight, targetWidth, targetHeight);
|
|
314
393
|
}
|
|
315
394
|
else {
|
|
316
|
-
resizedData = (0, resize_js_1.resizeBilinear)(srcData, srcWidth, srcHeight,
|
|
395
|
+
resizedData = (0, resize_js_1.resizeBilinear)(srcData, srcWidth, srcHeight, targetWidth, targetHeight);
|
|
317
396
|
}
|
|
318
397
|
// Preserve metadata when resizing
|
|
319
398
|
const metadata = this.imageData.metadata;
|
|
399
|
+
// If we need to center (fit mode) or crop (fill mode), create a canvas
|
|
400
|
+
if (shouldCenter && (targetWidth !== width || targetHeight !== height)) {
|
|
401
|
+
const canvas = new Uint8Array(width * height * 4);
|
|
402
|
+
// Fill with transparent black by default
|
|
403
|
+
canvas.fill(0);
|
|
404
|
+
if (fitMode === "fit") {
|
|
405
|
+
// Center the resized image (letterbox)
|
|
406
|
+
const offsetX = Math.floor((width - targetWidth) / 2);
|
|
407
|
+
const offsetY = Math.floor((height - targetHeight) / 2);
|
|
408
|
+
for (let y = 0; y < targetHeight; y++) {
|
|
409
|
+
for (let x = 0; x < targetWidth; x++) {
|
|
410
|
+
const srcIdx = (y * targetWidth + x) * 4;
|
|
411
|
+
const dstIdx = ((y + offsetY) * width + (x + offsetX)) * 4;
|
|
412
|
+
canvas[dstIdx] = resizedData[srcIdx];
|
|
413
|
+
canvas[dstIdx + 1] = resizedData[srcIdx + 1];
|
|
414
|
+
canvas[dstIdx + 2] = resizedData[srcIdx + 2];
|
|
415
|
+
canvas[dstIdx + 3] = resizedData[srcIdx + 3];
|
|
416
|
+
}
|
|
417
|
+
}
|
|
418
|
+
resizedData = canvas;
|
|
419
|
+
}
|
|
420
|
+
else {
|
|
421
|
+
// Crop to fill (center crop)
|
|
422
|
+
const offsetX = Math.floor((targetWidth - width) / 2);
|
|
423
|
+
const offsetY = Math.floor((targetHeight - height) / 2);
|
|
424
|
+
for (let y = 0; y < height; y++) {
|
|
425
|
+
for (let x = 0; x < width; x++) {
|
|
426
|
+
const srcIdx = ((y + offsetY) * targetWidth + (x + offsetX)) * 4;
|
|
427
|
+
const dstIdx = (y * width + x) * 4;
|
|
428
|
+
canvas[dstIdx] = resizedData[srcIdx];
|
|
429
|
+
canvas[dstIdx + 1] = resizedData[srcIdx + 1];
|
|
430
|
+
canvas[dstIdx + 2] = resizedData[srcIdx + 2];
|
|
431
|
+
canvas[dstIdx + 3] = resizedData[srcIdx + 3];
|
|
432
|
+
}
|
|
433
|
+
}
|
|
434
|
+
resizedData = canvas;
|
|
435
|
+
}
|
|
436
|
+
}
|
|
320
437
|
this.imageData = {
|
|
321
438
|
width,
|
|
322
439
|
height,
|
|
@@ -442,6 +559,17 @@ class Image {
|
|
|
442
559
|
this.imageData.data = (0, image_processing_js_1.adjustSaturation)(this.imageData.data, amount);
|
|
443
560
|
return this;
|
|
444
561
|
}
|
|
562
|
+
/**
|
|
563
|
+
* Adjust hue of the image by rotating the color wheel
|
|
564
|
+
* @param degrees Hue rotation in degrees (any value accepted, wraps at 360)
|
|
565
|
+
* @returns This image instance for chaining
|
|
566
|
+
*/
|
|
567
|
+
hue(degrees) {
|
|
568
|
+
if (!this.imageData)
|
|
569
|
+
throw new Error("No image loaded");
|
|
570
|
+
this.imageData.data = (0, image_processing_js_1.adjustHue)(this.imageData.data, degrees);
|
|
571
|
+
return this;
|
|
572
|
+
}
|
|
445
573
|
/**
|
|
446
574
|
* Invert colors of the image
|
|
447
575
|
* @returns This image instance for chaining
|
|
@@ -605,6 +733,124 @@ class Image {
|
|
|
605
733
|
this.imageData.data[idx + 3] = a;
|
|
606
734
|
return this;
|
|
607
735
|
}
|
|
736
|
+
/**
|
|
737
|
+
* Rotate the image 90 degrees clockwise
|
|
738
|
+
* @returns This image instance for chaining
|
|
739
|
+
*/
|
|
740
|
+
rotate90() {
|
|
741
|
+
if (!this.imageData)
|
|
742
|
+
throw new Error("No image loaded");
|
|
743
|
+
const result = (0, image_processing_js_1.rotate90)(this.imageData.data, this.imageData.width, this.imageData.height);
|
|
744
|
+
this.imageData.width = result.width;
|
|
745
|
+
this.imageData.height = result.height;
|
|
746
|
+
this.imageData.data = result.data;
|
|
747
|
+
// Update physical dimensions if DPI is set
|
|
748
|
+
if (this.imageData.metadata) {
|
|
749
|
+
const metadata = this.imageData.metadata;
|
|
750
|
+
if (metadata.dpiX && metadata.dpiY) {
|
|
751
|
+
// Swap physical dimensions
|
|
752
|
+
const tempPhysical = metadata.physicalWidth;
|
|
753
|
+
this.imageData.metadata.physicalWidth = metadata.physicalHeight;
|
|
754
|
+
this.imageData.metadata.physicalHeight = tempPhysical;
|
|
755
|
+
// Swap DPI
|
|
756
|
+
const tempDpi = metadata.dpiX;
|
|
757
|
+
this.imageData.metadata.dpiX = metadata.dpiY;
|
|
758
|
+
this.imageData.metadata.dpiY = tempDpi;
|
|
759
|
+
}
|
|
760
|
+
}
|
|
761
|
+
return this;
|
|
762
|
+
}
|
|
763
|
+
/**
|
|
764
|
+
* Rotate the image 180 degrees
|
|
765
|
+
* @returns This image instance for chaining
|
|
766
|
+
*/
|
|
767
|
+
rotate180() {
|
|
768
|
+
if (!this.imageData)
|
|
769
|
+
throw new Error("No image loaded");
|
|
770
|
+
this.imageData.data = (0, image_processing_js_1.rotate180)(this.imageData.data, this.imageData.width, this.imageData.height);
|
|
771
|
+
return this;
|
|
772
|
+
}
|
|
773
|
+
/**
|
|
774
|
+
* Rotate the image 270 degrees clockwise (or 90 degrees counter-clockwise)
|
|
775
|
+
* @returns This image instance for chaining
|
|
776
|
+
*/
|
|
777
|
+
rotate270() {
|
|
778
|
+
if (!this.imageData)
|
|
779
|
+
throw new Error("No image loaded");
|
|
780
|
+
const result = (0, image_processing_js_1.rotate270)(this.imageData.data, this.imageData.width, this.imageData.height);
|
|
781
|
+
this.imageData.width = result.width;
|
|
782
|
+
this.imageData.height = result.height;
|
|
783
|
+
this.imageData.data = result.data;
|
|
784
|
+
// Update physical dimensions if DPI is set
|
|
785
|
+
if (this.imageData.metadata) {
|
|
786
|
+
const metadata = this.imageData.metadata;
|
|
787
|
+
if (metadata.dpiX && metadata.dpiY) {
|
|
788
|
+
// Swap physical dimensions
|
|
789
|
+
const tempPhysical = metadata.physicalWidth;
|
|
790
|
+
this.imageData.metadata.physicalWidth = metadata.physicalHeight;
|
|
791
|
+
this.imageData.metadata.physicalHeight = tempPhysical;
|
|
792
|
+
// Swap DPI
|
|
793
|
+
const tempDpi = metadata.dpiX;
|
|
794
|
+
this.imageData.metadata.dpiX = metadata.dpiY;
|
|
795
|
+
this.imageData.metadata.dpiY = tempDpi;
|
|
796
|
+
}
|
|
797
|
+
}
|
|
798
|
+
return this;
|
|
799
|
+
}
|
|
800
|
+
/**
|
|
801
|
+
* Rotate the image by the specified angle in degrees
|
|
802
|
+
* @param degrees Rotation angle in degrees (positive = clockwise, negative = counter-clockwise)
|
|
803
|
+
* @returns This image instance for chaining
|
|
804
|
+
*
|
|
805
|
+
* @example
|
|
806
|
+
* ```ts
|
|
807
|
+
* image.rotate(90); // Rotate 90° clockwise
|
|
808
|
+
* image.rotate(-90); // Rotate 90° counter-clockwise
|
|
809
|
+
* image.rotate(180); // Rotate 180°
|
|
810
|
+
* image.rotate(45); // Rotate 45° clockwise (rounded to nearest 90°)
|
|
811
|
+
* ```
|
|
812
|
+
*/
|
|
813
|
+
rotate(degrees) {
|
|
814
|
+
// Normalize to 0-360 range
|
|
815
|
+
let normalizedDegrees = degrees % 360;
|
|
816
|
+
if (normalizedDegrees < 0) {
|
|
817
|
+
normalizedDegrees += 360;
|
|
818
|
+
}
|
|
819
|
+
// Round to nearest 90 degrees
|
|
820
|
+
const rounded = Math.round(normalizedDegrees / 90) * 90;
|
|
821
|
+
// Apply rotation based on rounded value
|
|
822
|
+
switch (rounded % 360) {
|
|
823
|
+
case 90:
|
|
824
|
+
return this.rotate90();
|
|
825
|
+
case 180:
|
|
826
|
+
return this.rotate180();
|
|
827
|
+
case 270:
|
|
828
|
+
return this.rotate270();
|
|
829
|
+
default:
|
|
830
|
+
// 0 or 360 degrees - no rotation needed
|
|
831
|
+
return this;
|
|
832
|
+
}
|
|
833
|
+
}
|
|
834
|
+
/**
|
|
835
|
+
* Flip the image horizontally (mirror)
|
|
836
|
+
* @returns This image instance for chaining
|
|
837
|
+
*/
|
|
838
|
+
flipHorizontal() {
|
|
839
|
+
if (!this.imageData)
|
|
840
|
+
throw new Error("No image loaded");
|
|
841
|
+
this.imageData.data = (0, image_processing_js_1.flipHorizontal)(this.imageData.data, this.imageData.width, this.imageData.height);
|
|
842
|
+
return this;
|
|
843
|
+
}
|
|
844
|
+
/**
|
|
845
|
+
* Flip the image vertically
|
|
846
|
+
* @returns This image instance for chaining
|
|
847
|
+
*/
|
|
848
|
+
flipVertical() {
|
|
849
|
+
if (!this.imageData)
|
|
850
|
+
throw new Error("No image loaded");
|
|
851
|
+
this.imageData.data = (0, image_processing_js_1.flipVertical)(this.imageData.data, this.imageData.width, this.imageData.height);
|
|
852
|
+
return this;
|
|
853
|
+
}
|
|
608
854
|
}
|
|
609
855
|
exports.Image = Image;
|
|
610
856
|
Object.defineProperty(Image, "formats", {
|
|
@@ -625,5 +871,7 @@ Object.defineProperty(Image, "formats", {
|
|
|
625
871
|
new pcx_js_1.PCXFormat(),
|
|
626
872
|
new ppm_js_1.PPMFormat(),
|
|
627
873
|
new ascii_js_1.ASCIIFormat(),
|
|
874
|
+
new heic_js_1.HEICFormat(),
|
|
875
|
+
new avif_js_1.AVIFFormat(),
|
|
628
876
|
]
|
|
629
877
|
});
|
package/script/src/types.d.ts
CHANGED
|
@@ -24,6 +24,42 @@ export interface ImageMetadata {
|
|
|
24
24
|
copyright?: string;
|
|
25
25
|
/** Creation date */
|
|
26
26
|
creationDate?: Date;
|
|
27
|
+
/** Camera make/manufacturer (e.g., "Canon", "Nikon") */
|
|
28
|
+
cameraMake?: string;
|
|
29
|
+
/** Camera model (e.g., "Canon EOS 5D Mark IV") */
|
|
30
|
+
cameraModel?: string;
|
|
31
|
+
/** Lens make/manufacturer */
|
|
32
|
+
lensMake?: string;
|
|
33
|
+
/** Lens model */
|
|
34
|
+
lensModel?: string;
|
|
35
|
+
/** ISO speed rating (e.g., 100, 400, 3200) */
|
|
36
|
+
iso?: number;
|
|
37
|
+
/** Exposure time / Shutter speed in seconds (e.g., 0.0125 = 1/80s) */
|
|
38
|
+
exposureTime?: number;
|
|
39
|
+
/** F-number / Aperture (e.g., 2.8, 5.6, 16) */
|
|
40
|
+
fNumber?: number;
|
|
41
|
+
/** Focal length in millimeters (e.g., 50, 85, 200) */
|
|
42
|
+
focalLength?: number;
|
|
43
|
+
/** Flash mode (0 = no flash, 1 = flash fired) */
|
|
44
|
+
flash?: number;
|
|
45
|
+
/** White balance mode (0 = auto, 1 = manual) */
|
|
46
|
+
whiteBalance?: number;
|
|
47
|
+
/** Orientation (1 = normal, 3 = 180°, 6 = 90° CW, 8 = 90° CCW) */
|
|
48
|
+
orientation?: number;
|
|
49
|
+
/** Software used to create/edit the image */
|
|
50
|
+
software?: string;
|
|
51
|
+
/** User comment / notes */
|
|
52
|
+
userComment?: string;
|
|
53
|
+
/** Image file format (e.g., "png", "jpeg", "gif", "webp", "tiff") */
|
|
54
|
+
format?: string;
|
|
55
|
+
/** Compression algorithm used (e.g., "deflate", "lzw", "dct", "vp8", "vp8l", "none") */
|
|
56
|
+
compression?: string;
|
|
57
|
+
/** Number of frames in multi-frame images (e.g., animated GIFs, APNGs, multi-page TIFFs) */
|
|
58
|
+
frameCount?: number;
|
|
59
|
+
/** Bit depth per channel (e.g., 8, 16) */
|
|
60
|
+
bitDepth?: number;
|
|
61
|
+
/** Color type (e.g., "grayscale", "rgb", "rgba", "indexed", "grayscale-alpha") */
|
|
62
|
+
colorType?: string;
|
|
27
63
|
/** Custom metadata fields */
|
|
28
64
|
custom?: Record<string, string | number | boolean>;
|
|
29
65
|
}
|
|
@@ -88,7 +124,16 @@ export interface ResizeOptions {
|
|
|
88
124
|
/** Target height in pixels */
|
|
89
125
|
height: number;
|
|
90
126
|
/** Resize method (default: "bilinear") */
|
|
91
|
-
method?: "nearest" | "bilinear";
|
|
127
|
+
method?: "nearest" | "bilinear" | "bicubic";
|
|
128
|
+
/**
|
|
129
|
+
* Fitting mode (default: "stretch")
|
|
130
|
+
* - "stretch": Stretch image to fill dimensions (may distort)
|
|
131
|
+
* - "fit": Fit image within dimensions maintaining aspect ratio (may have letterboxing)
|
|
132
|
+
* - "fill": Fill dimensions maintaining aspect ratio (may crop)
|
|
133
|
+
* - "cover": Alias for "fill"
|
|
134
|
+
* - "contain": Alias for "fit"
|
|
135
|
+
*/
|
|
136
|
+
fit?: "stretch" | "fit" | "fill" | "cover" | "contain";
|
|
92
137
|
}
|
|
93
138
|
/**
|
|
94
139
|
* Options for ASCII art encoding
|
|
@@ -163,5 +208,18 @@ export interface ImageFormat {
|
|
|
163
208
|
* Check if the format supports multiple frames
|
|
164
209
|
*/
|
|
165
210
|
supportsMultipleFrames?(): boolean;
|
|
211
|
+
/**
|
|
212
|
+
* Get the list of metadata fields supported by this format
|
|
213
|
+
* @returns Array of metadata field names that can be persisted
|
|
214
|
+
*/
|
|
215
|
+
getSupportedMetadata?(): Array<keyof ImageMetadata>;
|
|
216
|
+
/**
|
|
217
|
+
* Extract metadata from image data without fully decoding the pixel data
|
|
218
|
+
* This is useful for quickly reading EXIF, XMP, or other metadata from images
|
|
219
|
+
* that may have unsupported features or compression methods
|
|
220
|
+
* @param data Raw image data
|
|
221
|
+
* @returns Metadata extracted from the image, or undefined if extraction fails
|
|
222
|
+
*/
|
|
223
|
+
extractMetadata?(data: Uint8Array): Promise<ImageMetadata | undefined>;
|
|
166
224
|
}
|
|
167
225
|
//# sourceMappingURL=types.d.ts.map
|
|
@@ -44,6 +44,13 @@ export declare function adjustExposure(data: Uint8Array, amount: number): Uint8A
|
|
|
44
44
|
* @returns New image data with adjusted saturation
|
|
45
45
|
*/
|
|
46
46
|
export declare function adjustSaturation(data: Uint8Array, amount: number): Uint8Array;
|
|
47
|
+
/**
|
|
48
|
+
* Adjust hue of an image by rotating the hue wheel
|
|
49
|
+
* @param data Image data (RGBA)
|
|
50
|
+
* @param degrees Hue rotation in degrees (any value accepted, wraps at 360)
|
|
51
|
+
* @returns New image data with adjusted hue
|
|
52
|
+
*/
|
|
53
|
+
export declare function adjustHue(data: Uint8Array, degrees: number): Uint8Array;
|
|
47
54
|
/**
|
|
48
55
|
* Invert colors of an image
|
|
49
56
|
* @param data Image data (RGBA)
|
|
@@ -131,4 +138,52 @@ export declare function sepia(data: Uint8Array): Uint8Array;
|
|
|
131
138
|
* @returns New image data with median filter applied
|
|
132
139
|
*/
|
|
133
140
|
export declare function medianFilter(data: Uint8Array, width: number, height: number, radius?: number): Uint8Array;
|
|
141
|
+
/**
|
|
142
|
+
* Rotate image 90 degrees clockwise
|
|
143
|
+
* @param data Image data (RGBA)
|
|
144
|
+
* @param width Image width
|
|
145
|
+
* @param height Image height
|
|
146
|
+
* @returns Rotated image data with swapped dimensions
|
|
147
|
+
*/
|
|
148
|
+
export declare function rotate90(data: Uint8Array, width: number, height: number): {
|
|
149
|
+
data: Uint8Array;
|
|
150
|
+
width: number;
|
|
151
|
+
height: number;
|
|
152
|
+
};
|
|
153
|
+
/**
|
|
154
|
+
* Rotate image 180 degrees
|
|
155
|
+
* @param data Image data (RGBA)
|
|
156
|
+
* @param width Image width
|
|
157
|
+
* @param height Image height
|
|
158
|
+
* @returns Rotated image data with same dimensions
|
|
159
|
+
*/
|
|
160
|
+
export declare function rotate180(data: Uint8Array, width: number, height: number): Uint8Array;
|
|
161
|
+
/**
|
|
162
|
+
* Rotate image 270 degrees clockwise (or 90 degrees counter-clockwise)
|
|
163
|
+
* @param data Image data (RGBA)
|
|
164
|
+
* @param width Image width
|
|
165
|
+
* @param height Image height
|
|
166
|
+
* @returns Rotated image data with swapped dimensions
|
|
167
|
+
*/
|
|
168
|
+
export declare function rotate270(data: Uint8Array, width: number, height: number): {
|
|
169
|
+
data: Uint8Array;
|
|
170
|
+
width: number;
|
|
171
|
+
height: number;
|
|
172
|
+
};
|
|
173
|
+
/**
|
|
174
|
+
* Flip image horizontally (mirror)
|
|
175
|
+
* @param data Image data (RGBA)
|
|
176
|
+
* @param width Image width
|
|
177
|
+
* @param height Image height
|
|
178
|
+
* @returns Flipped image data
|
|
179
|
+
*/
|
|
180
|
+
export declare function flipHorizontal(data: Uint8Array, width: number, height: number): Uint8Array;
|
|
181
|
+
/**
|
|
182
|
+
* Flip image vertically
|
|
183
|
+
* @param data Image data (RGBA)
|
|
184
|
+
* @param width Image width
|
|
185
|
+
* @param height Image height
|
|
186
|
+
* @returns Flipped image data
|
|
187
|
+
*/
|
|
188
|
+
export declare function flipVertical(data: Uint8Array, width: number, height: number): Uint8Array;
|
|
134
189
|
//# sourceMappingURL=image_processing.d.ts.map
|