ag-psd 33.0.0 → 34.0.0

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 (86) hide show
  1. package/.v8-cache/v24.7.0-x64-ef5a0af0/05135617 +0 -0
  2. package/.v8-cache/v24.7.0-x64-ef5a0af0/226e2801 +0 -0
  3. package/.v8-cache/v24.7.0-x64-ef5a0af0/4511bacf +0 -0
  4. package/.v8-cache/v24.7.0-x64-ef5a0af0/68416cb2 +0 -0
  5. package/.v8-cache/v24.7.0-x64-ef5a0af0/71df9cb3 +0 -0
  6. package/.v8-cache/v24.7.0-x64-ef5a0af0/7ea06cea +0 -0
  7. package/.v8-cache/v24.7.0-x64-ef5a0af0/87cf2209 +0 -0
  8. package/.v8-cache/v24.7.0-x64-ef5a0af0/932f0770 +0 -0
  9. package/.v8-cache/v24.7.0-x64-ef5a0af0/acc36e66 +0 -0
  10. package/.v8-cache/v24.7.0-x64-ef5a0af0/b3c2fab7 +0 -0
  11. package/.v8-cache/v24.7.0-x64-ef5a0af0/c0b789d2 +0 -0
  12. package/.v8-cache/v24.7.0-x64-ef5a0af0/c314aece +0 -0
  13. package/.v8-cache/v24.7.0-x64-ef5a0af0/cfc49f4f +0 -0
  14. package/.v8-cache/v24.7.0-x64-ef5a0af0/d13d7610 +0 -0
  15. package/.v8-cache/v24.7.0-x64-ef5a0af0/dc86f000 +0 -0
  16. package/.v8-cache/v24.7.0-x64-ef5a0af0/e03e2acd +0 -0
  17. package/.v8-cache/v24.7.0-x64-ef5a0af0/e6b99945 +0 -0
  18. package/CHANGELOG.md +22 -0
  19. package/README.md +1 -1
  20. package/README_PSD.md +25 -9
  21. package/TODO.md +13 -84
  22. package/dist/abr.js +12 -7
  23. package/dist/abr.js.map +1 -1
  24. package/dist/additionalInfo.d.ts +2 -0
  25. package/dist/additionalInfo.js +361 -75
  26. package/dist/additionalInfo.js.map +1 -1
  27. package/dist/bundle.js +1524 -389
  28. package/dist/descriptor.d.ts +14 -5
  29. package/dist/descriptor.js +102 -64
  30. package/dist/descriptor.js.map +1 -1
  31. package/dist/helpers.d.ts +6 -0
  32. package/dist/helpers.js +242 -2
  33. package/dist/helpers.js.map +1 -1
  34. package/dist/imageResources.js +84 -3
  35. package/dist/imageResources.js.map +1 -1
  36. package/dist/index.d.ts +1 -0
  37. package/dist/index.js +1 -0
  38. package/dist/index.js.map +1 -1
  39. package/dist/psd.d.ts +144 -2
  40. package/dist/psd.js.map +1 -1
  41. package/dist/psdReader.js +206 -110
  42. package/dist/psdReader.js.map +1 -1
  43. package/dist/psdWriter.d.ts +1 -0
  44. package/dist/psdWriter.js +336 -91
  45. package/dist/psdWriter.js.map +1 -1
  46. package/dist/text.d.ts +32 -1
  47. package/dist/text.js +58 -0
  48. package/dist/text.js.map +1 -1
  49. package/dist-es/abr.js +12 -7
  50. package/dist-es/abr.js.map +1 -1
  51. package/dist-es/additionalInfo.d.ts +2 -0
  52. package/dist-es/additionalInfo.js +361 -75
  53. package/dist-es/additionalInfo.js.map +1 -1
  54. package/dist-es/descriptor.d.ts +14 -5
  55. package/dist-es/descriptor.js +102 -64
  56. package/dist-es/descriptor.js.map +1 -1
  57. package/dist-es/helpers.d.ts +6 -0
  58. package/dist-es/helpers.js +242 -2
  59. package/dist-es/helpers.js.map +1 -1
  60. package/dist-es/imageResources.js +84 -3
  61. package/dist-es/imageResources.js.map +1 -1
  62. package/dist-es/index.d.ts +1 -0
  63. package/dist-es/index.js +1 -0
  64. package/dist-es/index.js.map +1 -1
  65. package/dist-es/psd.d.ts +144 -2
  66. package/dist-es/psd.js.map +1 -1
  67. package/dist-es/psdReader.js +206 -110
  68. package/dist-es/psdReader.js.map +1 -1
  69. package/dist-es/psdWriter.d.ts +1 -0
  70. package/dist-es/psdWriter.js +336 -91
  71. package/dist-es/psdWriter.js.map +1 -1
  72. package/dist-es/text.d.ts +32 -1
  73. package/dist-es/text.js +58 -0
  74. package/dist-es/text.js.map +1 -1
  75. package/package.json +1 -1
  76. package/perf.js +312 -9
  77. package/src/abr.ts +14 -7
  78. package/src/additionalInfo.ts +433 -87
  79. package/src/descriptor.ts +103 -58
  80. package/src/helpers.ts +262 -2
  81. package/src/imageResources.ts +107 -4
  82. package/src/index.ts +1 -0
  83. package/src/psd.ts +115 -2
  84. package/src/psdReader.ts +226 -108
  85. package/src/psdWriter.ts +345 -90
  86. package/src/text.ts +76 -1
