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/esm/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/esm/src/image.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import { resizeBilinear, resizeNearest } from "./utils/resize.js";
|
|
2
|
-
import { adjustBrightness, adjustContrast, adjustExposure, adjustSaturation, boxBlur, composite, crop, fillRect, gaussianBlur, grayscale, invert, medianFilter, sepia, sharpen, } from "./utils/image_processing.js";
|
|
1
|
+
import { resizeBicubic, resizeBilinear, resizeNearest, } from "./utils/resize.js";
|
|
2
|
+
import { adjustBrightness, adjustContrast, adjustExposure, adjustHue, adjustSaturation, boxBlur, composite, crop, fillRect, flipHorizontal, flipVertical, gaussianBlur, grayscale, invert, medianFilter, rotate180, rotate270, rotate90, sepia, sharpen, } from "./utils/image_processing.js";
|
|
3
3
|
import { PNGFormat } from "./formats/png.js";
|
|
4
4
|
import { APNGFormat } from "./formats/apng.js";
|
|
5
5
|
import { JPEGFormat } from "./formats/jpeg.js";
|
|
@@ -13,6 +13,8 @@ import { PAMFormat } from "./formats/pam.js";
|
|
|
13
13
|
import { PCXFormat } from "./formats/pcx.js";
|
|
14
14
|
import { PPMFormat } from "./formats/ppm.js";
|
|
15
15
|
import { ASCIIFormat } from "./formats/ascii.js";
|
|
16
|
+
import { HEICFormat } from "./formats/heic.js";
|
|
17
|
+
import { AVIFFormat } from "./formats/avif.js";
|
|
16
18
|
import { validateImageDimensions } from "./utils/security.js";
|
|
17
19
|
/**
|
|
18
20
|
* Main Image class for reading, manipulating, and saving images
|
|
@@ -181,6 +183,42 @@ export class Image {
|
|
|
181
183
|
}
|
|
182
184
|
throw new Error("Unsupported or unrecognized image format");
|
|
183
185
|
}
|
|
186
|
+
/**
|
|
187
|
+
* Get supported metadata fields for a specific format
|
|
188
|
+
* @param format Format name (e.g., "jpeg", "png", "webp")
|
|
189
|
+
* @returns Array of supported metadata field names, or undefined if format doesn't support metadata
|
|
190
|
+
*/
|
|
191
|
+
static getSupportedMetadata(format) {
|
|
192
|
+
const formatHandler = Image.formats.find((f) => f.name === format.toLowerCase());
|
|
193
|
+
if (!formatHandler) {
|
|
194
|
+
throw new Error(`Unknown image format: ${format}`);
|
|
195
|
+
}
|
|
196
|
+
return formatHandler.getSupportedMetadata?.();
|
|
197
|
+
}
|
|
198
|
+
/**
|
|
199
|
+
* Extract metadata from image data without fully decoding the pixel data
|
|
200
|
+
* This is useful for quickly reading EXIF, XMP, or other metadata from images
|
|
201
|
+
* that may have unsupported features or compression methods
|
|
202
|
+
* @param data Raw image data
|
|
203
|
+
* @param format Optional format hint (e.g., "png", "jpeg", "webp")
|
|
204
|
+
* @returns Metadata extracted from the image, or undefined if extraction fails or format is unsupported
|
|
205
|
+
*/
|
|
206
|
+
static async extractMetadata(data, format) {
|
|
207
|
+
// Try specified format first
|
|
208
|
+
if (format) {
|
|
209
|
+
const handler = Image.formats.find((f) => f.name === format);
|
|
210
|
+
if (handler && handler.canDecode(data) && handler.extractMetadata) {
|
|
211
|
+
return await handler.extractMetadata(data);
|
|
212
|
+
}
|
|
213
|
+
}
|
|
214
|
+
// Auto-detect format
|
|
215
|
+
for (const handler of Image.formats) {
|
|
216
|
+
if (handler.canDecode(data) && handler.extractMetadata) {
|
|
217
|
+
return await handler.extractMetadata(data);
|
|
218
|
+
}
|
|
219
|
+
}
|
|
220
|
+
return undefined;
|
|
221
|
+
}
|
|
184
222
|
/**
|
|
185
223
|
* Read an image from bytes
|
|
186
224
|
* @deprecated Use `decode()` instead. This method will be removed in a future version.
|
|
@@ -301,19 +339,98 @@ export class Image {
|
|
|
301
339
|
resize(options) {
|
|
302
340
|
if (!this.imageData)
|
|
303
341
|
throw new Error("No image loaded");
|
|
304
|
-
const { width, height, method = "bilinear" } = options;
|
|
342
|
+
const { width, height, method = "bilinear", fit = "stretch" } = options;
|
|
305
343
|
// Validate new dimensions for security (prevent integer overflow and heap exhaustion)
|
|
306
344
|
validateImageDimensions(width, height);
|
|
307
345
|
const { data: srcData, width: srcWidth, height: srcHeight } = this.imageData;
|
|
346
|
+
// Handle fitting modes
|
|
347
|
+
let targetWidth = width;
|
|
348
|
+
let targetHeight = height;
|
|
349
|
+
let shouldCenter = false;
|
|
350
|
+
const fitMode = fit === "contain" ? "fit" : fit === "cover" ? "fill" : fit;
|
|
351
|
+
if (fitMode === "fit" || fitMode === "fill") {
|
|
352
|
+
const srcAspect = srcWidth / srcHeight;
|
|
353
|
+
const targetAspect = width / height;
|
|
354
|
+
if (fitMode === "fit") {
|
|
355
|
+
// Fit within dimensions (letterbox)
|
|
356
|
+
if (srcAspect > targetAspect) {
|
|
357
|
+
// Source is wider - fit to width
|
|
358
|
+
targetWidth = width;
|
|
359
|
+
targetHeight = Math.round(width / srcAspect);
|
|
360
|
+
}
|
|
361
|
+
else {
|
|
362
|
+
// Source is taller - fit to height
|
|
363
|
+
targetWidth = Math.round(height * srcAspect);
|
|
364
|
+
targetHeight = height;
|
|
365
|
+
}
|
|
366
|
+
shouldCenter = true;
|
|
367
|
+
}
|
|
368
|
+
else {
|
|
369
|
+
// Fill dimensions (crop)
|
|
370
|
+
if (srcAspect > targetAspect) {
|
|
371
|
+
// Source is wider - fit to height and crop width
|
|
372
|
+
targetWidth = Math.round(height * srcAspect);
|
|
373
|
+
targetHeight = height;
|
|
374
|
+
}
|
|
375
|
+
else {
|
|
376
|
+
// Source is taller - fit to width and crop height
|
|
377
|
+
targetWidth = width;
|
|
378
|
+
targetHeight = Math.round(width / srcAspect);
|
|
379
|
+
}
|
|
380
|
+
shouldCenter = true;
|
|
381
|
+
}
|
|
382
|
+
}
|
|
383
|
+
// Perform the resize
|
|
308
384
|
let resizedData;
|
|
309
385
|
if (method === "nearest") {
|
|
310
|
-
resizedData = resizeNearest(srcData, srcWidth, srcHeight,
|
|
386
|
+
resizedData = resizeNearest(srcData, srcWidth, srcHeight, targetWidth, targetHeight);
|
|
387
|
+
}
|
|
388
|
+
else if (method === "bicubic") {
|
|
389
|
+
resizedData = resizeBicubic(srcData, srcWidth, srcHeight, targetWidth, targetHeight);
|
|
311
390
|
}
|
|
312
391
|
else {
|
|
313
|
-
resizedData = resizeBilinear(srcData, srcWidth, srcHeight,
|
|
392
|
+
resizedData = resizeBilinear(srcData, srcWidth, srcHeight, targetWidth, targetHeight);
|
|
314
393
|
}
|
|
315
394
|
// Preserve metadata when resizing
|
|
316
395
|
const metadata = this.imageData.metadata;
|
|
396
|
+
// If we need to center (fit mode) or crop (fill mode), create a canvas
|
|
397
|
+
if (shouldCenter && (targetWidth !== width || targetHeight !== height)) {
|
|
398
|
+
const canvas = new Uint8Array(width * height * 4);
|
|
399
|
+
// Fill with transparent black by default
|
|
400
|
+
canvas.fill(0);
|
|
401
|
+
if (fitMode === "fit") {
|
|
402
|
+
// Center the resized image (letterbox)
|
|
403
|
+
const offsetX = Math.floor((width - targetWidth) / 2);
|
|
404
|
+
const offsetY = Math.floor((height - targetHeight) / 2);
|
|
405
|
+
for (let y = 0; y < targetHeight; y++) {
|
|
406
|
+
for (let x = 0; x < targetWidth; x++) {
|
|
407
|
+
const srcIdx = (y * targetWidth + x) * 4;
|
|
408
|
+
const dstIdx = ((y + offsetY) * width + (x + offsetX)) * 4;
|
|
409
|
+
canvas[dstIdx] = resizedData[srcIdx];
|
|
410
|
+
canvas[dstIdx + 1] = resizedData[srcIdx + 1];
|
|
411
|
+
canvas[dstIdx + 2] = resizedData[srcIdx + 2];
|
|
412
|
+
canvas[dstIdx + 3] = resizedData[srcIdx + 3];
|
|
413
|
+
}
|
|
414
|
+
}
|
|
415
|
+
resizedData = canvas;
|
|
416
|
+
}
|
|
417
|
+
else {
|
|
418
|
+
// Crop to fill (center crop)
|
|
419
|
+
const offsetX = Math.floor((targetWidth - width) / 2);
|
|
420
|
+
const offsetY = Math.floor((targetHeight - height) / 2);
|
|
421
|
+
for (let y = 0; y < height; y++) {
|
|
422
|
+
for (let x = 0; x < width; x++) {
|
|
423
|
+
const srcIdx = ((y + offsetY) * targetWidth + (x + offsetX)) * 4;
|
|
424
|
+
const dstIdx = (y * width + x) * 4;
|
|
425
|
+
canvas[dstIdx] = resizedData[srcIdx];
|
|
426
|
+
canvas[dstIdx + 1] = resizedData[srcIdx + 1];
|
|
427
|
+
canvas[dstIdx + 2] = resizedData[srcIdx + 2];
|
|
428
|
+
canvas[dstIdx + 3] = resizedData[srcIdx + 3];
|
|
429
|
+
}
|
|
430
|
+
}
|
|
431
|
+
resizedData = canvas;
|
|
432
|
+
}
|
|
433
|
+
}
|
|
317
434
|
this.imageData = {
|
|
318
435
|
width,
|
|
319
436
|
height,
|
|
@@ -439,6 +556,17 @@ export class Image {
|
|
|
439
556
|
this.imageData.data = adjustSaturation(this.imageData.data, amount);
|
|
440
557
|
return this;
|
|
441
558
|
}
|
|
559
|
+
/**
|
|
560
|
+
* Adjust hue of the image by rotating the color wheel
|
|
561
|
+
* @param degrees Hue rotation in degrees (any value accepted, wraps at 360)
|
|
562
|
+
* @returns This image instance for chaining
|
|
563
|
+
*/
|
|
564
|
+
hue(degrees) {
|
|
565
|
+
if (!this.imageData)
|
|
566
|
+
throw new Error("No image loaded");
|
|
567
|
+
this.imageData.data = adjustHue(this.imageData.data, degrees);
|
|
568
|
+
return this;
|
|
569
|
+
}
|
|
442
570
|
/**
|
|
443
571
|
* Invert colors of the image
|
|
444
572
|
* @returns This image instance for chaining
|
|
@@ -602,6 +730,124 @@ export class Image {
|
|
|
602
730
|
this.imageData.data[idx + 3] = a;
|
|
603
731
|
return this;
|
|
604
732
|
}
|
|
733
|
+
/**
|
|
734
|
+
* Rotate the image 90 degrees clockwise
|
|
735
|
+
* @returns This image instance for chaining
|
|
736
|
+
*/
|
|
737
|
+
rotate90() {
|
|
738
|
+
if (!this.imageData)
|
|
739
|
+
throw new Error("No image loaded");
|
|
740
|
+
const result = rotate90(this.imageData.data, this.imageData.width, this.imageData.height);
|
|
741
|
+
this.imageData.width = result.width;
|
|
742
|
+
this.imageData.height = result.height;
|
|
743
|
+
this.imageData.data = result.data;
|
|
744
|
+
// Update physical dimensions if DPI is set
|
|
745
|
+
if (this.imageData.metadata) {
|
|
746
|
+
const metadata = this.imageData.metadata;
|
|
747
|
+
if (metadata.dpiX && metadata.dpiY) {
|
|
748
|
+
// Swap physical dimensions
|
|
749
|
+
const tempPhysical = metadata.physicalWidth;
|
|
750
|
+
this.imageData.metadata.physicalWidth = metadata.physicalHeight;
|
|
751
|
+
this.imageData.metadata.physicalHeight = tempPhysical;
|
|
752
|
+
// Swap DPI
|
|
753
|
+
const tempDpi = metadata.dpiX;
|
|
754
|
+
this.imageData.metadata.dpiX = metadata.dpiY;
|
|
755
|
+
this.imageData.metadata.dpiY = tempDpi;
|
|
756
|
+
}
|
|
757
|
+
}
|
|
758
|
+
return this;
|
|
759
|
+
}
|
|
760
|
+
/**
|
|
761
|
+
* Rotate the image 180 degrees
|
|
762
|
+
* @returns This image instance for chaining
|
|
763
|
+
*/
|
|
764
|
+
rotate180() {
|
|
765
|
+
if (!this.imageData)
|
|
766
|
+
throw new Error("No image loaded");
|
|
767
|
+
this.imageData.data = rotate180(this.imageData.data, this.imageData.width, this.imageData.height);
|
|
768
|
+
return this;
|
|
769
|
+
}
|
|
770
|
+
/**
|
|
771
|
+
* Rotate the image 270 degrees clockwise (or 90 degrees counter-clockwise)
|
|
772
|
+
* @returns This image instance for chaining
|
|
773
|
+
*/
|
|
774
|
+
rotate270() {
|
|
775
|
+
if (!this.imageData)
|
|
776
|
+
throw new Error("No image loaded");
|
|
777
|
+
const result = rotate270(this.imageData.data, this.imageData.width, this.imageData.height);
|
|
778
|
+
this.imageData.width = result.width;
|
|
779
|
+
this.imageData.height = result.height;
|
|
780
|
+
this.imageData.data = result.data;
|
|
781
|
+
// Update physical dimensions if DPI is set
|
|
782
|
+
if (this.imageData.metadata) {
|
|
783
|
+
const metadata = this.imageData.metadata;
|
|
784
|
+
if (metadata.dpiX && metadata.dpiY) {
|
|
785
|
+
// Swap physical dimensions
|
|
786
|
+
const tempPhysical = metadata.physicalWidth;
|
|
787
|
+
this.imageData.metadata.physicalWidth = metadata.physicalHeight;
|
|
788
|
+
this.imageData.metadata.physicalHeight = tempPhysical;
|
|
789
|
+
// Swap DPI
|
|
790
|
+
const tempDpi = metadata.dpiX;
|
|
791
|
+
this.imageData.metadata.dpiX = metadata.dpiY;
|
|
792
|
+
this.imageData.metadata.dpiY = tempDpi;
|
|
793
|
+
}
|
|
794
|
+
}
|
|
795
|
+
return this;
|
|
796
|
+
}
|
|
797
|
+
/**
|
|
798
|
+
* Rotate the image by the specified angle in degrees
|
|
799
|
+
* @param degrees Rotation angle in degrees (positive = clockwise, negative = counter-clockwise)
|
|
800
|
+
* @returns This image instance for chaining
|
|
801
|
+
*
|
|
802
|
+
* @example
|
|
803
|
+
* ```ts
|
|
804
|
+
* image.rotate(90); // Rotate 90° clockwise
|
|
805
|
+
* image.rotate(-90); // Rotate 90° counter-clockwise
|
|
806
|
+
* image.rotate(180); // Rotate 180°
|
|
807
|
+
* image.rotate(45); // Rotate 45° clockwise (rounded to nearest 90°)
|
|
808
|
+
* ```
|
|
809
|
+
*/
|
|
810
|
+
rotate(degrees) {
|
|
811
|
+
// Normalize to 0-360 range
|
|
812
|
+
let normalizedDegrees = degrees % 360;
|
|
813
|
+
if (normalizedDegrees < 0) {
|
|
814
|
+
normalizedDegrees += 360;
|
|
815
|
+
}
|
|
816
|
+
// Round to nearest 90 degrees
|
|
817
|
+
const rounded = Math.round(normalizedDegrees / 90) * 90;
|
|
818
|
+
// Apply rotation based on rounded value
|
|
819
|
+
switch (rounded % 360) {
|
|
820
|
+
case 90:
|
|
821
|
+
return this.rotate90();
|
|
822
|
+
case 180:
|
|
823
|
+
return this.rotate180();
|
|
824
|
+
case 270:
|
|
825
|
+
return this.rotate270();
|
|
826
|
+
default:
|
|
827
|
+
// 0 or 360 degrees - no rotation needed
|
|
828
|
+
return this;
|
|
829
|
+
}
|
|
830
|
+
}
|
|
831
|
+
/**
|
|
832
|
+
* Flip the image horizontally (mirror)
|
|
833
|
+
* @returns This image instance for chaining
|
|
834
|
+
*/
|
|
835
|
+
flipHorizontal() {
|
|
836
|
+
if (!this.imageData)
|
|
837
|
+
throw new Error("No image loaded");
|
|
838
|
+
this.imageData.data = flipHorizontal(this.imageData.data, this.imageData.width, this.imageData.height);
|
|
839
|
+
return this;
|
|
840
|
+
}
|
|
841
|
+
/**
|
|
842
|
+
* Flip the image vertically
|
|
843
|
+
* @returns This image instance for chaining
|
|
844
|
+
*/
|
|
845
|
+
flipVertical() {
|
|
846
|
+
if (!this.imageData)
|
|
847
|
+
throw new Error("No image loaded");
|
|
848
|
+
this.imageData.data = flipVertical(this.imageData.data, this.imageData.width, this.imageData.height);
|
|
849
|
+
return this;
|
|
850
|
+
}
|
|
605
851
|
}
|
|
606
852
|
Object.defineProperty(Image, "formats", {
|
|
607
853
|
enumerable: true,
|
|
@@ -621,5 +867,7 @@ Object.defineProperty(Image, "formats", {
|
|
|
621
867
|
new PCXFormat(),
|
|
622
868
|
new PPMFormat(),
|
|
623
869
|
new ASCIIFormat(),
|
|
870
|
+
new HEICFormat(),
|
|
871
|
+
new AVIFFormat(),
|
|
624
872
|
]
|
|
625
873
|
});
|
package/esm/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
|