@poe-platform/safe-bash 0.1.844 → 0.1.846

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 (43) hide show
  1. package/dist/image-ast/ast.d.ts +12 -2
  2. package/dist/image-ast/browser/ast.d.ts +12 -2
  3. package/dist/image-ast/browser/codecs/exif-metadata.d.ts +2 -0
  4. package/dist/image-ast/browser/codecs/exif.d.ts +2 -0
  5. package/dist/image-ast/browser/codecs/gif-metadata-storage.d.ts +1 -0
  6. package/dist/image-ast/browser/codecs/gif-metadata.d.ts +1 -0
  7. package/dist/image-ast/browser/codecs/gif-output-parts.d.ts +1 -1
  8. package/dist/image-ast/browser/codecs/jpeg.d.ts +1 -1
  9. package/dist/image-ast/browser/codecs/netpbm-storage.d.ts +2 -2
  10. package/dist/image-ast/browser/codecs/netpbm.d.ts +4 -4
  11. package/dist/image-ast/browser/codecs/png-storage.d.ts +9 -1
  12. package/dist/image-ast/browser/codecs/png.d.ts +1 -0
  13. package/dist/image-ast/browser/codecs/tiff-layout.d.ts +3 -2
  14. package/dist/image-ast/codecs/exif-metadata.d.ts +2 -0
  15. package/dist/image-ast/codecs/exif.d.ts +2 -0
  16. package/dist/image-ast/codecs/gif-metadata-storage.d.ts +1 -0
  17. package/dist/image-ast/codecs/gif-metadata.d.ts +1 -0
  18. package/dist/image-ast/codecs/gif-output-parts.d.ts +1 -1
  19. package/dist/image-ast/codecs/jpeg.d.ts +1 -1
  20. package/dist/image-ast/codecs/netpbm-storage.d.ts +2 -2
  21. package/dist/image-ast/codecs/netpbm.d.ts +4 -4
  22. package/dist/image-ast/codecs/png-storage.d.ts +9 -1
  23. package/dist/image-ast/codecs/png.d.ts +1 -0
  24. package/dist/image-ast/codecs/tiff-layout.d.ts +3 -2
  25. package/dist/image-ast/index.js +554 -163
  26. package/dist/mp4-ast/types.d.ts +2 -0
  27. package/dist/safe-bash/command-chunks/{chunk-LORSYIAF.js → chunk-CSJHADNR.js} +31 -7
  28. package/dist/safe-bash/command-chunks/{chunk-KSCVKF7I.js → chunk-F42YLXD4.js} +3 -3
  29. package/dist/safe-bash/command-chunks/{chunk-2EEIB5O3.js → chunk-HUE2ATJY.js} +3 -3
  30. package/dist/safe-bash/command-chunks/{chunk-3SWIXYXT.js → chunk-NBQWCUOP.js} +1 -1
  31. package/dist/safe-bash/command-chunks/{chunk-U5TQVW64.js → chunk-ZTEK2JJD.js} +1 -1
  32. package/dist/safe-bash-command-ffmpeg/index.js +535 -88
  33. package/dist/safe-bash-command-ffprobe/index.js +2 -2
  34. package/dist/safe-bash-command-ffprobe/media.js +2 -2
  35. package/dist/safe-bash-command-imagemagick/compare-file.d.ts +3 -2
  36. package/dist/safe-bash-command-imagemagick/index.d.ts +1 -0
  37. package/dist/safe-bash-command-imagemagick/index.js +895 -276
  38. package/dist/safe-bash-command-imagemagick/raw-output.d.ts +10 -0
  39. package/dist/safe-bash-command-python/executor.js +3 -3
  40. package/dist/safe-bash-command-python/index.js +3 -3
  41. package/dist/safe-bash-command-python/jspi.js +2 -2
  42. package/dist/safe-bash-command-python/worker.js +1 -1
  43. package/package.json +2 -2
@@ -1301,6 +1301,18 @@ function canvasChunk(canvas) {
1301
1301
  let bytes = new Uint8Array(16), view = new DataView(bytes.buffer);
1302
1302
  return view.setUint32(0, canvas.width), view.setUint32(4, canvas.height), view.setInt32(8, canvas.x), view.setInt32(12, canvas.y), makeChunk("caNv", bytes);
1303
1303
  }
1304
+ function* textChunks(properties) {
1305
+ for (let [key, value] of Object.entries(properties ?? {})) {
1306
+ let keyword = Uint8Array.from(key, (char) => char.charCodeAt(0));
1307
+ if (!key.length || key.length > 79 || [...key].some((char) => char.charCodeAt(0) > 255 || char === "\0")) continue;
1308
+ let text = new TextEncoder().encode(value), payload = new Uint8Array(keyword.length + 5 + text.length);
1309
+ payload.set(keyword), payload.set(text, keyword.length + 5), yield makeChunk("iTXt", payload);
1310
+ }
1311
+ }
1312
+ function iccChunk(icc) {
1313
+ let compressed = transformBytes(icc, { direction: "encode", format: "zlib" }), payload = new Uint8Array(5 + compressed.length);
1314
+ return payload.set([73, 67, 67, 0, 0]), payload.set(compressed, 5), makeChunk("iCCP", payload);
1315
+ }
1304
1316
 
1305
1317
  // src/codecs/exif-metadata.ts
