ag-psd 32.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 (117) hide show
  1. package/.v8-cache/v24.7.0-x64-ef5a0af0/03e19709 +0 -0
  2. package/.v8-cache/v24.7.0-x64-ef5a0af0/05135617 +0 -0
  3. package/.v8-cache/v24.7.0-x64-ef5a0af0/1130885f +0 -0
  4. package/.v8-cache/v24.7.0-x64-ef5a0af0/226e2801 +0 -0
  5. package/.v8-cache/v24.7.0-x64-ef5a0af0/2a19133e +0 -0
  6. package/.v8-cache/v24.7.0-x64-ef5a0af0/4511bacf +0 -0
  7. package/.v8-cache/v24.7.0-x64-ef5a0af0/68416cb2 +0 -0
  8. package/.v8-cache/v24.7.0-x64-ef5a0af0/71df9cb3 +0 -0
  9. package/.v8-cache/v24.7.0-x64-ef5a0af0/7ea06cea +0 -0
  10. package/.v8-cache/v24.7.0-x64-ef5a0af0/87cf2209 +0 -0
  11. package/.v8-cache/v24.7.0-x64-ef5a0af0/932f0770 +0 -0
  12. package/.v8-cache/v24.7.0-x64-ef5a0af0/acc36e66 +0 -0
  13. package/.v8-cache/v24.7.0-x64-ef5a0af0/b3c2fab7 +0 -0
  14. package/.v8-cache/v24.7.0-x64-ef5a0af0/c0b789d2 +0 -0
  15. package/.v8-cache/v24.7.0-x64-ef5a0af0/c314aece +0 -0
  16. package/.v8-cache/v24.7.0-x64-ef5a0af0/cfc49f4f +0 -0
  17. package/.v8-cache/v24.7.0-x64-ef5a0af0/cff9434a +0 -0
  18. package/.v8-cache/v24.7.0-x64-ef5a0af0/d13d7610 +0 -0
  19. package/.v8-cache/v24.7.0-x64-ef5a0af0/dc86f000 +0 -0
  20. package/.v8-cache/v24.7.0-x64-ef5a0af0/e03e2acd +0 -0
  21. package/.v8-cache/v24.7.0-x64-ef5a0af0/e6745c2b +0 -0
  22. package/.v8-cache/v24.7.0-x64-ef5a0af0/e6b99945 +0 -0
  23. package/.v8-cache/v24.7.0-x64-ef5a0af0/eeeebdfe +0 -0
  24. package/.v8-cache/v24.7.0-x64-ef5a0af0/f6fd8125 +0 -0
  25. package/CHANGELOG.md +100 -0
  26. package/LICENSE +5 -0
  27. package/README.md +4 -7
  28. package/README_PSD.md +261 -84
  29. package/TODO.md +13 -84
  30. package/dist/abr.d.ts +11 -4
  31. package/dist/abr.js +362 -230
  32. package/dist/abr.js.map +1 -1
  33. package/dist/additionalInfo.d.ts +56 -2
  34. package/dist/additionalInfo.js +828 -361
  35. package/dist/additionalInfo.js.map +1 -1
  36. package/dist/ase.d.ts +6 -1
  37. package/dist/ase.js +61 -61
  38. package/dist/ase.js.map +1 -1
  39. package/dist/bundle.js +3321 -1364
  40. package/dist/descriptor.d.ts +28 -13
  41. package/dist/descriptor.js +295 -241
  42. package/dist/descriptor.js.map +1 -1
  43. package/dist/helpers.d.ts +7 -1
  44. package/dist/helpers.js +246 -4
  45. package/dist/helpers.js.map +1 -1
  46. package/dist/imageResources.d.ts +15 -3
  47. package/dist/imageResources.js +586 -144
  48. package/dist/imageResources.js.map +1 -1
  49. package/dist/index.d.ts +1 -0
  50. package/dist/index.js +1 -0
  51. package/dist/index.js.map +1 -1
  52. package/dist/jpeg.js +8 -8
  53. package/dist/jpeg.js.map +1 -1
  54. package/dist/psd.d.ts +292 -19
  55. package/dist/psd.js.map +1 -1
  56. package/dist/psdReader.d.ts +10 -0
  57. package/dist/psdReader.js +424 -216
  58. package/dist/psdReader.js.map +1 -1
  59. package/dist/psdWriter.d.ts +2 -0
  60. package/dist/psdWriter.js +373 -113
  61. package/dist/psdWriter.js.map +1 -1
  62. package/dist/text.d.ts +32 -1
  63. package/dist/text.js +75 -6
  64. package/dist/text.js.map +1 -1
  65. package/dist/utf8.js +0 -3
  66. package/dist/utf8.js.map +1 -1
  67. package/dist-es/abr.d.ts +11 -4
  68. package/dist-es/abr.js +362 -230
  69. package/dist-es/abr.js.map +1 -1
  70. package/dist-es/additionalInfo.d.ts +56 -2
  71. package/dist-es/additionalInfo.js +828 -361
  72. package/dist-es/additionalInfo.js.map +1 -1
  73. package/dist-es/ase.d.ts +6 -1
  74. package/dist-es/ase.js +61 -61
  75. package/dist-es/ase.js.map +1 -1
  76. package/dist-es/descriptor.d.ts +28 -13
  77. package/dist-es/descriptor.js +295 -241
  78. package/dist-es/descriptor.js.map +1 -1
  79. package/dist-es/helpers.d.ts +7 -1
  80. package/dist-es/helpers.js +246 -4
  81. package/dist-es/helpers.js.map +1 -1
  82. package/dist-es/imageResources.d.ts +15 -3
  83. package/dist-es/imageResources.js +586 -144
  84. package/dist-es/imageResources.js.map +1 -1
  85. package/dist-es/index.d.ts +1 -0
  86. package/dist-es/index.js +1 -0
  87. package/dist-es/index.js.map +1 -1
  88. package/dist-es/jpeg.js +8 -8
  89. package/dist-es/jpeg.js.map +1 -1
  90. package/dist-es/psd.d.ts +292 -19
  91. package/dist-es/psd.js.map +1 -1
  92. package/dist-es/psdReader.d.ts +10 -0
  93. package/dist-es/psdReader.js +424 -216
  94. package/dist-es/psdReader.js.map +1 -1
  95. package/dist-es/psdWriter.d.ts +2 -0
  96. package/dist-es/psdWriter.js +373 -113
  97. package/dist-es/psdWriter.js.map +1 -1
  98. package/dist-es/text.d.ts +32 -1
  99. package/dist-es/text.js +75 -6
  100. package/dist-es/text.js.map +1 -1
  101. package/dist-es/utf8.js +0 -3
  102. package/dist-es/utf8.js.map +1 -1
  103. package/package.json +2 -2
  104. package/perf.js +312 -9
  105. package/src/abr.ts +382 -235
  106. package/src/additionalInfo.ts +1071 -482
  107. package/src/ase.ts +66 -61
  108. package/src/descriptor.ts +291 -242
  109. package/src/helpers.ts +266 -3
  110. package/src/imageResources.ts +736 -169
  111. package/src/index.ts +1 -0
  112. package/src/jpeg.ts +8 -8
  113. package/src/psd.ts +271 -32
  114. package/src/psdReader.ts +440 -221
  115. package/src/psdWriter.ts +402 -110
  116. package/src/text.ts +93 -7
  117. package/src/utf8.ts +0 -4