package/dist/psdReader.js CHANGED
@@ -206,6 +206,8 @@ function readUnicodeString(reader) {
206
206
  return readUnicodeStringWithLength(reader, length);
207
207
  }
208
208
  const STRING_CHUNK_SIZE = 0x2000;
209
+ // shorter strings (most descriptor keys, OSTypes and names) are faster to build with simple loop than String.fromCharCode.apply
210
+ const SHORT_STRING_LENGTH = 64;
209
211
  function checkReadBounds(reader, size) {
210
212
  if (reader.offset < 0 || (reader.offset + size) > reader.view.byteLength) {
211
213
  throw new RangeError('Offset is outside the bounds of the DataView');
@@ -216,9 +218,18 @@ function readUtf16String(reader, length, littleEndian) {
216
218
  return '';
217
219
  checkReadBounds(reader, length * 2);
218
220
  const view = reader.view;
219
- const codes = new Uint16Array(Math.min(length, STRING_CHUNK_SIZE));
220
221
  let offset = reader.offset;
221
222
  let text = '';
223
+ if (length <= SHORT_STRING_LENGTH) {
224
+ for (let i = 0; i < length; i++, offset += 2) {
225
+ text += String.fromCharCode(view.getUint16(offset, littleEndian));
226
+ }
227
+ reader.offset = offset;
228
+ if (text.charCodeAt(text.length - 1) === 0)
229
+ text = text.substring(0, text.length - 1);
230
+ return text;
231
+ }
232
+ const codes = new Uint16Array(Math.min(length, STRING_CHUNK_SIZE));
222
233
  for (let i = 0; i < length; i += STRING_CHUNK_SIZE) {
223
234
  const count = Math.min(STRING_CHUNK_SIZE, length - i);
224
235
  for (let j = 0; j < count; j++, offset += 2) {
@@ -249,6 +260,15 @@ function readAsciiString(reader, length) {
249
260
  if (!(length > 0))
250
261
  return '';
251
262
  checkReadBounds(reader, length);
263
+ if (length <= SHORT_STRING_LENGTH) {
264
+ const view = reader.view;
265
+ let text = '';
266
+ for (let i = 0, offset = reader.offset; i < length; i++, offset++) {
267
+ text += String.fromCharCode(view.getUint8(offset));
268
+ }
269
+ reader.offset += length;
270
+ return text;
271
+ }
252
272
  const text = bytesToString(new Uint8Array(reader.view.buffer, reader.view.byteOffset + reader.offset, length));
253
273
  reader.offset += length;
254
274
  return text;
@@ -264,7 +284,14 @@ function checkSignature(reader, a, b) {
264
284
  }
265
285
  }
266
286
  function readShortString(reader, length) {
267
- return bytesToString(readBytes(reader, length));
287
+ const bytes = readBytes(reader, length);
288
+ if (length > SHORT_STRING_LENGTH)
289
+ return bytesToString(bytes);
290
+ let text = '';
291
+ for (let i = 0; i < bytes.length; i++) {
292
+ text += String.fromCharCode(bytes[i]);
293
+ }
294
+ return text;
268
295
  }
269
296
  function isValidSignature(sig) {
270
297
  return sig === '8BIM' || sig === 'MeSa' || sig === 'AgHg' || sig === 'PHUT' || sig === 'DCSR';
@@ -450,6 +477,9 @@ function readLayerInfo(reader, psd, imageResources) {
450
477
  if (type === 1 /* SectionDividerType.OpenFolder */ || type === 2 /* SectionDividerType.ClosedFolder */) {
451
478
  l.opened = type === 1 /* SectionDividerType.OpenFolder */;
452
479
  l.children = [];
480
+ // group layers can't have image data, some broken files have it, ignore it like Photoshop does
481
+ delete l.canvas;
482
+ delete l.imageData;
453
483
  if ((_a = l.sectionDivider) === null || _a === void 0 ? void 0 : _a.key) {
454
484
  l.blendMode = (_b = helpers_1.toBlendMode[l.sectionDivider.key]) !== null && _b !== void 0 ? _b : l.blendMode;
455
485
  }
@@ -648,9 +678,8 @@ function readLayerChannelImageData(reader, psd, layer, channels) {
648
678
  const start = reader.offset;
649
679
  let compression = 0 /* Compression.RawData */;
650
680
  let data = undefined;
651
- if (channel.length === 1)
652
- throw new Error('Invalid channel length');
653
- if (channel.length) {
681
+ // channel length of 1 is invalid (should be 0 or at least 2), skip the byte and treat channel as empty
682
+ if (channel.length >= 2) {
654
683
  compression = readUint16(reader);
655
684
  // try to fix broken files where there's 1 byte shift of channel
656
685
  if (compression > 3) {
@@ -672,7 +701,16 @@ function readLayerChannelImageData(reader, psd, layer, channels) {
672
701
  layer.rawData.channels.push({ id: channel.id, compression, data });
673
702
  }
674
703
  if (!reader.useRawData) {
675
- decodeLayerImageData(layer, reader);
704
+ try {
705
+ decodeLayerImageData(layer, reader);
706
+ }
707
+ catch (e) {
708
+ // corrupted image data of one layer shouldn't prevent reading rest of the file
709
+ if (reader.throwForMissingFeatures || e.message === 'Exceeded memory limit')
710
+ throw e;
711
+ reader.logMissingFeatures && reader.log(`Failed to decode layer image data: ${e.message}`);
712
+ delete layer.rawData;
713
+ }
676
714
  }
677
715
  }
678
716
  function resetAlpha({ data }, cmyk) {
@@ -876,7 +914,7 @@ function readGlobalLayerMaskInfo(reader) {
876
914
  const colorSpace2 = readUint16(reader);
877
915
  const colorSpace3 = readUint16(reader);
878
916
  const colorSpace4 = readUint16(reader);
879
- const opacity = readUint16(reader) / 100; // 0 - 100
917
+ const opacity = Math.min(readUint16(reader), 100) / 100; // 0 - 100 (some files have values out of range)
880
918
  const kind = readUint8(reader);
881
919
  skipBytes(reader, left()); // 3 bytes of padding ?
882
920
  return { overlayColorSpace, colorSpace1, colorSpace2, colorSpace3, colorSpace4, opacity, kind };
@@ -1126,10 +1164,7 @@ function toRange(value, max) {
1126
1164
  value = Math.round(value * (max / 255));
1127
1165
  return value <= 0 ? 0 : (value >= max ? max : value);
1128
1166
  }
1129
- // Polynomial approximation of U.S. Web Coated (SWOP) v2 CMYK to RGB conversion (default CMYK profile of Photoshop).
1130
- // Coefficients are taken from pdf.js (DeviceCmykCS in src/core/colorspace.js, https://github.com/mozilla/pdf.js),
1131
- // Copyright 2012 Mozilla Foundation, licensed under the Apache License, Version 2.0
1132
- // (http://www.apache.org/licenses/LICENSE-2.0)
1167
+ // converts CMYK data to RGB using approximation of U.S. Web Coated (SWOP) v2 profile (see cmykToRgbValues)
1133
1168
  // cmyk - 8 or 16 bit data with 5 channels (cmyka), rgb - 8 or 16 bit data with 4 channels (rgba)
1134
1169
  // removeMatte - colors are blended with white (no ink), used for composite image of documents with global alpha
1135
1170
  function cmykToRgb(cmyk, rgb, removeMatte) {
@@ -1138,6 +1173,7 @@ function cmykToRgb(cmyk, rgb, removeMatte) {
1138
1173
  const dstData = rgb.data;
1139
1174
  const srcMax = srcData instanceof Uint16Array ? 0xffff : 0xff;
1140
1175
  const dstMax = dstData instanceof Uint16Array ? 0xffff : 0xff;
1176
+ const out = [0, 0, 0];
1141
1177
  for (let src = 0, dst = 0; dst < size; src += 5, dst += 4) {
1142
1178
  const a = srcData[src + 4];
1143
1179
  // image data stores inverted values (0 = 100% ink)
@@ -1152,21 +1188,10 @@ function cmykToRgb(cmyk, rgb, removeMatte) {
1152
1188
  y = Math.min(1, y * ra);
1153
1189
  k = Math.min(1, k * ra);
1154
1190
  }
1155
- dstData[dst] = toRange(255 +
1156
- c * (-4.387332384609988 * c + 54.48615194189176 * m + 18.82290502165302 * y + 212.25662451639585 * k - 285.2331026137004) +
1157
- m * (1.7149763477362134 * m - 5.6096736904047315 * y - 17.873870861415444 * k - 5.497006427196366) +
1158
- y * (-2.5217340131683033 * y - 21.248923337353073 * k + 17.5119270841813) +
1159
- k * (-21.86122147463605 * k - 189.48180835922747), dstMax);
1160
- dstData[dst + 1] = toRange(255 +
1161
- c * (8.841041422036149 * c + 60.118027045597366 * m + 6.871425592049007 * y + 31.159100130055922 * k - 79.2970844816548) +
1162
- m * (-15.310361306967817 * m + 17.575251261109482 * y + 131.35250912493976 * k - 190.9453302588951) +
1163
- y * (4.444339102852739 * y + 9.8632861493405 * k - 24.86741582555878) +
1164
- k * (-20.737325471181034 * k - 187.80453709719578), dstMax);
1165
- dstData[dst + 2] = toRange(255 +
1166
- c * (0.8842522430003296 * c + 8.078677503112928 * m + 30.89978309703729 * y - 0.23883238689178934 * k - 14.183576799673286) +
1167
- m * (10.49593273432072 * m + 63.02378494754052 * y + 50.606957656360734 * k - 112.23884253719248) +
1168
- y * (0.03296041114873217 * y + 115.60384449646641 * k - 193.58209356861505) +
1169
- k * (-22.33816807309886 * k - 180.12613974708367), dstMax);
1191
+ (0, helpers_1.cmykToRgbValues)(c, m, y, k, out);
1192
+ dstData[dst] = toRange(out[0], dstMax);
1193
+ dstData[dst + 1] = toRange(out[1], dstMax);
1194
+ dstData[dst + 2] = toRange(out[2], dstMax);
1170
1195
  dstData[dst + 3] = srcMax === dstMax ? a : Math.round(a * dstMax / srcMax);
1171
1196
  }
1172
1197
  }
@@ -1333,8 +1358,8 @@ function readDataZip(compressed, pixelData, width, height, bitDepth, step, offse
1333
1358
  recoverMemory(reader, expectedSize);
1334
1359
  }
1335
1360
  }
1336
- // decodes single PackBits row, returns number of decoded values
1337
- function decodeRLERow(src, i, end, dst, p, step, count) {
1361
+ // decodes single PackBits row into byte buffer, returns number of decoded bytes
1362
+ function decodeRLERow(src, i, end, dst, count) {
1338
1363
  let x = 0;
1339
1364
  while (i < end && x < count) {
1340
1365
  const header = src[i++];
@@ -1345,10 +1370,8 @@ function decodeRLERow(src, i, end, dst, p, step, count) {
1345
1370
  let n = 257 - header;
1346
1371
  if (n > count - x)
1347
1372
  n = count - x;
1373
+ dst.fill(value, x, x + n);
1348
1374
  x += n;
1349
- for (; n > 0; n--, p += step) {
1350
- dst[p] = value;
1351
- }
1352
1375
  }
1353
1376
  else if (header < 128) {
1354
1377
  let n = header + 1;
@@ -1356,9 +1379,8 @@ function decodeRLERow(src, i, end, dst, p, step, count) {
1356
1379
  n = count - x;
1357
1380
  if (n > end - i)
1358
1381
  n = end - i;
1359
- x += n;
1360
- for (; n > 0; n--, p += step) {
1361
- dst[p] = src[i++];
1382
+ for (; n > 0; n--) {
1383
+ dst[x++] = src[i++];
1362
1384
  }
1363
1385
  }
1364
1386
  else {
@@ -1367,6 +1389,47 @@ function decodeRLERow(src, i, end, dst, p, step, count) {
1367
1389
  }
1368
1390
  return x;
1369
1391
  }
1392
+ // decodes RLE rows of single 8bit channel, writing values to `data` with `step`
1393
+ // (kept as single function with the loops, inlining row decoder into readDataRLE makes V8 generate much slower code)
1394
+ function decodeRLEChannel8(src, lengths, li, pos, width, height, data, offset, step) {
1395
+ const srcEnd = src.length | 0;
1396
+ for (let y = 0; y < height; y++, li++) {
1397
+ let i = (pos < srcEnd ? pos : srcEnd) | 0;
1398
+ pos += lengths[li];
1399
+ const end = (pos < srcEnd ? pos : srcEnd) | 0;
1400
+ let p = (offset + y * width * step) | 0;
1401
+ let x = 0;
1402
+ while (i < end && x < width) {
1403
+ const header = src[i++] | 0;
1404
+ if (header > 128) {
1405
+ if (i >= end)
1406
+ break;
1407
+ const value = src[i++] | 0;
1408
+ let n = (257 - header) | 0;
1409
+ if (n > width - x)
1410
+ n = (width - x) | 0;
1411
+ x = (x + n) | 0;
1412
+ for (const pe = (p + n * step) | 0; p < pe; p = (p + step) | 0) {
1413
+ data[p] = value;
1414
+ }
1415
+ }
1416
+ else if (header < 128) {
1417
+ let n = (header + 1) | 0;
1418
+ if (n > width - x)
1419
+ n = (width - x) | 0;
1420
+ if (n > end - i)
1421
+ n = (end - i) | 0;
1422
+ x = (x + n) | 0;
1423
+ for (const ie = (i + n) | 0; i < ie; i = (i + 1) | 0, p = (p + step) | 0) {
1424
+ data[p] = src[i];
1425
+ }
1426
+ }
1427
+ else {
1428
+ // ignore 128
1429
+ }
1430
+ }
1431
+ }
1432
+ }
1370
1433
  // converts big-endian 16/32-bit values from bytes and writes them to `data` starting at `p` with `step`
1371
1434
  function scatterValues(bytes, count, bpv, data, p, step) {
1372
1435
  if (bpv === 2) {
@@ -1381,8 +1444,8 @@ function scatterValues(bytes, count, bpv, data, p, step) {
1381
1444
  }
1382
1445
  }
1383
1446
  }
1384
- // decodes RLE rows of single channel, each row starts at the beginning of the image row
1385
- function decodeRLEChannel(src, lengths, li, pos, width, height, data, offset, step, bpv, row) {
1447
+ // decodes RLE rows of single 16bit or 32bit channel, each row is decoded into `row` buffer and then converted to values
1448
+ function decodeRLEChannelWide(src, lengths, li, pos, width, height, data, offset, step, bpv, row) {
1386
1449
  const srcEnd = src.length;
1387
1450
  const rowSize = width * bpv;
1388
1451
  for (let y = 0; y < height; y++, li++) {
@@ -1391,25 +1454,24 @@ function decodeRLEChannel(src, lengths, li, pos, width, height, data, offset, st
1391
1454
  const rowEnd = pos < srcEnd ? pos : srcEnd;
1392
1455
  if (rowStart >= rowEnd)
1393
1456
  continue;
1394
- if (bpv === 1) {
1395
- decodeRLERow(src, rowStart, rowEnd, data, offset + y * width * step, step, width);
1396
- }
1397
- else {
1398
- const decoded = decodeRLERow(src, rowStart, rowEnd, row, 0, 1, rowSize);
1399
- scatterValues(row, Math.floor(decoded / bpv), bpv, data, offset + y * width * step, step);
1400
- }
1457
+ const decoded = decodeRLERow(src, rowStart, rowEnd, row, rowSize);
1458
+ scatterValues(row, Math.floor(decoded / bpv), bpv, data, offset + y * width * step, step);
1401
1459
  }
1402
1460
  }
1403
1461
  function readDataRLE(reader, pixelData, width, height, bitDepth, step, offsets, large) {
1404
1462
  const data = pixelData && pixelData.data;
1405
1463
  const rows = offsets.length * height;
1406
1464
  const lengthSize = large ? 4 : 2;
1407
- consumeMemory(reader, rows * lengthSize);
1465
+ const lengthsMemory = rows * lengthSize;
1466
+ const srcEnd = reader.view.byteLength;
1467
+ const bpv = bytesPerValue(bitDepth); // throws before consuming memory
1468
+ let lengths;
1469
+ consumeMemory(reader, lengthsMemory);
1408
1470
  try {
1409
- if ((reader.offset + rows * lengthSize) > reader.view.byteLength) {
1471
+ if ((reader.offset + lengthsMemory) > srcEnd) {
1410
1472
  throw new Error('RLE row lengths exceed buffer length');
1411
1473
  }
1412
- const lengths = large ? new Uint32Array(rows) : new Uint16Array(rows);
1474
+ lengths = large ? new Uint32Array(rows) : new Uint16Array(rows);
1413
1475
  let total = 0;
1414
1476
  if (large) {
1415
1477
  for (let i = 0; i < rows; i++)
@@ -1419,32 +1481,37 @@ function readDataRLE(reader, pixelData, width, height, bitDepth, step, offsets,
1419
1481
  for (let i = 0; i < rows; i++)
1420
1482
  total += (lengths[i] = readUint16(reader));
1421
1483
  }
1422
- const start = reader.offset;
1423
- const srcEnd = reader.view.byteLength;
1424
- if (total > (srcEnd - start)) {
1484
+ if (total > (srcEnd - reader.offset)) {
1425
1485
  // broken or truncated file, decode only data that is available
1426
1486
  warnOrThrow(reader, 'RLE data exceeds buffer length');
1427
1487
  }
1428
- const bpv = bytesPerValue(bitDepth);
1429
1488
  if (data)
1430
1489
  verifyArrayType(data, bpv);
1431
- const src = new Uint8Array(reader.view.buffer, reader.view.byteOffset, srcEnd);
1432
- const row = (data && bpv > 1) ? new Uint8Array(width * bpv) : undefined;
1433
- let pos = start;
1434
- for (let c = 0, li = 0; c < offsets.length; c++, li += height) {
1435
- const offset = offsets[c] | 0;
1436
- const channelStart = pos;
1437
- for (let y = 0; y < height; y++)
1438
- pos += lengths[li + y];
1439
- if (data && offset >= 0 && offset < step) {
1440
- decodeRLEChannel(src, lengths, li, channelStart, width, height, data, offset, step, bpv, row);
1490
+ }
1491
+ catch (e) {
1492
+ recoverMemory(reader, lengthsMemory);
1493
+ throw e;
1494
+ }
1495
+ // decoding can't throw, it's kept outside of try block because V8 generates much slower code for loops inlined into try block
1496
+ const src = new Uint8Array(reader.view.buffer, reader.view.byteOffset, srcEnd);
1497
+ const row = (data && bpv > 1) ? new Uint8Array(width * bpv) : undefined;
1498
+ let pos = reader.offset;
1499
+ for (let c = 0, li = 0; c < offsets.length; c++, li += height) {
1500
+ const offset = offsets[c] | 0;
1501
+ const channelStart = pos;
1502
+ for (let y = 0; y < height; y++)
1503
+ pos += lengths[li + y];
1504
+ if (data && offset >= 0 && offset < step) {
1505
+ if (bpv === 1) {
1506
+ decodeRLEChannel8(src, lengths, li, channelStart, width, height, data, offset, step);
1507
+ }
1508
+ else {
1509
+ decodeRLEChannelWide(src, lengths, li, channelStart, width, height, data, offset, step, bpv, row);
1441
1510
  }
1442
1511
  }
1443
- reader.offset = pos;
1444
- }
1445
- finally {
1446
- recoverMemory(reader, rows * lengthSize);
1447
1512
  }
1513
+ reader.offset = pos;
1514
+ recoverMemory(reader, lengthsMemory);
1448
1515
  }
1449
1516
  function readSection(reader, round, func, skipEmpty = true, eightBytes = false) {
1450
1517
  let length = readUint32(reader);
@@ -1586,19 +1653,13 @@ function readPattern(reader) {
1586
1653
  // channels cover whole pattern, each 2 bytes of RLE data can produce at most 128 bytes
1587
1654
  if (width * height > (Math.min(end, reader.view.byteLength) - reader.offset) * 64)
1588
1655
  throw new Error('Pattern data out of bounds');
1589
- const size = width * height * 4;
1590
- consumeMemory(reader, size);
1591
- const data = new Uint8Array(size);
1592
- for (let i = 3; i < data.byteLength; i += 4) {
1593
- data[i] = 255;
1594
- }
1595
1656
  // first written arrays are color channels, next one is transparency (Photoshop stores it in the last, sheet mask, array)
1596
1657
  const colorChannels = colorMode === 3 /* ColorMode.RGB */ ? 3 : (colorMode === 4 /* ColorMode.CMYK */ ? 4 : 1);
1597
- // CMYK channels are decoded into separate buffer (5 values per pixel, inverted, 0 = 100% ink) and converted to RGB at the end
1598
- const cmyk = colorMode === 4 /* ColorMode.CMYK */ ? new Uint8Array(width * height * 5).fill(255) : undefined;
1599
- if (cmyk)
1600
- consumeMemory(reader, cmyk.byteLength);
1601
- const alphaTarget = cmyk ? 4 : 3;
1658
+ const isCmyk = colorMode === 4 /* ColorMode.CMYK */;
1659
+ const alphaTarget = isCmyk ? 4 : 3;
1660
+ const channels = [];
1661
+ let maxDecodeSize = 0;
1662
+ // validate all channels before allocating any image data
1602
1663
  for (let i = 0, ch = 0; i < (channelsCount + 2) && reader.offset + 4 <= end; i++) {
1603
1664
  const has = readUint32(reader);
1604
1665
  if (!has)
@@ -1630,72 +1691,107 @@ function readPattern(reader) {
1630
1691
  const h = cbottom - ctop;
1631
1692
  const rowBytes = w * (depth / 8);
1632
1693
  const channelSize = rowBytes * h;
1633
- let bytes;
1634
1694
  if (compressionMode === 0) {
1635
1695
  if (cdata.byteLength < channelSize)
1636
1696
  throw new Error('Pattern channel data out of bounds');
1637
- bytes = cdata;
1638
1697
  }
1639
1698
  else if (compressionMode === 1) {
1640
1699
  // each 2 bytes of RLE data can produce at most 128 bytes
1641
1700
  if (channelSize > cdata.byteLength * 64)
1642
1701
  throw new Error('Pattern channel data out of bounds');
1643
- consumeMemory(reader, channelSize);
1644
- bytes = new Uint8Array(channelSize);
1645
- const cdataReader = createReader(cdata.buffer, cdata.byteOffset, cdata.byteLength);
1646
- readDataRLE(cdataReader, { data: bytes, width: rowBytes, height: h }, rowBytes, h, 8, 1, [0], false);
1702
+ maxDecodeSize = Math.max(maxDecodeSize, channelSize);
1647
1703
  }
1648
1704
  else {
1649
1705
  throw new Error('Invalid pattern compression mode');
1650
1706
  }
1707
+ channels.push({ target, depth, compressionMode, cdata, ctop, cleft, w, h, rowBytes, channelSize });
1708
+ }
1709
+ // check peak memory up front (image data, CMYK buffer and largest RLE decode buffer),
1710
+ // image data is consumed only after successful decoding so failed patterns don't reduce the limit
1711
+ const size = width * height * 4;
1712
+ const cmykSize = isCmyk ? width * height * 5 : 0;
1713
+ consumeMemory(reader, size + cmykSize + maxDecodeSize);
1714
+ recoverMemory(reader, size + cmykSize + maxDecodeSize);
1715
+ const data = new Uint8Array(size);
1716
+ for (let i = 3; i < data.byteLength; i += 4) {
1717
+ data[i] = 255;
1718
+ }
1719
+ // CMYK channels are decoded into separate buffer (5 values per pixel, inverted, 0 = 100% ink) and converted to RGB at the end
1720
+ const cmyk = isCmyk ? new Uint8Array(cmykSize).fill(255) : undefined;
1721
+ for (const { target, depth, compressionMode, cdata, ctop, cleft, w, h, rowBytes, channelSize } of channels) {
1722
+ let bytes = cdata;
1723
+ if (compressionMode === 1) {
1724
+ bytes = new Uint8Array(channelSize);
1725
+ const cdataReader = createReader(cdata.buffer, cdata.byteOffset, cdata.byteLength);
1726
+ readDataRLE(cdataReader, { data: bytes, width: rowBytes, height: h }, rowBytes, h, 8, 1, [0], false);
1727
+ }
1728
+ if (depth !== 8)
1729
+ bytes = patternChannelTo8bit(bytes, w * h, depth);
1651
1730
  const ox = cleft - left;
1652
1731
  const oy = ctop - top;
1653
- for (let y = 0; y < h; y++) {
1654
- for (let x = 0; x < w; x++) {
1655
- const src = x + y * w;
1656
- const dst = (ox + x + (y + oy) * width) * 4;
1657
- const value = depth === 8 ? bytes[src] : patternValueTo8bit(bytes, src, depth);
1658
- if (cmyk) {
1659
- cmyk[(ox + x + (y + oy) * width) * 5 + target] = value;
1660
- }
1661
- else if (target === 3) {
1662
- data[dst + 3] = value;
1663
- }
1664
- else if (colorMode === 2 /* ColorMode.Indexed */) {
1665
- const color = palette[value];
1666
- data[dst + 0] = color.r;
1667
- data[dst + 1] = color.g;
1668
- data[dst + 2] = color.b;
1732
+ if (cmyk) {
1733
+ copyPatternChannel(bytes, cmyk, w, h, (ox + oy * width) * 5 + target, width * 5, 5);
1734
+ }
1735
+ else if (target === 3 || colorMode === 3 /* ColorMode.RGB */) {
1736
+ copyPatternChannel(bytes, data, w, h, (ox + oy * width) * 4 + target, width * 4, 4);
1737
+ }
1738
+ else if (colorMode === 2 /* ColorMode.Indexed */) {
1739
+ const colors = new Uint8Array(256 * 3);
1740
+ for (let i = 0; i < 256; i++) {
1741
+ colors[i * 3 + 0] = palette[i].r;
1742
+ colors[i * 3 + 1] = palette[i].g;
1743
+ colors[i * 3 + 2] = palette[i].b;
1744
+ }
1745
+ for (let y = 0; y < h; y++) {
1746
+ for (let x = 0, src = y * w, dst = (ox + (y + oy) * width) * 4; x < w; x++, src++, dst += 4) {
1747
+ const index = bytes[src] * 3;
1748
+ data[dst + 0] = colors[index + 0];
1749
+ data[dst + 1] = colors[index + 1];
1750
+ data[dst + 2] = colors[index + 2];
1669
1751
  }
1670
- else if (colorMode === 1 /* ColorMode.Grayscale */) {
1752
+ }
1753
+ }
1754
+ else { // grayscale
1755
+ for (let y = 0; y < h; y++) {
1756
+ for (let x = 0, src = y * w, dst = (ox + (y + oy) * width) * 4; x < w; x++, src++, dst += 4) {
1757
+ const value = bytes[src];
1671
1758
  data[dst + 0] = value;
1672
1759
  data[dst + 1] = value;
1673
1760
  data[dst + 2] = value;
1674
1761
  }
1675
- else {
1676
- data[dst + target] = value;
1677
- }
1678
1762
  }
1679
1763
  }
1680
- if (compressionMode === 1)
1681
- recoverMemory(reader, channelSize);
1682
1764
  }
1683
1765
  if (cmyk) {
1684
1766
  cmykToRgb({ width, height, data: cmyk }, { width, height, data }, false);
1685
- recoverMemory(reader, cmyk.byteLength);
1686
1767
  }
1768
+ consumeMemory(reader, size);
1687
1769
  reader.offset = end;
1688
1770
  return { id, name, x, y, bounds: { x: left, y: top, w: width, h: height }, data };
1689
1771
  }
1690
- // converts big-endian 16bit or 32bit (float) value at given index to 8bit value
1691
- function patternValueTo8bit(bytes, index, depth) {
1772
+ // converts big-endian 16bit or 32bit (float) values to 8bit values
1773
+ function patternChannelTo8bit(bytes, count, depth) {
1774
+ const result = new Uint8Array(count);
1692
1775
  if (depth === 16) {
1693
- return Math.round(((bytes[index * 2] << 8) | bytes[index * 2 + 1]) / 257);
1776
+ for (let i = 0; i < count; i++) {
1777
+ result[i] = Math.round(((bytes[i * 2] << 8) | bytes[i * 2 + 1]) / 257);
1778
+ }
1694
1779
  }
1695
1780
  else {
1696
- const view = new DataView(bytes.buffer, bytes.byteOffset + index * 4, 4);
1697
- const value = view.getFloat32(0, false);
1698
- return value > 0 ? (value < 1 ? Math.round(value * 255) : 255) : 0;
1781
+ const view = new DataView(bytes.buffer, bytes.byteOffset, bytes.byteLength);
1782
+ for (let i = 0; i < count; i++) {
1783
+ const value = view.getFloat32(i * 4, false);
1784
+ result[i] = value > 0 ? (value < 1 ? Math.round(value * 255) : 255) : 0;
1785
+ }
1786
+ }
1787
+ return result;
1788
+ }
1789
+ // copies w x h channel values into interleaved data starting at `offset`
1790
+ function copyPatternChannel(bytes, data, w, h, offset, stride, step) {
1791
+ for (let y = 0, src = 0; y < h; y++) {
1792
+ for (let x = 0, dst = offset + y * stride; x < w; x++, src++, dst += step) {
1793
+ data[dst] = bytes[src];
1794
+ }
1699
1795
  }
1700
1796
  }
1701
1797
  function consumeMemory(reader, size) {