ag-psd 32.0.0 → 33.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 (103) 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/932f0770 +0 -0
  11. package/.v8-cache/v24.7.0-x64-ef5a0af0/acc36e66 +0 -0
  12. package/.v8-cache/v24.7.0-x64-ef5a0af0/b3c2fab7 +0 -0
  13. package/.v8-cache/v24.7.0-x64-ef5a0af0/c314aece +0 -0
  14. package/.v8-cache/v24.7.0-x64-ef5a0af0/cfc49f4f +0 -0
  15. package/.v8-cache/v24.7.0-x64-ef5a0af0/cff9434a +0 -0
  16. package/.v8-cache/v24.7.0-x64-ef5a0af0/dc86f000 +0 -0
  17. package/.v8-cache/v24.7.0-x64-ef5a0af0/e03e2acd +0 -0
  18. package/.v8-cache/v24.7.0-x64-ef5a0af0/e6745c2b +0 -0
  19. package/.v8-cache/v24.7.0-x64-ef5a0af0/e6b99945 +0 -0
  20. package/.v8-cache/v24.7.0-x64-ef5a0af0/eeeebdfe +0 -0
  21. package/.v8-cache/v24.7.0-x64-ef5a0af0/f6fd8125 +0 -0
  22. package/CHANGELOG.md +78 -0
  23. package/LICENSE +5 -0
  24. package/README.md +3 -6
  25. package/README_PSD.md +242 -81
  26. package/dist/abr.d.ts +11 -4
  27. package/dist/abr.js +357 -230
  28. package/dist/abr.js.map +1 -1
  29. package/dist/additionalInfo.d.ts +54 -2
  30. package/dist/additionalInfo.js +487 -306
  31. package/dist/additionalInfo.js.map +1 -1
  32. package/dist/ase.d.ts +6 -1
  33. package/dist/ase.js +61 -61
  34. package/dist/ase.js.map +1 -1
  35. package/dist/bundle.js +1937 -1115
  36. package/dist/descriptor.d.ts +14 -8
  37. package/dist/descriptor.js +202 -186
  38. package/dist/descriptor.js.map +1 -1
  39. package/dist/helpers.d.ts +1 -1
  40. package/dist/helpers.js +4 -2
  41. package/dist/helpers.js.map +1 -1
  42. package/dist/imageResources.d.ts +15 -3
  43. package/dist/imageResources.js +503 -142
  44. package/dist/imageResources.js.map +1 -1
  45. package/dist/jpeg.js +8 -8
  46. package/dist/jpeg.js.map +1 -1
  47. package/dist/psd.d.ts +148 -17
  48. package/dist/psd.js.map +1 -1
  49. package/dist/psdReader.d.ts +10 -0
  50. package/dist/psdReader.js +278 -166
  51. package/dist/psdReader.js.map +1 -1
  52. package/dist/psdWriter.d.ts +1 -0
  53. package/dist/psdWriter.js +77 -62
  54. package/dist/psdWriter.js.map +1 -1
  55. package/dist/text.js +17 -6
  56. package/dist/text.js.map +1 -1
  57. package/dist/utf8.js +0 -3
  58. package/dist/utf8.js.map +1 -1
  59. package/dist-es/abr.d.ts +11 -4
  60. package/dist-es/abr.js +357 -230
  61. package/dist-es/abr.js.map +1 -1
  62. package/dist-es/additionalInfo.d.ts +54 -2
  63. package/dist-es/additionalInfo.js +487 -306
  64. package/dist-es/additionalInfo.js.map +1 -1
  65. package/dist-es/ase.d.ts +6 -1
  66. package/dist-es/ase.js +61 -61
  67. package/dist-es/ase.js.map +1 -1
  68. package/dist-es/descriptor.d.ts +14 -8
  69. package/dist-es/descriptor.js +202 -186
  70. package/dist-es/descriptor.js.map +1 -1
  71. package/dist-es/helpers.d.ts +1 -1
  72. package/dist-es/helpers.js +4 -2
  73. package/dist-es/helpers.js.map +1 -1
  74. package/dist-es/imageResources.d.ts +15 -3
  75. package/dist-es/imageResources.js +503 -142
  76. package/dist-es/imageResources.js.map +1 -1
  77. package/dist-es/jpeg.js +8 -8
  78. package/dist-es/jpeg.js.map +1 -1
  79. package/dist-es/psd.d.ts +148 -17
  80. package/dist-es/psd.js.map +1 -1
  81. package/dist-es/psdReader.d.ts +10 -0
  82. package/dist-es/psdReader.js +278 -166
  83. package/dist-es/psdReader.js.map +1 -1
  84. package/dist-es/psdWriter.d.ts +1 -0
  85. package/dist-es/psdWriter.js +77 -62
  86. package/dist-es/psdWriter.js.map +1 -1
  87. package/dist-es/text.js +17 -6
  88. package/dist-es/text.js.map +1 -1
  89. package/dist-es/utf8.js +0 -3
  90. package/dist-es/utf8.js.map +1 -1
  91. package/package.json +2 -2
  92. package/src/abr.ts +375 -235
  93. package/src/additionalInfo.ts +663 -420
  94. package/src/ase.ts +66 -61
  95. package/src/descriptor.ts +199 -195
  96. package/src/helpers.ts +4 -1
  97. package/src/imageResources.ts +632 -168
  98. package/src/jpeg.ts +8 -8
  99. package/src/psd.ts +156 -30
  100. package/src/psdReader.ts +273 -172
  101. package/src/psdWriter.ts +95 -58
  102. package/src/text.ts +17 -6
  103. package/src/utf8.ts +0 -4
package/src/psdReader.ts CHANGED
@@ -1,7 +1,8 @@
1
1
  import { Inflate } from 'pako';
2
2
  import { Psd, Layer, ColorMode, SectionDividerType, LayerAdditionalInfo, ReadOptions, LayerMaskData, Color, PatternInfo, GlobalLayerMaskInfo, RGB, PixelData, PixelArray, ChannelID, Compression } from './psd';
3
- import { resetImageData, offsetForChannel, decodeBitmap, createImageData, toBlendMode, LayerMaskFlags, MaskParams, ColorSpace, RAW_IMAGE_DATA, largeAdditionalInfoKeys, imageDataToCanvas, normalizeLabAB } from './helpers';
3
+ import { resetImageData, offsetForChannel, decodeBitmap, createImageData, toBlendMode, LayerMaskFlags, MaskParams, ColorSpace, largeAdditionalInfoKeys, largeAdditionalInfoKeysObserved, imageDataToCanvas, normalizeLabAB } from './helpers';
4
4
  import { infoHandlersMap } from './additionalInfo';