@@ -12,6 +12,10 @@ var __rest = (this && this.__rest) || function (s, e) {
12
12
  };
13
13
  Object.defineProperty(exports, "__esModule", { value: true });
14
14
  exports.supportedColorModes = void 0;
15
+ exports.getUtf8Decoder = getUtf8Decoder;
16
+ exports.getUtf8StrictDecoder = getUtf8StrictDecoder;
17
+ exports.getGbkStrictDecoder = getGbkStrictDecoder;
18
+ exports.decodeUtf8 = decodeUtf8;
15
19
  exports.createReader = createReader;
16
20
  exports.warnOrThrow = warnOrThrow;
17
21
  exports.readUint8 = readUint8;
@@ -53,12 +57,15 @@ exports.readDataZip = readDataZip;
53
57
  exports.readDataRLE = readDataRLE;
54
58
  exports.readSection = readSection;
55
59
  exports.readColor = readColor;
60
+ exports.readPatternSafe = readPatternSafe;
56
61
  exports.readPattern = readPattern;
62
+ exports.consumeMemory = consumeMemory;
57
63
  const pako_1 = require("pako");
58
64
  const helpers_1 = require("./helpers");
59
65
  const additionalInfo_1 = require("./additionalInfo");
66
+ const utf8_1 = require("./utf8");
60
67
  const imageResources_1 = require("./imageResources");
61
- exports.supportedColorModes = [0 /* ColorMode.Bitmap */, 1 /* ColorMode.Grayscale */, 3 /* ColorMode.RGB */, 2 /* ColorMode.Indexed */];
68
+ exports.supportedColorModes = [0 /* ColorMode.Bitmap */, 1 /* ColorMode.Grayscale */, 3 /* ColorMode.RGB */, 2 /* ColorMode.Indexed */, 4 /* ColorMode.CMYK */];
62
69
  const colorModes = ['bitmap', 'grayscale', 'indexed', 'RGB', 'CMYK', '', '', 'multichannel', 'duotone', 'lab'];
63
70
  function setupGrayscale(data) {
64
71
  const size = data.width * data.height * 4;
@@ -68,6 +75,37 @@ function setupGrayscale(data) {
68
75
  data.data[i + 2] = c;
69
76
  }
70
77
  }
