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.
- package/README.md +160 -32
- package/esm/mod.d.ts +2 -1
- package/esm/mod.js +2 -1
- package/esm/src/formats/jpeg.d.ts +12 -1
- package/esm/src/formats/jpeg.js +633 -4
- 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 +50 -0
- package/esm/src/formats/ppm.js +242 -0
- package/esm/src/formats/tiff.d.ts +10 -1
- package/esm/src/formats/tiff.js +194 -44
- package/esm/src/formats/webp.d.ts +9 -2
- package/esm/src/formats/webp.js +211 -62
- package/esm/src/image.d.ts +81 -0
- package/esm/src/image.js +282 -5
- package/esm/src/types.d.ts +41 -1
- package/esm/src/utils/image_processing.d.ts +98 -0
- package/esm/src/utils/image_processing.js +440 -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 +1 -1
- package/script/mod.d.ts +2 -1
- package/script/mod.js +4 -2
- package/script/src/formats/jpeg.d.ts +12 -1
- package/script/src/formats/jpeg.js +633 -4
- 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 +50 -0
- package/script/src/formats/ppm.js +246 -0
- package/script/src/formats/tiff.d.ts +10 -1
- package/script/src/formats/tiff.js +194 -44
- package/script/src/formats/webp.d.ts +9 -2
- package/script/src/formats/webp.js +211 -62
- package/script/src/image.d.ts +81 -0
- package/script/src/image.js +280 -3
- package/script/src/types.d.ts +41 -1
- package/script/src/utils/image_processing.d.ts +98 -0
- package/script/src/utils/image_processing.js +451 -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/formats/webp.js
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { validateImageDimensions } from "../utils/security.js";
|
|
2
2
|
import { readUint32LE } from "../utils/byte_utils.js";
|
|
3
|
+
import { createXMP, parseXMP } from "../utils/metadata/xmp.js";
|
|
3
4
|
// Default quality for WebP encoding when not specified
|
|
4
5
|
const DEFAULT_WEBP_QUALITY = 90;
|
|
5
6
|
/**
|
|
@@ -219,6 +220,7 @@ export class WebPFormat {
|
|
|
219
220
|
const numEntries = littleEndian
|
|
220
221
|
? data[ifd0Offset] | (data[ifd0Offset + 1] << 8)
|
|
221
222
|
: (data[ifd0Offset] << 8) | data[ifd0Offset + 1];
|
|
223
|
+
let gpsIfdOffset = 0;
|
|
222
224
|
// Parse basic EXIF tags (simplified version)
|
|
223
225
|
for (let i = 0; i < numEntries && i < 50; i++) {
|
|
224
226
|
const entryOffset = ifd0Offset + 2 + i * 12;
|
|
@@ -245,32 +247,103 @@ export class WebPFormat {
|
|
|
245
247
|
}
|
|
246
248
|
}
|
|
247
249
|
}
|
|
250
|
+
// GPS IFD Pointer tag (0x8825)
|
|
251
|
+
if (tag === 0x8825) {
|
|
252
|
+
gpsIfdOffset = littleEndian
|
|
253
|
+
? data[entryOffset + 8] | (data[entryOffset + 9] << 8) |
|
|
254
|
+
(data[entryOffset + 10] << 16) | (data[entryOffset + 11] << 24)
|
|
255
|
+
: (data[entryOffset + 8] << 24) | (data[entryOffset + 9] << 16) |
|
|
256
|
+
(data[entryOffset + 10] << 8) | data[entryOffset + 11];
|
|
257
|
+
}
|
|
258
|
+
}
|
|
259
|
+
// Parse GPS IFD if present
|
|
260
|
+
if (gpsIfdOffset > 0 && gpsIfdOffset + 2 <= data.length) {
|
|
261
|
+
this.parseGPSIFD(data, gpsIfdOffset, littleEndian, metadata);
|
|
248
262
|
}
|
|
249
263
|
}
|
|
250
264
|
catch (_e) {
|
|
251
265
|
// Ignore EXIF parsing errors
|
|
252
266
|
}
|
|
253
267
|
}
|
|
268
|
+
parseGPSIFD(data, gpsIfdOffset, littleEndian, metadata) {
|
|
269
|
+
try {
|
|
270
|
+
const numEntries = littleEndian
|
|
271
|
+
? data[gpsIfdOffset] | (data[gpsIfdOffset + 1] << 8)
|
|
272
|
+
: (data[gpsIfdOffset] << 8) | data[gpsIfdOffset + 1];
|
|
273
|
+
let latRef = "";
|
|
274
|
+
let lonRef = "";
|
|
275
|
+
let latitude;
|
|
276
|
+
let longitude;
|
|
277
|
+
for (let i = 0; i < numEntries; i++) {
|
|
278
|
+
const entryOffset = gpsIfdOffset + 2 + i * 12;
|
|
279
|
+
if (entryOffset + 12 > data.length)
|
|
280
|
+
break;
|
|
281
|
+
const tag = littleEndian
|
|
282
|
+
? data[entryOffset] | (data[entryOffset + 1] << 8)
|
|
283
|
+
: (data[entryOffset] << 8) | data[entryOffset + 1];
|
|
284
|
+
const type = littleEndian
|
|
285
|
+
? data[entryOffset + 2] | (data[entryOffset + 3] << 8)
|
|
286
|
+
: (data[entryOffset + 2] << 8) | data[entryOffset + 3];
|
|
287
|
+
const valueOffset = littleEndian
|
|
288
|
+
? data[entryOffset + 8] | (data[entryOffset + 9] << 8) |
|
|
289
|
+
(data[entryOffset + 10] << 16) | (data[entryOffset + 11] << 24)
|
|
290
|
+
: (data[entryOffset + 8] << 24) | (data[entryOffset + 9] << 16) |
|
|
291
|
+
(data[entryOffset + 10] << 8) | data[entryOffset + 11];
|
|
292
|
+
// GPSLatitudeRef (0x0001)
|
|
293
|
+
if (tag === 0x0001 && type === 2) {
|
|
294
|
+
latRef = String.fromCharCode(data[entryOffset + 8]);
|
|
295
|
+
}
|
|
296
|
+
// GPSLatitude (0x0002)
|
|
297
|
+
if (tag === 0x0002 && type === 5 && valueOffset + 24 <= data.length) {
|
|
298
|
+
const degrees = this.readRational(data, valueOffset, littleEndian);
|
|
299
|
+
const minutes = this.readRational(data, valueOffset + 8, littleEndian);
|
|
300
|
+
const seconds = this.readRational(data, valueOffset + 16, littleEndian);
|
|
301
|
+
latitude = degrees + minutes / 60 + seconds / 3600;
|
|
302
|
+
}
|
|
303
|
+
// GPSLongitudeRef (0x0003)
|
|
304
|
+
if (tag === 0x0003 && type === 2) {
|
|
305
|
+
lonRef = String.fromCharCode(data[entryOffset + 8]);
|
|
306
|
+
}
|
|
307
|
+
// GPSLongitude (0x0004)
|
|
308
|
+
if (tag === 0x0004 && type === 5 && valueOffset + 24 <= data.length) {
|
|
309
|
+
const degrees = this.readRational(data, valueOffset, littleEndian);
|
|
310
|
+
const minutes = this.readRational(data, valueOffset + 8, littleEndian);
|
|
311
|
+
const seconds = this.readRational(data, valueOffset + 16, littleEndian);
|
|
312
|
+
longitude = degrees + minutes / 60 + seconds / 3600;
|
|
313
|
+
}
|
|
314
|
+
}
|
|
315
|
+
// Apply hemisphere references
|
|
316
|
+
if (latitude !== undefined && latRef) {
|
|
317
|
+
metadata.latitude = latRef === "S" ? -latitude : latitude;
|
|
318
|
+
}
|
|
319
|
+
if (longitude !== undefined && lonRef) {
|
|
320
|
+
metadata.longitude = lonRef === "W" ? -longitude : longitude;
|
|
321
|
+
}
|
|
322
|
+
}
|
|
323
|
+
catch (_e) {
|
|
324
|
+
// Ignore GPS parsing errors
|
|
325
|
+
}
|
|
326
|
+
}
|
|
327
|
+
readRational(data, offset, littleEndian) {
|
|
328
|
+
const numerator = littleEndian
|
|
329
|
+
? data[offset] | (data[offset + 1] << 8) | (data[offset + 2] << 16) |
|
|
330
|
+
(data[offset + 3] << 24)
|
|
331
|
+
: (data[offset] << 24) | (data[offset + 1] << 16) |
|
|
332
|
+
(data[offset + 2] << 8) | data[offset + 3];
|
|
333
|
+
const denominator = littleEndian
|
|
334
|
+
? data[offset + 4] | (data[offset + 5] << 8) | (data[offset + 6] << 16) |
|
|
335
|
+
(data[offset + 7] << 24)
|
|
336
|
+
: (data[offset + 4] << 24) | (data[offset + 5] << 16) |
|
|
337
|
+
(data[offset + 6] << 8) | data[offset + 7];
|
|
338
|
+
return denominator !== 0 ? numerator / denominator : 0;
|
|
339
|
+
}
|
|
254
340
|
parseXMP(data, metadata) {
|
|
255
|
-
// XMP
|
|
341
|
+
// Parse XMP using the centralized utility
|
|
256
342
|
try {
|
|
257
343
|
const xmpStr = new TextDecoder().decode(data);
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
metadata.title = titleMatch[1].trim();
|
|
262
|
-
// Extract description
|
|
263
|
-
const descMatch = xmpStr.match(/<dc:description[^>]*>([^<]+)<\/dc:description>/);
|
|
264
|
-
if (descMatch)
|
|
265
|
-
metadata.description = descMatch[1].trim();
|
|
266
|
-
// Extract creator/author
|
|
267
|
-
const creatorMatch = xmpStr.match(/<dc:creator[^>]*>([^<]+)<\/dc:creator>/);
|
|
268
|
-
if (creatorMatch)
|
|
269
|
-
metadata.author = creatorMatch[1].trim();
|
|
270
|
-
// Extract rights/copyright
|
|
271
|
-
const rightsMatch = xmpStr.match(/<dc:rights[^>]*>([^<]+)<\/dc:rights>/);
|
|
272
|
-
if (rightsMatch)
|
|
273
|
-
metadata.copyright = rightsMatch[1].trim();
|
|
344
|
+
const parsedMetadata = parseXMP(xmpStr);
|
|
345
|
+
// Merge parsed metadata into the existing metadata object
|
|
346
|
+
Object.assign(metadata, parsedMetadata);
|
|
274
347
|
}
|
|
275
348
|
catch (_e) {
|
|
276
349
|
// Ignore XMP parsing errors
|
|
@@ -284,8 +357,9 @@ export class WebPFormat {
|
|
|
284
357
|
chunks.push(webpData.slice(0, 12));
|
|
285
358
|
// Create metadata chunks
|
|
286
359
|
const metadataChunks = [];
|
|
287
|
-
// Create EXIF chunk if we have date or
|
|
288
|
-
if (metadata.creationDate
|
|
360
|
+
// Create EXIF chunk if we have date or GPS data
|
|
361
|
+
if (metadata.creationDate ||
|
|
362
|
+
(metadata.latitude !== undefined && metadata.longitude !== undefined)) {
|
|
289
363
|
const exifData = this.createEXIFChunk(metadata);
|
|
290
364
|
if (exifData) {
|
|
291
365
|
metadataChunks.push(exifData);
|
|
@@ -341,7 +415,10 @@ export class WebPFormat {
|
|
|
341
415
|
return finalData;
|
|
342
416
|
}
|
|
343
417
|
createEXIFChunk(metadata) {
|
|
344
|
-
|
|
418
|
+
const hasDate = metadata.creationDate !== undefined;
|
|
419
|
+
const hasGPS = metadata.latitude !== undefined &&
|
|
420
|
+
metadata.longitude !== undefined;
|
|
421
|
+
if (!hasDate && !hasGPS)
|
|
345
422
|
return null;
|
|
346
423
|
const exifData = [];
|
|
347
424
|
// Byte order marker (little endian)
|
|
@@ -349,21 +426,47 @@ export class WebPFormat {
|
|
|
349
426
|
exifData.push(0x2a, 0x00); // 42
|
|
350
427
|
// IFD0 offset
|
|
351
428
|
exifData.push(0x08, 0x00, 0x00, 0x00);
|
|
352
|
-
// Number of entries
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
429
|
+
// Number of entries (DateTime + GPS IFD pointer if needed)
|
|
430
|
+
const numEntries = (hasDate ? 1 : 0) + (hasGPS ? 1 : 0);
|
|
431
|
+
exifData.push(numEntries & 0xff, (numEntries >> 8) & 0xff);
|
|
432
|
+
let dataOffset = 8 + 2 + numEntries * 12 + 4;
|
|
433
|
+
// DateTime entry (if present)
|
|
434
|
+
if (hasDate) {
|
|
435
|
+
const date = metadata.creationDate;
|
|
436
|
+
const dateStr = `${date.getFullYear()}:${String(date.getMonth() + 1).padStart(2, "0")}:${String(date.getDate()).padStart(2, "0")} ${String(date.getHours()).padStart(2, "0")}:${String(date.getMinutes()).padStart(2, "0")}:${String(date.getSeconds()).padStart(2, "0")}\0`;
|
|
437
|
+
const dateBytes = new TextEncoder().encode(dateStr);
|
|
438
|
+
// Tag 0x0132, Type 2 (ASCII), Count, Offset
|
|
439
|
+
exifData.push(0x32, 0x01, 0x02, 0x00);
|
|
440
|
+
exifData.push(dateBytes.length & 0xff, (dateBytes.length >> 8) & 0xff, (dateBytes.length >> 16) & 0xff, (dateBytes.length >> 24) & 0xff);
|
|
441
|
+
exifData.push(dataOffset & 0xff, (dataOffset >> 8) & 0xff, (dataOffset >> 16) & 0xff, (dataOffset >> 24) & 0xff);
|
|
442
|
+
dataOffset += dateBytes.length;
|
|
443
|
+
}
|
|
444
|
+
// GPS IFD pointer (if present)
|
|
445
|
+
let gpsIfdOffset = 0;
|
|
446
|
+
if (hasGPS) {
|
|
447
|
+
gpsIfdOffset = dataOffset;
|
|
448
|
+
exifData.push(0x25, 0x88); // Tag 0x8825
|
|
449
|
+
exifData.push(0x04, 0x00); // Type LONG
|
|
450
|
+
exifData.push(0x01, 0x00, 0x00, 0x00); // Count 1
|
|
451
|
+
exifData.push(gpsIfdOffset & 0xff, (gpsIfdOffset >> 8) & 0xff, (gpsIfdOffset >> 16) & 0xff, (gpsIfdOffset >> 24) & 0xff);
|
|
452
|
+
}
|
|
362
453
|
// Next IFD
|
|
363
454
|
exifData.push(0x00, 0x00, 0x00, 0x00);
|
|
364
|
-
// Date string data
|
|
365
|
-
|
|
366
|
-
|
|
455
|
+
// Date string data (if present)
|
|
456
|
+
if (hasDate) {
|
|
457
|
+
const date = metadata.creationDate;
|
|
458
|
+
const dateStr = `${date.getFullYear()}:${String(date.getMonth() + 1).padStart(2, "0")}:${String(date.getDate()).padStart(2, "0")} ${String(date.getHours()).padStart(2, "0")}:${String(date.getMinutes()).padStart(2, "0")}:${String(date.getSeconds()).padStart(2, "0")}\0`;
|
|
459
|
+
const dateBytes = new TextEncoder().encode(dateStr);
|
|
460
|
+
for (const byte of dateBytes) {
|
|
461
|
+
exifData.push(byte);
|
|
462
|
+
}
|
|
463
|
+
}
|
|
464
|
+
// GPS IFD data (if present)
|
|
465
|
+
if (hasGPS) {
|
|
466
|
+
const gpsIfd = this.createGPSIFD(metadata, gpsIfdOffset);
|
|
467
|
+
for (const byte of gpsIfd) {
|
|
468
|
+
exifData.push(byte);
|
|
469
|
+
}
|
|
367
470
|
}
|
|
368
471
|
// Create chunk header
|
|
369
472
|
const chunkData = new Uint8Array(exifData);
|
|
@@ -376,29 +479,59 @@ export class WebPFormat {
|
|
|
376
479
|
chunk.set(chunkData, 8);
|
|
377
480
|
return chunk;
|
|
378
481
|
}
|
|
482
|
+
createGPSIFD(metadata, gpsIfdStart) {
|
|
483
|
+
const gps = [];
|
|
484
|
+
const numEntries = 4;
|
|
485
|
+
gps.push(numEntries & 0xff, (numEntries >> 8) & 0xff);
|
|
486
|
+
const latitude = metadata.latitude;
|
|
487
|
+
const longitude = metadata.longitude;
|
|
488
|
+
const absLat = Math.abs(latitude);
|
|
489
|
+
const absLon = Math.abs(longitude);
|
|
490
|
+
const latDeg = Math.floor(absLat);
|
|
491
|
+
const latMin = Math.floor((absLat - latDeg) * 60);
|
|
492
|
+
const latSec = ((absLat - latDeg) * 60 - latMin) * 60;
|
|
493
|
+
const lonDeg = Math.floor(absLon);
|
|
494
|
+
const lonMin = Math.floor((absLon - lonDeg) * 60);
|
|
495
|
+
const lonSec = ((absLon - lonDeg) * 60 - lonMin) * 60;
|
|
496
|
+
let dataOffset = gpsIfdStart + 2 + numEntries * 12 + 4;
|
|
497
|
+
// GPSLatitudeRef
|
|
498
|
+
gps.push(0x01, 0x00);
|
|
499
|
+
gps.push(0x02, 0x00);
|
|
500
|
+
gps.push(0x02, 0x00, 0x00, 0x00);
|
|
501
|
+
gps.push(latitude >= 0 ? 78 : 83, 0x00, 0x00, 0x00);
|
|
502
|
+
// GPSLatitude
|
|
503
|
+
gps.push(0x02, 0x00);
|
|
504
|
+
gps.push(0x05, 0x00);
|
|
505
|
+
gps.push(0x03, 0x00, 0x00, 0x00);
|
|
506
|
+
gps.push(dataOffset & 0xff, (dataOffset >> 8) & 0xff, (dataOffset >> 16) & 0xff, (dataOffset >> 24) & 0xff);
|
|
507
|
+
dataOffset += 24;
|
|
508
|
+
// GPSLongitudeRef
|
|
509
|
+
gps.push(0x03, 0x00);
|
|
510
|
+
gps.push(0x02, 0x00);
|
|
511
|
+
gps.push(0x02, 0x00, 0x00, 0x00);
|
|
512
|
+
gps.push(longitude >= 0 ? 69 : 87, 0x00, 0x00, 0x00);
|
|
513
|
+
// GPSLongitude
|
|
514
|
+
gps.push(0x04, 0x00);
|
|
515
|
+
gps.push(0x05, 0x00);
|
|
516
|
+
gps.push(0x03, 0x00, 0x00, 0x00);
|
|
517
|
+
gps.push(dataOffset & 0xff, (dataOffset >> 8) & 0xff, (dataOffset >> 16) & 0xff, (dataOffset >> 24) & 0xff);
|
|
518
|
+
gps.push(0x00, 0x00, 0x00, 0x00);
|
|
519
|
+
// Write rationals
|
|
520
|
+
this.writeRational(gps, latDeg, 1);
|
|
521
|
+
this.writeRational(gps, latMin, 1);
|
|
522
|
+
this.writeRational(gps, Math.round(latSec * 1000000), 1000000);
|
|
523
|
+
this.writeRational(gps, lonDeg, 1);
|
|
524
|
+
this.writeRational(gps, lonMin, 1);
|
|
525
|
+
this.writeRational(gps, Math.round(lonSec * 1000000), 1000000);
|
|
526
|
+
return gps;
|
|
527
|
+
}
|
|
528
|
+
writeRational(output, numerator, denominator) {
|
|
529
|
+
output.push(numerator & 0xff, (numerator >> 8) & 0xff, (numerator >> 16) & 0xff, (numerator >> 24) & 0xff);
|
|
530
|
+
output.push(denominator & 0xff, (denominator >> 8) & 0xff, (denominator >> 16) & 0xff, (denominator >> 24) & 0xff);
|
|
531
|
+
}
|
|
379
532
|
createXMPChunk(metadata) {
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
xmpParts.push('<x:xmpmeta xmlns:x="adobe:ns:meta/">');
|
|
383
|
-
xmpParts.push('<rdf:RDF xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#">');
|
|
384
|
-
xmpParts.push('<rdf:Description xmlns:dc="http://purl.org/dc/elements/1.1/">');
|
|
385
|
-
if (metadata.title) {
|
|
386
|
-
xmpParts.push(`<dc:title>${this.escapeXML(metadata.title)}</dc:title>`);
|
|
387
|
-
}
|
|
388
|
-
if (metadata.description) {
|
|
389
|
-
xmpParts.push(`<dc:description>${this.escapeXML(metadata.description)}</dc:description>`);
|
|
390
|
-
}
|
|
391
|
-
if (metadata.author) {
|
|
392
|
-
xmpParts.push(`<dc:creator>${this.escapeXML(metadata.author)}</dc:creator>`);
|
|
393
|
-
}
|
|
394
|
-
if (metadata.copyright) {
|
|
395
|
-
xmpParts.push(`<dc:rights>${this.escapeXML(metadata.copyright)}</dc:rights>`);
|
|
396
|
-
}
|
|
397
|
-
xmpParts.push("</rdf:Description>");
|
|
398
|
-
xmpParts.push("</rdf:RDF>");
|
|
399
|
-
xmpParts.push("</x:xmpmeta>");
|
|
400
|
-
xmpParts.push('<?xpacket end="w"?>');
|
|
401
|
-
const xmpStr = xmpParts.join("\n");
|
|
533
|
+
// Use the centralized XMP utility to create the XMP packet
|
|
534
|
+
const xmpStr = createXMP(metadata);
|
|
402
535
|
const xmpData = new TextEncoder().encode(xmpStr);
|
|
403
536
|
// Create chunk
|
|
404
537
|
const chunk = new Uint8Array(8 + xmpData.length);
|
|
@@ -410,12 +543,28 @@ export class WebPFormat {
|
|
|
410
543
|
chunk.set(xmpData, 8);
|
|
411
544
|
return chunk;
|
|
412
545
|
}
|
|
413
|
-
|
|
414
|
-
|
|
415
|
-
|
|
416
|
-
|
|
417
|
-
|
|
418
|
-
|
|
419
|
-
|
|
546
|
+
/**
|
|
547
|
+
* Get the list of metadata fields supported by WebP format
|
|
548
|
+
*/
|
|
549
|
+
getSupportedMetadata() {
|
|
550
|
+
return [
|
|
551
|
+
// EXIF chunk
|
|
552
|
+
"creationDate",
|
|
553
|
+
"latitude",
|
|
554
|
+
"longitude",
|
|
555
|
+
// XMP chunk (enhanced support)
|
|
556
|
+
"title",
|
|
557
|
+
"description",
|
|
558
|
+
"author",
|
|
559
|
+
"copyright",
|
|
560
|
+
"cameraMake",
|
|
561
|
+
"cameraModel",
|
|
562
|
+
"orientation",
|
|
563
|
+
"software",
|
|
564
|
+
"iso",
|
|
565
|
+
"exposureTime",
|
|
566
|
+
"fNumber",
|
|
567
|
+
"focalLength",
|
|
568
|
+
];
|
|
420
569
|
}
|
|
421
570
|
}
|
package/esm/src/image.d.ts
CHANGED
|
@@ -79,6 +79,12 @@ 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;
|
|
82
88
|
/**
|
|
83
89
|
* Read an image from bytes
|
|
84
90
|
* @deprecated Use `decode()` instead. This method will be removed in a future version.
|
|
@@ -197,6 +203,12 @@ export declare class Image {
|
|
|
197
203
|
* @returns This image instance for chaining
|
|
198
204
|
*/
|
|
199
205
|
saturation(amount: number): this;
|
|
206
|
+
/**
|
|
207
|
+
* Adjust hue of the image by rotating the color wheel
|
|
208
|
+
* @param degrees Hue rotation in degrees (any value accepted, wraps at 360)
|
|
209
|
+
* @returns This image instance for chaining
|
|
210
|
+
*/
|
|
211
|
+
hue(degrees: number): this;
|
|
200
212
|
/**
|
|
201
213
|
* Invert colors of the image
|
|
202
214
|
* @returns This image instance for chaining
|
|
@@ -207,6 +219,36 @@ export declare class Image {
|
|
|
207
219
|
* @returns This image instance for chaining
|
|
208
220
|
*/
|
|
209
221
|
grayscale(): this;
|
|
222
|
+
/**
|
|
223
|
+
* Apply sepia tone effect to the image
|
|
224
|
+
* @returns This image instance for chaining
|
|
225
|
+
*/
|
|
226
|
+
sepia(): this;
|
|
227
|
+
/**
|
|
228
|
+
* Apply box blur filter to the image
|
|
229
|
+
* @param radius Blur radius (default: 1)
|
|
230
|
+
* @returns This image instance for chaining
|
|
231
|
+
*/
|
|
232
|
+
blur(radius?: number): this;
|
|
233
|
+
/**
|
|
234
|
+
* Apply Gaussian blur filter to the image
|
|
235
|
+
* @param radius Blur radius (default: 1)
|
|
236
|
+
* @param sigma Optional standard deviation (if not provided, calculated from radius)
|
|
237
|
+
* @returns This image instance for chaining
|
|
238
|
+
*/
|
|
239
|
+
gaussianBlur(radius?: number, sigma?: number): this;
|
|
240
|
+
/**
|
|
241
|
+
* Apply sharpen filter to the image
|
|
242
|
+
* @param amount Sharpening amount (0-1, default: 0.5)
|
|
243
|
+
* @returns This image instance for chaining
|
|
244
|
+
*/
|
|
245
|
+
sharpen(amount?: number): this;
|
|
246
|
+
/**
|
|
247
|
+
* Apply median filter to reduce noise
|
|
248
|
+
* @param radius Filter radius (default: 1)
|
|
249
|
+
* @returns This image instance for chaining
|
|
250
|
+
*/
|
|
251
|
+
medianFilter(radius?: number): this;
|
|
210
252
|
/**
|
|
211
253
|
* Fill a rectangular region with a color
|
|
212
254
|
* @param x Starting X position
|
|
@@ -252,5 +294,44 @@ export declare class Image {
|
|
|
252
294
|
* @returns This image instance for chaining
|
|
253
295
|
*/
|
|
254
296
|
setPixel(x: number, y: number, r: number, g: number, b: number, a?: number): this;
|
|
297
|
+
/**
|
|
298
|
+
* Rotate the image 90 degrees clockwise
|
|
299
|
+
* @returns This image instance for chaining
|
|
300
|
+
*/
|
|
301
|
+
rotate90(): this;
|
|
302
|
+
/**
|
|
303
|
+
* Rotate the image 180 degrees
|
|
304
|
+
* @returns This image instance for chaining
|
|
305
|
+
*/
|
|
306
|
+
rotate180(): this;
|
|
307
|
+
/**
|
|
308
|
+
* Rotate the image 270 degrees clockwise (or 90 degrees counter-clockwise)
|
|
309
|
+
* @returns This image instance for chaining
|
|
310
|
+
*/
|
|
311
|
+
rotate270(): this;
|
|
312
|
+
/**
|
|
313
|
+
* Rotate the image by the specified angle in degrees
|
|
314
|
+
* @param degrees Rotation angle in degrees (positive = clockwise, negative = counter-clockwise)
|
|
315
|
+
* @returns This image instance for chaining
|
|
316
|
+
*
|
|
317
|
+
* @example
|
|
318
|
+
* ```ts
|
|
319
|
+
* image.rotate(90); // Rotate 90° clockwise
|
|
320
|
+
* image.rotate(-90); // Rotate 90° counter-clockwise
|
|
321
|
+
* image.rotate(180); // Rotate 180°
|
|
322
|
+
* image.rotate(45); // Rotate 45° clockwise (rounded to nearest 90°)
|
|
323
|
+
* ```
|
|
324
|
+
*/
|
|
325
|
+
rotate(degrees: number): this;
|
|
326
|
+
/**
|
|
327
|
+
* Flip the image horizontally (mirror)
|
|
328
|
+
* @returns This image instance for chaining
|
|
329
|
+
*/
|
|
330
|
+
flipHorizontal(): this;
|
|
331
|
+
/**
|
|
332
|
+
* Flip the image vertically
|
|
333
|
+
* @returns This image instance for chaining
|
|
334
|
+
*/
|
|
335
|
+
flipVertical(): this;
|
|
255
336
|
}
|
|
256
337
|
//# sourceMappingURL=image.d.ts.map
|