5
+ import { decodeString } from './utf8';
5
6
  import { InternalImageResources, resourceHandlersMap } from './imageResources';
6
7
 
7
8
  interface ChannelInfo {
@@ -9,7 +10,7 @@ interface ChannelInfo {
9
10
  length: number;
10
11
  }
11
12
 
12
- export const supportedColorModes = [ColorMode.Bitmap, ColorMode.Grayscale, ColorMode.RGB, ColorMode.Indexed];
13
+ export const supportedColorModes = [ColorMode.Bitmap, ColorMode.Grayscale, ColorMode.RGB, ColorMode.Indexed, ColorMode.CMYK];
13
14
  const colorModes = ['bitmap', 'grayscale', 'indexed', 'RGB', 'CMYK', '', '', 'multichannel', 'duotone', 'lab'];
14
15
 
15
16
  function setupGrayscale(data: PixelData) {
@@ -27,7 +28,44 @@ export interface PsdReader extends ReadOptions {
27
28
  view: DataView;
28
29
  large: boolean;
29
30
  globalAlpha: boolean;
31
+ layersInGlobalInfo?: boolean; // layers were read from Lr16/Lr32/Layr global additional info
30
32
  log(...args: any[]): void;
33
+ // text decoders, created lazily (null if not supported)
34
+ utf8Decoder?: TextDecoder | null;
35
+ utf8StrictDecoder?: TextDecoder | null;
36
+ gbkDecoder?: TextDecoder | null;
37
+ }
38
+
39
+ function createTextDecoder(label: string, options?: TextDecoderOptions) {
40
+ try {
41
+ return typeof TextDecoder !== 'undefined' ? new TextDecoder(label, options) : null;
42
+ } catch {
43
+ return null; // encoding not supported
44
+ }
45
+ }
46
+
47
+ // non-fatal UTF-8 decoder, malformed byte sequences are replaced with U+FFFD
48
+ export function getUtf8Decoder(reader: PsdReader) {
49
+ if (reader.utf8Decoder === undefined) reader.utf8Decoder = createTextDecoder('utf-8', { ignoreBOM: true });
50
+ return reader.utf8Decoder;
51
+ }
52
+
53
+ // UTF-8 decoder that throws on malformed byte sequences
54
+ export function getUtf8StrictDecoder(reader: PsdReader) {
55
+ if (reader.utf8StrictDecoder === undefined) reader.utf8StrictDecoder = createTextDecoder('utf-8', { ignoreBOM: true, fatal: true });
56
+ return reader.utf8StrictDecoder;
57
+ }
58
+
59
+ // GBK decoder that throws on malformed byte sequences
60
+ export function getGbkStrictDecoder(reader: PsdReader) {
61
+ if (reader.gbkDecoder === undefined) reader.gbkDecoder = createTextDecoder('gbk', { fatal: true });
62
+ return reader.gbkDecoder;
63
+ }
64
+
65
+ export function decodeUtf8(reader: PsdReader, buffer: Uint8Array) {
66
+ // native decoder is faster only for longer strings
67
+ const decoder = buffer.byteLength > 1000 ? getUtf8Decoder(reader) : null;
68
+ return decoder ? decoder.decode(buffer) : decodeString(buffer);
31
69
  }
32
70
 
33
71
  export function createReader(buffer: ArrayBufferLike, offset?: number, length?: number): PsdReader {
@@ -244,7 +282,8 @@ export function readPsd(reader: PsdReader, readOptions: ReadOptions = {}) {
244
282
  const maxSize = version === 1 ? 30000 : 300000;
245
283
 
246
284
  if (width > maxSize || height > maxSize) throw new Error(`Invalid size: ${width}x${height}`);
247
- if (channels < 1 || channels > 56) throw new Error(`Invalid channel count: ${channels}`);
285
+ // up to 56 channels plus transparency of composite image (Photoshop CS3 writes 57 channels for 53 alpha channels)
286
+ if (channels < 1 || channels > 57) throw new Error(`Invalid channel count: ${channels}`);
248
287
  if (![1, 8, 16, 32].includes(bitsPerChannel)) throw new Error(`Invalid bitsPerChannel: ${bitsPerChannel}`);
249
288
  if (supportedColorModes.indexOf(colorMode) === -1) throw new Error(`Color mode not supported: ${colorModes[colorMode] ?? colorMode}`);
250
289
 
@@ -253,6 +292,7 @@ export function readPsd(reader: PsdReader, readOptions: ReadOptions = {}) {
253
292
  Object.assign(reader, readOptions);
254
293
  reader.large = version === 2;
255
294
  reader.globalAlpha = false;
295
+ reader.layersInGlobalInfo = false;
256
296
  if (!('totalMemoryLimit' in reader)) { // setting totalMemoryLimit to undefined explicitly disables memory limit
257
297
  reader.totalMemoryLimit = 2 * 1024 * 1024 * 1024; // default 2GB memory limit
258
298
  }
@@ -280,12 +320,16 @@ export function readPsd(reader: PsdReader, readOptions: ReadOptions = {}) {
280
320
  // image resources
281
321
 
282
322
  const imageResources: InternalImageResources = {};
323
+ let unreadResourceBytes = 0;
283
324
 
284
325
  readSection(reader, 1, left => {
285
- while (left() > 0) {
286
- realignWithSignature(reader, isValidSignature);
326
+ // stop if there's not enough data left for resource header (some files have few trailing bytes in this section)
327
+ while (left() >= 12) {
328
+ const signature = realignWithSignature(reader, isValidSignature);
287
329
  const id = readUint16(reader);
288
- readPascalString(reader, 2); // name
330
+ const nameLength = readUint8(reader);
331
+ const name = readBytes(reader, nameLength);
332
+ skipBytes(reader, (nameLength + 1) % 2); // pad to even size
289
333
 
290
334
  readSection(reader, 2, left => {
291
335
  const handler = resourceHandlersMap[id];
@@ -293,20 +337,29 @@ export function readPsd(reader: PsdReader, readOptions: ReadOptions = {}) {
293
337
 
294
338
  if (handler && !skip) {
295
339
  try {
296
- handler.read(reader, imageResources, left);
340
+ handler.read(reader, imageResources, left, { signature, id, name, width, height });
297
341
  } catch (e) {
298
342
  if (reader.throwForMissingFeatures) throw e;
299
343
  skipBytes(reader, left());
300
344
  }
301
345
  } else {
302
- // options.logMissingFeatures && console.log(`Unhandled image resource: ${id} (${left()})`);
346
+ reader.logMissingFeatures && reader.log(`Unhandled image resource: ${id} (${left()})`);
303
347
  skipBytes(reader, left());
304
348
  }
305
349
  });
306
350
  }
351
+
352
+ unreadResourceBytes = left();
353
+ skipBytes(reader, left());
307
354
  });
308
355
 
309
- const { layersGroup, layerGroupsEnabledId, ...rest } = imageResources;
356
+ // some files have image resources section length few bytes larger than the data, layer and mask info section
357
+ // starts right after the last resource in that case
358
+ if (unreadResourceBytes && !sectionFits(reader, reader.offset) && sectionFits(reader, reader.offset - unreadResourceBytes)) {
359
+ reader.offset -= unreadResourceBytes;
360
+ }
361
+
362
+ const { layersGroup, layerGroupsEnabledId, savedPathsOrigination, ...rest } = imageResources;
310
363
 
311
364
  if (Object.keys(rest).length) {
312
365
  psd.imageResources = rest;
@@ -354,6 +407,7 @@ export function readPsd(reader: PsdReader, readOptions: ReadOptions = {}) {
354
407
  const rawCompositeData = new Uint8Array(reader.view.buffer, reader.view.byteOffset + rawOffset, reader.view.byteLength - rawOffset);
355
408
  psd.rawCompositeData = rawCompositeData;
356
409
  psd.rawCompositeDataInfo = { large: reader.large, globalAlpha: reader.globalAlpha };
410
+ if (reader.layersInGlobalInfo) psd.rawCompositeDataInfo.layersInGlobalInfo = true;
357
411
  } else {
358
412
  const imageData = readImageData(reader, psd);
359
413
  if (reader.useImageData) {
@@ -364,10 +418,6 @@ export function readPsd(reader: PsdReader, readOptions: ReadOptions = {}) {
364
418
  }
365
419
  }
366
420
 
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
-
371
421
  return psd;
372
422
  }
373
423
 
@@ -452,8 +502,11 @@ function readLayerRecord(reader: PsdReader, psd: Psd, imageResources: InternalIm
452
502
 
453
503
  checkSignature(reader, '8BIM');
454
504
  const blendMode = readSignature(reader);
455
- if (!toBlendMode[blendMode]) throw new Error(`Invalid blend mode: '${blendMode}'`);
456
- layer.blendMode = toBlendMode[blendMode];
505
+ if (!toBlendMode[blendMode]) {
506
+ if (reader.throwForMissingFeatures) throw new Error(`Invalid blend mode: '${blendMode}'`);
507
+ reader.logMissingFeatures && reader.log(`Unknown blend mode: '${blendMode}', using normal blend mode`);
508
+ }
509
+ layer.blendMode = toBlendMode[blendMode] || 'normal';
457
510
 
458
511
  layer.opacity = readUint8(reader) / 0xff;
459
512
  layer.clipping = readUint8(reader) === 1;
@@ -487,11 +540,20 @@ function readLayerRecord(reader: PsdReader, psd: Psd, imageResources: InternalIm
487
540
  return { layer, channels };
488
541
  }
489
542
 
543
+ // negative sizes are treated as empty (Photoshop writes them for some empty layers, like 0,0,-1,0 bounds of fill layer)
544
+ // checks if length of section (layer and mask info) at given offset fits in the file
545
+ function sectionFits(reader: PsdReader, offset: number) {
546
+ const lengthSize = reader.large ? 8 : 4;
547
+ if (offset < 0 || offset + lengthSize > reader.view.byteLength) return false;
548
+ const length = reader.large ? reader.view.getUint32(offset) * 0x100000000 + reader.view.getUint32(offset + 4) : reader.view.getUint32(offset);
549
+ return offset + lengthSize + length <= reader.view.byteLength;
550
+ }
551
+
490
552
  function isValidBoxSize(box: { top?: number; left?: number; bottom?: number; right?: number; }, reader: PsdReader) {
491
553
  const width = (box.right || 0) - (box.left || 0);
492
554
  const height = (box.bottom || 0) - (box.top || 0);
493
555
  const maxSize = reader.large ? 300000 : 30000;
494
- return width >= 0 && height >= 0 && width <= maxSize && height <= maxSize;
556
+ return width <= maxSize && height <= maxSize;
495
557
  }
496
558
 
497
559
  function readLayerMaskData(reader: PsdReader, layer: Layer) {
@@ -622,9 +684,9 @@ function readLayerChannelImageData(reader: PsdReader, psd: Psd, layer: Layer, ch
622
684
  compression = readUint16(reader) as Compression;
623
685
  }
624
686
 
625
- // try to fix broken files where there's 1 byte shift of channel
687
+ // try to fix broken files where there's 1 byte shift of channel (in the other direction)
626
688
  if (compression > 3) {
627
- reader.offset -= 3;
689
+ reader.offset -= 4;
628
690
  compression = readUint16(reader) as Compression;
629
691
  }
630
692
 
@@ -665,6 +727,7 @@ export function getCompositeImageData(psd: Psd) {
665
727
  if (info) {
666
728
  reader.large = info.large;
667
729
  reader.globalAlpha = info.globalAlpha;
730
+ reader.layersInGlobalInfo = !!info.layersInGlobalInfo;
668
731
  }
669
732
 
670
733
  return readImageData(reader, psd);
@@ -760,18 +823,13 @@ function getDataFromLayer(layer: Layer, read: LayerDataType, throwForMissingFeat
760
823
 
761
824
  if (layerWidth && layerHeight && read === LayerDataType.Layer) {
762
825
  if (cmyk) {
763
- if (bitsPerChannel !== 8) throw new Error('bitsPerChannel not supported');
764
- imageData = { width: layerWidth, height: layerHeight, data: new Uint8ClampedArray(layerWidth * layerHeight * 5) } as any as ImageData;
826
+ if (bitsPerChannel !== 8 && bitsPerChannel !== 16) throw new Error('bitsPerChannel not supported');
827
+ imageData = createImageDataBitDepth(layerWidth, layerHeight, bitsPerChannel, 5, memoryLimit);
765
828
  } else {
766
829
  imageData = createImageDataBitDepth(layerWidth, layerHeight, bitsPerChannel, 4, memoryLimit);
767
830
  }
768
831
  }
769
832
 
770
- if (RAW_IMAGE_DATA) { // TODO: use layer.rawData instead
771
- (layer as any).imageDataRaw = [];
772
- (layer as any).imageDataRawCompression = [];
773
- }
774
-
775
833
  for (const { id, compression, data } of channels) {
776
834
  if (!data) continue;
777
835
 
@@ -794,15 +852,6 @@ function getDataFromLayer(layer: Layer, read: LayerDataType, throwForMissingFeat
794
852
  maskData = createImageDataBitDepth(maskWidth, maskHeight, bitsPerChannel, 4, memoryLimit);
795
853
  if (memoryLimit !== undefined) dataReader.totalMemoryLimit = Math.max(0, dataReader.totalMemoryLimit! - maskData.data.byteLength);
796
854
  readData(dataReader, data.byteLength, maskData, compression, maskWidth, maskHeight, bitsPerChannel, 0, large, 4);
797
- if (RAW_IMAGE_DATA) { // TODO: use layer.rawData instead
798
- if (id === ChannelID.UserMask) {
799
- (layer as any).maskDataRawCompression = compression;
800
- (layer as any).maskDataRaw = data;
801
- } else {
802
- (layer as any).realMaskDataRawCompression = compression;
803
- (layer as any).realMaskDataRaw = data;
804
- }
805
- }
806
855
  setupGrayscale(maskData);
807
856
  resetAlpha(maskData, false);
808
857
  }
@@ -821,12 +870,6 @@ function getDataFromLayer(layer: Layer, read: LayerDataType, throwForMissingFeat
821
870
  }
822
871
 
823
872
  readData(dataReader, data.byteLength, targetData, compression, layerWidth, layerHeight, bitsPerChannel, offset, large, cmyk ? 5 : 4);
824
-
825
- if (RAW_IMAGE_DATA) { // TODO: use layer.rawData instead
826
- (layer as any).imageDataRawCompression[id] = compression;
827
- (layer as any).imageDataRaw[id] = data;
828
- }
829
-
830
873
  }
831
874
 
832
875
  if (id === ChannelID.Transparency) {
@@ -840,7 +883,8 @@ function getDataFromLayer(layer: Layer, read: LayerDataType, throwForMissingFeat
840
883
 
841
884
  if (cmyk) {
842
885
  const cmykData = imageData;
843
- imageData = createImageData(cmykData.width, cmykData.height);
886
+ const rgbMemoryLimit = memoryLimit === undefined ? undefined : Math.max(0, memoryLimit - cmykData.data.byteLength);
887
+ imageData = createImageDataBitDepth(cmykData.width, cmykData.height, bitsPerChannel, 4, rgbMemoryLimit);
844
888
  cmykToRgb(cmykData, imageData, false);
845
889
  }
846
890
  }
@@ -918,7 +962,16 @@ export function readAdditionalLayerInfo(reader: PsdReader, target: LayerAddition
918
962
  const key = readSignature(reader);
919
963
 
920
964
  // `largeAdditionalInfoKeys` fallback, because some keys don't have 8B64 signature even when they are 64bit
921
- const u64 = sig === '8B64' || (reader.large && largeAdditionalInfoKeys.indexOf(key) !== -1);
965
+ let u64 = sig === '8B64' || (reader.large && largeAdditionalInfoKeys.indexOf(key) !== -1);
966
+
967
+ // these keys were written by older versions of this library with 32bit length and 8BIM signature,
968
+ // 64bit length always has high 32bits set to 0 (lengths over 4GB are not supported)
969
+ if (reader.large && sig === '8BIM' && largeAdditionalInfoKeysObserved.indexOf(key) !== -1) {
970
+ // 32bit length of 0 is followed by signature of the next block, 64bit length would continue with its low 32bits
971
+ const next = reader.offset + 8 <= reader.view.byteLength ? reader.view.getUint32(reader.offset + 4) : 0;
972
+ const nextIsSignature = next === 0x3842494d /* 8BIM */ || next === 0x38423634 /* 8B64 */;
973
+ u64 = reader.view.getUint32(reader.offset) === 0 && !nextIsSignature;
974
+ }
922
975
 
923
976
  readSection(reader, 2, left => {
924
977
  const handler = infoHandlersMap[key];
@@ -930,7 +983,7 @@ export function readAdditionalLayerInfo(reader: PsdReader, target: LayerAddition
930
983
  if (reader.throwForMissingFeatures) throw e;
931
984
  }
932
985
  } else {
933
- reader.logMissingFeatures && reader.log(`Unhandled additional info: ${key}`);
986
+ reader.logMissingFeatures && key !== 'GenI' && key !== 'OCIO' && key !== 'CAI ' && reader.log(`Unhandled additional info: ${key}`);
934
987
  skipBytes(reader, left());
935
988
  }
936
989
 
@@ -996,14 +1049,8 @@ function readImageData(reader: PsdReader, psd: Psd) {
996
1049
  case ColorMode.Grayscale: {
997
1050
  const colorChannels = psd.colorMode === ColorMode.Grayscale ? 1 : 3;
998
1051
  const offsets = compositeChannelOffsets(reader, psd, colorChannels, 3);
999
- const start = reader.offset;
1000
-
1001
1052
  readCompositeChannels(reader, compression, imageData, bitsPerChannel, 4, offsets);
1002
1053
 
1003
- if (RAW_IMAGE_DATA && compression === Compression.RleCompressed) {
1004
- (psd as any).imageDataRaw = new Uint8Array(reader.view.buffer, reader.view.byteOffset + start, reader.offset - start);
1005
- }
1006
-
1007
1054
  if (psd.colorMode === ColorMode.Grayscale) {
1008
1055
  setupGrayscale(imageData);
1009
1056
  }
@@ -1016,29 +1063,25 @@ function readImageData(reader: PsdReader, psd: Psd) {
1016
1063
  const offsets = compositeChannelOffsets(reader, psd, 1, -1);
1017
1064
  const indexedImageData: PixelData = { width, height, data: new Uint8Array(width * height) };
1018
1065
  readCompositeChannels(reader, compression, indexedImageData, 8, 1, offsets);
1019
- indexedToRgb(indexedImageData, imageData, psd.palette);
1066
+ indexedToRgb(indexedImageData, imageData, psd.palette, psd.imageResources?.transparencyIndex ?? -1);
1020
1067
  break;
1021
1068
  }
1022
1069
  case ColorMode.CMYK: {
1023
- if (bitsPerChannel !== 8) throw new Error('bitsPerChannel not supported');
1070
+ if (bitsPerChannel !== 8 && bitsPerChannel !== 16) throw new Error('bitsPerChannel not supported');
1024
1071
 
1025
1072
  const offsets = compositeChannelOffsets(reader, psd, 4, 4);
1026
- const cmykImageData: PixelData = { width, height, data: new Uint8Array(width * height * 5) };
1027
- const start = reader.offset;
1028
-
1029
- readCompositeChannels(reader, compression, cmykImageData, 8, 5, offsets);
1030
- cmykToRgb(cmykImageData, imageData, true);
1073
+ const cmykImageData = createImageDataBitDepth(width, height, bitsPerChannel, 5, reader.totalMemoryLimit);
1031
1074
 
1032
- if (RAW_IMAGE_DATA && compression === Compression.RleCompressed) {
1033
- (psd as any).imageDataRaw = new Uint8Array(reader.view.buffer, reader.view.byteOffset + start, reader.offset - start);
1034
- }
1075
+ resetAlpha(cmykImageData, true);
1076
+ readCompositeChannels(reader, compression, cmykImageData, bitsPerChannel, 5, offsets);
1077
+ cmykToRgb(cmykImageData, imageData, !!reader.globalAlpha);
1035
1078
  break;
1036
1079
  }
1037
1080
  default: throw new Error(`Color mode not supported: ${psd.colorMode}`);
1038
1081
  }
1039
1082
 
1040
- // remove weird white matte
1041
- if (reader.globalAlpha) {
1083
+ // remove weird white matte (already removed when converting from CMYK)
1084
+ if (reader.globalAlpha && psd.colorMode !== ColorMode.CMYK) {
1042
1085
  removeWhiteMatte(imageData);
1043
1086
  }
1044
1087
 
@@ -1053,8 +1096,12 @@ function compositeChannelOffsets(reader: PsdReader, psd: Psd, colorChannels: num
1053
1096
  // First extra channel is transparency if document has global alpha (negative layer count). Some non-Photoshop
1054
1097
  // applications write transparency channel without setting global alpha, in that case extra channel is not
1055
1098
  // listed in alpha channel names (Photoshop names all user alpha channels / saved selections).
1056
- const namedChannels = psd.imageResources?.alphaChannelNames?.length ?? 0;
1057
- const hasTransparency = reader.globalAlpha || (channelCount - colorChannels) > namedChannels;
1099
+ // Photoshop also lists merged transparency as first alpha channel named "Transparency" (in documents marked with
1100
+ // Mtrn/Mt16/Mt32 additional info, or in some 16bit documents with layers stored in Lr16 without any other mark)
1101
+ const alphaNames = psd.imageResources?.alphaChannelNames ?? [];
1102
+ const namedChannels = alphaNames.length;
1103
+ const namedTransparency = !!reader.layersInGlobalInfo && alphaNames[0] === 'Transparency' && namedChannels === channelCount - colorChannels;
1104
+ const hasTransparency = reader.globalAlpha || namedTransparency || (channelCount - colorChannels) > namedChannels;
1058
1105
  const offsets: number[] = [];
1059
1106
 
1060
1107
  for (let i = 0; i < channelCount; i++) {
@@ -1138,45 +1185,74 @@ function removeWhiteMatte({ data, width, height }: PixelData) {
1138
1185
  }
1139
1186
  }
1140
1187
 
1141
- function cmykToRgb(cmyk: PixelData, rgb: PixelData, reverseAlpha: boolean) {
1188
+ // converts value in 0-255 range to target range, rounding and clamping it
1189
+ function toRange(value: number, max: number) {
1190
+ value = Math.round(value * (max / 255));
1191
+ return value <= 0 ? 0 : (value >= max ? max : value);
1192
+ }
1193
+
1194
+ // Polynomial approximation of U.S. Web Coated (SWOP) v2 CMYK to RGB conversion (default CMYK profile of Photoshop).
1195
+ // Coefficients are taken from pdf.js (DeviceCmykCS in src/core/colorspace.js, https://github.com/mozilla/pdf.js),
1196
+ // Copyright 2012 Mozilla Foundation, licensed under the Apache License, Version 2.0
1197
+ // (http://www.apache.org/licenses/LICENSE-2.0)
1198
+ // cmyk - 8 or 16 bit data with 5 channels (cmyka), rgb - 8 or 16 bit data with 4 channels (rgba)
1199
+ // removeMatte - colors are blended with white (no ink), used for composite image of documents with global alpha
1200
+ function cmykToRgb(cmyk: PixelData, rgb: PixelData, removeMatte: boolean) {
1142
1201
  const size = rgb.width * rgb.height * 4;
1143
1202
  const srcData = cmyk.data;
1144
1203
  const dstData = rgb.data;
1204
+ const srcMax = srcData instanceof Uint16Array ? 0xffff : 0xff;
1205
+ const dstMax = dstData instanceof Uint16Array ? 0xffff : 0xff;
1145
1206
 
1146
1207
  for (let src = 0, dst = 0; dst < size; src += 5, dst += 4) {
1147
- const c = srcData[src];
1148
- const m = srcData[src + 1];
1149
- const y = srcData[src + 2];
1150
- const k = srcData[src + 3];
1151
- dstData[dst] = ((((c * k) | 0) / 255) | 0);
1152
- dstData[dst + 1] = ((((m * k) | 0) / 255) | 0);
1153
- dstData[dst + 2] = ((((y * k) | 0) / 255) | 0);
1154
- dstData[dst + 3] = reverseAlpha ? 255 - srcData[src + 4] : srcData[src + 4];
1155
- }
1156
-
1157
- // for (let src = 0, dst = 0; dst < size; src += 5, dst += 4) {
1158
- // const c = 1 - (srcData[src + 0] / 255);
1159
- // const m = 1 - (srcData[src + 1] / 255);
1160
- // const y = 1 - (srcData[src + 2] / 255);
1161
- // // const k = srcData[src + 3] / 255;
1162
- // dstData[dst + 0] = ((1 - c * 0.8) * 255) | 0;
1163
- // dstData[dst + 1] = ((1 - m * 0.8) * 255) | 0;
1164
- // dstData[dst + 2] = ((1 - y * 0.8) * 255) | 0;
1165
- // dstData[dst + 3] = reverseAlpha ? 255 - srcData[src + 4] : srcData[src + 4];
1166
- // }
1167
- }
1168
-
1169
- function indexedToRgb(indexed: PixelData, rgb: PixelData, palette: RGB[]) {
1208
+ const a = srcData[src + 4];
1209
+
1210
+ // image data stores inverted values (0 = 100% ink)
1211
+ let c = 1 - srcData[src] / srcMax;
1212
+ let m = 1 - srcData[src + 1] / srcMax;
1213
+ let y = 1 - srcData[src + 2] / srcMax;
1214
+ let k = 1 - srcData[src + 3] / srcMax;
1215
+
1216
+ if (removeMatte && a > 0 && a < srcMax) {
1217
+ const ra = srcMax / a;
1218
+ c = Math.min(1, c * ra);
1219
+ m = Math.min(1, m * ra);
1220
+ y = Math.min(1, y * ra);
1221
+ k = Math.min(1, k * ra);
1222
+ }
1223
+
1224
+ dstData[dst] = toRange(255 +
1225
+ c * (-4.387332384609988 * c + 54.48615194189176 * m + 18.82290502165302 * y + 212.25662451639585 * k - 285.2331026137004) +
1226
+ m * (1.7149763477362134 * m - 5.6096736904047315 * y - 17.873870861415444 * k - 5.497006427196366) +
1227
+ y * (-2.5217340131683033 * y - 21.248923337353073 * k + 17.5119270841813) +
1228
+ k * (-21.86122147463605 * k - 189.48180835922747), dstMax);
1229
+ dstData[dst + 1] = toRange(255 +
1230
+ c * (8.841041422036149 * c + 60.118027045597366 * m + 6.871425592049007 * y + 31.159100130055922 * k - 79.2970844816548) +
1231
+ m * (-15.310361306967817 * m + 17.575251261109482 * y + 131.35250912493976 * k - 190.9453302588951) +
1232
+ y * (4.444339102852739 * y + 9.8632861493405 * k - 24.86741582555878) +
1233
+ k * (-20.737325471181034 * k - 187.80453709719578), dstMax);
1234
+ dstData[dst + 2] = toRange(255 +
1235
+ c * (0.8842522430003296 * c + 8.078677503112928 * m + 30.89978309703729 * y - 0.23883238689178934 * k - 14.183576799673286) +
1236
+ m * (10.49593273432072 * m + 63.02378494754052 * y + 50.606957656360734 * k - 112.23884253719248) +
1237
+ y * (0.03296041114873217 * y + 115.60384449646641 * k - 193.58209356861505) +
1238
+ k * (-22.33816807309886 * k - 180.12613974708367), dstMax);
1239
+ dstData[dst + 3] = srcMax === dstMax ? a : Math.round(a * dstMax / srcMax);
1240
+ }
1241
+ }
1242
+
1243
+ // transparencyIndex - palette index of transparent color (-1 if there's none)
1244
+ function indexedToRgb(indexed: PixelData, rgb: PixelData, palette: RGB[], transparencyIndex: number) {
1170
1245
  const size = indexed.width * indexed.height;
1171
1246
  const srcData = indexed.data;
1172
1247
  const dstData = rgb.data;
1173
1248
 
1174
1249
  for (let src = 0, dst = 0; src < size; src++, dst += 4) {
1175
- const c = palette[srcData[src]];
1250
+ const index = srcData[src];
1251
+ const c = palette[index];
1176
1252
  dstData[dst + 0] = c.r;
1177
1253
  dstData[dst + 1] = c.g;
1178
1254
  dstData[dst + 2] = c.b;
1179
- dstData[dst + 3] = 255;
1255
+ dstData[dst + 3] = index === transparencyIndex ? 0 : 255;
1180
1256
  }
1181
1257
  }
1182
1258
 
@@ -1520,11 +1596,11 @@ export function readColor(reader: PsdReader): Color {
1520
1596
  skipBytes(reader, 2);
1521
1597
  return { h, s, b };
1522
1598
  }
1523
- case ColorSpace.CMYK: {
1524
- const c = readUint16(reader) / 257;
1525
- const m = readUint16(reader) / 257;
1526
- const y = readUint16(reader) / 257;
1527
- const k = readUint16(reader) / 257;
1599
+ case ColorSpace.CMYK: { // 0 = 100% ink
1600
+ const c = (0xffff - readUint16(reader)) / 257;
1601
+ const m = (0xffff - readUint16(reader)) / 257;
1602
+ const y = (0xffff - readUint16(reader)) / 257;
1603
+ const k = (0xffff - readUint16(reader)) / 257;
1528
1604
  return { c, m, y, k };
1529
1605
  }
1530
1606
  case ColorSpace.Lab: {
@@ -1546,6 +1622,24 @@ export function readColor(reader: PsdReader): Color {
1546
1622
  }
1547
1623
  }
1548
1624
 
1625
+ const MAX_PATTERN_SIZE = 30000;
1626
+
1627
+ // reads pattern, unsupported or invalid patterns are skipped (returns undefined) unless throwForMissingFeatures is set
1628
+ export function readPatternSafe(reader: PsdReader): PatternInfo | undefined {
1629
+ let length = reader.view.getUint32(reader.offset);
1630
+ while (length % 4) length++;
1631
+ const end = reader.offset + 4 + length;
1632
+
1633
+ try {
1634
+ return readPattern(reader);
1635
+ } catch (e) {
1636
+ if (reader.throwForMissingFeatures) throw e;
1637
+ reader.logMissingFeatures && reader.log(`Skipped pattern: ${(e as Error).message}`);
1638
+ reader.offset = end;
1639
+ return undefined;
1640
+ }
1641
+ }
1642
+
1549
1643
  export function readPattern(reader: PsdReader): PatternInfo {
1550
1644
  let length = readUint32(reader);
1551
1645
  while (length % 4) length++;
@@ -1557,8 +1651,8 @@ export function readPattern(reader: PsdReader): PatternInfo {
1557
1651
  const x = readInt16(reader);
1558
1652
  const y = readInt16(reader);
1559
1653
 
1560
- // we only support RGB and grayscale for now
1561
- if (colorMode !== ColorMode.RGB && colorMode !== ColorMode.Grayscale && colorMode !== ColorMode.Indexed) {
1654
+ // we only support RGB, grayscale, indexed and CMYK for now
1655
+ if (colorMode !== ColorMode.RGB && colorMode !== ColorMode.Grayscale && colorMode !== ColorMode.Indexed && colorMode !== ColorMode.CMYK) {
1562
1656
  throw new Error(`Unsupported pattern color mode: ${colorMode}`);
1563
1657
  }
1564
1658
 
@@ -1575,7 +1669,8 @@ export function readPattern(reader: PsdReader): PatternInfo {
1575
1669
  })
1576
1670
  }
1577
1671
 
1578
- skipBytes(reader, 4); // no idea what this is
1672
+ // some writers follow the palette with 4 extra bytes (unknown)
1673
+ if (reader.view.getUint32(reader.offset) !== 3) skipBytes(reader, 4);
1579
1674
  }
1580
1675
 
1581
1676
  // virtual memory array list
@@ -1590,6 +1685,11 @@ export function readPattern(reader: PsdReader): PatternInfo {
1590
1685
  const channelsCount = readUint32(reader);
1591
1686
  const width = right - left;
1592
1687
  const height = bottom - top;
1688
+ if (width < 0 || height < 0) throw new Error('Invalid pattern bounds');
1689
+ if (width > MAX_PATTERN_SIZE || height > MAX_PATTERN_SIZE) throw new Error(`Pattern too large (${width}x${height})`);
1690
+ if (channelsCount > 64) throw new Error(`Invalid pattern channel count: ${channelsCount}`);
1691
+ // channels cover whole pattern, each 2 bytes of RLE data can produce at most 128 bytes
1692
+ if (width * height > (Math.min(end, reader.view.byteLength) - reader.offset) * 64) throw new Error('Pattern data out of bounds');
1593
1693
  const size = width * height * 4;
1594
1694
  consumeMemory(reader, size);
1595
1695
  const data = new Uint8Array(size);
@@ -1598,11 +1698,21 @@ export function readPattern(reader: PsdReader): PatternInfo {
1598
1698
  data[i] = 255;
1599
1699
  }
1600
1700
 
1601
- for (let i = 0, ch = 0; i < (channelsCount + 2); i++) {
1701
+ // first written arrays are color channels, next one is transparency (Photoshop stores it in the last, sheet mask, array)
1702
+ const colorChannels = colorMode === ColorMode.RGB ? 3 : (colorMode === ColorMode.CMYK ? 4 : 1);
1703
+ // CMYK channels are decoded into separate buffer (5 values per pixel, inverted, 0 = 100% ink) and converted to RGB at the end
1704
+ const cmyk = colorMode === ColorMode.CMYK ? new Uint8Array(width * height * 5).fill(255) : undefined;
1705
+ if (cmyk) consumeMemory(reader, cmyk.byteLength);
1706
+ const alphaTarget = cmyk ? 4 : 3;
1707
+
1708
+ for (let i = 0, ch = 0; i < (channelsCount + 2) && reader.offset + 4 <= end; i++) {
1602
1709
  const has = readUint32(reader);
1603
1710
  if (!has) continue;
1604
1711
 
1605
1712
  const length = readUint32(reader);
1713
+ if (!length) continue;
1714
+ if (length < 23) throw new Error('Invalid pattern channel length');
1715
+
1606
1716
  const pixelDepth = readUint32(reader);
1607
1717
  const ctop = readUint32(reader);
1608
1718
  const cleft = readUint32(reader);
@@ -1610,82 +1720,75 @@ export function readPattern(reader: PsdReader): PatternInfo {
1610
1720
  const cright = readUint32(reader);
1611
1721
  const pixelDepth2 = readUint16(reader);
1612
1722
  const compressionMode = readUint8(reader); // 0 - raw, 1 - rle
1613
- const dataLength = length - (4 + 16 + 2 + 1);
1614
- const cdata = readBytes(reader, dataLength);
1723
+ const cdata = readBytes(reader, length - 23);
1724
+ const depth = (pixelDepth2 === 8 || pixelDepth2 === 16 || pixelDepth2 === 32) ? pixelDepth2 : pixelDepth;
1725
+
1726
+ if (depth !== 8 && depth !== 16 && depth !== 32) throw new Error(`Unsupported pattern pixel depth: ${depth}`);
1615
1727
 
1616
- if (pixelDepth !== 8 || pixelDepth2 !== 8) {
1617
- throw new Error('16bit pixel depth not supported for patterns');
1728
+ if (ctop < top || cleft < left || cbottom > bottom || cright > right || cbottom < ctop || cright < cleft) {
1729
+ throw new Error('Pattern channel bounds outside of pattern bounds');
1618
1730
  }
1619
1731
 
1732
+ const target = ch < colorChannels ? ch : (ch === colorChannels ? alphaTarget : -1);
1733
+ ch++;
1734
+
1735
+ if (target === -1) continue;
1736
+
1620
1737
  const w = cright - cleft;
1621
1738
  const h = cbottom - ctop;
1622
- const ox = cleft - left;
1623
- const oy = ctop - top;
1739
+ const rowBytes = w * (depth / 8);
1740
+ const channelSize = rowBytes * h;
1741
+ let bytes: Uint8Array;
1624
1742
 
1625
1743
  if (compressionMode === 0) {
1626
- if (colorMode === ColorMode.RGB && ch < 3) {
1627
- for (let y = 0; y < h; y++) {
1628
- for (let x = 0; x < w; x++) {
1629
- const src = x + y * w;
1630
- const dst = (ox + x + (y + oy) * width) * 4;
1631
- data[dst + ch] = cdata[src];
1632
- }
1633
- }
1634
- } else if (colorMode === ColorMode.Grayscale && ch < 1) {
1635
- for (let y = 0; y < h; y++) {
1636
- for (let x = 0; x < w; x++) {
1637
- const src = x + y * w;
1638
- const dst = (ox + x + (y + oy) * width) * 4;
1639
- const value = cdata[src];
1640
- data[dst + 0] = value;
1641
- data[dst + 1] = value;
1642
- data[dst + 2] = value;
1643
- }
1644
- }
1645
- } else if (colorMode === ColorMode.Indexed) {
1646
- for (let y = 0; y < h; y++) {
1647
- for (let x = 0; x < w; x++) {
1648
- const src = x + y * w;
1649
- const dst = (ox + x + (y + oy) * width) * 4;
1650
- const index = cdata[src];
1651
- const color = palette[index];
1652
- data[dst + 0] = color.r;
1653
- data[dst + 1] = color.g;
1654
- data[dst + 2] = color.b;
1655
- }
1656
- }
1657
- } else {
1658
- if (reader.throwForMissingFeatures) throw new Error('Invalid color pattern');
1659
- }
1744
+ if (cdata.byteLength < channelSize) throw new Error('Pattern channel data out of bounds');
1745
+ bytes = cdata;
1660
1746
  } else if (compressionMode === 1) {
1661
- consumeMemory(reader, w * h);
1747
+ // each 2 bytes of RLE data can produce at most 128 bytes
1748
+ if (channelSize > cdata.byteLength * 64) throw new Error('Pattern channel data out of bounds');
1662
1749
 
1663
- const pixelData: PixelData = { data, width, height };
1664
- const tempData: PixelData = { data: new Uint8Array(w * h), width: w, height: h };
1750
+ consumeMemory(reader, channelSize);
1751
+ bytes = new Uint8Array(channelSize);
1665
1752
  const cdataReader = createReader(cdata.buffer, cdata.byteOffset, cdata.byteLength);
1753
+ readDataRLE(cdataReader, { data: bytes, width: rowBytes, height: h }, rowBytes, h, 8, 1, [0], false);
1754
+ } else {
1755
+ throw new Error('Invalid pattern compression mode');
1756
+ }
1666
1757
 
1667
- if (colorMode === ColorMode.RGB && ch < 3) {
1668
- readDataRLE(cdataReader, tempData, w, h, 8, 1, [0], false);
1669
- copyChannelToRGBA(tempData, pixelData, ox, oy, ch);
1670
- }
1671
-
1672
- if (colorMode === ColorMode.Grayscale && ch < 1) {
1673
- readDataRLE(cdataReader, tempData, w, h, 8, 1, [0], false);
1674
- copyChannelToRGBA(tempData, pixelData, ox, oy, 0);
1675
- setupGrayscale(pixelData);
1676
- }
1758
+ const ox = cleft - left;
1759
+ const oy = ctop - top;
1677
1760
 
1678
- if (colorMode === ColorMode.Indexed) {
1679
- // TODO:
1680
- throw new Error('Indexed pattern color mode not implemented');
1761
+ for (let y = 0; y < h; y++) {
1762
+ for (let x = 0; x < w; x++) {
1763
+ const src = x + y * w;
1764
+ const dst = (ox + x + (y + oy) * width) * 4;
1765
+ const value = depth === 8 ? bytes[src] : patternValueTo8bit(bytes, src, depth);
1766
+
1767
+ if (cmyk) {
1768
+ cmyk[(ox + x + (y + oy) * width) * 5 + target] = value;
1769
+ } else if (target === 3) {
1770
+ data[dst + 3] = value;
1771
+ } else if (colorMode === ColorMode.Indexed) {
1772
+ const color = palette[value];
1773
+ data[dst + 0] = color.r;
1774
+ data[dst + 1] = color.g;
1775
+ data[dst + 2] = color.b;
1776
+ } else if (colorMode === ColorMode.Grayscale) {
1777
+ data[dst + 0] = value;
1778
+ data[dst + 1] = value;
1779
+ data[dst + 2] = value;
1780
+ } else {
1781
+ data[dst + target] = value;
1782
+ }
1681
1783
  }
1682
-
1683
- recoverMemory(reader, w * h);
1684
- } else {
1685
- throw new Error('Invalid pattern compression mode');
1686
1784
  }
1687
1785
 
1688
- ch++;
1786
+ if (compressionMode === 1) recoverMemory(reader, channelSize);
1787
+ }
1788
+
1789
+ if (cmyk) {
1790
+ cmykToRgb({ width, height, data: cmyk }, { width, height, data }, false);
1791
+ recoverMemory(reader, cmyk.byteLength);
1689
1792
  }
1690
1793
 
1691
1794
  reader.offset = end;
@@ -1693,22 +1796,20 @@ export function readPattern(reader: PsdReader): PatternInfo {
1693
1796
  return { id, name, x, y, bounds: { x: left, y: top, w: width, h: height }, data };
1694
1797
  }
1695
1798
 
1696
- function copyChannelToRGBA(srcData: PixelData, dstData: PixelData, ox: number, oy: number, offset: number) {
1697
- const w = srcData.width;
1698
- const h = srcData.height;
1699
- const width = dstData.width;
1700
-
1701
- for (let y = 0; y < h; y++) {
1702
- for (let x = 0; x < w; x++) {
1703
- const src = x + y * w;
1704
- const dst = (ox + x + (y + oy) * width) * 4;
1705
- const value = srcData.data[src];
1706
- dstData.data[dst + offset] = value;
1707
- }
1799
+ // converts big-endian 16bit or 32bit (float) value at given index to 8bit value
1800
+ function patternValueTo8bit(bytes: Uint8Array, index: number, depth: number) {
1801
+ if (depth === 16) {
1802
+ return Math.round(((bytes[index * 2] << 8) | bytes[index * 2 + 1]) / 257);
1803
+ } else {
1804
+ const view = new DataView(bytes.buffer, bytes.byteOffset + index * 4, 4);
1805
+ const value = view.getFloat32(0, false);
1806
+ return value > 0 ? (value < 1 ? Math.round(value * 255) : 255) : 0;
1708
1807
  }
1709
1808
  }
1710
1809
 
1711
- function consumeMemory(reader: PsdReader, size: number) {
1810
+ export function consumeMemory(reader: PsdReader, size: number) {
1811
+ if (!(size >= 0)) throw new Error('Invalid size');
1812
+
1712
1813
  if (reader.totalMemoryLimit !== undefined) {
1713
1814
  if (reader.totalMemoryLimit < size) throw new Error('Exceeded memory limit');
1714
1815
  reader.totalMemoryLimit -= size;