1306
1318
  function* exifMetadataSteps(size, base = 0) {
@@ -1316,7 +1328,7 @@ function* exifMetadataSteps(size, base = 0) {
1316
1328
  if (view.getUint16(2, little) !== 42) return {};
1317
1329
  let ifd = view.getUint32(4, little), length = size - offset, start = base + offset;
1318
1330
  if (ifd + 2 > length) return {};
1319
- let countBytes = yield { position: start + ifd, length: 2 }, count = new DataView(countBytes.buffer, countBytes.byteOffset, countBytes.byteLength).getUint16(0, little), orientation, xRes, unit = 2;
1331
+ let countBytes = yield { position: start + ifd, length: 2 }, count = new DataView(countBytes.buffer, countBytes.byteOffset, countBytes.byteLength).getUint16(0, little), orientation, xRes, yRes, unit = 2;
1320
1332
  for (let i = 0; i < count; i++) {
1321
1333
  let position = ifd + 2 + i * 12;
1322
1334
  if (position + 12 > length) break;
@@ -1326,14 +1338,17 @@ function* exifMetadataSteps(size, base = 0) {
1326
1338
  value >= 1 && value <= 8 && (orientation = value);
1327
1339
  } else if (tag === 296) {
1328
1340
  let value = type === 3 ? entry.getUint16(8, little) : valueOffset;
1329
- (value === 2 || value === 3) && (unit = value);
1330
- } else if (tag === 282 && type === 5 && valueOffset + 8 <= length) {
1341
+ (value === 1 || value === 2 || value === 3) && (unit = value);
1342
+ } else if ((tag === 282 || tag === 283) && type === 5 && valueOffset + 8 <= length) {
1331
1343
  let bytes2 = yield { position: start + valueOffset, length: 8 }, ratio = new DataView(bytes2.buffer, bytes2.byteOffset, bytes2.byteLength), den = ratio.getUint32(4, little);
1332
- den > 0 && (xRes = ratio.getUint32(0, little) / den);
1344
+ if (den > 0) {
1345
+ let value = ratio.getUint32(0, little) / den;
1346
+ tag === 282 ? xRes = value : yRes = value;
1347
+ }
1333
1348
  }
1334
1349
  }
1335
1350
  let density = xRes !== void 0 && xRes > 0 ? Math.round(xRes * (unit === 3 ? 2.54 : 1)) : void 0;
1336
- return { ...orientation === void 0 ? {} : { orientation }, ...density === void 0 ? {} : { density } };
1351
+ return { ...xRes !== void 0 && yRes !== void 0 ? { resolution: { x: xRes, y: yRes, unit: unit === 3 ? "cm" : unit === 1 ? "unknown" : "inch" } } : {}, ...orientation === void 0 ? {} : { orientation }, ...density === void 0 ? {} : { density } };
1337
1352
  }
1338
1353
 
1339
1354
  // src/codecs/png-metadata.ts
@@ -1341,13 +1356,68 @@ function* pngMetadataSteps(size) {
1341
1356
  let signature = size >= 8 ? yield { position: 0, length: 8 } : new Uint8Array();
1342
1357
  if (size < 24 || !PNG_SIGNATURE.every((byte2, index) => signature[index] === byte2))
1343
1358
  throw new Error("Invalid PNG header");
1344
- let width = 0, height = 0, bitDepth = 8, colorType = 6, interlace = 0, density = 72, resolution, hasTrns = !1, virtualCanvas, orientation, backgroundColor, palette, pos = 8;
1359
+ let width = 0, height = 0, bitDepth = 8, colorType = 6, interlace = 0, density = 72, resolution, hasTrns = !1, virtualCanvas, properties = /* @__PURE__ */ Object.create(null), icc, orientation, backgroundColor, palette, pos = 8;
1345
1360
  for (; pos + 8 <= size; ) {
1346
1361
  let header = yield { position: pos, length: 8 }, len = new DataView(header.buffer, header.byteOffset, header.byteLength).getUint32(0, !1), type = String.fromCharCode(header[4], header[5], header[6], header[7]), dataStart = pos + 8;
1347
1362
  if (dataStart + len > size) break;
1348
1363
  if (type === "IHDR" && len >= 13) {
1349
1364
  let chunk = yield { position: dataStart, length: 13 }, view = new DataView(chunk.buffer, chunk.byteOffset, chunk.byteLength);
1350
1365
  width = view.getUint32(0, !1), height = view.getUint32(4, !1), bitDepth = chunk[8], colorType = chunk[9], interlace = chunk[12];
1366
+ } else if (type === "tEXt" || type === "zTXt" || type === "iTXt") {
1367
+ if (len > 16 * 1024 * 1024) throw new Error("PNG text exceeds 16 MiB");
1368
+ let end = (yield { position: dataStart, length: Math.min(len, 80) }).indexOf(0);
1369
+ if (end < 1 || end > 79) {
1370
+ pos = dataStart + len + 4;
1371
+ continue;
1372
+ }
1373
+ let chunk = yield { position: dataStart, length: len }, start = end + 1, compressed = type === "zTXt";
1374
+ if (type === "zTXt") {
1375
+ if (chunk[start++] !== 0) throw new Error("Invalid PNG text compression");
1376
+ } else if (type === "iTXt") {
1377
+ let flag = chunk[start++];
1378
+ if (flag !== 0 && flag !== 1 || chunk[start++] !== 0) throw new Error("Invalid PNG text compression");
1379
+ compressed = flag === 1;
1380
+ for (let field = 0; field < 2; field++) {
1381
+ let zero = chunk.indexOf(0, start);
1382
+ if (zero < 0) throw new Error("Invalid PNG international text");
1383
+ start = zero + 1;
1384
+ }
1385
+ }
1386
+ let value = chunk.subarray(start);
1387
+ if (compressed) {
1388
+ let codec = createByteCodec({ direction: "decode", format: "zlib" }), parts = [], length = 0;
1389
+ try {
1390
+ for (let part of codec.push(value, !0)) {
1391
+ if (length += part.length, length > 16 * 1024 * 1024) throw new Error("PNG text exceeds 16 MiB");
1392
+ parts.push(part);
1393
+ }
1394
+ } finally {
1395
+ codec.close();
1396
+ }
1397
+ value = new Uint8Array(length);
1398
+ let offset = 0;
1399
+ for (let part of parts)
1400
+ value.set(part, offset), offset += part.length;
1401
+ }
1402
+ let latin1 = (bytes) => Array.from(bytes, (byte2) => String.fromCharCode(byte2)).join("");
1403
+ properties[latin1(chunk.subarray(0, end))] = type === "iTXt" ? new TextDecoder().decode(value) : latin1(value);
1404
+ } else if (type === "iCCP") {
1405
+ if (len > 16 * 1024 * 1024) throw new Error("ICC profile exceeds 16 MiB");
1406
+ let chunk = yield { position: dataStart, length: len }, end = chunk.indexOf(0);
1407
+ if (end < 1 || end > 79 || chunk[end + 1] !== 0) throw new Error("Invalid PNG ICC profile");
1408
+ let codec = createByteCodec({ direction: "decode", format: "zlib" }), parts = [], length = 0;
1409
+ try {
1410
+ for (let part of codec.push(chunk.subarray(end + 2), !0)) {
1411
+ if (length += part.length, length > 16 * 1024 * 1024) throw new Error("ICC profile exceeds 16 MiB");
1412
+ parts.push(part);
1413
+ }
1414
+ } finally {
1415
+ codec.close();
1416
+ }
1417
+ icc = new Uint8Array(length);
1418
+ let offset = 0;
1419
+ for (let part of parts)
1420
+ icc.set(part, offset), offset += part.length;
1351
1421
  } else if (type === "PLTE" && len <= 768)
1352
1422
  palette = yield { position: dataStart, length: len };
1353
1423
  else if (type === "bKGD") {
@@ -1370,15 +1440,20 @@ function* pngMetadataSteps(size) {
1370
1440
  virtualCanvas = { width: view.getUint32(0), height: view.getUint32(4), x: view.getInt32(8), y: view.getInt32(12) };
1371
1441
  } else if (type === "tRNS")
1372
1442
  hasTrns = !0;
1373
- else if (type === "eXIf" && len >= 8) {
1443
+ else if (type === "orNT" && len === 1) {
1444
+ let chunk = yield { position: dataStart, length: 1 };
1445
+ chunk[0] <= 8 && (orientation = chunk[0]);
1446
+ } else if (type === "eXIf" && len >= 8) {
1374
1447
  let exif = yield* exifMetadataSteps(len, dataStart);
1375
- exif.orientation !== void 0 && (orientation = exif.orientation), exif.density !== void 0 && (density = exif.density);
1448
+ exif.orientation !== void 0 && (orientation = exif.orientation), exif.density !== void 0 && (density = exif.density), resolution ??= exif.resolution;
1376
1449
  } else if ((type === "IDAT" || type === "IEND") && type === "IEND")
1377
1450
  break;
1378
1451
  pos = dataStart + len + 4;
1379
1452
  }
1380
1453
  let channels = colorType === 6 ? 4 : colorType === 4 ? 2 : colorType === 2 || colorType === 3 ? hasTrns ? 4 : 3 : hasTrns ? 2 : 1, hasAlpha = colorType === 6 || colorType === 4 || hasTrns, space = colorType === 0 || colorType === 4 ? bitDepth === 16 ? "grey16" : "b-w" : bitDepth === 16 ? "rgb16" : "srgb", depth = bitDepth === 16 ? "ushort" : bitDepth < 8 ? "bit" : "uchar";
1381
1454
  return {
1455
+ ...Object.keys(properties).length ? { properties } : {},
1456
+ ...icc === void 0 ? {} : { icc },
1382
1457
  format: "png",
1383
1458
  ...colorType === 3 ? { isPalette: !0 } : {},
1384
1459
  width,
@@ -1460,7 +1535,7 @@ function parseExifBuffer(raw) {
1460
1535
  return next.value;
1461
1536
  }
1462
1537
  function buildExifApp1Segment(options) {
1463
- let orientation = options.orientation ?? 1, density = Math.max(1, Math.round(options.density ?? 72)), payload = new Uint8Array(84), view = new DataView(payload.buffer);
1538
+ let orientation = options.orientation ?? 1, density = Math.max(1, Math.round(options.density ?? 72)), resolution = options.resolution, scale = resolution?.unit === "meter" ? 100 : 1, x = resolution ? resolution.x / scale : density, y = resolution ? resolution.y / scale : density, denominator = Number.isInteger(x) && Number.isInteger(y) ? 1 : 1e4, payload = new Uint8Array(84), view = new DataView(payload.buffer);
1464
1539
  payload[0] = 69, payload[1] = 120, payload[2] = 105, payload[3] = 102, payload[4] = 0, payload[5] = 0;
1465
1540
  let tiff = 6;
1466
1541
  payload[tiff] = 73, payload[tiff + 1] = 73, view.setUint16(tiff + 2, 42, !0), view.setUint32(tiff + 4, 8, !0);
@@ -1470,7 +1545,7 @@ function buildExifApp1Segment(options) {
1470
1545
  let p = ifd + 2 + idx * 12;
1471
1546
  view.setUint16(p, tag, !0), view.setUint16(p + 2, type, !0), view.setUint32(p + 4, count, !0), type === 3 && count === 1 ? (view.setUint16(p + 8, valueOrOffset, !0), view.setUint16(p + 10, 0, !0)) : view.setUint32(p + 8, valueOrOffset, !0);
1472
1547
  }, ratOffsetX = 62, ratOffsetY = ratOffsetX + 8;
1473
- return writeEntry(0, 274, 3, 1, orientation), writeEntry(1, 282, 5, 1, ratOffsetX), writeEntry(2, 283, 5, 1, ratOffsetY), writeEntry(3, 296, 3, 1, 2), view.setUint32(ifd + 2 + 48, 0, !0), view.setUint32(tiff + ratOffsetX, density, !0), view.setUint32(tiff + ratOffsetX + 4, 1, !0), view.setUint32(tiff + ratOffsetY, density, !0), view.setUint32(tiff + ratOffsetY + 4, 1, !0), payload;
1548
+ return writeEntry(0, 274, 3, 1, orientation), writeEntry(1, 282, 5, 1, ratOffsetX), writeEntry(2, 283, 5, 1, ratOffsetY), writeEntry(3, 296, 3, 1, resolution?.unit === "meter" || resolution?.unit === "cm" ? 3 : resolution?.unit === "unknown" ? 1 : 2), view.setUint32(ifd + 2 + 48, 0, !0), view.setUint32(tiff + ratOffsetX, Math.round(x * denominator), !0), view.setUint32(tiff + ratOffsetX + 4, denominator, !0), view.setUint32(tiff + ratOffsetY, Math.round(y * denominator), !0), view.setUint32(tiff + ratOffsetY + 4, denominator, !0), payload;
1474
1549
  }
1475
1550
 
1476
1551
  // src/codecs/png.ts
@@ -1609,14 +1684,17 @@ function decodePngImage(bytes) {
1609
1684
  ...meta.bitsPerSample === void 0 ? {} : { bitsPerSample: meta.bitsPerSample },
1610
1685
  ...meta.backgroundColor === void 0 ? {} : { backgroundColor: meta.backgroundColor },
1611
1686
  density: meta.density,
1687
+ ...meta.resolution ? { resolution: meta.resolution } : {},
1612
1688
  hasAlpha: meta.hasAlpha,
1613
1689
  ...meta.virtualCanvas === void 0 ? {} : { virtualCanvas: meta.virtualCanvas },
1690
+ ...meta.properties === void 0 ? {} : { properties: meta.properties },
1691
+ ...meta.icc === void 0 ? {} : { icc: meta.icc },
1614
1692
  ...meta.orientation !== void 0 ? { orientation: meta.orientation } : {},
1615
1693
  ...meta.isProgressive !== void 0 ? { isProgressive: meta.isProgressive } : {}
1616
1694
  };
1617
1695
  }
1618
1696
  function encodePngImage(img, options) {
1619
- let { width, height, data, data16 } = img, density = options?.density ?? img.density ?? 72, hasAlpha = !!(img.hasAlpha || img.channels === 4 || img.channels === 2);
1697
+ let { width, height, data, data16 } = img, density = options?.density ?? img.density ?? 72, resolution = options?.density === void 0 ? img.resolution : void 0, scale = resolution?.unit === "inch" ? 1 / 0.0254 : resolution?.unit === "cm" ? 100 : 1, hasAlpha = !!(img.hasAlpha || img.channels === 4 || img.channels === 2);
1620
1698
  if (options?.forceOpaque)
1621
1699
  hasAlpha = !1;
1622
1700
  else if (!hasAlpha) {
@@ -1676,12 +1754,14 @@ function encodePngImage(img, options) {
1676
1754
  let ihdr = new Uint8Array(13), ihdrView = new DataView(ihdr.buffer);
1677
1755
  ihdrView.setUint32(0, width, !1), ihdrView.setUint32(4, height, !1), ihdr[8] = data16 ? 16 : 8, ihdr[9] = colorType, ihdr[10] = 0, ihdr[11] = 0, ihdr[12] = 0;
1678
1756
  let phys = new Uint8Array(9), physView = new DataView(phys.buffer), ppu = Math.round(density / 0.0254);
1679
- physView.setUint32(0, ppu, !1), physView.setUint32(4, ppu, !1), phys[8] = 1;
1680
- let orientation = options?.orientation ?? img.orientation, exifChunk = orientation !== void 0 ? [makeChunk("eXIf", buildExifApp1Segment({ orientation, density }).subarray(6))] : [], chunks2 = [
1757
+ physView.setUint32(0, resolution ? Math.round(resolution.x * scale) : ppu, !1), physView.setUint32(4, resolution ? Math.round(resolution.y * scale) : ppu, !1), phys[8] = resolution?.unit === "unknown" ? 0 : 1;
1758
+ let orientation = options?.orientation ?? img.orientation, exifChunk = orientation !== void 0 ? [makeChunk("eXIf", buildExifApp1Segment({ orientation, density, resolution }).subarray(6))] : [], chunks2 = [
1681
1759
  PNG_SIGNATURE,
1682
1760
  makeChunk("IHDR", ihdr),
1683
- makeChunk("pHYs", phys),
1761
+ ...!options?.omitPngResolution && (resolution !== void 0 || options?.density !== void 0) ? [makeChunk("pHYs", phys)] : [],
1684
1762
  ...exifChunk,
1763
+ ...textChunks(img.properties),
1764
+ ...img.icc ? [iccChunk(img.icc)] : [],
1685
1765
  ...img.virtualCanvas === void 0 ? [] : [canvasChunk(img.virtualCanvas)],
1686
1766
  makeChunk("IDAT", compressed),
1687
1767
  makeChunk("IEND", new Uint8Array(0))
@@ -1980,7 +2060,7 @@ async function tryPdfDecode(source, storage, fs, directory, signal, options = {}
1980
2060
  await storage.write(position + written, bytes, { signal }), signal.throwIfAborted(), written += bytes.length;
1981
2061
  }
1982
2062
  if (written !== length) throw new Error("Truncated PDF raster pixels");
1983
- return { format: "pdf", width, height, space: "srgb", channels: 4, depth: "uchar", density: settings.density ?? 72, hasAlpha: !0, isProgressive: !1, pages: owner.metadata.pages, position };
2063
+ return { format: "pdf", width, height, space: "srgb", channels: 4, depth: "uchar", density: settings.density ?? 72, hasAlpha: !0, bitsPerSample: 8, isProgressive: !1, pages: owner.metadata.pages, position };
1984
2064
  } catch (error) {
1985
2065
  throw failed = !0, error;
1986
2066
  } finally {
@@ -1995,21 +2075,63 @@ function imageOutputFormat(input, requested) {
1995
2075
  return requested === "svg" ? "png" : requested ?? (input === "svg" || input === "pdf" ? "png" : input);
1996
2076
  }
1997
2077
 
2078
+ // src/codecs/miff.ts
2079
+ function miffHeader(image) {
2080
+ let { width, height } = image;
2081
+ if (!Number.isSafeInteger(width) || width <= 0 || !Number.isSafeInteger(height) || height <= 0 || !Number.isSafeInteger(width * height * 4)) throw new RangeError("Invalid MIFF dimensions");
2082
+ return new TextEncoder().encode(`id=ImageMagick version=1.0
2083
+ class=DirectClass colors=0 matte=True
2084
+ columns=${width} rows=${height} depth=8
2085
+ colorspace=sRGB
2086
+ compression=None
2087
+ \f
2088
+ :`);
2089
+ }
2090
+ function encodeMiffImage(image) {
2091
+ let chunks2 = [], length = 0;
2092
+ for (let page of image.miffPages ?? [image]) {
2093
+ let header = miffHeader(page), size = page.width * page.height * 4;
2094
+ if (page.data.length !== size) throw new Error("Invalid MIFF pixels");
2095
+ chunks2.push(header, page.data), length += header.length + size;
2096
+ }
2097
+ let bytes = new Uint8Array(length), offset = 0;
2098
+ for (let chunk of chunks2)
2099
+ bytes.set(chunk, offset), offset += chunk.length;
2100
+ return bytes;
2101
+ }
2102
+ async function* encodeMiffFromStorage(image, storage, signal) {
2103
+ for (let index = 0; index < (image.storedMiffPages?.length ?? 1); index++) {
2104
+ signal.throwIfAborted();
2105
+ let page = image.storedMiffPages ? await image.storedMiffPages.at(index) : image;
2106
+ signal.throwIfAborted(), yield miffHeader(page);
2107
+ let length = page.width * page.height * 4;
2108
+ for (let offset = 0; offset < length; offset += 4096) {
2109
+ signal.throwIfAborted(), offset % 65536 === 0 && await defaultRuntime.yieldTurn(signal);
2110
+ let size = Math.min(4096, length - offset), bytes = await storage.read(page.position + offset, size, { signal });
2111
+ if (signal.throwIfAborted(), bytes.length !== size) throw new Error("Truncated MIFF backing storage");
2112
+ yield new Uint8Array(bytes);
2113
+ }
2114
+ }
2115
+ }
2116
+
1998
2117
  // src/codecs/tiff-layout.ts
1999
2118
  function tiffChannels(img) {
2000
2119
  return img.space === "b-w" || img.space === "grey16" ? img.hasAlpha ? 2 : 1 : 4;
2001
2120
  }
2002
- function createTiffLayout(img, options) {
2003
- let { width, height } = img, channels = tiffChannels(img), hasAlpha = channels !== 1, pixelBytes = width * height * channels, density = Math.max(1, Math.round(options?.density ?? img.density ?? 72)), orientation = options?.orientation ?? img.orientation ?? 1, padding = pixelBytes % 2, ifdOffset = 8 + pixelBytes + padding, numEntries = hasAlpha ? 15 : 14, bpsOffset = ifdOffset + 2 + numEntries * 12 + 4, xResOffset = bpsOffset + 8, yResOffset = xResOffset + 8;
2004
- if (!Number.isSafeInteger(width) || width <= 0 || width > 4294967295 || !Number.isSafeInteger(height) || height <= 0 || height > 4294967295 || !Number.isSafeInteger(pixelBytes) || pixelBytes > 4294967295 || yResOffset + 8 > 4294967296)
2121
+ function createTiffLayout(img, options, offset = 0, hasNext = !1) {
2122
+ let { width, height } = img, channels = tiffChannels(img), hasAlpha = channels !== 1, pixelBytes = width * height * channels, density = Math.max(1, Math.round(options?.density ?? img.density ?? 72)), resolution = options?.density === void 0 ? img.resolution : void 0, scale = resolution?.unit === "meter" ? 100 : 1, x = resolution ? resolution.x / scale : density, y = resolution ? resolution.y / scale : density, denominator = Number.isInteger(x) && Number.isInteger(y) ? 1 : 1e4, orientation = options?.orientation ?? img.orientation ?? 1, padding = pixelBytes % 2, comment = img.properties?.comment === void 0 ? void 0 : new TextEncoder().encode(img.properties.comment + "\0"), commentSize = comment ? comment.length + comment.length % 2 : 0, numEntries = (hasAlpha ? 15 : 14) + (comment ? 1 : 0), directorySize = 2 + numEntries * 12 + 4 + 24 + commentSize, pixelOffset = offset === 0 ? 8 : offset + directorySize, ifdOffset = offset === 0 ? pixelOffset + pixelBytes + padding : offset, bpsOffset = ifdOffset + 2 + numEntries * 12 + 4, xResOffset = bpsOffset + 8, yResOffset = xResOffset + 8;
2123
+ if (!Number.isSafeInteger(offset) || offset < 0 || !Number.isSafeInteger(width) || width <= 0 || width > 4294967295 || !Number.isSafeInteger(height) || height <= 0 || height > 4294967295 || !Number.isSafeInteger(pixelBytes) || pixelBytes > 4294967295 || pixelOffset + pixelBytes + padding > 4294967296 || yResOffset + 8 + commentSize > 4294967296)
2005
2124
  throw new RangeError("TIFF layout exceeds classic TIFF offsets");
2006
- let header = new Uint8Array(8), directory = new Uint8Array(yResOffset + 8 - ifdOffset), headerView = new DataView(header.buffer), view = new DataView(directory.buffer);
2125
+ let header = new Uint8Array(8), directory = new Uint8Array(yResOffset + 8 + commentSize - ifdOffset), headerView = new DataView(header.buffer), view = new DataView(directory.buffer);
2007
2126
  header[0] = 73, header[1] = 73, headerView.setUint16(2, 42, !0), headerView.setUint32(4, ifdOffset, !0), view.setUint16(0, numEntries, !0);
2008
2127
  let writeEntry = (idx, tag, type, count, val) => {
2009
- let p = 2 + idx * 12;
2128
+ let p = 2 + (idx + (comment && tag > 270 ? 1 : 0)) * 12;
2010
2129
  view.setUint16(p, tag, !0), view.setUint16(p + 2, type, !0), view.setUint32(p + 4, count, !0), type === 3 && count === 1 ? view.setUint16(p + 8, val, !0) : view.setUint32(p + 8, val, !0);
2011
2130
  };
2012
- return writeEntry(0, 256, 4, 1, width), writeEntry(1, 257, 4, 1, height), writeEntry(2, 258, 3, channels, channels === 1 ? 8 : channels === 2 ? 524296 : bpsOffset), writeEntry(3, 259, 3, 1, 1), writeEntry(4, 262, 3, 1, channels < 3 ? 1 : 2), writeEntry(5, 273, 4, 1, 8), writeEntry(6, 274, 3, 1, orientation), writeEntry(7, 277, 3, 1, channels), writeEntry(8, 278, 4, 1, height), writeEntry(9, 279, 4, 1, pixelBytes), writeEntry(10, 282, 5, 1, xResOffset), writeEntry(11, 283, 5, 1, yResOffset), writeEntry(12, 284, 3, 1, 1), writeEntry(13, 296, 3, 1, 2), hasAlpha && writeEntry(14, 338, 3, 1, 2), view.setUint32(2 + numEntries * 12, 0, !0), view.setUint16(bpsOffset - ifdOffset, 8, !0), view.setUint16(bpsOffset - ifdOffset + 2, 8, !0), view.setUint16(bpsOffset - ifdOffset + 4, 8, !0), view.setUint16(bpsOffset - ifdOffset + 6, 8, !0), view.setUint32(xResOffset - ifdOffset, density, !0), view.setUint32(xResOffset - ifdOffset + 4, 1, !0), view.setUint32(yResOffset - ifdOffset, density, !0), view.setUint32(yResOffset - ifdOffset + 4, 1, !0), { header, directory, channels, padding };
2131
+ writeEntry(0, 256, 4, 1, width), writeEntry(1, 257, 4, 1, height), writeEntry(2, 258, 3, channels, channels === 1 ? 8 : channels === 2 ? 524296 : bpsOffset), writeEntry(3, 259, 3, 1, 1), writeEntry(4, 262, 3, 1, channels < 3 ? 1 : 2), writeEntry(5, 273, 4, 1, pixelOffset), writeEntry(6, 274, 3, 1, orientation), writeEntry(7, 277, 3, 1, channels), writeEntry(8, 278, 4, 1, height), writeEntry(9, 279, 4, 1, pixelBytes), writeEntry(10, 282, 5, 1, xResOffset), writeEntry(11, 283, 5, 1, yResOffset), writeEntry(12, 284, 3, 1, 1), writeEntry(13, 296, 3, 1, resolution?.unit === "meter" || resolution?.unit === "cm" ? 3 : resolution?.unit === "unknown" ? 1 : 2), hasAlpha && writeEntry(14, 338, 3, 1, 2), comment && (writeEntry(5, 270, 2, comment.length, comment.length <= 4 ? 0 : yResOffset + 8), directory.set(comment, comment.length <= 4 ? 70 : yResOffset + 8 - ifdOffset));
2132
+ let end = offset === 0 ? yResOffset + 8 + commentSize : pixelOffset + pixelBytes + padding;
2133
+ if (hasNext && end > 4294967295) throw new RangeError("TIFF layout exceeds classic TIFF offsets");
2134
+ return view.setUint32(2 + numEntries * 12, hasNext ? end : 0, !0), view.setUint16(bpsOffset - ifdOffset, 8, !0), view.setUint16(bpsOffset - ifdOffset + 2, 8, !0), view.setUint16(bpsOffset - ifdOffset + 4, 8, !0), view.setUint16(bpsOffset - ifdOffset + 6, 8, !0), view.setUint32(xResOffset - ifdOffset, Math.round(x * denominator), !0), view.setUint32(xResOffset - ifdOffset + 4, denominator, !0), view.setUint32(yResOffset - ifdOffset, Math.round(y * denominator), !0), view.setUint32(yResOffset - ifdOffset + 4, denominator, !0), { header, directory, channels, padding, end };
2013
2135
  }
2014
2136
 
2015
2137
  // src/codecs/pdf-storage.ts
@@ -2318,15 +2440,31 @@ var JpegTables = class {
2318
2440
 
2319
2441
  // src/codecs/jpeg-metadata.ts
2320
2442
  function applyJpegMetadata(state, marker, payload) {
2443
+ marker === 254 && (state.properties ??= {}, state.properties.comment = (state.properties.comment ?? "") + new TextDecoder().decode(payload));
2321
2444
  let { width, height, channels, isProgressive, density, orientation } = state;
2445
+ if (marker === 226 && payload.length >= 14 && new TextDecoder().decode(payload.subarray(0, 12)) === "ICC_PROFILE\0") {
2446
+ let index = payload[12], count = payload[13];
2447
+ if (!index || index > count || state.iccCount !== void 0 && state.iccCount !== count || state.iccParts?.has(index)) throw new Error("Invalid JPEG ICC sequence");
2448
+ if (state.iccCount = count, (state.iccParts ??= /* @__PURE__ */ new Map()).set(index, payload.slice(14)), state.iccParts.size === count) {
2449
+ let length = 0;
2450
+ for (let part of state.iccParts.values()) length += part.length;
2451
+ state.icc = new Uint8Array(length);
2452
+ let offset = 0;
2453
+ for (let i = 1; i <= count; i++) {
2454
+ let part = state.iccParts.get(i);
2455
+ state.icc.set(part, offset), offset += part.length;
2456
+ }
2457
+ }
2458
+ }
2459
+ if (marker === 218 && state.iccParts && !state.icc) throw new Error("Incomplete JPEG ICC sequence");
2322
2460
  if (marker === 219 && (state.tables ??= new JpegTables(), state.tables.read(marker, payload), state.quality = estimateJpegQuality(state.tables.quantTables)), marker === 224 && payload.length >= 12) {
2323
2461
  if (payload[0] === 74 && payload[1] === 70 && payload[2] === 73 && payload[3] === 70 && payload[4] === 0) {
2324
- let units = payload[7], xDensity = payload[8] << 8 | payload[9];
2325
- xDensity > 0 && (units === 1 ? density = xDensity : units === 2 && (density = Math.round(xDensity * 2.54)));
2462
+ let units = payload[7], yDensity = payload[10] << 8 | payload[11], xDensity = payload[8] << 8 | payload[9];
2463
+ xDensity > 0 && (state.resolution = { x: xDensity, y: yDensity, unit: units === 1 ? "inch" : units === 2 ? "cm" : "unknown" }, units === 1 ? density = xDensity : units === 2 && (density = Math.round(xDensity * 2.54)));
2326
2464
  }
2327
2465
  } else if (marker === 225 && payload.length >= 8) {
2328
2466
  let exif = parseExifBuffer(payload);
2329
- exif.orientation !== void 0 && (orientation = exif.orientation), exif.density !== void 0 && (density = exif.density);
2467
+ exif.orientation !== void 0 && (orientation = exif.orientation), exif.resolution !== void 0 && (state.resolution = exif.resolution), exif.density !== void 0 && (density = exif.density);
2330
2468
  } else if (marker === 192 || marker === 193 || marker === 194) {
2331
2469
  isProgressive = marker === 194, height = payload[1] << 8 | payload[2], width = payload[3] << 8 | payload[4];
2332
2470
  let comps = payload[5];
@@ -3345,6 +3483,8 @@ function readJpegMetadata(bytes) {
3345
3483
  if (width <= 0 || height <= 0)
3346
3484
  throw new Error("Invalid JPEG dimensions");
3347
3485
  return {
3486
+ ...state.properties === void 0 ? {} : { properties: state.properties },
3487
+ ...state.icc === void 0 ? {} : { icc: state.icc },
3348
3488
  format: "jpeg",
3349
3489
  ...state.jpegSamplingFactor === void 0 ? {} : { jpegSamplingFactor: state.jpegSamplingFactor },
3350
3490
  width,
@@ -3353,6 +3493,7 @@ function readJpegMetadata(bytes) {
3353
3493
  channels,
3354
3494
  depth: "uchar",
3355
3495
  density,
3496
+ ...state.resolution ? { resolution: state.resolution } : {},
3356
3497
  hasAlpha: !1,
3357
3498
  ...orientation !== void 0 ? { orientation } : {},
3358
3499
  ...state.quality === void 0 ? {} : { quality: state.quality },
@@ -3575,7 +3716,10 @@ function decodeJpegImage(bytes, options) {
3575
3716
  channels: meta.channels,
3576
3717
  depth: "uchar",
3577
3718
  density: meta.density,
3719
+ ...meta.resolution ? { resolution: meta.resolution } : {},
3578
3720
  hasAlpha: !1,
3721
+ ...meta.properties === void 0 ? {} : { properties: meta.properties },
3722
+ ...meta.icc === void 0 ? {} : { icc: meta.icc },
3579
3723
  ...meta.orientation !== void 0 ? { orientation: meta.orientation } : {},
3580
3724
  ...meta.isProgressive !== void 0 ? { isProgressive: meta.isProgressive } : {}
3581
3725
  };
@@ -3623,7 +3767,10 @@ function decodeJpegImage(bytes, options) {
3623
3767
  channels: meta.channels,
3624
3768
  depth: "uchar",
3625
3769
  density: meta.density,
3770
+ ...meta.resolution ? { resolution: meta.resolution } : {},
3626
3771
  hasAlpha: !1,
3772
+ ...meta.properties === void 0 ? {} : { properties: meta.properties },
3773
+ ...meta.icc === void 0 ? {} : { icc: meta.icc },
3627
3774
  ...meta.orientation !== void 0 ? { orientation: meta.orientation } : {},
3628
3775
  ...meta.isProgressive !== void 0 ? { isProgressive: meta.isProgressive } : {}
3629
3776
  };
@@ -3742,7 +3889,7 @@ function createJpegEncoder(img, options) {
3742
3889
  let maxH = Math.max(...factors.map((value) => value[0])), maxV = Math.max(...factors.map((value) => value[1]));
3743
3890
  if (factors.length !== 3 || factors.some((value) => value.length !== 2 || value.some((n) => !Number.isInteger(n) || n < 1 || n > 4) || maxH % value[0] !== 0 || maxV % value[1] !== 0) || factors.reduce((sum, value) => sum + value[0] * value[1], 0) > 10)
3744
3891
  throw new RangeError(`Invalid JPEG sampling factor: ${spec}`);
3745
- let blockWidth = maxH * 8, blockHeight = maxV * 8, quality = options?.quality ?? 85, density = Math.max(1, Math.round(options?.density ?? img.density ?? 72)), orientation = options?.orientation ?? img.orientation, lumaQ = scaleQuantTable(STD_LUMA_QUANT, quality), chromaQ = scaleQuantTable(STD_CHROMA_QUANT, quality), bytes = [], writeU16 = (v) => {
3892
+ let blockWidth = maxH * 8, blockHeight = maxV * 8, quality = options?.quality ?? 85, density = Math.max(1, Math.round(options?.density ?? img.density ?? 72)), resolution = options?.density === void 0 ? img.resolution : void 0, scale = resolution?.unit === "meter" ? 100 : 1, xDensity = resolution ? Math.round(resolution.x / scale) : density, yDensity = resolution ? Math.round(resolution.y / scale) : density, orientation = options?.orientation ?? img.orientation, lumaQ = scaleQuantTable(STD_LUMA_QUANT, quality), chromaQ = scaleQuantTable(STD_CHROMA_QUANT, quality), bytes = [], writeU16 = (v) => {
3746
3893
  bytes.push(v >>> 8 & 255, v & 255);
3747
3894
  }, writeSegment = (marker, payload) => {
3748
3895
  bytes.push(255, marker), writeU16(payload.length + 2);
@@ -3757,16 +3904,32 @@ function createJpegEncoder(img, options) {
3757
3904
  0,
3758
3905
  1,
3759
3906
  1,
3760
- 1,
3761
- // units = DPI
3762
- density >>> 8 & 255,
3763
- density & 255,
3764
- density >>> 8 & 255,
3765
- density & 255,
3907
+ resolution?.unit === "meter" || resolution?.unit === "cm" ? 2 : resolution?.unit === "unknown" ? 0 : 1,
3908
+ // density units
3909
+ xDensity >>> 8 & 255,
3910
+ xDensity & 255,
3911
+ yDensity >>> 8 & 255,
3912
+ yDensity & 255,
3766
3913
  0,
3767
3914
  0
3768
3915
  ]);
3769
- writeSegment(224, app0), orientation !== void 0 && orientation >= 1 && orientation <= 8 && writeSegment(225, buildExifApp1Segment({ orientation, density }));
3916
+ if (writeSegment(224, app0), img.properties?.comment !== void 0) {
3917
+ let comment = new TextEncoder().encode(img.properties.comment);
3918
+ for (let offset = 0; offset < comment.length; ) {
3919
+ let end = Math.min(comment.length, offset + 65533);
3920
+ for (; end < comment.length && (comment[end] & 192) === 128; ) end--;
3921
+ writeSegment(254, comment.subarray(offset, end)), offset = end;
3922
+ }
3923
+ }
3924
+ if (img.icc) {
3925
+ let count = Math.ceil(img.icc.length / 65519);
3926
+ if (count > 255) throw new Error("ICC profile exceeds JPEG segment limit");
3927
+ for (let index = 0; index < count; index++) {
3928
+ let part = img.icc.subarray(index * 65519, (index + 1) * 65519), payload = new Uint8Array(14 + part.length);
3929
+ payload.set(new TextEncoder().encode("ICC_PROFILE\0")), payload[12] = index + 1, payload[13] = count, payload.set(part, 14), writeSegment(226, payload);
3930
+ }
3931
+ }
3932
+ orientation !== void 0 && orientation >= 1 && orientation <= 8 && writeSegment(225, buildExifApp1Segment({ orientation, density, resolution }));
3770
3933
  let dqt = new Uint8Array(130);
3771
3934
  dqt[0] = 0, dqt.set(lumaQ, 1), dqt[65] = 1, dqt.set(chromaQ, 66), writeSegment(219, dqt);
3772
3935
  let sof0 = new Uint8Array([
@@ -4437,11 +4600,19 @@ async function readJpegMetadataFromSource(source, signal) {
4437
4600
  if (position + 2 > source.size) break;
4438
4601
  let length = await reader.at(position) << 8 | await reader.at(position + 1);
4439
4602
  if (length < 2 || position + length > source.size) break;
4440
- if (marker === 219 || marker === 218 || marker === 224 || marker === 225 || marker === 192 || marker === 193 || marker === 194) {
4603
+ if (marker === 254) {
4604
+ let decoder = new TextDecoder(), comment = "";
4605
+ for (let offset = 0; offset < length - 2; offset += 4096) {
4606
+ let payload = await payloadBytes(reader, position + 2 + offset, Math.min(4096, length - 2 - offset));
4607
+ comment += decoder.decode(payload, { stream: !0 });
4608
+ }
4609
+ state.properties ??= {}, state.properties.comment = (state.properties.comment ?? "") + comment + decoder.decode();
4610
+ }
4611
+ if (marker === 219 || marker === 218 || marker === 224 || marker === 226 || marker === 225 || marker === 192 || marker === 193 || marker === 194) {
4441
4612
  let payload = await payloadBytes(
4442
4613
  reader,
4443
4614
  position + 2,
4444
- Math.min(length - 2, marker === 224 ? 12 : marker === 225 || marker === 219 ? 65533 : 771)
4615
+ Math.min(length - 2, marker === 224 ? 12 : marker === 226 || marker === 225 || marker === 219 ? 65533 : 771)
4445
4616
  );
4446
4617
  if (applyJpegMetadata(state, marker, payload)) break;
4447
4618
  }
@@ -4451,6 +4622,8 @@ async function readJpegMetadataFromSource(source, signal) {
4451
4622
  let { width, height, channels, isProgressive, density, orientation } = state;
4452
4623
  if (width <= 0 || height <= 0) throw new Error("Invalid JPEG dimensions");
4453
4624
  return {
4625
+ ...state.properties === void 0 ? {} : { properties: state.properties },
4626
+ ...state.icc === void 0 ? {} : { icc: state.icc },
4454
4627
  format: "jpeg",
4455
4628
  ...state.jpegSamplingFactor === void 0 ? {} : { jpegSamplingFactor: state.jpegSamplingFactor },
4456
4629
  width,
@@ -4459,6 +4632,7 @@ async function readJpegMetadataFromSource(source, signal) {
4459
4632
  channels,
4460
4633
  depth: "uchar",
4461
4634
  density,
4635
+ ...state.resolution ? { resolution: state.resolution } : {},
4462
4636
  hasAlpha: !1,
4463
4637
  ...orientation === void 0 ? {} : { orientation },
4464
4638
  ...state.quality === void 0 ? {} : { quality: state.quality },
@@ -4636,7 +4810,10 @@ async function decodeJpegToStorage(source, storage, signal, options) {
4636
4810
  channels: meta.channels,
4637
4811
  depth: "uchar",
4638
4812
  density: meta.density,
4813
+ ...meta.resolution ? { resolution: meta.resolution } : {},
4639
4814
  hasAlpha: !1,
4815
+ ...meta.properties === void 0 ? {} : { properties: meta.properties },
4816
+ ...meta.icc === void 0 ? {} : { icc: meta.icc },
4640
4817
  ...meta.orientation !== void 0 ? { orientation: meta.orientation } : {},
4641
4818
  ...meta.isProgressive !== void 0 ? { isProgressive: meta.isProgressive } : {}
4642
4819
  };
@@ -4690,7 +4867,10 @@ async function decodeJpegToStorage(source, storage, signal, options) {
4690
4867
  channels: meta.channels,
4691
4868
  depth: "uchar",
4692
4869
  density: meta.density,
4870
+ ...meta.resolution ? { resolution: meta.resolution } : {},
4693
4871
  hasAlpha: !1,
4872
+ ...meta.properties === void 0 ? {} : { properties: meta.properties },
4873
+ ...meta.icc === void 0 ? {} : { icc: meta.icc },
4694
4874
  ...meta.orientation !== void 0 ? { orientation: meta.orientation } : {},
4695
4875
  ...meta.isProgressive !== void 0 ? { isProgressive: meta.isProgressive } : {}
4696
4876
  };
@@ -4822,10 +5002,18 @@ async function* encodePngFromStorage(image, storage, signal, options = {}) {
4822
5002
  }
4823
5003
  let gray = image.space === "b-w" || image.channels === 1 || image.channels === 2, channels = gray ? alpha ? [0, 3] : [0] : alpha ? [0, 1, 2, 3] : [0, 1, 2], sampleBytes = image.storedData16 ? 2 : 1, header = new Uint8Array(13), view = new DataView(header.buffer);
4824
5004
  view.setUint32(0, width), view.setUint32(4, height), header[8] = sampleBytes * 8, header[9] = gray ? alpha ? 4 : 0 : alpha ? 6 : 2, yield new Uint8Array(PNG_SIGNATURE), signal.throwIfAborted(), yield makeChunk("IHDR", header);
4825
- let density = options.density ?? image.density ?? 72, physical = new Uint8Array(9), physicalView = new DataView(physical.buffer);
4826
- physicalView.setUint32(0, Math.round(density / 0.0254)), physicalView.setUint32(4, Math.round(density / 0.0254)), physical[8] = 1, signal.throwIfAborted(), yield makeChunk("pHYs", physical), image.virtualCanvas !== void 0 && (signal.throwIfAborted(), yield canvasChunk(image.virtualCanvas));
5005
+ let density = options.density ?? image.density ?? 72, resolution = options.density === void 0 ? image.resolution : void 0, scale = resolution?.unit === "inch" ? 1 / 0.0254 : resolution?.unit === "cm" ? 100 : 1, physical = new Uint8Array(9), physicalView = new DataView(physical.buffer);
5006
+ physicalView.setUint32(0, Math.round(resolution ? resolution.x * scale : density / 0.0254)), physicalView.setUint32(4, Math.round(resolution ? resolution.y * scale : density / 0.0254)), physical[8] = resolution?.unit === "unknown" ? 0 : 1, !options.omitPngResolution && (resolution !== void 0 || options.density !== void 0) && (signal.throwIfAborted(), yield makeChunk("pHYs", physical)), image.virtualCanvas !== void 0 && (signal.throwIfAborted(), yield canvasChunk(image.virtualCanvas));
5007
+ for (let chunk of textChunks(image.properties))
5008
+ for (let offset = 0; offset < chunk.length; offset += 4096)
5009
+ signal.throwIfAborted(), yield chunk.subarray(offset, offset + 4096);
5010
+ if (image.icc) {
5011
+ let chunk = iccChunk(image.icc);
5012
+ for (let offset = 0; offset < chunk.length; offset += 4096)
5013
+ signal.throwIfAborted(), yield chunk.subarray(offset, offset + 4096);
5014
+ }
4827
5015
  let orientation = options.orientation ?? image.orientation;
4828
- orientation !== void 0 && (signal.throwIfAborted(), yield makeChunk("eXIf", buildExifApp1Segment({ orientation, density }).subarray(6)));
5016
+ orientation !== void 0 && (signal.throwIfAborted(), yield makeChunk("eXIf", buildExifApp1Segment({ orientation, density, resolution }).subarray(6)));
4829
5017
  let encoder = createByteCodec({ direction: "encode", format: "zlib", chunkSize: 4084, level: Math.max(0, Math.min(9, options.compressionLevel ?? 6)) });
4830
5018
  try {
4831
5019
  for (let y = 0; y < height; y++) {
@@ -6059,7 +6247,7 @@ function gifLayout(image, options = {}) {
6059
6247
  return { width, height, frames, frameHeight, framePixels: width * frameHeight };
6060
6248
  }
6061
6249
  function gifHeader(image, options, layout, palette) {
6062
- let { frames } = layout, width = image.virtualCanvas?.width || layout.width, frameHeight = image.virtualCanvas?.height || layout.frameHeight, loop = options.loop ?? image.loop ?? (frames > 1 ? 0 : void 0), out = new Uint8Array(781 + (loop === void 0 || loop === 1 ? 0 : 19));
6250
+ let comment = new TextEncoder().encode(image.properties?.comment ?? ""), { frames } = layout, width = image.virtualCanvas?.width || layout.width, frameHeight = image.virtualCanvas?.height || layout.frameHeight, loop = options.loop ?? image.loop ?? (frames > 1 ? 0 : void 0), out = new Uint8Array(781 + (loop === void 0 || loop === 1 ? 0 : 19) + (comment.length ? 3 + comment.length + Math.ceil(comment.length / 255) : 0));
6063
6251
  if (out.set([
6064
6252
  71,
6065
6253
  73,
@@ -6101,6 +6289,14 @@ function gifHeader(image, options, layout, palette) {
6101
6289
  781
6102
6290
  );
6103
6291
  }
6292
+ if (comment.length) {
6293
+ let at = 781 + (loop === void 0 || loop === 1 ? 0 : 19);
6294
+ out.set([33, 254], at), at += 2;
6295
+ for (let offset = 0; offset < comment.length; offset += 255) {
6296
+ let part = comment.subarray(offset, offset + 255);
6297
+ out[at++] = part.length, out.set(part, at), at += part.length;
6298
+ }
6299
+ }
6104
6300
  return out;
6105
6301
  }
6106
6302
  function gifFrameHeader(image, options, layout, frame, transparent, frameDelay = image.delay?.[frame], frameDisposal = typeof image.disposal == "number" ? image.disposal : image.disposal?.[frame], rectangle = { left: 0, top: 0, width: layout.width, height: layout.frameHeight }) {
@@ -6150,7 +6346,7 @@ function* gifAnimationSteps(size, start) {
6150
6346
  return at2;
6151
6347
  }, word3 = function* (at2) {
6152
6348
  return ((yield at2) ?? 0) | ((yield at2 + 1) ?? 0) << 8;
6153
- }, at = start, pending = 100, frames = 0, loop;
6349
+ }, comments = [], at = start, pending = 100, frames = 0, loop;
6154
6350
  for (; at < size; ) {
6155
6351
  let intro = yield at++;
6156
6352
  if (intro === 59) break;
@@ -6171,7 +6367,14 @@ function* gifAnimationSteps(size, start) {
6171
6367
  }
6172
6368
  at += n;
6173
6369
  }
6174
- } else at = yield* skip(at);
6370
+ } else if (label === 254)
6371
+ for (; at < size; ) {
6372
+ let n = (yield at++) ?? 0;
6373
+ if (!n) break;
6374
+ if (comments.length + n > 16 * 1024 * 1024) throw new Error("GIF comment exceeds 16 MiB");
6375
+ for (let i = 0; i < n && at < size; i++) comments.push((yield at++) ?? 0);
6376
+ }
6377
+ else at = yield* skip(at);
6175
6378
  } else if (intro === 44) {
6176
6379
  if (at + 9 > size) break;
6177
6380
  yield { frame: frames++, delay: pending }, pending = 100;
@@ -6179,7 +6382,7 @@ function* gifAnimationSteps(size, start) {
6179
6382
  at += 9, flags & 128 && (at += 3 * (1 << (flags & 7) + 1)), at = yield* skip(at + 1);
6180
6383
  } else break;
6181
6384
  }
6182
- return { frames, loop };
6385
+ return { frames, loop, ...comments.length ? { properties: { comment: new TextDecoder().decode(new Uint8Array(comments)) } } : {} };
6183
6386
  }
6184
6387
  function gifMetadataFields(width, height, size, frames, loop, options) {
6185
6388
  let total = Math.max(1, frames), multi = options?.animated === !0 || options?.pages === -1 || options?.pages !== void 0 && options.pages > 1, page = Math.max(0, options?.page ?? 0), pages = multi ? options?.pages !== void 0 && options.pages > 0 ? Math.min(options.pages, Math.max(1, total - page)) : Math.max(1, total - page) : 1;
@@ -6205,7 +6408,7 @@ function readGifMetadata(bytes, options) {
6205
6408
  let start = 13 + (bytes[10] & 128 ? 3 * (1 << (bytes[10] & 7) + 1) : 0), steps = gifAnimationSteps(bytes.length, start), delays = [], next = steps.next();
6206
6409
  for (; !next.done; )
6207
6410
  typeof next.value == "number" ? next = steps.next(bytes[next.value]) : (delays.push(next.value.delay), next = steps.next());
6208
- return { ...gifMetadataFields(bytes[6] | bytes[7] << 8, bytes[8] | bytes[9] << 8, bytes.length, next.value.frames, next.value.loop, options), ...delays.length ? { delay: delays } : {} };
6411
+ return { ...next.value.properties ? { properties: next.value.properties } : {}, ...gifMetadataFields(bytes[6] | bytes[7] << 8, bytes[8] | bytes[9] << 8, bytes.length, next.value.frames, next.value.loop, options), ...delays.length ? { delay: delays } : {} };
6209
6412
  }
6210
6413
  function lzwDecode(minCodeSize, data, pixelCount) {
6211
6414
  let clearCode = 1 << minCodeSize, eoiCode = clearCode + 1, codeSize = minCodeSize + 1, nextCode = eoiCode + 1, prefix = new Int32Array(4096), suffix = new Uint8Array(4096), stack = new Uint8Array(4096);
@@ -6292,6 +6495,7 @@ function decodeGifImage(bytes, options) {
6292
6495
  index !== transparentIdx && data.set([palette[index * 3] ?? 0, palette[index * 3 + 1] ?? 0, palette[index * 3 + 2] ?? 0, 255], at);
6293
6496
  }
6294
6497
  return {
6498
+ ...meta.properties ? { properties: meta.properties } : {},
6295
6499
  width: imgW,
6296
6500
  height: imgH,
6297
6501
  data,
@@ -6350,6 +6554,7 @@ function decodeGifImage(bytes, options) {
6350
6554
  width,
6351
6555
  height: height * numPages,
6352
6556
  data: outData,
6557
+ ...meta.properties ? { properties: meta.properties } : {},
6353
6558
  format: "gif",
6354
6559
  space: "srgb",
6355
6560
  channels: 4,
@@ -6421,7 +6626,32 @@ function createBmpHeader(img) {
6421
6626
 
6422
6627
  // src/codecs/netpbm-header.ts
6423
6628
  function* netpbmHeaderSteps(size) {
6424
- let magic = String.fromCharCode(yield 0, yield 1), format = magic === "P1" || magic === "P4" ? "pbm" : magic === "P2" || magic === "P5" ? "pgm" : "ppm", pos = 2, tokens = [], needed = format === "pbm" ? 2 : 3;
6629
+ let magic = String.fromCharCode(yield 0, yield 1);
6630
+ if (magic === "P7") {
6631
+ let pos2 = 2, fields2 = /* @__PURE__ */ new Map();
6632
+ for (; pos2 < size; ) {
6633
+ let line = "";
6634
+ for (; pos2 < size; ) {
6635
+ let byte2 = yield pos2++;
6636
+ if (byte2 === 10) break;
6637
+ if (line += String.fromCharCode(byte2), line.length > 4096) throw new Error("PAM header line too long");
6638
+ }
6639
+ if (line = line.split("#")[0].trim(), line === "ENDHDR") {
6640
+ let width = Number(fields2.get("WIDTH")), height = Number(fields2.get("HEIGHT")), channels = Number(fields2.get("DEPTH")), maxval = Number(fields2.get("MAXVAL")), tuple = fields2.get("TUPLTYPE");
6641
+ if (!Number.isSafeInteger(width * height * 4) || !Number.isInteger(width) || !Number.isInteger(height) || width <= 0 || height <= 0 || ![1, 2, 3, 4].includes(channels) || !Number.isInteger(maxval) || maxval < 1 || maxval > 65535)
6642
+ throw new Error("Invalid PAM header");
6643
+ if (tuple && { BLACKANDWHITE: 1, BLACKANDWHITE_ALPHA: 2, GRAYSCALE: 1, GRAYSCALE_ALPHA: 2, RGB: 3, RGB_ALPHA: 4 }[tuple] !== channels) throw new Error("Unsupported PAM tuple type");
6644
+ return { magic, format: "pam", width, height, maxval, channels, hasAlpha: channels === 2 || channels === 4, dataOffset: pos2 };
6645
+ }
6646
+ if (!line) continue;
6647
+ let separator = 0;
6648
+ for (; separator < line.length && line[separator] !== " " && line[separator] !== " "; ) separator++;
6649
+ let key = line.slice(0, separator);
6650
+ ["WIDTH", "HEIGHT", "DEPTH", "MAXVAL", "TUPLTYPE"].includes(key) && fields2.set(key, line.slice(separator).trim());
6651
+ }
6652
+ throw new Error("Missing PAM ENDHDR");
6653
+ }
6654
+ let format = magic === "P1" || magic === "P4" ? "pbm" : magic === "P2" || magic === "P5" ? "pgm" : "ppm", pos = 2, tokens = [], needed = format === "pbm" ? 2 : 3;
6425
6655
  for (; pos < size && tokens.length < needed; ) {
6426
6656
  for (; pos < size; ) {
6427
6657
  let c = yield pos;
@@ -6448,11 +6678,53 @@ function* netpbmHeaderSteps(size) {
6448
6678
  width: tokens[0] ?? 0,
6449
6679
  height: tokens[1] ?? 0,
6450
6680
  maxval: format === "pbm" ? 1 : tokens[2] ?? 255,
6681
+ channels: format === "ppm" ? 3 : 1,
6682
+ hasAlpha: !1,
6451
6683
  dataOffset: pos
6452
6684
  };
6453
6685
  }
6454
- function parseNetpbmHeader(bytes) {
6455
- let steps = netpbmHeaderSteps(bytes.length), next = steps.next();
6686
+ function* netpbmSequenceSteps(size, page) {
6687
+ let offset = 0, pages = 0, first;
6688
+ for (; offset < size; ) {
6689
+ let steps = netpbmHeaderSteps(size - offset), next = steps.next();
6690
+ for (; !next.done; ) next = steps.next(yield offset + next.value);
6691
+ let header = { ...next.value, dataOffset: offset + next.value.dataOffset };
6692
+ if (!["P1", "P2", "P3", "P4", "P5", "P6", "P7"].includes(header.magic)) throw new Error("Invalid Netpbm signature");
6693
+ if (first ??= header, pages++ === page) return { header, pages };
6694
+ let { width, height, magic, maxval } = header;
6695
+ if (!Number.isSafeInteger(width * height) || width <= 0 || height <= 0) throw new RangeError("Invalid Netpbm dimensions");
6696
+ if (offset = header.dataOffset, magic === "P4") offset += Math.ceil(width / 8) * height;
6697
+ else if (magic === "P5" || magic === "P6" || magic === "P7") {
6698
+ if (offset += width * height * header.channels * (maxval > 255 ? 2 : 1), magic === "P7" && offset > size) throw new Error("Truncated PAM raster");
6699
+ } else
6700
+ for (let samples = width * height * (magic === "P3" ? 3 : 1); samples > 0 && offset < size; samples--) {
6701
+ for (; offset < size; ) {
6702
+ let c = yield offset;
6703
+ if (c === 35)
6704
+ for (; offset < size && (yield offset) !== 10 && (yield offset) !== 13; ) offset++;
6705
+ else if (c <= 32) offset++;
6706
+ else break;
6707
+ }
6708
+ let start = offset;
6709
+ for (; offset < size && (yield offset) >= 48 && (yield offset) <= 57; ) offset++;
6710
+ if (start === offset) {
6711
+ offset = size;
6712
+ break;
6713
+ }
6714
+ }
6715
+ for (; offset < size; ) {
6716
+ let c = yield offset;
6717
+ if (c === 35)
6718
+ for (; offset < size && (yield offset) !== 10 && (yield offset) !== 13; ) offset++;
6719
+ else if (c <= 32) offset++;
6720
+ else break;
6721
+ }
6722
+ }
6723
+ if (!first || page !== void 0) throw new RangeError("Netpbm page out of range");
6724
+ return { header: first, pages };
6725
+ }
6726
+ function parseNetpbmSequence(bytes, page) {
6727
+ let steps = netpbmSequenceSteps(bytes.length, page), next = steps.next();
6456
6728
  for (; !next.done; ) next = steps.next(bytes[next.value]);
6457
6729
  return next.value;
6458
6730
  }
@@ -6461,7 +6733,7 @@ function parseNetpbmHeader(bytes) {
6461
6733
  function isNetpbmBytes(bytes) {
6462
6734
  if (bytes.length < 7 || bytes[0] !== 80) return !1;
6463
6735
  let m = bytes[1];
6464
- if (m < 49 || m > 54) return !1;
6736
+ if (m < 49 || m > 55) return !1;
6465
6737
  let ws = bytes[2];
6466
6738
  return ws === 32 || ws === 10 || ws === 13 || ws === 9;
6467
6739
  }
@@ -6472,21 +6744,26 @@ function isTiffBytes(bytes) {
6472
6744
  return bytes.length >= 8 && (bytes[0] === 73 && bytes[1] === 73 && bytes[2] === 42 && bytes[3] === 0 || bytes[0] === 77 && bytes[1] === 77 && bytes[2] === 0 && bytes[3] === 42);
6473
6745
  }
6474
6746
  function readNetpbmMetadata(bytes) {
6475
- let hdr = parseNetpbmHeader(bytes);
6747
+ let { header: hdr, pages } = parseNetpbmSequence(bytes);
6476
6748
  return {
6477
6749
  format: hdr.format,
6478
6750
  width: hdr.width,
6479
6751
  height: hdr.height,
6480
- space: hdr.format === "ppm" ? "srgb" : "b-w",
6481
- channels: hdr.format === "ppm" ? 3 : 1,
6752
+ space: hdr.channels >= 3 ? "srgb" : "b-w",
6753
+ channels: hdr.channels,
6482
6754
  depth: hdr.format === "pbm" ? "bit" : hdr.maxval > 255 ? "ushort" : "uchar",
6483
6755
  density: 72,
6484
- hasAlpha: !1,
6485
- size: bytes.byteLength
6756
+ hasAlpha: hdr.hasAlpha,
6757
+ size: bytes.byteLength,
6758
+ ...pages > 1 ? { pages } : {}
6486
6759
  };
6487
6760
  }
6488
- function decodeNetpbmImage(bytes) {
6489
- let hdr = parseNetpbmHeader(bytes), { width, height, maxval, magic, format } = hdr, rgba = new Uint8Array(width * height * 4), pos = hdr.dataOffset, readNextAsciiInt = () => {
6761
+ function decodeNetpbmImage(bytes, page = 0) {
6762
+ let { header: hdr } = parseNetpbmSequence(bytes, page), { width, height, maxval, magic, format, channels, hasAlpha } = hdr;
6763
+ if (magic === "P6" && bytes.length - hdr.dataOffset < width * height * 3 * (maxval > 255 ? 2 : 1))
6764
+ throw new Error("Truncated PPM raster");
6765
+ if (magic === "P7" && bytes.length - hdr.dataOffset < width * height * channels * (maxval > 255 ? 2 : 1)) throw new Error("Truncated PAM raster");
6766
+ let rgba = new Uint8Array(width * height * 4), data16 = maxval > 255 ? new Uint16Array(width * height * 4) : void 0, pos = hdr.dataOffset, readNextAsciiInt = () => {
6490
6767
  for (; pos < bytes.length; ) {
6491
6768
  let c = bytes[pos];
6492
6769
  if (c === 35)
@@ -6501,21 +6778,16 @@ function decodeNetpbmImage(bytes) {
6501
6778
  val = val * 10 + (bytes[pos] - 48), pos++;
6502
6779
  return val;
6503
6780
  };
6504
- if (magic === "P6") {
6505
- let readSample = maxval > 255 ? () => {
6506
- let hi = bytes[pos++] ?? 0, lo = bytes[pos++] ?? 0;
6507
- return hi << 8 | lo;
6508
- } : () => bytes[pos++] ?? 0;
6509
- for (let i = 0; i < width * height; i++)
6510
- rgba[i * 4] = Math.round(readSample() * 255 / maxval), rgba[i * 4 + 1] = Math.round(readSample() * 255 / maxval), rgba[i * 4 + 2] = Math.round(readSample() * 255 / maxval), rgba[i * 4 + 3] = 255;
6511
- } else if (magic === "P5") {
6512
- let readSample = maxval > 255 ? () => {
6513
- let hi = bytes[pos++] ?? 0, lo = bytes[pos++] ?? 0;
6514
- return hi << 8 | lo;
6515
- } : () => bytes[pos++] ?? 0;
6781
+ if (magic === "P7" || magic === "P6" || magic === "P5" || magic === "P3" || magic === "P2") {
6782
+ let readSample = magic === "P3" || magic === "P2" ? readNextAsciiInt : () => {
6783
+ let hi = bytes[pos++] ?? 0;
6784
+ return maxval > 255 ? hi << 8 | (bytes[pos++] ?? 0) : hi;
6785
+ };
6516
6786
  for (let i = 0; i < width * height; i++) {
6517
- let g = Math.round(readSample() * 255 / maxval);
6518
- rgba[i * 4] = g, rgba[i * 4 + 1] = g, rgba[i * 4 + 2] = g, rgba[i * 4 + 3] = 255;
6787
+ let r = readSample(), g = channels >= 3 ? readSample() : r, b = channels >= 3 ? readSample() : r, a = hasAlpha ? readSample() : maxval;
6788
+ for (let [channel, value] of [r, g, b, a].entries())
6789
+ rgba[i * 4 + channel] = Math.round(value * 255 / maxval), data16 && (data16[i * 4 + channel] = Math.round(value * 65535 / maxval));
6790
+ hasAlpha || (rgba[i * 4 + 3] = 255, data16 && (data16[i * 4 + 3] = 65535));
6519
6791
  }
6520
6792
  } else if (magic === "P4") {
6521
6793
  let rowBytes = Math.ceil(width / 8);
@@ -6526,15 +6798,7 @@ function decodeNetpbmImage(bytes) {
6526
6798
  rgba[idx] = g, rgba[idx + 1] = g, rgba[idx + 2] = g, rgba[idx + 3] = 255;
6527
6799
  }
6528
6800
  }
6529
- } else if (magic === "P3")
6530
- for (let i = 0; i < width * height; i++)
6531
- rgba[i * 4] = Math.round(readNextAsciiInt() * 255 / maxval), rgba[i * 4 + 1] = Math.round(readNextAsciiInt() * 255 / maxval), rgba[i * 4 + 2] = Math.round(readNextAsciiInt() * 255 / maxval), rgba[i * 4 + 3] = 255;
6532
- else if (magic === "P2")
6533
- for (let i = 0; i < width * height; i++) {
6534
- let g = Math.round(readNextAsciiInt() * 255 / maxval);
6535
- rgba[i * 4] = g, rgba[i * 4 + 1] = g, rgba[i * 4 + 2] = g, rgba[i * 4 + 3] = 255;
6536
- }
6537
- else if (magic === "P1")
6801
+ } else if (magic === "P1")
6538
6802
  for (let i = 0; i < width * height; i++) {
6539
6803
  let g = readNextAsciiInt() === 1 ? 0 : 255;
6540
6804
  rgba[i * 4] = g, rgba[i * 4 + 1] = g, rgba[i * 4 + 2] = g, rgba[i * 4 + 3] = 255;
@@ -6543,54 +6807,87 @@ function decodeNetpbmImage(bytes) {
6543
6807
  width,
6544
6808
  height,
6545
6809
  data: rgba,
6810
+ ...data16 ? { data16 } : {},
6546
6811
  format,
6547
- space: format === "ppm" ? "srgb" : "b-w",
6548
- channels: format === "ppm" ? 3 : 1,
6812
+ space: channels >= 3 ? "srgb" : "b-w",
6813
+ channels,
6549
6814
  depth: format === "pbm" ? "bit" : maxval > 255 ? "ushort" : "uchar",
6550
6815
  density: 72,
6551
- hasAlpha: !1
6816
+ hasAlpha
6552
6817
  };
6553
6818
  }
6554
- function encodePpm(img) {
6555
- let header = new TextEncoder().encode(`P6
6819
+ function encodePpm(img, depth = img.data16 ? 16 : 8, ascii = !1, pam = !1) {
6820
+ pam && (ascii = !1);
6821
+ let channels = pam && img.hasAlpha ? 4 : 3;
6822
+ if (!Number.isInteger(depth) || depth < 1 || depth > 16) throw new RangeError("Invalid Netpbm depth");
6823
+ let maximum = 2 ** depth - 1, text = [], header = pam ? `P7
6824
+ WIDTH ${img.width}
6825
+ HEIGHT ${img.height}
6826
+ DEPTH ${channels}
6827
+ MAXVAL ${maximum}
6828
+ TUPLTYPE ${img.hasAlpha ? "RGB_ALPHA" : "RGB"}
6829
+ ENDHDR
6830
+ ` : `${ascii ? "P3" : "P6"}
6556
6831
  ${img.width} ${img.height}
6557
- 255
6558
- `), out = new Uint8Array(header.length + img.width * img.height * 3);
6559
- out.set(header, 0);
6832
+ ${maximum}
6833
+ `, out = new Uint8Array(ascii ? 0 : header.length + img.width * img.height * channels * (depth > 8 ? 2 : 1));
6834
+ ascii || out.set(new TextEncoder().encode(header), 0);
6560
6835
  let dst = header.length;
6561
6836
  for (let i = 0; i < img.width * img.height; i++)
6562
- out[dst++] = img.data[i * 4], out[dst++] = img.data[i * 4 + 1], out[dst++] = img.data[i * 4 + 2];
6563
- return out;
6837
+ for (let channel = 0; channel < channels; channel++) {
6838
+ let value = Math.round((img.data16 ?? img.data)[i * 4 + channel] * maximum / (img.data16 ? 65535 : 255));
6839
+ if (ascii) {
6840
+ text.push(String(value));
6841
+ continue;
6842
+ }
6843
+ depth > 8 && (out[dst++] = value >>> 8), out[dst++] = value & 255;
6844
+ }
6845
+ return ascii ? new TextEncoder().encode(header + text.join(`
6846
+ `) + `
6847
+ `) : out;
6564
6848
  }
6565
- function encodePgm(img) {
6566
- let header = new TextEncoder().encode(`P5
6849
+ function encodePgm(img, depth = img.data16 ? 16 : 8, ascii = !1) {
6850
+ if (!Number.isInteger(depth) || depth < 1 || depth > 16) throw new RangeError("Invalid Netpbm depth");
6851
+ let maximum = 2 ** depth - 1, text = [], header = `${ascii ? "P2" : "P5"}
6567
6852
  ${img.width} ${img.height}
6568
- 255
6569
- `), out = new Uint8Array(header.length + img.width * img.height);
6570
- out.set(header, 0);
6853
+ ${maximum}
6854
+ `, out = new Uint8Array(ascii ? 0 : header.length + img.width * img.height * (depth > 8 ? 2 : 1));
6855
+ ascii || out.set(new TextEncoder().encode(header), 0);
6571
6856
  let dst = header.length;
6572
6857
  for (let i = 0; i < img.width * img.height; i++) {
6573
- let r = img.data[i * 4], g = img.data[i * 4 + 1], b = img.data[i * 4 + 2];
6574
- out[dst++] = Math.round(0.299 * r + 0.587 * g + 0.114 * b);
6858
+ let samples = img.data16 ?? img.data, r = samples[i * 4], g = samples[i * 4 + 1], b = samples[i * 4 + 2], value = Math.round((0.212656 * r + 0.715158 * g + 0.072186 * b) * maximum / (img.data16 ? 65535 : 255));
6859
+ if (ascii) {
6860
+ text.push(String(value));
6861
+ continue;
6862
+ }
6863
+ depth > 8 && (out[dst++] = value >>> 8), out[dst++] = value & 255;
6575
6864
  }
6576
- return out;
6865
+ return ascii ? new TextEncoder().encode(header + text.join(`
6866
+ `) + `
6867
+ `) : out;
6577
6868
  }
6578
- function encodePbm(img) {
6579
- let header = new TextEncoder().encode(`P4
6869
+ function encodePbm(img, ascii = !1) {
6870
+ let text = [], header = `${ascii ? "P1" : "P4"}
6580
6871
  ${img.width} ${img.height}
6581
- `), rowBytes = Math.ceil(img.width / 8), out = new Uint8Array(header.length + rowBytes * img.height);
6582
- out.set(header, 0);
6872
+ `, rowBytes = Math.ceil(img.width / 8), out = new Uint8Array(ascii ? 0 : header.length + rowBytes * img.height);
6873
+ ascii || out.set(new TextEncoder().encode(header), 0);
6583
6874
  for (let y = 0; y < img.height; y++) {
6584
6875
  let rowOffset = header.length + y * rowBytes;
6585
6876
  for (let x = 0; x < img.width; x++) {
6586
- let idx = (y * img.width + x) * 4;
6587
- if (0.299 * img.data[idx] + 0.587 * img.data[idx + 1] + 0.114 * img.data[idx + 2] < 128 ? 1 : 0) {
6877
+ let idx = (y * img.width + x) * 4, bit = 0.299 * img.data[idx] + 0.587 * img.data[idx + 1] + 0.114 * img.data[idx + 2] < 128 ? 1 : 0;
6878
+ if (ascii) {
6879
+ text.push(String(bit));
6880
+ continue;
6881
+ }
6882
+ if (bit) {
6588
6883
  let bIdx = rowOffset + (x >>> 3);
6589
6884
  out[bIdx] = (out[bIdx] ?? 0) | 1 << 7 - (x & 7);
6590
6885
  }
6591
6886
  }
6592
6887
  }
6593
- return out;
6888
+ return ascii ? new TextEncoder().encode(header + text.join(`
6889
+ `) + `
6890
+ `) : out;
6594
6891
  }
6595
6892
  function readBmpMetadata(bytes) {
6596
6893
  let view = new DataView(bytes.buffer, bytes.byteOffset, bytes.byteLength), width = Math.abs(view.getInt32(18, !0)), height = Math.abs(view.getInt32(22, !0)), bpp = view.getUint16(28, !0), ppmX = view.getInt32(38, !0), density = ppmX > 0 ? Math.max(1, Math.round(ppmX * 0.0254)) : 72;
@@ -6653,11 +6950,18 @@ function encodeBmpImage(img) {
6653
6950
  return out;
6654
6951
  }
6655
6952
  function encodeTiffImage(img, options) {
6656
- let { header, directory, channels, padding } = createTiffLayout(img, options), pixelBytes = img.width * img.height * channels, out = new Uint8Array(header.length + pixelBytes + padding + directory.length);
6657
- if (out.set(header), channels === 4) out.set(img.data, header.length);
6658
- else for (let src = 0, dst = header.length; dst < header.length + pixelBytes; src += 4)
6659
- out[dst++] = img.data[src], channels === 2 && (out[dst++] = img.data[src + 3]);
6660
- return out.set(directory, header.length + pixelBytes + padding), out;
6953
+ let pages = img.tiffPages ?? [img], layouts = [], end = 0;
6954
+ for (let index = 0; index < pages.length; index++)
6955
+ layouts[index] = createTiffLayout(pages[index], options, end, index + 1 < pages.length), end = layouts[index].end;
6956
+ let out = new Uint8Array(end), offset = 0;
6957
+ for (let index = 0; index < pages.length; index++) {
6958
+ let page = pages[index], { header, directory, channels, padding } = layouts[index], pixelBytes = page.width * page.height * channels;
6959
+ if (out.set(index === 0 ? header : directory, offset), offset += index === 0 ? header.length : directory.length, channels === 4) out.set(page.data.subarray(0, pixelBytes), offset);
6960
+ else for (let src = 0, dst = offset; dst < offset + pixelBytes; src += 4)
6961
+ out[dst++] = page.data[src], channels === 2 && (out[dst++] = page.data[src + 3]);
6962
+ offset += pixelBytes + padding, index === 0 && (out.set(directory, offset), offset += directory.length);
6963
+ }
6964
+ return out;
6661
6965
  }
6662
6966
  function readTiffTagValues(view, entryPos, le) {
6663
6967
  let type = view.getUint16(entryPos + 2, le), count = view.getUint32(entryPos + 4, le), elemSize = type === 3 ? 2 : type === 4 ? 4 : type === 5 ? 8 : 1, valPos = count * elemSize <= 4 ? entryPos + 8 : view.getUint32(entryPos + 8, le), values = [];
@@ -6734,7 +7038,7 @@ function decodeTiffLzw(src, expectedLen) {
6734
7038
  return out;
6735
7039
  }
6736
7040
  function decodeTiffImage(bytes) {
6737
- let le = bytes[0] === 73, view = new DataView(bytes.buffer, bytes.byteOffset, bytes.byteLength), ifdOffset = view.getUint32(4, le), numEntries = view.getUint16(ifdOffset, le), width = 0, height = 0, bitsPerSample = 8, compression = 1, photometric = 2, stripOffsets = [8], stripByteCounts = [], tileWidth = 0, tileLength = 0, tileOffsets = [], tileByteCounts = [], samplesPerPixel = 4, rowsPerStrip = 0, predictor = 1, xRes = 72, resUnit = 2, orientation, jpegTables;
7041
+ let le = bytes[0] === 73, view = new DataView(bytes.buffer, bytes.byteOffset, bytes.byteLength), ifdOffset = view.getUint32(4, le), numEntries = view.getUint16(ifdOffset, le), width = 0, height = 0, bitsPerSample = 8, compression = 1, photometric = 2, stripOffsets = [8], stripByteCounts = [], tileWidth = 0, tileLength = 0, tileOffsets = [], tileByteCounts = [], samplesPerPixel = 4, rowsPerStrip = 0, predictor = 1, xRes = 72, yRes = 72, resUnit = 2, orientation, properties, jpegTables;
6738
7042
  for (let i = 0; i < numEntries; i++) {
6739
7043
  let p = ifdOffset + 2 + i * 12, tag = view.getUint16(p, le), vals = readTiffTagValues(view, p, le), first = vals[0] ?? 0;
6740
7044
  if (tag === 256) width = first;
@@ -6742,13 +7046,15 @@ function decodeTiffImage(bytes) {
6742
7046
  else if (tag === 258 && first > 0) bitsPerSample = first;
6743
7047
  else if (tag === 259 && first > 0) compression = first;
6744
7048
  else if (tag === 262) photometric = first;
7049
+ else if (tag === 270) properties = { comment: new TextDecoder().decode(new Uint8Array(vals.slice(0, vals.indexOf(0) < 0 ? vals.length : vals.indexOf(0)))) };
6745
7050
  else if (tag === 273 && vals.length > 0) stripOffsets = vals;
6746
7051
  else if (tag === 274 && first >= 1 && first <= 8) orientation = first;
6747
7052
  else if (tag === 277 && first > 0) samplesPerPixel = first;
6748
7053
  else if (tag === 278 && first > 0) rowsPerStrip = first;
6749
7054
  else if (tag === 279 && vals.length > 0) stripByteCounts = vals;
6750
7055
  else if (tag === 282 && first > 0) xRes = first;
6751
- else if (tag === 296 && (first === 2 || first === 3)) resUnit = first;
7056
+ else if (tag === 283 && first > 0) yRes = first;
7057
+ else if (tag === 296 && (first === 1 || first === 2 || first === 3)) resUnit = first;
6752
7058
  else if (tag === 317 && first > 0) predictor = first;
6753
7059
  else if (tag === 322 && first > 0) tileWidth = first;
6754
7060
  else if (tag === 323 && first > 0) tileLength = first;
@@ -6841,7 +7147,9 @@ function decodeTiffImage(bytes) {
6841
7147
  depth: bitsPerSample === 16 ? "ushort" : "uchar",
6842
7148
  bitsPerSample,
6843
7149
  density,
7150
+ resolution: { x: xRes, y: yRes, unit: resUnit === 3 ? "cm" : resUnit === 1 ? "unknown" : "inch" },
6844
7151
  hasAlpha: samplesPerPixel === 2 || samplesPerPixel === 4,
7152
+ ...properties ? { properties } : {},
6845
7153
  ...orientation !== void 0 ? { orientation } : {}
6846
7154
  };
6847
7155
  }
@@ -6855,7 +7163,7 @@ function detectImageFormat(bytes) {
6855
7163
  if (isGifBytes(bytes)) return "gif";
6856
7164
  if (isNetpbmBytes(bytes)) {
6857
7165
  let m = String.fromCharCode(bytes[0], bytes[1]);
6858
- return m === "P1" || m === "P4" ? "pbm" : m === "P2" || m === "P5" ? "pgm" : "ppm";
7166
+ return m === "P7" ? "pam" : m === "P1" || m === "P4" ? "pbm" : m === "P2" || m === "P5" ? "pgm" : "ppm";
6859
7167
  }
6860
7168
  if (isBmpBytes(bytes)) return "bmp";
6861
7169
  if (isTiffBytes(bytes)) return "tiff";
@@ -6935,6 +7243,7 @@ function readImageMetadata(bytes, options) {
6935
7243
  case "gif":
6936
7244
  meta = readGifMetadata(bytes, options);
6937
7245
  break;
7246
+ case "pam":
6938
7247
  case "ppm":
6939
7248
  case "pgm":
6940
7249
  case "pbm":
@@ -6946,6 +7255,7 @@ function readImageMetadata(bytes, options) {
6946
7255
  case "tiff": {
6947
7256
  let decoded = decodeTiffImage(bytes);
6948
7257
  meta = {
7258
+ ...decoded.properties ? { properties: decoded.properties } : {},
6949
7259
  format: "tiff",
6950
7260
  width: decoded.width,
6951
7261
  height: decoded.height,
@@ -6954,6 +7264,7 @@ function readImageMetadata(bytes, options) {
6954
7264
  depth: decoded.depth,
6955
7265
  ...decoded.bitsPerSample !== void 0 ? { bitsPerSample: decoded.bitsPerSample } : {},
6956
7266
  density: decoded.density,
7267
+ ...decoded.resolution ? { resolution: decoded.resolution } : {},
6957
7268
  hasAlpha: decoded.hasAlpha,
6958
7269
  ...decoded.orientation !== void 0 ? { orientation: decoded.orientation } : {},
6959
7270
  size: bytes.byteLength
@@ -7073,10 +7384,11 @@ function decodeImage(bytes, options) {
7073
7384
  case "gif":
7074
7385
  decoded = decodeGifImage(bytes, options);
7075
7386
  break;
7387
+ case "pam":
7076
7388
  case "ppm":
7077
7389
  case "pgm":
7078
7390
  case "pbm":
7079
- decoded = decodeNetpbmImage(bytes);
7391
+ decoded = decodeNetpbmImage(bytes, options?.page);
7080
7392
  break;
7081
7393
  case "bmp":
7082
7394
  decoded = decodeBmpImage(bytes);
@@ -7098,11 +7410,14 @@ function decodeImage(bytes, options) {
7098
7410
  function encodeImage(img, options) {
7099
7411
  let fmt = imageOutputFormat(img.format, options.format);
7100
7412
  switch (fmt) {
7413
+ case "miff":
7414
+ return { data: encodeMiffImage(img), format: "miff", channels: 4 };
7101
7415
  case "png": {
7102
7416
  let data = encodePngImage(img, {
7417
+ ...options.omitPngResolution ? { omitPngResolution: !0 } : {},
7103
7418
  ...options.compressionLevel !== void 0 ? { compressionLevel: options.compressionLevel } : {},
7104
7419
  ...options.consumeInput ? { consumeInput: !0 } : {},
7105
- density: options.density ?? img.density,
7420
+ ...options.density === void 0 ? {} : { density: options.density },
7106
7421
  ...options.orientation !== void 0 ? { orientation: options.orientation } : img.orientation !== void 0 ? { orientation: img.orientation } : {}
7107
7422
  }), outChannels = img.space === "b-w" || img.channels === 1 || img.channels === 2 ? img.hasAlpha ? 2 : 1 : img.hasAlpha ? 4 : 3;
7108
7423
  return { data, format: "png", channels: outChannels };
@@ -7112,7 +7427,7 @@ function encodeImage(img, options) {
7112
7427
  quality: options.quality ?? 85,
7113
7428
  ...options.samplingFactor === void 0 ? {} : { samplingFactor: options.samplingFactor },
7114
7429
  ...options.chromaSubsampling === void 0 ? {} : { chromaSubsampling: options.chromaSubsampling },
7115
- density: options.density ?? img.density,
7430
+ ...options.density === void 0 ? {} : { density: options.density },
7116
7431
  ...options.orientation !== void 0 ? { orientation: options.orientation } : {}
7117
7432
  }), format: "jpeg", channels: 3 };
7118
7433
  case "webp": {
@@ -7144,18 +7459,20 @@ function encodeImage(img, options) {
7144
7459
  ...options.loop !== void 0 ? { loop: options.loop } : {},
7145
7460
  ...options.optimizeFrames ? { optimizeFrames: !0 } : {}
7146
7461
  }), format: "gif", channels: 4 };
7462
+ case "pam":
7463
+ return { data: encodePpm(img, options.netpbmDepth, !1, !0), format: "pam", channels: img.hasAlpha ? 4 : 3 };
7147
7464
  case "ppm":
7148
- return { data: encodePpm(img), format: "ppm", channels: 3 };
7465
+ return { data: encodePpm(img, options.netpbmDepth, options.netpbmAscii), format: "ppm", channels: 3 };
7149
7466
  case "pgm":
7150
- return { data: encodePgm(img), format: "pgm", channels: 1 };
7467
+ return { data: encodePgm(img, options.netpbmDepth, options.netpbmAscii), format: "pgm", channels: 1 };
7151
7468
  case "pbm":
7152
- return { data: encodePbm(img), format: "pbm", channels: 1 };
7469
+ return { data: encodePbm(img, options.netpbmAscii), format: "pbm", channels: 1 };
7153
7470
  case "bmp":
7154
7471
  return { data: encodeBmpImage(img), format: "bmp", channels: 3 };
7155
7472
  case "tiff": {
7156
7473
  let orientation = options.orientation ?? img.orientation;
7157
7474
  return { data: encodeTiffImage(img, {
7158
- density: options.density ?? img.density,
7475
+ ...options.density === void 0 ? {} : { density: options.density },
7159
7476
  ...orientation !== void 0 ? { orientation } : {}
7160
7477
  }), format: "tiff", channels: tiffChannels(img) };
7161
7478
  }
@@ -7395,33 +7712,41 @@ async function* encodeGifFromStorage(image, storage, signal, options = {}) {
7395
7712
  // src/codecs/tiff-storage.ts
7396
7713
  async function* encodeTiffFromStorage(image, storage, signal, options = {}) {
7397
7714
  signal.throwIfAborted();
7398
- let { width, height, position } = image, length = width * height * 4;
7399
- if (!Number.isSafeInteger(width) || width <= 0 || !Number.isSafeInteger(height) || height <= 0 || !Number.isSafeInteger(length) || !Number.isSafeInteger(position) || position < 0 || !Number.isSafeInteger(position + length)) throw new RangeError("Invalid TIFF backing dimensions");
7400
- let { header, directory, channels, padding } = createTiffLayout(image, options);
7401
- yield header;
7402
- for (let offset = 0; offset < length; offset += 4096) {
7403
- signal.throwIfAborted(), offset % 65536 === 0 && await defaultRuntime.yieldTurn(signal);
7404
- let size = Math.min(4096, length - offset), bytes = await storage.read(position + offset, size, { signal });
7405
- if (signal.throwIfAborted(), !(bytes instanceof Uint8Array) || bytes.length !== size) throw new Error("Truncated TIFF backing storage");
7406
- let output = new Uint8Array(size / 4 * channels);
7407
- if (channels === 4) output.set(bytes);
7408
- else for (let src = 0, dst = 0; src < size; src += 4)
7409
- output[dst++] = bytes[src], channels === 2 && (output[dst++] = bytes[src + 3]);
7410
- yield output;
7715
+ let end = 0, count = image.storedTiffPages?.length ?? 1;
7716
+ for (let index = 0; index < count; index++) {
7717
+ let page = image.storedTiffPages ? await image.storedTiffPages.at(index) : image;
7718
+ signal.throwIfAborted();
7719
+ let { width, height, position } = page, length = width * height * 4;
7720
+ if (!Number.isSafeInteger(width) || width <= 0 || !Number.isSafeInteger(height) || height <= 0 || !Number.isSafeInteger(length) || !Number.isSafeInteger(position) || position < 0 || !Number.isSafeInteger(position + length)) throw new RangeError("Invalid TIFF backing dimensions");
7721
+ let { header, directory, channels, padding, end: nextEnd } = createTiffLayout(page, options, end, index + 1 < count);
7722
+ end = nextEnd, yield index === 0 ? header : directory;
7723
+ for (let offset = 0; offset < length; offset += 4096) {
7724
+ signal.throwIfAborted(), offset % 65536 === 0 && await defaultRuntime.yieldTurn(signal);
7725
+ let size = Math.min(4096, length - offset), bytes = await storage.read(position + offset, size, { signal });
7726
+ if (signal.throwIfAborted(), !(bytes instanceof Uint8Array) || bytes.length !== size) throw new Error("Truncated TIFF backing storage");
7727
+ let output = new Uint8Array(size / 4 * channels);
7728
+ if (channels === 4) output.set(bytes);
7729
+ else for (let src = 0, dst = 0; src < size; src += 4)
7730
+ output[dst++] = bytes[src], channels === 2 && (output[dst++] = bytes[src + 3]);
7731
+ yield output;
7732
+ }
7733
+ signal.throwIfAborted(), padding && (yield new Uint8Array(padding)), signal.throwIfAborted(), index === 0 && (yield directory);
7411
7734
  }
7412
- signal.throwIfAborted(), padding && (yield new Uint8Array(padding)), signal.throwIfAborted(), yield directory;
7413
7735
  }
7414
7736
 
7415
7737
  // src/codecs/netpbm-storage.ts
7416
7738
  async function decodeNetpbmToStorage(source, storage, signal, options) {
7417
7739
  signal.throwIfAborted();
7418
- let reader = new SourceBytes(source, signal, "Netpbm"), steps = netpbmHeaderSteps(source.size), next = steps.next();
7740
+ let reader = new SourceBytes(source, signal, "Netpbm"), steps = netpbmSequenceSteps(source.size, options?.page ?? 0), next = steps.next();
7419
7741
  for (; !next.done; ) next = steps.next(await reader.at(next.value));
7420
- let { width, height, maxval, magic, format, dataOffset } = next.value;
7421
- if (!["P1", "P2", "P3", "P4", "P5", "P6"].includes(magic)) throw new Error("Invalid Netpbm signature");
7742
+ let { width, height, maxval, magic, format, dataOffset, channels, hasAlpha } = next.value.header;
7743
+ if (!["P1", "P2", "P3", "P4", "P5", "P6", "P7"].includes(magic)) throw new Error("Invalid Netpbm signature");
7422
7744
  if (!Number.isSafeInteger(width) || width <= 0 || !Number.isSafeInteger(height) || height <= 0 || !Number.isSafeInteger(width * height * 4)) throw new RangeError("Invalid Netpbm dimensions");
7423
- checkLimitInputPixels(width, height, options);
7424
- let metadata = { width, height, position: 0, format, space: format === "ppm" ? "srgb" : "b-w", channels: format === "ppm" ? 3 : 1, depth: format === "pbm" ? "bit" : maxval > 255 ? "ushort" : "uchar", density: 72, hasAlpha: !1 }, output = new Output(metadata, width, height, storage, signal), position = dataOffset, ascii = async () => {
7745
+ if (checkLimitInputPixels(width, height, options), magic === "P6" && source.size - dataOffset < width * height * 3 * (maxval > 255 ? 2 : 1)) throw new Error("Truncated PPM raster");
7746
+ if (magic === "P7" && source.size - dataOffset < width * height * channels * (maxval > 255 ? 2 : 1)) throw new Error("Truncated PAM raster");
7747
+ let metadata = { width, height, position: 0, format, space: channels >= 3 ? "srgb" : "b-w", channels, depth: format === "pbm" ? "bit" : maxval > 255 ? "ushort" : "uchar", density: 72, hasAlpha }, output = new Output(metadata, width, height, storage, signal), position = dataOffset, storedData16 = maxval > 255 ? { position: storage.allocate(width * height * 8), length: width * height * 4 } : void 0, samples16 = storedData16 ? new Uint16Array(2048) : void 0, used16 = 0, offset16 = 0, flush16 = async () => {
7748
+ !storedData16 || !samples16 || !used16 || (await storage.write(storedData16.position + offset16, new Uint8Array(samples16.buffer, 0, used16 * 2), { signal }), offset16 += used16 * 2, used16 = 0);
7749
+ }, ascii = async () => {
7425
7750
  for (; position < source.size; ) {
7426
7751
  let value2 = await reader.at(position);
7427
7752
  if (value2 === 35)
@@ -7448,30 +7773,79 @@ async function decodeNetpbmToStorage(source, storage, signal, options) {
7448
7773
  } else if (magic === "P1") {
7449
7774
  let g = await ascii() === 1 ? 0 : 255;
7450
7775
  await output.pixel(g, g, g, 255);
7451
- } else if (format === "ppm") await output.pixel(Math.round(await sample() * 255 / maxval), Math.round(await sample() * 255 / maxval), Math.round(await sample() * 255 / maxval), 255);
7452
- else {
7453
- let g = Math.round(await sample() * 255 / maxval);
7454
- await output.pixel(g, g, g, 255);
7455
- }
7456
- return output.finish();
7457
- }
7458
- async function* encodeNetpbmFromStorage(image, storage, signal, format) {
7459
- if (signal.throwIfAborted(), format !== "ppm" && format !== "pgm" && format !== "pbm") throw new Error(`Unsupported Netpbm output format: ${format}`);
7460
- let { width, height, position } = image;
7776
+ } else {
7777
+ let r = await sample(), g = channels >= 3 ? await sample() : r, b = channels >= 3 ? await sample() : r, a = hasAlpha ? await sample() : maxval;
7778
+ if (await output.pixel(Math.round(r * 255 / maxval), Math.round(g * 255 / maxval), Math.round(b * 255 / maxval), hasAlpha ? Math.round(a * 255 / maxval) : 255), samples16) {
7779
+ for (let value of [r, g, b, a]) samples16[used16++] = Math.round(value * 65535 / maxval);
7780
+ used16 === samples16.length && await flush16();
7781
+ }
7782
+ }
7783
+ return await flush16(), { ...await output.finish(), ...storedData16 ? { storedData16 } : {} };
7784
+ }
7785
+ async function* encodeNetpbmFromStorage(image, storage, signal, format, depth = image.storedData16 ? 16 : 8, ascii = !1) {
7786
+ if (signal.throwIfAborted(), format !== "pam" && format !== "ppm" && format !== "pgm" && format !== "pbm") throw new Error(`Unsupported Netpbm output format: ${format}`);
7787
+ if (!Number.isInteger(depth) || depth < 1 || depth > 16) throw new RangeError("Invalid Netpbm depth");
7788
+ let maximum = 2 ** depth - 1;
7789
+ format === "pam" && (ascii = !1);
7790
+ let channels = format === "pam" ? image.hasAlpha ? 4 : 3 : format === "ppm" ? 3 : 1, pamHeader = `P7
7791
+ WIDTH ${image.width}
7792
+ HEIGHT ${image.height}
7793
+ DEPTH ${channels}
7794
+ MAXVAL ${maximum}
7795
+ TUPLTYPE ${image.hasAlpha ? "RGB_ALPHA" : "RGB"}
7796
+ ENDHDR
7797
+ `, { width, height, position } = image;
7461
7798
  if (!Number.isSafeInteger(width) || width <= 0 || !Number.isSafeInteger(height) || height <= 0 || !Number.isSafeInteger(width * height * 4) || !Number.isSafeInteger(position) || position < 0 || !Number.isSafeInteger(position + width * height * 4)) throw new RangeError("Invalid Netpbm backing dimensions");
7462
- yield new TextEncoder().encode(`${format === "ppm" ? "P6" : format === "pgm" ? "P5" : "P4"}
7799
+ if (format !== "pbm" && image.storedData16) {
7800
+ let samples = image.storedData16, count = width * height;
7801
+ if (!Number.isSafeInteger(samples.position) || samples.position < 0 || !Number.isSafeInteger(samples.position + count * 8) || !Number.isSafeInteger(samples.length) || samples.length < count * 4) throw new RangeError("Invalid Netpbm sample backing");
7802
+ yield new TextEncoder().encode(format === "pam" ? pamHeader : `${format === "ppm" ? ascii ? "P3" : "P6" : ascii ? "P2" : "P5"}
7463
7803
  ${width} ${height}
7464
- ${format === "pbm" ? "" : `255
7804
+ ${maximum}
7805
+ `);
7806
+ for (let offset = 0; offset < count; offset += 512) {
7807
+ signal.throwIfAborted(), offset % 16384 === 0 && await defaultRuntime.yieldTurn(signal);
7808
+ let pixels2 = Math.min(512, count - offset), length = pixels2 * 8, bytes = await storage.read(samples.position + offset * 8, length, { signal });
7809
+ if (signal.throwIfAborted(), bytes.length !== length) throw new Error("Truncated Netpbm sample storage");
7810
+ let data = new Uint16Array(new Uint8Array(bytes).buffer), out = new Uint8Array(ascii ? 0 : pixels2 * channels * (depth > 8 ? 2 : 1)), dst = 0, text = [];
7811
+ for (let i = 0; i < pixels2; i++) {
7812
+ let r = data[i * 4], g = data[i * 4 + 1], b = data[i * 4 + 2];
7813
+ for (let value of format === "pam" ? image.hasAlpha ? [r, g, b, data[i * 4 + 3]] : [r, g, b] : format === "ppm" ? [r, g, b] : [0.212656 * r + 0.715158 * g + 0.072186 * b]) {
7814
+ let scaled = Math.round(value * maximum / 65535);
7815
+ if (ascii) {
7816
+ text.push(String(scaled));
7817
+ continue;
7818
+ }
7819
+ depth > 8 && (out[dst++] = scaled >>> 8), out[dst++] = scaled & 255;
7820
+ }
7821
+ }
7822
+ yield ascii ? new TextEncoder().encode(text.join(`
7823
+ `) + `
7824
+ `) : out;
7825
+ }
7826
+ return;
7827
+ }
7828
+ yield new TextEncoder().encode(format === "pam" ? pamHeader : `${format === "ppm" ? ascii ? "P3" : "P6" : format === "pgm" ? ascii ? "P2" : "P5" : ascii ? "P1" : "P4"}
7829
+ ${width} ${height}
7830
+ ${format === "pbm" ? "" : `${maximum}
7465
7831
  `}`);
7466
7832
  let pixels = new Pixels(image, storage, signal), buffer = new Uint8Array(4096), used = 0;
7467
7833
  for (let y = 0; y < height; y++) {
7468
7834
  let bits = 0;
7469
7835
  for (let x = 0; x < width; x++) {
7470
7836
  let value = await pixels.pixel(y * width + x), r = value & 255, g = value >>> 8 & 255, b = value >>> 16 & 255;
7471
- if (format === "ppm")
7472
- for (let channel of [r, g, b])
7473
- buffer[used++] = channel, used === buffer.length && (yield new Uint8Array(buffer), used = 0);
7474
- else format === "pgm" ? buffer[used++] = Math.round(0.299 * r + 0.587 * g + 0.114 * b) : (0.299 * r + 0.587 * g + 0.114 * b < 128 && (bits |= 1 << 7 - (x & 7)), ((x & 7) === 7 || x + 1 === width) && (buffer[used++] = bits, bits = 0));
7837
+ if (format !== "pbm")
7838
+ for (let channel of format === "pam" ? image.hasAlpha ? [r, g, b, value >>> 24] : [r, g, b] : format === "ppm" ? [r, g, b] : [0.212656 * r + 0.715158 * g + 0.072186 * b]) {
7839
+ let scaled = Math.round(channel * maximum / 255);
7840
+ if (ascii) {
7841
+ for (let byte2 of new TextEncoder().encode(`${scaled}
7842
+ `))
7843
+ buffer[used++] = byte2, used === buffer.length && (yield new Uint8Array(buffer), used = 0);
7844
+ continue;
7845
+ }
7846
+ depth > 8 && (buffer[used++] = scaled >>> 8, used === buffer.length && (yield new Uint8Array(buffer), used = 0)), buffer[used++] = scaled & 255, used === buffer.length && (yield new Uint8Array(buffer), used = 0);
7847
+ }
7848
+ else ascii ? (buffer[used++] = 0.299 * r + 0.587 * g + 0.114 * b < 128 ? 49 : 48, used === buffer.length && (yield new Uint8Array(buffer), used = 0), buffer[used++] = 10) : (0.299 * r + 0.587 * g + 0.114 * b < 128 && (bits |= 1 << 7 - (x & 7)), ((x & 7) === 7 || x + 1 === width) && (buffer[used++] = bits, bits = 0));
7475
7849
  used === buffer.length && (yield new Uint8Array(buffer), used = 0);
7476
7850
  }
7477
7851
  }
@@ -7529,16 +7903,16 @@ async function* encodeBmpFromStorage(image, storage, signal) {
7529
7903
 
7530
7904
  // src/image-encode.ts
7531
7905
  function isStoredOutputFormat(format) {
7532
- return ["pdf", "heic", "heif", "avif", "raw", "png", "ppm", "pgm", "pbm", "bmp", "tiff", "gif", "jpeg", "webp"].includes(format);
7906
+ return ["miff", "pdf", "heic", "heif", "avif", "raw", "png", "pam", "ppm", "pgm", "pbm", "bmp", "tiff", "gif", "jpeg", "webp"].includes(format);
7533
7907
  }
7534
7908
  async function* encodeStoredImage(image, backing, signal, encoding) {
7535
7909
  let format = imageOutputFormat(image.format, encoding.format);
7536
7910
  if (!isStoredOutputFormat(format)) throw new UnsupportedStoredResource();
7537
7911
  let size = 0;
7538
- for await (let bytes of format === "pdf" ? encodePdfFromStorage(image, backing, signal) : ["heic", "heif", "avif"].includes(format) ? encodeHeifFromStorage(image, backing, signal, { ...encoding, format }) : format === "raw" ? encodeRawFromStorage(image, backing, signal, encoding) : format === "png" ? encodePngFromStorage(image, backing, signal, encoding) : format === "webp" ? encodeWebpFromStorage(image, backing, signal, encoding) : format === "jpeg" ? encodeJpegFromStorage(image, backing, signal, encoding) : format === "gif" ? encodeGifFromStorage(image, backing, signal, encoding) : format === "tiff" ? encodeTiffFromStorage(image, backing, signal, encoding) : format === "bmp" ? encodeBmpFromStorage(image, backing, signal) : encodeNetpbmFromStorage(image, backing, signal, format))
7912
+ for await (let bytes of format === "miff" ? encodeMiffFromStorage(image, backing, signal) : format === "pdf" ? encodePdfFromStorage(image, backing, signal) : ["heic", "heif", "avif"].includes(format) ? encodeHeifFromStorage(image, backing, signal, { ...encoding, format }) : format === "raw" ? encodeRawFromStorage(image, backing, signal, encoding) : format === "png" ? encodePngFromStorage(image, backing, signal, encoding) : format === "webp" ? encodeWebpFromStorage(image, backing, signal, encoding) : format === "jpeg" ? encodeJpegFromStorage(image, backing, signal, encoding) : format === "gif" ? encodeGifFromStorage(image, backing, signal, encoding) : format === "tiff" ? encodeTiffFromStorage(image, backing, signal, encoding) : format === "bmp" ? encodeBmpFromStorage(image, backing, signal) : encodeNetpbmFromStorage(image, backing, signal, format, encoding.netpbmDepth, encoding.netpbmAscii))
7539
7913
  size += bytes.length, yield bytes;
7540
7914
  let gray = image.space === "b-w" || image.channels === 1 || image.channels === 2;
7541
- return { ...image.textAutofitDpi === void 0 ? {} : { textAutofitDpi: image.textAutofitDpi }, format, width: image.width, height: image.height, ...format === "raw" ? { depth: encoding.rawDepth ?? image.depth } : {}, channels: format === "raw" ? image.channels : format === "webp" || format === "pdf" ? image.hasAlpha ? 4 : 3 : format === "tiff" ? tiffChannels(image) : format === "gif" ? 4 : format === "ppm" || format === "bmp" || format === "jpeg" ? 3 : format === "pgm" || format === "pbm" ? 1 : gray ? image.hasAlpha ? 2 : 1 : image.hasAlpha ? 4 : 3, premultiplied: !!image.wasPremultiplied, ...image.pageHeight === void 0 ? {} : { pageHeight: image.pageHeight }, ...image.pageHeight !== void 0 && (image.sourcePages ?? image.pages) !== void 0 ? { pages: image.sourcePages ?? image.pages } : {}, ...image.trimOffsetLeft === void 0 ? {} : { trimOffsetLeft: image.trimOffsetLeft }, ...image.trimOffsetTop === void 0 ? {} : { trimOffsetTop: image.trimOffsetTop }, size };
7915
+ return { ...image.textAutofitDpi === void 0 ? {} : { textAutofitDpi: image.textAutofitDpi }, format, width: image.width, height: image.height, ...format === "raw" ? { depth: encoding.rawDepth ?? image.depth } : {}, channels: format === "miff" ? 4 : format === "raw" ? image.channels : format === "pam" || format === "webp" || format === "pdf" ? image.hasAlpha ? 4 : 3 : format === "tiff" ? tiffChannels(image) : format === "gif" ? 4 : format === "ppm" || format === "bmp" || format === "jpeg" ? 3 : format === "pgm" || format === "pbm" ? 1 : gray ? image.hasAlpha ? 2 : 1 : image.hasAlpha ? 4 : 3, premultiplied: !!image.wasPremultiplied, ...image.pageHeight === void 0 ? {} : { pageHeight: image.pageHeight }, ...image.pageHeight !== void 0 && (image.sourcePages ?? image.pages) !== void 0 ? { pages: image.sourcePages ?? image.pages } : {}, ...image.trimOffsetLeft === void 0 ? {} : { trimOffsetLeft: image.trimOffsetLeft }, ...image.trimOffsetTop === void 0 ? {} : { trimOffsetTop: image.trimOffsetTop }, size };
7542
7916
  }
7543
7917
 
7544
7918
  // src/codecs/resource-storage.ts
@@ -11361,7 +11735,7 @@ async function readGifMetadataFromSource(source, storage, signal, options) {
11361
11735
  let captured = { ...options }, reader = new SourceBytes(source, signal, "GIF"), header = new Uint8Array(Math.min(13, source.size));
11362
11736
  for (let i = 0; i < header.length; i++) header[i] = await reader.at(i) ?? 0;
11363
11737
  if (!isGifBytes(header) || header.length < 13) throw new Error("Invalid GIF header");
11364
- let start = 13 + (header[10] & 128 ? 3 * (1 << (header[10] & 7) + 1) : 0), { frames, loop } = await scanGifAnimation(reader, source.size, start), metadata = gifMetadataFields(header[6] | header[7] << 8, header[8] | header[9] << 8, source.size, frames, loop, captured);
11738
+ let start = 13 + (header[10] & 128 ? 3 * (1 << (header[10] & 7) + 1) : 0), { frames, loop, properties } = await scanGifAnimation(reader, source.size, start), metadata = { ...properties ? { properties } : {}, ...gifMetadataFields(header[6] | header[7] << 8, header[8] | header[9] << 8, source.size, frames, loop, captured) };
11365
11739
  if (!frames) return metadata;
11366
11740
  let position = storage.allocate(frames * 4);
11367
11741
  if (!Number.isSafeInteger(position) || position < 0 || !Number.isSafeInteger(position + frames * 4)) throw new RangeError("Invalid GIF delay storage");
@@ -11432,7 +11806,7 @@ async function decodeGifToStorage(source, storage, signal, options) {
11432
11806
  at2 += length;
11433
11807
  }
11434
11808
  return at2;
11435
- }, { frames, loop } = await scanGifAnimation(reader, source.size, start), totalPages = Math.max(1, frames), multi = options?.coalesce !== !1 && (options?.animated === !0 || options?.pages === -1 || options?.pages !== void 0 && options.pages > 1), page = Math.max(0, options?.page ?? 0), pages = multi ? options?.pages !== void 0 && options.pages > 0 ? Math.min(options.pages, Math.max(1, totalPages - page)) : Math.max(1, totalPages - page) : 1, end = page + pages - 1;
11809
+ }, { frames, loop, properties } = await scanGifAnimation(reader, source.size, start), totalPages = Math.max(1, frames), multi = options?.coalesce !== !1 && (options?.animated === !0 || options?.pages === -1 || options?.pages !== void 0 && options.pages > 1), page = Math.max(0, options?.page ?? 0), pages = multi ? options?.pages !== void 0 && options.pages > 0 ? Math.min(options.pages, Math.max(1, totalPages - page)) : Math.max(1, totalPages - page) : 1, end = page + pages - 1;
11436
11810
  if (!Number.isSafeInteger(frameBytes * pages)) throw new RangeError("Invalid GIF dimensions");
11437
11811
  checkLimitInputPixels(width, height * pages, options);
11438
11812
  let allocate = (length) => {
@@ -11537,6 +11911,7 @@ async function decodeGifToStorage(source, storage, signal, options) {
11537
11911
  height: height * (virtualCanvas ? 1 : pages),
11538
11912
  ...virtualCanvas ? { virtualCanvas, disposal } : {},
11539
11913
  ...backgroundColor ? { backgroundColor } : {},
11914
+ ...properties ? { properties } : {},
11540
11915
  position: output,
11541
11916
  format: "gif",
11542
11917
  space: "srgb",
@@ -11652,7 +12027,7 @@ async function decodeTiffToStorage(source, storage, signal, options) {
11652
12027
  let value = 0;
11653
12028
  for (let i = 0; i < size; i++) value += await byte2(at + i) * 2 ** (8 * (le ? i : size - i - 1));
11654
12029
  return value;
11655
- }, ifd = await number(4, 4), count = await number(ifd, 2), width = 0, height = 0, bits = 8, compression = 1, photometric = 2, samples = 4, rows = 0, predictor = 1, xRes = 72, resUnit = 2, tileWidth = 0, tileHeight = 0, orientation, jpegTables, offsets = { count: 1, async at(i) {
12030
+ }, ifd = await number(4, 4), count = await number(ifd, 2), width = 0, height = 0, bits = 8, compression = 1, photometric = 2, samples = 4, rows = 0, predictor = 1, xRes = 72, yRes = 72, resUnit = 2, tileWidth = 0, tileHeight = 0, orientation, properties, jpegTables, offsets = { count: 1, async at(i) {
11656
12031
  return i === 0 ? 8 : void 0;
11657
12032
  } }, counts = empty, tileOffsets = empty, tileCounts = empty;
11658
12033
  for (let i = 0; i < count; i++) {
@@ -11663,6 +12038,16 @@ async function decodeTiffToStorage(source, storage, signal, options) {
11663
12038
  let num = await number(at, 4), den = await number(at + 4, 4);
11664
12039
  return den > 0 ? num / den : num;
11665
12040
  } }, first = await values.at(0) ?? 0;
12041
+ if (tag === 270 && type === 2) {
12042
+ if (available > 16 * 1024 * 1024) throw new Error("TIFF comment exceeds 16 MiB");
12043
+ let text = [];
12044
+ for (let j = 0; j < available; j++) {
12045
+ let value = await values.at(j);
12046
+ if (!value) break;
12047
+ text.push(value);
12048
+ }
12049
+ properties = { comment: new TextDecoder().decode(new Uint8Array(text)) };
12050
+ }
11666
12051
  if (tag === 256) width = first;
11667
12052
  else if (tag === 257) height = first;
11668
12053
  else if (tag === 258 && first > 0) bits = first;
@@ -11674,7 +12059,8 @@ async function decodeTiffToStorage(source, storage, signal, options) {
11674
12059
  else if (tag === 278 && first > 0) rows = first;
11675
12060
  else if (tag === 279 && available) counts = values;
11676
12061
  else if (tag === 282 && first > 0) xRes = first;
11677
- else if (tag === 296 && (first === 2 || first === 3)) resUnit = first;
12062
+ else if (tag === 283 && first > 0) yRes = first;
12063
+ else if (tag === 296 && (first === 1 || first === 2 || first === 3)) resUnit = first;
11678
12064
  else if (tag === 317 && first > 0) predictor = first;
11679
12065
  else if (tag === 322 && first > 0) tileWidth = first;
11680
12066
  else if (tag === 323 && first > 0) tileHeight = first;
@@ -11828,7 +12214,7 @@ async function decodeTiffToStorage(source, storage, signal, options) {
11828
12214
  }
11829
12215
  await predict(combined, total, height, stride);
11830
12216
  }
11831
- let metadata = { width, height, position: combined, format: "tiff", space: samples < 3 ? bits === 16 ? "grey16" : "b-w" : bits === 16 ? "rgb16" : "srgb", channels: samples === 4 ? 4 : samples === 1 ? 1 : samples === 2 ? 2 : 3, depth: bits === 16 ? "ushort" : "uchar", bitsPerSample: bits, density: Math.max(1, Math.round(xRes * (resUnit === 3 ? 2.54 : 1))), hasAlpha: samples === 2 || samples === 4, ...orientation === void 0 ? {} : { orientation } }, raster = new Raster(combined, total, storage, signal), output = new Output(metadata, width, height, storage, signal);
12217
+ let metadata = { ...properties ? { properties } : {}, width, height, position: combined, format: "tiff", space: samples < 3 ? bits === 16 ? "grey16" : "b-w" : bits === 16 ? "rgb16" : "srgb", channels: samples === 4 ? 4 : samples === 1 ? 1 : samples === 2 ? 2 : 3, depth: bits === 16 ? "ushort" : "uchar", bitsPerSample: bits, density: Math.max(1, Math.round(xRes * (resUnit === 3 ? 2.54 : 1))), resolution: { x: xRes, y: yRes, unit: resUnit === 3 ? "cm" : resUnit === 1 ? "unknown" : "inch" }, hasAlpha: samples === 2 || samples === 4, ...orientation === void 0 ? {} : { orientation } }, raster = new Raster(combined, total, storage, signal), output = new Output(metadata, width, height, storage, signal);
11832
12218
  for (let i = 0; i < width * height; i++) {
11833
12219
  let base = i * pixelBytes + (sampleBytes === 2 && le ? 1 : 0), r = await raster.at(base);
11834
12220
  if (samples < 3) {
@@ -12180,6 +12566,7 @@ async function readImageMetadataFromSource(source, signal, options, storage) {
12180
12566
  if (!storage) throw new UnsupportedStoredResource();
12181
12567
  let decoded = await decodeTiffToStorage(source, storage, signal);
12182
12568
  metadata = {
12569
+ ...decoded.properties ? { properties: decoded.properties } : {},
12183
12570
  format: "tiff",
12184
12571
  width: decoded.width,
12185
12572
  height: decoded.height,
@@ -12188,16 +12575,17 @@ async function readImageMetadataFromSource(source, signal, options, storage) {
12188
12575
  depth: decoded.depth,
12189
12576
  ...decoded.bitsPerSample === void 0 ? {} : { bitsPerSample: decoded.bitsPerSample },
12190
12577
  density: decoded.density,
12578
+ ...decoded.resolution ? { resolution: decoded.resolution } : {},
12191
12579
  hasAlpha: decoded.hasAlpha,
12192
12580
  ...decoded.orientation === void 0 ? {} : { orientation: decoded.orientation },
12193
12581
  size: source.size
12194
12582
  };
12195
12583
  } else if (isBmpBytes(prefix)) metadata = { ...readBmpMetadata(prefix), size: source.size };
12196
12584
  else if (isNetpbmBytes(prefix)) {
12197
- let reader = new SourceBytes(source, signal, "Netpbm"), steps = netpbmHeaderSteps(source.size), next = steps.next();
12585
+ let reader = new SourceBytes(source, signal, "Netpbm"), steps = netpbmSequenceSteps(source.size), next = steps.next();
12198
12586
  for (; !next.done; ) next = steps.next(await reader.at(next.value));
12199
- let { format, width, height, maxval } = next.value;
12200
- metadata = { format, width, height, space: format === "ppm" ? "srgb" : "b-w", channels: format === "ppm" ? 3 : 1, depth: format === "pbm" ? "bit" : maxval > 255 ? "ushort" : "uchar", density: 72, hasAlpha: !1, size: source.size };
12587
+ let { header: { format, width, height, maxval, channels, hasAlpha }, pages } = next.value;
12588
+ metadata = { format, width, height, space: channels >= 3 ? "srgb" : "b-w", channels, depth: format === "pbm" ? "bit" : maxval > 255 ? "ushort" : "uchar", density: 72, hasAlpha, size: source.size, ...pages > 1 ? { pages } : {} };
12201
12589
  } else {
12202
12590
  let length2 = Math.min(1029, source.size), head = await source.read(0, length2, { signal });
12203
12591
  if (signal.throwIfAborted(), !(head instanceof Uint8Array) || head.length !== length2) throw new Error("Truncated image metadata source");
@@ -12643,6 +13031,8 @@ function inferFormatFromPath(fileOut) {
12643
13031
  return "heif";
12644
13032
  case "pdf":
12645
13033
  return "pdf";
13034
+ case "pam":
13035
+ return "pam";
12646
13036
  case "ppm":
12647
13037
  return "ppm";
12648
13038
  case "pgm":
@@ -14020,6 +14410,7 @@ var SharpInstance = class _SharpInstance extends Duplex {
14020
14410
  "gif",
14021
14411
  "svg",
14022
14412
  "pdf",
14413
+ "pam",
14023
14414
  "ppm",
14024
14415
  "pgm",
14025
14416
  "pbm",
@@ -14220,7 +14611,7 @@ Object.assign(sharp, {
14220
14611
  vertexSplitQuadraticBasisSpline: "vsqbs"
14221
14612
  },
14222
14613
  format: Object.fromEntries(
14223
- ["jpeg", "png", "webp", "tiff", "heif", "heic", "avif", "gif", "svg", "pdf", "ppm", "pgm", "pbm", "bmp", "raw"].map(
14614
+ ["jpeg", "png", "webp", "tiff", "heif", "heic", "avif", "gif", "svg", "pdf", "pam", "ppm", "pgm", "pbm", "bmp", "raw"].map(
14224
14615
  (id) => [
14225
14616
  id,
14226
14617
  {