78
+ function createTextDecoder(label, options) {
79
+ try {
80
+ return typeof TextDecoder !== 'undefined' ? new TextDecoder(label, options) : null;
81
+ }
82
+ catch (_a) {
83
+ return null; // encoding not supported
84
+ }
85
+ }
86
+ // non-fatal UTF-8 decoder, malformed byte sequences are replaced with U+FFFD
87
+ function getUtf8Decoder(reader) {
88
+ if (reader.utf8Decoder === undefined)
89
+ reader.utf8Decoder = createTextDecoder('utf-8', { ignoreBOM: true });
90
+ return reader.utf8Decoder;
91
+ }
92
+ // UTF-8 decoder that throws on malformed byte sequences
93
+ function getUtf8StrictDecoder(reader) {
94
+ if (reader.utf8StrictDecoder === undefined)
95
+ reader.utf8StrictDecoder = createTextDecoder('utf-8', { ignoreBOM: true, fatal: true });
96
+ return reader.utf8StrictDecoder;
97
+ }
98
+ // GBK decoder that throws on malformed byte sequences
99
+ function getGbkStrictDecoder(reader) {
100
+ if (reader.gbkDecoder === undefined)
101
+ reader.gbkDecoder = createTextDecoder('gbk', { fatal: true });
102
+ return reader.gbkDecoder;
103
+ }
104
+ function decodeUtf8(reader, buffer) {
105
+ // native decoder is faster only for longer strings
106
+ const decoder = buffer.byteLength > 1000 ? getUtf8Decoder(reader) : null;
107
+ return decoder ? decoder.decode(buffer) : (0, utf8_1.decodeString)(buffer);
108
+ }
71
109
  function createReader(buffer, offset, length) {
72
110
  const view = new DataView(buffer, offset, length);
73
111
  return { view, offset: 0, strict: false, debug: false, large: false, globalAlpha: false, log: console.log };
@@ -168,6 +206,8 @@ function readUnicodeString(reader) {
168
206
  return readUnicodeStringWithLength(reader, length);
169
207
  }
170
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;
171
211
  function checkReadBounds(reader, size) {
172
212
  if (reader.offset < 0 || (reader.offset + size) > reader.view.byteLength) {
173
213
  throw new RangeError('Offset is outside the bounds of the DataView');
@@ -178,9 +218,18 @@ function readUtf16String(reader, length, littleEndian) {
178
218
  return '';
179
219
  checkReadBounds(reader, length * 2);
180
220
  const view = reader.view;
181
- const codes = new Uint16Array(Math.min(length, STRING_CHUNK_SIZE));
182
221
  let offset = reader.offset;
183
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));
184
233
  for (let i = 0; i < length; i += STRING_CHUNK_SIZE) {
185
234
  const count = Math.min(STRING_CHUNK_SIZE, length - i);
186
235
  for (let j = 0; j < count; j++, offset += 2) {
@@ -211,6 +260,15 @@ function readAsciiString(reader, length) {
211
260
  if (!(length > 0))
212
261
  return '';
213
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
+ }
214
272
  const text = bytesToString(new Uint8Array(reader.view.buffer, reader.view.byteOffset + reader.offset, length));
215
273
  reader.offset += length;
216
274
  return text;
@@ -226,7 +284,14 @@ function checkSignature(reader, a, b) {
226
284
  }
227
285
  }
228
286
  function readShortString(reader, length) {
229
- 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;
230
295
  }
231
296
  function isValidSignature(sig) {
232
297
  return sig === '8BIM' || sig === 'MeSa' || sig === 'AgHg' || sig === 'PHUT' || sig === 'DCSR';
@@ -247,7 +312,8 @@ function readPsd(reader, readOptions = {}) {
247
312
  const maxSize = version === 1 ? 30000 : 300000;
248
313
  if (width > maxSize || height > maxSize)
249
314
  throw new Error(`Invalid size: ${width}x${height}`);
250
- if (channels < 1 || channels > 56)
315
+ // up to 56 channels plus transparency of composite image (Photoshop CS3 writes 57 channels for 53 alpha channels)
316
+ if (channels < 1 || channels > 57)
251
317
  throw new Error(`Invalid channel count: ${channels}`);
252
318
  if (![1, 8, 16, 32].includes(bitsPerChannel))
253
319
  throw new Error(`Invalid bitsPerChannel: ${bitsPerChannel}`);
@@ -257,6 +323,7 @@ function readPsd(reader, readOptions = {}) {
257
323
  Object.assign(reader, readOptions);
258
324
  reader.large = version === 2;
259
325
  reader.globalAlpha = false;
326
+ reader.layersInGlobalInfo = false;
260
327
  if (!('totalMemoryLimit' in reader)) { // setting totalMemoryLimit to undefined explicitly disables memory limit
261
328
  reader.totalMemoryLimit = 2 * 1024 * 1024 * 1024; // default 2GB memory limit
262
329
  }
@@ -284,17 +351,21 @@ function readPsd(reader, readOptions = {}) {
284
351
  });
285
352
  // image resources
286
353
  const imageResources = {};
354
+ let unreadResourceBytes = 0;
287
355
  readSection(reader, 1, left => {
288
- while (left() > 0) {
289
- realignWithSignature(reader, isValidSignature);
356
+ // stop if there's not enough data left for resource header (some files have few trailing bytes in this section)
357
+ while (left() >= 12) {
358
+ const signature = realignWithSignature(reader, isValidSignature);
290
359
  const id = readUint16(reader);
291
- readPascalString(reader, 2); // name
360
+ const nameLength = readUint8(reader);
361
+ const name = readBytes(reader, nameLength);
362
+ skipBytes(reader, (nameLength + 1) % 2); // pad to even size
292
363
  readSection(reader, 2, left => {
293
364
  const handler = imageResources_1.resourceHandlersMap[id];
294
365
  const skip = id === 1036 && !!reader.skipThumbnail;
295
366
  if (handler && !skip) {
296
367
  try {
297
- handler.read(reader, imageResources, left);
368
+ handler.read(reader, imageResources, left, { signature, id, name, width, height });
298
369
  }
299
370
  catch (e) {
300
371
  if (reader.throwForMissingFeatures)
@@ -303,13 +374,20 @@ function readPsd(reader, readOptions = {}) {
303
374
  }
304
375
  }
305
376
  else {
306
- // options.logMissingFeatures && console.log(`Unhandled image resource: ${id} (${left()})`);
377
+ reader.logMissingFeatures && reader.log(`Unhandled image resource: ${id} (${left()})`);
307
378
  skipBytes(reader, left());
308
379
  }
309
380
  });
310
381
  }
382
+ unreadResourceBytes = left();
383
+ skipBytes(reader, left());
311
384
  });
312
- const { layersGroup, layerGroupsEnabledId } = imageResources, rest = __rest(imageResources, ["layersGroup", "layerGroupsEnabledId"]);
385
+ // some files have image resources section length few bytes larger than the data, layer and mask info section
386
+ // starts right after the last resource in that case
387
+ if (unreadResourceBytes && !sectionFits(reader, reader.offset) && sectionFits(reader, reader.offset - unreadResourceBytes)) {
388
+ reader.offset -= unreadResourceBytes;
389
+ }
390
+ const { layersGroup, layerGroupsEnabledId, savedPathsOrigination } = imageResources, rest = __rest(imageResources, ["layersGroup", "layerGroupsEnabledId", "savedPathsOrigination"]);
313
391
  if (Object.keys(rest).length) {
314
392
  psd.imageResources = rest;
315
393
  }
@@ -353,6 +431,8 @@ function readPsd(reader, readOptions = {}) {
353
431
  const rawCompositeData = new Uint8Array(reader.view.buffer, reader.view.byteOffset + rawOffset, reader.view.byteLength - rawOffset);
354
432
  psd.rawCompositeData = rawCompositeData;
355
433
  psd.rawCompositeDataInfo = { large: reader.large, globalAlpha: reader.globalAlpha };
434
+ if (reader.layersInGlobalInfo)
435
+ psd.rawCompositeDataInfo.layersInGlobalInfo = true;
356
436
  }
357
437
  else {
358
438
  const imageData = readImageData(reader, psd);
@@ -364,9 +444,6 @@ function readPsd(reader, readOptions = {}) {
364
444
  }
365
445
  }
366
446
  }
367
- // TODO: show converted color mode instead of original PSD file color mode
368
- // but add option to preserve file color mode (need to return image data instead of canvas in that case)
369
- // psd.colorMode = ColorMode.RGB; // we convert all color modes to RGB
370
447
  return psd;
371
448
  }
372
449
  function readLayerInfo(reader, psd, imageResources) {
@@ -400,6 +477,9 @@ function readLayerInfo(reader, psd, imageResources) {
400
477
  if (type === 1 /* SectionDividerType.OpenFolder */ || type === 2 /* SectionDividerType.ClosedFolder */) {
401
478
  l.opened = type === 1 /* SectionDividerType.OpenFolder */;
402
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;
403
483
  if ((_a = l.sectionDivider) === null || _a === void 0 ? void 0 : _a.key) {
404
484
  l.blendMode = (_b = helpers_1.toBlendMode[l.sectionDivider.key]) !== null && _b !== void 0 ? _b : l.blendMode;
405
485
  }
@@ -442,9 +522,12 @@ function readLayerRecord(reader, psd, imageResources) {
442
522
  }
443
523
  checkSignature(reader, '8BIM');
444
524
  const blendMode = readSignature(reader);
445
- if (!helpers_1.toBlendMode[blendMode])
446
- throw new Error(`Invalid blend mode: '${blendMode}'`);
447
- layer.blendMode = helpers_1.toBlendMode[blendMode];
525
+ if (!helpers_1.toBlendMode[blendMode]) {
526
+ if (reader.throwForMissingFeatures)
527
+ throw new Error(`Invalid blend mode: '${blendMode}'`);
528
+ reader.logMissingFeatures && reader.log(`Unknown blend mode: '${blendMode}', using normal blend mode`);
529
+ }
530
+ layer.blendMode = helpers_1.toBlendMode[blendMode] || 'normal';
448
531
  layer.opacity = readUint8(reader) / 0xff;
449
532
  layer.clipping = readUint8(reader) === 1;
450
533
  const flags = readUint8(reader);
@@ -472,11 +555,20 @@ function readLayerRecord(reader, psd, imageResources) {
472
555
  });
473
556
  return { layer, channels };
474
557
  }
558
+ // negative sizes are treated as empty (Photoshop writes them for some empty layers, like 0,0,-1,0 bounds of fill layer)
559
+ // checks if length of section (layer and mask info) at given offset fits in the file
560
+ function sectionFits(reader, offset) {
561
+ const lengthSize = reader.large ? 8 : 4;
562
+ if (offset < 0 || offset + lengthSize > reader.view.byteLength)
563
+ return false;
564
+ const length = reader.large ? reader.view.getUint32(offset) * 0x100000000 + reader.view.getUint32(offset + 4) : reader.view.getUint32(offset);
565
+ return offset + lengthSize + length <= reader.view.byteLength;
566
+ }
475
567
  function isValidBoxSize(box, reader) {
476
568
  const width = (box.right || 0) - (box.left || 0);
477
569
  const height = (box.bottom || 0) - (box.top || 0);
478
570
  const maxSize = reader.large ? 300000 : 30000;
479
- return width >= 0 && height >= 0 && width <= maxSize && height <= maxSize;
571
+ return width <= maxSize && height <= maxSize;
480
572
  }
481
573
  function readLayerMaskData(reader, layer) {
482
574
  return readSection(reader, 1, left => {
@@ -586,18 +678,17 @@ function readLayerChannelImageData(reader, psd, layer, channels) {
586
678
  const start = reader.offset;
587
679
  let compression = 0 /* Compression.RawData */;
588
680
  let data = undefined;
589
- if (channel.length === 1)
590
- throw new Error('Invalid channel length');
591
- 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) {
592
683
  compression = readUint16(reader);
593
684
  // try to fix broken files where there's 1 byte shift of channel
594
685
  if (compression > 3) {
595
686
  reader.offset -= 1;
596
687
  compression = readUint16(reader);
597
688
  }
598
- // try to fix broken files where there's 1 byte shift of channel
689
+ // try to fix broken files where there's 1 byte shift of channel (in the other direction)
599
690
  if (compression > 3) {
600
- reader.offset -= 3;
691
+ reader.offset -= 4;
601
692
  compression = readUint16(reader);
602
693
  }
603
694
  if (compression > 3)
@@ -610,7 +701,16 @@ function readLayerChannelImageData(reader, psd, layer, channels) {
610
701
  layer.rawData.channels.push({ id: channel.id, compression, data });
611
702
  }
612
703
  if (!reader.useRawData) {
613
- 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
+ }
614
714
  }
615
715
  }
616
716
  function resetAlpha({ data }, cmyk) {
@@ -631,6 +731,7 @@ function getCompositeImageData(psd) {
631
731
  if (info) {
632
732
  reader.large = info.large;
633
733
  reader.globalAlpha = info.globalAlpha;
734
+ reader.layersInGlobalInfo = !!info.layersInGlobalInfo;
634
735
  }
635
736
  return readImageData(reader, psd);
636
737
  }
@@ -711,18 +812,14 @@ function getDataFromLayer(layer, read, throwForMissingFeatures, memoryLimit, log
711
812
  let initializedAlpha = false;
712
813
  if (layerWidth && layerHeight && read === LayerDataType.Layer) {
713
814
  if (cmyk) {
714
- if (bitsPerChannel !== 8)
815
+ if (bitsPerChannel !== 8 && bitsPerChannel !== 16)
715
816
  throw new Error('bitsPerChannel not supported');
716
- imageData = { width: layerWidth, height: layerHeight, data: new Uint8ClampedArray(layerWidth * layerHeight * 5) };
817
+ imageData = createImageDataBitDepth(layerWidth, layerHeight, bitsPerChannel, 5, memoryLimit);
717
818
  }
718
819
  else {
719
820
  imageData = createImageDataBitDepth(layerWidth, layerHeight, bitsPerChannel, 4, memoryLimit);
720
821
  }
721
822
  }
722
- if (helpers_1.RAW_IMAGE_DATA) { // TODO: use layer.rawData instead
723
- layer.imageDataRaw = [];
724
- layer.imageDataRawCompression = [];
725
- }
726
823
  for (const { id, compression, data } of channels) {
727
824
  if (!data)
728
825
  continue;
@@ -747,16 +844,6 @@ function getDataFromLayer(layer, read, throwForMissingFeatures, memoryLimit, log
747
844
  if (memoryLimit !== undefined)
748
845
  dataReader.totalMemoryLimit = Math.max(0, dataReader.totalMemoryLimit - maskData.data.byteLength);
749
846
  readData(dataReader, data.byteLength, maskData, compression, maskWidth, maskHeight, bitsPerChannel, 0, large, 4);
750
- if (helpers_1.RAW_IMAGE_DATA) { // TODO: use layer.rawData instead
751
- if (id === -2 /* ChannelID.UserMask */) {
752
- layer.maskDataRawCompression = compression;
753
- layer.maskDataRaw = data;
754
- }
755
- else {
756
- layer.realMaskDataRawCompression = compression;
757
- layer.realMaskDataRaw = data;
758
- }
759
- }
760
847
  setupGrayscale(maskData);
761
848
  resetAlpha(maskData, false);
762
849
  }
@@ -773,10 +860,6 @@ function getDataFromLayer(layer, read, throwForMissingFeatures, memoryLimit, log
773
860
  }
774
861
  }
775
862
  readData(dataReader, data.byteLength, targetData, compression, layerWidth, layerHeight, bitsPerChannel, offset, large, cmyk ? 5 : 4);
776
- if (helpers_1.RAW_IMAGE_DATA) { // TODO: use layer.rawData instead
777
- layer.imageDataRawCompression[id] = compression;
778
- layer.imageDataRaw[id] = data;
779
- }
780
863
  }
781
864
  if (id === -1 /* ChannelID.Transparency */) {
782
865
  initializedAlpha = true;
@@ -789,7 +872,8 @@ function getDataFromLayer(layer, read, throwForMissingFeatures, memoryLimit, log
789
872
  resetAlpha(imageData, cmyk);
790
873
  if (cmyk) {
791
874
  const cmykData = imageData;
792
- imageData = (0, helpers_1.createImageData)(cmykData.width, cmykData.height);
875
+ const rgbMemoryLimit = memoryLimit === undefined ? undefined : Math.max(0, memoryLimit - cmykData.data.byteLength);
876
+ imageData = createImageDataBitDepth(cmykData.width, cmykData.height, bitsPerChannel, 4, rgbMemoryLimit);
793
877
  cmykToRgb(cmykData, imageData, false);
794
878
  }
795
879
  }
@@ -830,7 +914,7 @@ function readGlobalLayerMaskInfo(reader) {
830
914
  const colorSpace2 = readUint16(reader);
831
915
  const colorSpace3 = readUint16(reader);
832
916
  const colorSpace4 = readUint16(reader);
833
- 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)
834
918
  const kind = readUint8(reader);
835
919
  skipBytes(reader, left()); // 3 bytes of padding ?
836
920
  return { overlayColorSpace, colorSpace1, colorSpace2, colorSpace3, colorSpace4, opacity, kind };
@@ -862,7 +946,15 @@ function readAdditionalLayerInfo(reader, target, psd, imageResources) {
862
946
  const sig = realignWithSignature(reader, isValidAdditionalInfoSignature);
863
947
  const key = readSignature(reader);
864
948
  // `largeAdditionalInfoKeys` fallback, because some keys don't have 8B64 signature even when they are 64bit
865
- const u64 = sig === '8B64' || (reader.large && helpers_1.largeAdditionalInfoKeys.indexOf(key) !== -1);
949
+ let u64 = sig === '8B64' || (reader.large && helpers_1.largeAdditionalInfoKeys.indexOf(key) !== -1);
950
+ // these keys were written by older versions of this library with 32bit length and 8BIM signature,
951
+ // 64bit length always has high 32bits set to 0 (lengths over 4GB are not supported)
952
+ if (reader.large && sig === '8BIM' && helpers_1.largeAdditionalInfoKeysObserved.indexOf(key) !== -1) {
953
+ // 32bit length of 0 is followed by signature of the next block, 64bit length would continue with its low 32bits
954
+ const next = reader.offset + 8 <= reader.view.byteLength ? reader.view.getUint32(reader.offset + 4) : 0;
955
+ const nextIsSignature = next === 0x3842494d /* 8BIM */ || next === 0x38423634 /* 8B64 */;
956
+ u64 = reader.view.getUint32(reader.offset) === 0 && !nextIsSignature;
957
+ }
866
958
  readSection(reader, 2, left => {
867
959
  const handler = additionalInfo_1.infoHandlersMap[key];
868
960
  if (handler) {
@@ -875,7 +967,7 @@ function readAdditionalLayerInfo(reader, target, psd, imageResources) {
875
967
  }
876
968
  }
877
969
  else {
878
- reader.logMissingFeatures && reader.log(`Unhandled additional info: ${key}`);
970
+ reader.logMissingFeatures && key !== 'GenI' && key !== 'OCIO' && key !== 'CAI ' && reader.log(`Unhandled additional info: ${key}`);
879
971
  skipBytes(reader, left());
880
972
  }
881
973
  if (left()) {
@@ -907,7 +999,7 @@ function createImageDataBitDepth(width, height, bitDepth, channels, memoryLimit)
907
999
  }
908
1000
  }
909
1001
  function readImageData(reader, psd) {
910
- var _a;
1002
+ var _a, _b, _c;
911
1003
  const compression = readUint16(reader);
912
1004
  const bitsPerChannel = (_a = psd.bitsPerChannel) !== null && _a !== void 0 ? _a : 8;
913
1005
  const { width, height } = psd;
@@ -939,11 +1031,7 @@ function readImageData(reader, psd) {
939
1031
  case 1 /* ColorMode.Grayscale */: {
940
1032
  const colorChannels = psd.colorMode === 1 /* ColorMode.Grayscale */ ? 1 : 3;
941
1033
  const offsets = compositeChannelOffsets(reader, psd, colorChannels, 3);
942
- const start = reader.offset;
943
1034
  readCompositeChannels(reader, compression, imageData, bitsPerChannel, 4, offsets);
944
- if (helpers_1.RAW_IMAGE_DATA && compression === 1 /* Compression.RleCompressed */) {
945
- psd.imageDataRaw = new Uint8Array(reader.view.buffer, reader.view.byteOffset + start, reader.offset - start);
946
- }
947
1035
  if (psd.colorMode === 1 /* ColorMode.Grayscale */) {
948
1036
  setupGrayscale(imageData);
949
1037
  }
@@ -957,41 +1045,42 @@ function readImageData(reader, psd) {
957
1045
  const offsets = compositeChannelOffsets(reader, psd, 1, -1);
958
1046
  const indexedImageData = { width, height, data: new Uint8Array(width * height) };
959
1047
  readCompositeChannels(reader, compression, indexedImageData, 8, 1, offsets);
960
- indexedToRgb(indexedImageData, imageData, psd.palette);
1048
+ indexedToRgb(indexedImageData, imageData, psd.palette, (_c = (_b = psd.imageResources) === null || _b === void 0 ? void 0 : _b.transparencyIndex) !== null && _c !== void 0 ? _c : -1);
961
1049
  break;
962
1050
  }
963
1051
  case 4 /* ColorMode.CMYK */: {
964
- if (bitsPerChannel !== 8)
1052
+ if (bitsPerChannel !== 8 && bitsPerChannel !== 16)
965
1053
  throw new Error('bitsPerChannel not supported');
966
1054
  const offsets = compositeChannelOffsets(reader, psd, 4, 4);
967
- const cmykImageData = { width, height, data: new Uint8Array(width * height * 5) };
968
- const start = reader.offset;
969
- readCompositeChannels(reader, compression, cmykImageData, 8, 5, offsets);
970
- cmykToRgb(cmykImageData, imageData, true);
971
- if (helpers_1.RAW_IMAGE_DATA && compression === 1 /* Compression.RleCompressed */) {
972
- psd.imageDataRaw = new Uint8Array(reader.view.buffer, reader.view.byteOffset + start, reader.offset - start);
973
- }
1055
+ const cmykImageData = createImageDataBitDepth(width, height, bitsPerChannel, 5, reader.totalMemoryLimit);
1056
+ resetAlpha(cmykImageData, true);
1057
+ readCompositeChannels(reader, compression, cmykImageData, bitsPerChannel, 5, offsets);
1058
+ cmykToRgb(cmykImageData, imageData, !!reader.globalAlpha);
974
1059
  break;
975
1060
  }
976
1061
  default: throw new Error(`Color mode not supported: ${psd.colorMode}`);
977
1062
  }
978
- // remove weird white matte
979
- if (reader.globalAlpha) {
1063
+ // remove weird white matte (already removed when converting from CMYK)
1064
+ if (reader.globalAlpha && psd.colorMode !== 4 /* ColorMode.CMYK */) {
980
1065
  removeWhiteMatte(imageData);
981
1066
  }
982
1067
  return imageData;
983
1068
  }
984
1069
  // returns target offset for each of the composite image channels stored in the file, -1 for channels that are skipped
985
1070
  function compositeChannelOffsets(reader, psd, colorChannels, alphaOffset) {
986
- var _a, _b, _c, _d;
1071
+ var _a, _b, _c;
987
1072
  const channelCount = (_a = psd.channels) !== null && _a !== void 0 ? _a : (colorChannels + (reader.globalAlpha ? 1 : 0));
988
1073
  if (channelCount < colorChannels)
989
1074
  throw new Error(`Invalid channel count: ${channelCount}`);
990
1075
  // First extra channel is transparency if document has global alpha (negative layer count). Some non-Photoshop
991
1076
  // applications write transparency channel without setting global alpha, in that case extra channel is not
992
1077
  // listed in alpha channel names (Photoshop names all user alpha channels / saved selections).
993
- const namedChannels = (_d = (_c = (_b = psd.imageResources) === null || _b === void 0 ? void 0 : _b.alphaChannelNames) === null || _c === void 0 ? void 0 : _c.length) !== null && _d !== void 0 ? _d : 0;
994
- const hasTransparency = reader.globalAlpha || (channelCount - colorChannels) > namedChannels;
1078
+ // Photoshop also lists merged transparency as first alpha channel named "Transparency" (in documents marked with
1079
+ // Mtrn/Mt16/Mt32 additional info, or in some 16bit documents with layers stored in Lr16 without any other mark)
1080
+ const alphaNames = (_c = (_b = psd.imageResources) === null || _b === void 0 ? void 0 : _b.alphaChannelNames) !== null && _c !== void 0 ? _c : [];
1081
+ const namedChannels = alphaNames.length;
1082
+ const namedTransparency = !!reader.layersInGlobalInfo && alphaNames[0] === 'Transparency' && namedChannels === channelCount - colorChannels;
1083
+ const hasTransparency = reader.globalAlpha || namedTransparency || (channelCount - colorChannels) > namedChannels;
995
1084
  const offsets = [];
996
1085
  for (let i = 0; i < channelCount; i++) {
997
1086
  if (i < colorChannels) {
@@ -1070,41 +1159,54 @@ function removeWhiteMatte({ data, width, height }) {
1070
1159
  }
1071
1160
  }
1072
1161
  }
1073
- function cmykToRgb(cmyk, rgb, reverseAlpha) {
1162
+ // converts value in 0-255 range to target range, rounding and clamping it
1163
+ function toRange(value, max) {
1164
+ value = Math.round(value * (max / 255));
1165
+ return value <= 0 ? 0 : (value >= max ? max : value);
1166
+ }
1167
+ // converts CMYK data to RGB using approximation of U.S. Web Coated (SWOP) v2 profile (see cmykToRgbValues)
1168
+ // cmyk - 8 or 16 bit data with 5 channels (cmyka), rgb - 8 or 16 bit data with 4 channels (rgba)
1169
+ // removeMatte - colors are blended with white (no ink), used for composite image of documents with global alpha
1170
+ function cmykToRgb(cmyk, rgb, removeMatte) {
1074
1171
  const size = rgb.width * rgb.height * 4;
1075
1172
  const srcData = cmyk.data;
1076
1173
  const dstData = rgb.data;
1174
+ const srcMax = srcData instanceof Uint16Array ? 0xffff : 0xff;
1175
+ const dstMax = dstData instanceof Uint16Array ? 0xffff : 0xff;
1176
+ const out = [0, 0, 0];
1077
1177
  for (let src = 0, dst = 0; dst < size; src += 5, dst += 4) {
1078
- const c = srcData[src];
1079
- const m = srcData[src + 1];
1080
- const y = srcData[src + 2];
1081
- const k = srcData[src + 3];
1082
- dstData[dst] = ((((c * k) | 0) / 255) | 0);
1083
- dstData[dst + 1] = ((((m * k) | 0) / 255) | 0);
1084
- dstData[dst + 2] = ((((y * k) | 0) / 255) | 0);
1085
- dstData[dst + 3] = reverseAlpha ? 255 - srcData[src + 4] : srcData[src + 4];
1086
- }
1087
- // for (let src = 0, dst = 0; dst < size; src += 5, dst += 4) {
1088
- // const c = 1 - (srcData[src + 0] / 255);
1089
- // const m = 1 - (srcData[src + 1] / 255);
1090
- // const y = 1 - (srcData[src + 2] / 255);
1091
- // // const k = srcData[src + 3] / 255;
1092
- // dstData[dst + 0] = ((1 - c * 0.8) * 255) | 0;
1093
- // dstData[dst + 1] = ((1 - m * 0.8) * 255) | 0;
1094
- // dstData[dst + 2] = ((1 - y * 0.8) * 255) | 0;
1095
- // dstData[dst + 3] = reverseAlpha ? 255 - srcData[src + 4] : srcData[src + 4];
1096
- // }
1097
- }
1098
- function indexedToRgb(indexed, rgb, palette) {
1178
+ const a = srcData[src + 4];
1179
+ // image data stores inverted values (0 = 100% ink)
1180
+ let c = 1 - srcData[src] / srcMax;
1181
+ let m = 1 - srcData[src + 1] / srcMax;
1182
+ let y = 1 - srcData[src + 2] / srcMax;
1183
+ let k = 1 - srcData[src + 3] / srcMax;
1184
+ if (removeMatte && a > 0 && a < srcMax) {
1185
+ const ra = srcMax / a;
1186
+ c = Math.min(1, c * ra);
1187
+ m = Math.min(1, m * ra);
1188
+ y = Math.min(1, y * ra);
1189
+ k = Math.min(1, k * ra);
1190
+ }
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);
1195
+ dstData[dst + 3] = srcMax === dstMax ? a : Math.round(a * dstMax / srcMax);
1196
+ }
1197
+ }
1198
+ // transparencyIndex - palette index of transparent color (-1 if there's none)
1199
+ function indexedToRgb(indexed, rgb, palette, transparencyIndex) {
1099
1200
  const size = indexed.width * indexed.height;
1100
1201
  const srcData = indexed.data;
1101
1202
  const dstData = rgb.data;
1102
1203
  for (let src = 0, dst = 0; src < size; src++, dst += 4) {
1103
- const c = palette[srcData[src]];
1204
+ const index = srcData[src];
1205
+ const c = palette[index];
1104
1206
  dstData[dst + 0] = c.r;
1105
1207
  dstData[dst + 1] = c.g;
1106
1208
  dstData[dst + 2] = c.b;
1107
- dstData[dst + 3] = 255;
1209
+ dstData[dst + 3] = index === transparencyIndex ? 0 : 255;
1108
1210
  }
1109
1211
  }
1110
1212
  // number of bytes used to store single channel value, 1-bit data is handled as bytes
@@ -1256,8 +1358,8 @@ function readDataZip(compressed, pixelData, width, height, bitDepth, step, offse
1256
1358
  recoverMemory(reader, expectedSize);
1257
1359
  }
1258
1360
  }
1259
- // decodes single PackBits row, returns number of decoded values
1260
- 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) {
1261
1363
  let x = 0;
1262
1364
  while (i < end && x < count) {
1263
1365
  const header = src[i++];
@@ -1268,10 +1370,8 @@ function decodeRLERow(src, i, end, dst, p, step, count) {
1268
1370
  let n = 257 - header;
1269
1371
  if (n > count - x)
1270
1372
  n = count - x;
1373
+ dst.fill(value, x, x + n);
1271
1374
  x += n;
1272
- for (; n > 0; n--, p += step) {
1273
- dst[p] = value;
1274
- }
1275
1375
  }
1276
1376
  else if (header < 128) {
1277
1377
  let n = header + 1;
@@ -1279,9 +1379,8 @@ function decodeRLERow(src, i, end, dst, p, step, count) {
1279
1379
  n = count - x;
1280
1380
  if (n > end - i)
1281
1381
  n = end - i;
1282
- x += n;
1283
- for (; n > 0; n--, p += step) {
1284
- dst[p] = src[i++];
1382
+ for (; n > 0; n--) {
1383
+ dst[x++] = src[i++];
1285
1384
  }
1286
1385
  }
1287
1386
  else {
@@ -1290,6 +1389,47 @@ function decodeRLERow(src, i, end, dst, p, step, count) {
1290
1389
  }
1291
1390
  return x;
1292
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
+ }
1293
1433
  // converts big-endian 16/32-bit values from bytes and writes them to `data` starting at `p` with `step`
1294
1434
  function scatterValues(bytes, count, bpv, data, p, step) {
1295
1435
  if (bpv === 2) {
@@ -1304,8 +1444,8 @@ function scatterValues(bytes, count, bpv, data, p, step) {
1304
1444
  }
1305
1445
  }
1306
1446
  }
1307
- // decodes RLE rows of single channel, each row starts at the beginning of the image row
1308
- 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) {
1309
1449
  const srcEnd = src.length;
1310
1450
  const rowSize = width * bpv;
1311
1451
  for (let y = 0; y < height; y++, li++) {
@@ -1314,25 +1454,24 @@ function decodeRLEChannel(src, lengths, li, pos, width, height, data, offset, st
1314
1454
  const rowEnd = pos < srcEnd ? pos : srcEnd;
1315
1455
  if (rowStart >= rowEnd)
1316
1456
  continue;
1317
- if (bpv === 1) {
1318
- decodeRLERow(src, rowStart, rowEnd, data, offset + y * width * step, step, width);
1319
- }
1320
- else {
1321
- const decoded = decodeRLERow(src, rowStart, rowEnd, row, 0, 1, rowSize);
1322
- scatterValues(row, Math.floor(decoded / bpv), bpv, data, offset + y * width * step, step);
1323
- }
1457
+ const decoded = decodeRLERow(src, rowStart, rowEnd, row, rowSize);
1458
+ scatterValues(row, Math.floor(decoded / bpv), bpv, data, offset + y * width * step, step);
1324
1459
  }
1325
1460
  }
1326
1461
  function readDataRLE(reader, pixelData, width, height, bitDepth, step, offsets, large) {
1327
1462
  const data = pixelData && pixelData.data;
1328
1463
  const rows = offsets.length * height;
1329
1464
  const lengthSize = large ? 4 : 2;
1330
- 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);
1331
1470
  try {
1332
- if ((reader.offset + rows * lengthSize) > reader.view.byteLength) {
1471
+ if ((reader.offset + lengthsMemory) > srcEnd) {
1333
1472
  throw new Error('RLE row lengths exceed buffer length');
1334
1473
  }
1335
- const lengths = large ? new Uint32Array(rows) : new Uint16Array(rows);
1474
+ lengths = large ? new Uint32Array(rows) : new Uint16Array(rows);
1336
1475
  let total = 0;
1337
1476
  if (large) {
1338
1477
  for (let i = 0; i < rows; i++)
@@ -1342,32 +1481,37 @@ function readDataRLE(reader, pixelData, width, height, bitDepth, step, offsets,
1342
1481
  for (let i = 0; i < rows; i++)
1343
1482
  total += (lengths[i] = readUint16(reader));
1344
1483
  }
1345
- const start = reader.offset;
1346
- const srcEnd = reader.view.byteLength;
1347
- if (total > (srcEnd - start)) {
1484
+ if (total > (srcEnd - reader.offset)) {
1348
1485
  // broken or truncated file, decode only data that is available
1349
1486
  warnOrThrow(reader, 'RLE data exceeds buffer length');
1350
1487
  }
1351
- const bpv = bytesPerValue(bitDepth);
1352
1488
  if (data)
1353
1489
  verifyArrayType(data, bpv);
1354
- const src = new Uint8Array(reader.view.buffer, reader.view.byteOffset, srcEnd);
1355
- const row = (data && bpv > 1) ? new Uint8Array(width * bpv) : undefined;
1356
- let pos = start;
1357
- for (let c = 0, li = 0; c < offsets.length; c++, li += height) {
1358
- const offset = offsets[c] | 0;
1359
- const channelStart = pos;
1360
- for (let y = 0; y < height; y++)
1361
- pos += lengths[li + y];
1362
- if (data && offset >= 0 && offset < step) {
1363
- 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);
1364
1510
  }
1365
1511
  }
1366
- reader.offset = pos;
1367
- }
1368
- finally {
1369
- recoverMemory(reader, rows * lengthSize);
1370
1512
  }
1513
+ reader.offset = pos;
1514
+ recoverMemory(reader, lengthsMemory);
1371
1515
  }
1372
1516
  function readSection(reader, round, func, skipEmpty = true, eightBytes = false) {
1373
1517
  let length = readUint32(reader);
@@ -1415,11 +1559,11 @@ function readColor(reader) {
1415
1559
  skipBytes(reader, 2);
1416
1560
  return { h, s, b };
1417
1561
  }
1418
- case 2 /* ColorSpace.CMYK */: {
1419
- const c = readUint16(reader) / 257;
1420
- const m = readUint16(reader) / 257;
1421
- const y = readUint16(reader) / 257;
1422
- const k = readUint16(reader) / 257;
1562
+ case 2 /* ColorSpace.CMYK */: { // 0 = 100% ink
1563
+ const c = (0xffff - readUint16(reader)) / 257;
1564
+ const m = (0xffff - readUint16(reader)) / 257;
1565
+ const y = (0xffff - readUint16(reader)) / 257;
1566
+ const k = (0xffff - readUint16(reader)) / 257;
1423
1567
  return { c, m, y, k };
1424
1568
  }
1425
1569
  case 7 /* ColorSpace.Lab */: {
@@ -1440,6 +1584,24 @@ function readColor(reader) {
1440
1584
  throw new Error('Invalid color space');
1441
1585
  }
1442
1586
  }
1587
+ const MAX_PATTERN_SIZE = 30000;
1588
+ // reads pattern, unsupported or invalid patterns are skipped (returns undefined) unless throwForMissingFeatures is set
1589
+ function readPatternSafe(reader) {
1590
+ let length = reader.view.getUint32(reader.offset);
1591
+ while (length % 4)
1592
+ length++;
1593
+ const end = reader.offset + 4 + length;
1594
+ try {
1595
+ return readPattern(reader);
1596
+ }
1597
+ catch (e) {
1598
+ if (reader.throwForMissingFeatures)
1599
+ throw e;
1600
+ reader.logMissingFeatures && reader.log(`Skipped pattern: ${e.message}`);
1601
+ reader.offset = end;
1602
+ return undefined;
1603
+ }
1604
+ }
1443
1605
  function readPattern(reader) {
1444
1606
  let length = readUint32(reader);
1445
1607
  while (length % 4)
@@ -1451,8 +1613,8 @@ function readPattern(reader) {
1451
1613
  const colorMode = readUint32(reader);
1452
1614
  const x = readInt16(reader);
1453
1615
  const y = readInt16(reader);
1454
- // we only support RGB and grayscale for now
1455
- if (colorMode !== 3 /* ColorMode.RGB */ && colorMode !== 1 /* ColorMode.Grayscale */ && colorMode !== 2 /* ColorMode.Indexed */) {
1616
+ // we only support RGB, grayscale, indexed and CMYK for now
1617
+ if (colorMode !== 3 /* ColorMode.RGB */ && colorMode !== 1 /* ColorMode.Grayscale */ && colorMode !== 2 /* ColorMode.Indexed */ && colorMode !== 4 /* ColorMode.CMYK */) {
1456
1618
  throw new Error(`Unsupported pattern color mode: ${colorMode}`);
1457
1619
  }
1458
1620
  let name = readUnicodeString(reader);
@@ -1466,7 +1628,9 @@ function readPattern(reader) {
1466
1628
  b: readUint8(reader),
1467
1629
  });
1468
1630
  }
1469
- skipBytes(reader, 4); // no idea what this is
1631
+ // some writers follow the palette with 4 extra bytes (unknown)
1632
+ if (reader.view.getUint32(reader.offset) !== 3)
1633
+ skipBytes(reader, 4);
1470
1634
  }
1471
1635
  // virtual memory array list
1472
1636
  const version2 = readUint32(reader);
@@ -1480,17 +1644,31 @@ function readPattern(reader) {
1480
1644
  const channelsCount = readUint32(reader);
1481
1645
  const width = right - left;
1482
1646
  const height = bottom - top;
1483
- const size = width * height * 4;
1484
- consumeMemory(reader, size);
1485
- const data = new Uint8Array(size);
1486
- for (let i = 3; i < data.byteLength; i += 4) {
1487
- data[i] = 255;
1488
- }
1489
- for (let i = 0, ch = 0; i < (channelsCount + 2); i++) {
1647
+ if (width < 0 || height < 0)
1648
+ throw new Error('Invalid pattern bounds');
1649
+ if (width > MAX_PATTERN_SIZE || height > MAX_PATTERN_SIZE)
1650
+ throw new Error(`Pattern too large (${width}x${height})`);
1651
+ if (channelsCount > 64)
1652
+ throw new Error(`Invalid pattern channel count: ${channelsCount}`);
1653
+ // channels cover whole pattern, each 2 bytes of RLE data can produce at most 128 bytes
1654
+ if (width * height > (Math.min(end, reader.view.byteLength) - reader.offset) * 64)
1655
+ throw new Error('Pattern data out of bounds');
1656
+ // first written arrays are color channels, next one is transparency (Photoshop stores it in the last, sheet mask, array)
1657
+ const colorChannels = colorMode === 3 /* ColorMode.RGB */ ? 3 : (colorMode === 4 /* ColorMode.CMYK */ ? 4 : 1);
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
1663
+ for (let i = 0, ch = 0; i < (channelsCount + 2) && reader.offset + 4 <= end; i++) {
1490
1664
  const has = readUint32(reader);
1491
1665
  if (!has)
1492
1666
  continue;
1493
1667
  const length = readUint32(reader);
1668
+ if (!length)
1669
+ continue;
1670
+ if (length < 23)
1671
+ throw new Error('Invalid pattern channel length');
1494
1672
  const pixelDepth = readUint32(reader);
1495
1673
  const ctop = readUint32(reader);
1496
1674
  const cleft = readUint32(reader);
@@ -1498,97 +1676,127 @@ function readPattern(reader) {
1498
1676
  const cright = readUint32(reader);
1499
1677
  const pixelDepth2 = readUint16(reader);
1500
1678
  const compressionMode = readUint8(reader); // 0 - raw, 1 - rle
1501
- const dataLength = length - (4 + 16 + 2 + 1);
1502
- const cdata = readBytes(reader, dataLength);
1503
- if (pixelDepth !== 8 || pixelDepth2 !== 8) {
1504
- throw new Error('16bit pixel depth not supported for patterns');
1505
- }
1679
+ const cdata = readBytes(reader, length - 23);
1680
+ const depth = (pixelDepth2 === 8 || pixelDepth2 === 16 || pixelDepth2 === 32) ? pixelDepth2 : pixelDepth;
1681
+ if (depth !== 8 && depth !== 16 && depth !== 32)
1682
+ throw new Error(`Unsupported pattern pixel depth: ${depth}`);
1683
+ if (ctop < top || cleft < left || cbottom > bottom || cright > right || cbottom < ctop || cright < cleft) {
1684
+ throw new Error('Pattern channel bounds outside of pattern bounds');
1685
+ }
1686
+ const target = ch < colorChannels ? ch : (ch === colorChannels ? alphaTarget : -1);
1687
+ ch++;
1688
+ if (target === -1)
1689
+ continue;
1506
1690
  const w = cright - cleft;
1507
1691
  const h = cbottom - ctop;
1508
- const ox = cleft - left;
1509
- const oy = ctop - top;
1692
+ const rowBytes = w * (depth / 8);
1693
+ const channelSize = rowBytes * h;
1510
1694
  if (compressionMode === 0) {
1511
- if (colorMode === 3 /* ColorMode.RGB */ && ch < 3) {
1512
- for (let y = 0; y < h; y++) {
1513
- for (let x = 0; x < w; x++) {
1514
- const src = x + y * w;
1515
- const dst = (ox + x + (y + oy) * width) * 4;
1516
- data[dst + ch] = cdata[src];
1517
- }
1518
- }
1519
- }
1520
- else if (colorMode === 1 /* ColorMode.Grayscale */ && ch < 1) {
1521
- for (let y = 0; y < h; y++) {
1522
- for (let x = 0; x < w; x++) {
1523
- const src = x + y * w;
1524
- const dst = (ox + x + (y + oy) * width) * 4;
1525
- const value = cdata[src];
1526
- data[dst + 0] = value;
1527
- data[dst + 1] = value;
1528
- data[dst + 2] = value;
1529
- }
1530
- }
1531
- }
1532
- else if (colorMode === 2 /* ColorMode.Indexed */) {
1533
- for (let y = 0; y < h; y++) {
1534
- for (let x = 0; x < w; x++) {
1535
- const src = x + y * w;
1536
- const dst = (ox + x + (y + oy) * width) * 4;
1537
- const index = cdata[src];
1538
- const color = palette[index];
1539
- data[dst + 0] = color.r;
1540
- data[dst + 1] = color.g;
1541
- data[dst + 2] = color.b;
1542
- }
1543
- }
1544
- }
1545
- else {
1546
- if (reader.throwForMissingFeatures)
1547
- throw new Error('Invalid color pattern');
1548
- }
1695
+ if (cdata.byteLength < channelSize)
1696
+ throw new Error('Pattern channel data out of bounds');
1549
1697
  }
1550
1698
  else if (compressionMode === 1) {
1551
- consumeMemory(reader, w * h);
1552
- const pixelData = { data, width, height };
1553
- const tempData = { data: new Uint8Array(w * h), width: w, height: h };
1699
+ // each 2 bytes of RLE data can produce at most 128 bytes
1700
+ if (channelSize > cdata.byteLength * 64)
1701
+ throw new Error('Pattern channel data out of bounds');
1702
+ maxDecodeSize = Math.max(maxDecodeSize, channelSize);
1703
+ }
1704
+ else {
1705
+ throw new Error('Invalid pattern compression mode');
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);
1554
1725
  const cdataReader = createReader(cdata.buffer, cdata.byteOffset, cdata.byteLength);
1555
- if (colorMode === 3 /* ColorMode.RGB */ && ch < 3) {
1556
- readDataRLE(cdataReader, tempData, w, h, 8, 1, [0], false);
1557
- copyChannelToRGBA(tempData, pixelData, ox, oy, ch);
1558
- }
1559
- if (colorMode === 1 /* ColorMode.Grayscale */ && ch < 1) {
1560
- readDataRLE(cdataReader, tempData, w, h, 8, 1, [0], false);
1561
- copyChannelToRGBA(tempData, pixelData, ox, oy, 0);
1562
- setupGrayscale(pixelData);
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);
1730
+ const ox = cleft - left;
1731
+ const oy = ctop - top;
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;
1563
1744
  }
1564
- if (colorMode === 2 /* ColorMode.Indexed */) {
1565
- // TODO:
1566
- throw new Error('Indexed pattern color mode not implemented');
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];
1751
+ }
1567
1752
  }
1568
- recoverMemory(reader, w * h);
1569
1753
  }
1570
- else {
1571
- throw new Error('Invalid pattern compression mode');
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];
1758
+ data[dst + 0] = value;
1759
+ data[dst + 1] = value;
1760
+ data[dst + 2] = value;
1761
+ }
1762
+ }
1572
1763
  }
1573
- ch++;
1574
1764
  }
1765
+ if (cmyk) {
1766
+ cmykToRgb({ width, height, data: cmyk }, { width, height, data }, false);
1767
+ }
1768
+ consumeMemory(reader, size);
1575
1769
  reader.offset = end;
1576
1770
  return { id, name, x, y, bounds: { x: left, y: top, w: width, h: height }, data };
1577
1771
  }
1578
- function copyChannelToRGBA(srcData, dstData, ox, oy, offset) {
1579
- const w = srcData.width;
1580
- const h = srcData.height;
1581
- const width = dstData.width;
1582
- for (let y = 0; y < h; y++) {
1583
- for (let x = 0; x < w; x++) {
1584
- const src = x + y * w;
1585
- const dst = (ox + x + (y + oy) * width) * 4;
1586
- const value = srcData.data[src];
1587
- dstData.data[dst + offset] = value;
1772
+ // converts big-endian 16bit or 32bit (float) values to 8bit values
1773
+ function patternChannelTo8bit(bytes, count, depth) {
1774
+ const result = new Uint8Array(count);
1775
+ if (depth === 16) {
1776
+ for (let i = 0; i < count; i++) {
1777
+ result[i] = Math.round(((bytes[i * 2] << 8) | bytes[i * 2 + 1]) / 257);
1778
+ }
1779
+ }
1780
+ else {
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];
1588
1794
  }
1589
1795
  }
1590
1796
  }
1591
1797
  function consumeMemory(reader, size) {
1798
+ if (!(size >= 0))
1799
+ throw new Error('Invalid size');
1592
1800
  if (reader.totalMemoryLimit !== undefined) {
1593
1801
  if (reader.totalMemoryLimit < size)
1594
1802
  throw new Error('Exceeded memory limit');