ag-psd 21.0.1 → 22.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.
package/src/psdReader.ts CHANGED
@@ -9,11 +9,6 @@ interface ChannelInfo {
9
9
  length: number;
10
10
  }
11
11
 
12
- export interface ReadOptionsExt extends ReadOptions {
13
- large: boolean;
14
- globalAlpha: boolean;
15
- }
16
-
17
12
  export const supportedColorModes = [ColorMode.Bitmap, ColorMode.Grayscale, ColorMode.RGB];
18
13
  const colorModes = ['bitmap', 'grayscale', 'indexed', 'RGB', 'CMYK', 'multichannel', 'duotone', 'lab'];
19
14
 
@@ -26,21 +21,22 @@ function setupGrayscale(data: PixelData) {
26
21
  }
27
22
  }
28
23
 
29
- export interface PsdReader {
24
+ export interface PsdReader extends ReadOptions {
30
25
  offset: number;
31
26
  view: DataView;
32
- strict: boolean;
33
- debug: boolean;
27
+ large: boolean;
28
+ globalAlpha: boolean;
29
+ log(...args: any[]): void;
34
30
  }
35
31
 
36
32
  export function createReader(buffer: ArrayBuffer, offset?: number, length?: number): PsdReader {
37
33
  const view = new DataView(buffer, offset, length);
38
- return { view, offset: 0, strict: false, debug: false };
34
+ return { view, offset: 0, strict: false, debug: false, large: false, globalAlpha: false, log: console.log };
39
35
  }
40
36
 
41
37
  export function warnOrThrow(reader: PsdReader, message: string) {
42
38
  if (reader.strict) throw new Error(message);
43
- if (reader.debug) console.warn(message);
39
+ if (reader.debug) reader.log(message);
44
40
  }
45
41
 
46
42
  export function readUint8(reader: PsdReader) {
@@ -123,6 +119,14 @@ export function readSignature(reader: PsdReader) {
123
119
  return readShortString(reader, 4);
124
120
  }
125
121
 
122
+ export function validSignatureAt(reader: PsdReader, offset: number) {
123
+ const sig = String.fromCharCode(reader.view.getUint8(offset))
124
+ + String.fromCharCode(reader.view.getUint8(offset + 1))
125
+ + String.fromCharCode(reader.view.getUint8(offset + 2))
126
+ + String.fromCharCode(reader.view.getUint8(offset + 3));
127
+ return sig == '8BIM' || sig == '8B64';
128
+ }
129
+
126
130
  export function readPascalString(reader: PsdReader, padTo: number) {
127
131
  let length = readUint8(reader);
128
132
  const text = length ? readShortString(reader, length) : '';
@@ -225,7 +229,11 @@ export function readPsd(reader: PsdReader, readOptions: ReadOptions = {}) {
225
229
  if (supportedColorModes.indexOf(colorMode) === -1) throw new Error(`Color mode not supported: ${colorModes[colorMode] ?? colorMode}`);
226
230
 
227
231
  const psd: Psd = { width, height, channels, bitsPerChannel, colorMode };
228
- const options: ReadOptionsExt = { ...readOptions, large: version === 2, globalAlpha: false };
232
+
233
+ Object.assign(reader, readOptions);
234
+ reader.large = version === 2;
235
+ reader.globalAlpha = false;
236
+
229
237
  const fixOffsets = [0, 1, -1, 2, -2, 3, -3, 4, -4];
230
238
 
231
239
  // color mode data
@@ -267,7 +275,7 @@ export function readPsd(reader: PsdReader, readOptions: ReadOptions = {}) {
267
275
 
268
276
  readSection(reader, 2, left => {
269
277
  const handler = resourceHandlersMap[id];
270
- const skip = id === 1036 && !!options.skipThumbnail;
278
+ const skip = id === 1036 && !!reader.skipThumbnail;
271
279
 
272
280
  if (!psd.imageResources) {
273
281
  psd.imageResources = {};
@@ -275,9 +283,9 @@ export function readPsd(reader: PsdReader, readOptions: ReadOptions = {}) {
275
283
 
276
284
  if (handler && !skip) {
277
285
  try {
278
- handler.read(reader, psd.imageResources, left, options);
286
+ handler.read(reader, psd.imageResources, left);
279
287
  } catch (e) {
280
- if (options.throwForMissingFeatures) throw e;
288
+ if (reader.throwForMissingFeatures) throw e;
281
289
  skipBytes(reader, left());
282
290
  }
283
291
  } else {
@@ -291,9 +299,9 @@ export function readPsd(reader: PsdReader, readOptions: ReadOptions = {}) {
291
299
  // layer and mask info
292
300
  readSection(reader, 1, left => {
293
301
  readSection(reader, 2, left => {
294
- readLayerInfo(reader, psd, options);
302
+ readLayerInfo(reader, psd);
295
303
  skipBytes(reader, left());
296
- }, undefined, options.large);
304
+ }, undefined, reader.large);
297
305
 
298
306
  // SAI does not include this section
299
307
  if (left() > 0) {
@@ -313,19 +321,19 @@ export function readPsd(reader: PsdReader, readOptions: ReadOptions = {}) {
313
321
  }
314
322
 
315
323
  if (left() >= 12) {
316
- readAdditionalLayerInfo(reader, psd, psd, options);
324
+ readAdditionalLayerInfo(reader, psd, psd,);
317
325
  } else {
318
326
  // opt.logMissingFeatures && console.log('skipping leftover bytes', left());
319
327
  skipBytes(reader, left());
320
328
  }
321
329
  }
322
- }, undefined, options.large);
330
+ }, undefined, reader.large);
323
331
 
324
332
  const hasChildren = psd.children && psd.children.length;
325
- const skipComposite = options.skipCompositeImageData && (options.skipLayerImageData || hasChildren);
333
+ const skipComposite = reader.skipCompositeImageData && (reader.skipLayerImageData || hasChildren);
326
334
 
327
335
  if (!skipComposite) {
328
- readImageData(reader, psd, options);
336
+ readImageData(reader, psd);
329
337
  }
330
338
 
331
339
  // TODO: show converted color mode instead of original PSD file color mode
@@ -335,11 +343,11 @@ export function readPsd(reader: PsdReader, readOptions: ReadOptions = {}) {
335
343
  return psd;
336
344
  }
337
345
 
338
- export function readLayerInfo(reader: PsdReader, psd: Psd, options: ReadOptionsExt) {
346
+ export function readLayerInfo(reader: PsdReader, psd: Psd) {
339
347
  let layerCount = readInt16(reader);
340
348
 
341
349
  if (layerCount < 0) {
342
- options.globalAlpha = true;
350
+ reader.globalAlpha = true;
343
351
  layerCount = -layerCount;
344
352
  }
345
353
 
@@ -347,14 +355,14 @@ export function readLayerInfo(reader: PsdReader, psd: Psd, options: ReadOptionsE
347
355
  const layerChannels: ChannelInfo[][] = [];
348
356
 
349
357
  for (let i = 0; i < layerCount; i++) {
350
- const { layer, channels } = readLayerRecord(reader, psd, options);
358
+ const { layer, channels } = readLayerRecord(reader, psd);
351
359
  layers.push(layer);
352
360
  layerChannels.push(channels);
353
361
  }
354
362
 
355
- if (!options.skipLayerImageData) {
363
+ if (!reader.skipLayerImageData) {
356
364
  for (let i = 0; i < layerCount; i++) {
357
- readLayerChannelImageData(reader, psd, layers[i], layerChannels[i], options);
365
+ readLayerChannelImageData(reader, psd, layers[i], layerChannels[i]);
358
366
  }
359
367
  }
360
368
 
@@ -383,7 +391,7 @@ export function readLayerInfo(reader: PsdReader, psd: Psd, options: ReadOptionsE
383
391
  }
384
392
  }
385
393
 
386
- function readLayerRecord(reader: PsdReader, psd: Psd, options: ReadOptionsExt) {
394
+ function readLayerRecord(reader: PsdReader, psd: Psd) {
387
395
  const layer: Layer = {};
388
396
  layer.top = readInt32(reader);
389
397
  layer.left = readInt32(reader);
@@ -397,7 +405,7 @@ function readLayerRecord(reader: PsdReader, psd: Psd, options: ReadOptionsExt) {
397
405
  let id = readInt16(reader) as ChannelID;
398
406
  let length = readUint32(reader);
399
407
 
400
- if (options.large) {
408
+ if (reader.large) {
401
409
  if (length !== 0) throw new Error('Sizes larger than 4GB are not supported');
402
410
  length = readUint32(reader);
403
411
  }
@@ -425,21 +433,22 @@ function readLayerRecord(reader: PsdReader, psd: Psd, options: ReadOptionsExt) {
425
433
  skipBytes(reader, 1);
426
434
 
427
435
  readSection(reader, 1, left => {
428
- const mask = readLayerMaskData(reader, options);
436
+ const mask = readLayerMaskData(reader);
429
437
  if (mask) layer.mask = mask;
430
438
 
431
439
  /*const blendingRanges =*/ readLayerBlendingRanges(reader);
432
- layer.name = readPascalString(reader, 4);
440
+ layer.name = readPascalString(reader, 1); // should be padded to 4, but is not sometimes
433
441
 
434
- while (left() > 0) {
435
- readAdditionalLayerInfo(reader, layer, psd, options);
436
- }
442
+ // HACK: fix for sometimes layer.name string not being padded correctly, just skip until we get valid signature
443
+ while (left() > 4 && !validSignatureAt(reader, reader.offset)) reader.offset++;
444
+
445
+ while (left() > 4) readAdditionalLayerInfo(reader, layer, psd);
437
446
  });
438
447
 
439
448
  return { layer, channels };
440
449
  }
441
450
 
442
- function readLayerMaskData(reader: PsdReader, options: ReadOptions) {
451
+ function readLayerMaskData(reader: PsdReader) {
443
452
  return readSection<LayerMaskData | undefined>(reader, 1, left => {
444
453
  if (!left()) return undefined;
445
454
 
@@ -475,8 +484,8 @@ function readLayerMaskData(reader: PsdReader, options: ReadOptions) {
475
484
  // TEMP
476
485
  (mask as any)._real = { realFlags, realUserMaskBackground, top2, left2, bottom2, right2 };*/
477
486
 
478
- if (options.logMissingFeatures) {
479
- console.log('Unhandled extra real user mask params');
487
+ if (reader.logMissingFeatures) {
488
+ reader.log('Unhandled extra real user mask params');
480
489
  }
481
490
  }
482
491
 
@@ -501,7 +510,7 @@ function readLayerBlendingRanges(reader: PsdReader) {
501
510
  });
502
511
  }
503
512
 
504
- function readLayerChannelImageData(reader: PsdReader, psd: Psd, layer: Layer, channels: ChannelInfo[], options: ReadOptionsExt) {
513
+ function readLayerChannelImageData(reader: PsdReader, psd: Psd, layer: Layer, channels: ChannelInfo[]) {
505
514
  const layerWidth = (layer.right || 0) - (layer.left || 0);
506
515
  const layerHeight = (layer.bottom || 0) - (layer.top || 0);
507
516
  const cmyk = psd.colorMode === ColorMode.CMYK;
@@ -556,7 +565,7 @@ function readLayerChannelImageData(reader: PsdReader, psd: Psd, layer: Layer, ch
556
565
  resetImageData(maskData);
557
566
 
558
567
  const start = reader.offset;
559
- readData(reader, channel.length, maskData, compression, maskWidth, maskHeight, psd.bitsPerChannel ?? 8, 0, options.large, 4);
568
+ readData(reader, channel.length, maskData, compression, maskWidth, maskHeight, psd.bitsPerChannel ?? 8, 0, reader.large, 4);
560
569
 
561
570
  if (RAW_IMAGE_DATA) {
562
571
  (layer as any).maskDataRaw = new Uint8Array(reader.view.buffer, reader.view.byteOffset + start, reader.offset - start);
@@ -564,15 +573,15 @@ function readLayerChannelImageData(reader: PsdReader, psd: Psd, layer: Layer, ch
564
573
 
565
574
  setupGrayscale(maskData);
566
575
 
567
- if (options.useImageData) {
576
+ if (reader.useImageData) {
568
577
  mask.imageData = maskData;
569
578
  } else {
570
579
  mask.canvas = imageDataToCanvas(maskData);
571
580
  }
572
581
  }
573
582
  } else if (channel.id === ChannelID.RealUserMask) {
574
- if (options.logMissingFeatures) {
575
- console.log(`RealUserMask not supported`);
583
+ if (reader.logMissingFeatures) {
584
+ reader.log(`RealUserMask not supported`);
576
585
  }
577
586
 
578
587
  reader.offset = start + channel.length;
@@ -583,12 +592,12 @@ function readLayerChannelImageData(reader: PsdReader, psd: Psd, layer: Layer, ch
583
592
  if (offset < 0) {
584
593
  targetData = undefined;
585
594
 
586
- if (options.throwForMissingFeatures) {
595
+ if (reader.throwForMissingFeatures) {
587
596
  throw new Error(`Channel not supported: ${channel.id}`);
588
597
  }
589
598
  }
590
599
 
591
- readData(reader, channel.length, targetData, compression, layerWidth, layerHeight, psd.bitsPerChannel ?? 8, offset, options.large, cmyk ? 5 : 4);
600
+ readData(reader, channel.length, targetData, compression, layerWidth, layerHeight, psd.bitsPerChannel ?? 8, offset, reader.large, cmyk ? 5 : 4);
592
601
 
593
602
  if (RAW_IMAGE_DATA) {
594
603
  (layer as any).imageDataRaw[channel.id] = new Uint8Array(reader.view.buffer, reader.view.byteOffset + start + 2, channel.length - 2);
@@ -609,7 +618,7 @@ function readLayerChannelImageData(reader: PsdReader, psd: Psd, layer: Layer, ch
609
618
  cmykToRgb(cmykData, imageData, false);
610
619
  }
611
620
 
612
- if (options.useImageData) {
621
+ if (reader.useImageData) {
613
622
  layer.imageData = imageData;
614
623
  } else {
615
624
  layer.canvas = imageDataToCanvas(imageData);
@@ -647,30 +656,30 @@ export function readGlobalLayerMaskInfo(reader: PsdReader) {
647
656
  });
648
657
  }
649
658
 
650
- export function readAdditionalLayerInfo(reader: PsdReader, target: LayerAdditionalInfo, psd: Psd, options: ReadOptionsExt) {
659
+ export function readAdditionalLayerInfo(reader: PsdReader, target: LayerAdditionalInfo, psd: Psd) {
651
660
  const sig = readSignature(reader);
652
661
  if (sig !== '8BIM' && sig !== '8B64') throw new Error(`Invalid signature: '${sig}' at 0x${(reader.offset - 4).toString(16)}`);
653
662
  const key = readSignature(reader);
654
663
 
655
664
  // `largeAdditionalInfoKeys` fallback, because some keys don't have 8B64 signature even when they are 64bit
656
- const u64 = sig === '8B64' || (options.large && largeAdditionalInfoKeys.indexOf(key) !== -1);
665
+ const u64 = sig === '8B64' || (reader.large && largeAdditionalInfoKeys.indexOf(key) !== -1);
657
666
 
658
667
  readSection(reader, 2, left => {
659
668
  const handler = infoHandlersMap[key];
660
669
 
661
670
  if (handler) {
662
671
  try {
663
- handler.read(reader, target, left, psd, options);
672
+ handler.read(reader, target, left, psd);
664
673
  } catch (e) {
665
- if (options.throwForMissingFeatures) throw e;
674
+ if (reader.throwForMissingFeatures) throw e;
666
675
  }
667
676
  } else {
668
- options.logMissingFeatures && console.log(`Unhandled additional info: ${key}`);
677
+ reader.logMissingFeatures && reader.log(`Unhandled additional info: ${key}`);
669
678
  skipBytes(reader, left());
670
679
  }
671
680
 
672
681
  if (left()) {
673
- options.logMissingFeatures && console.log(`Unread ${left()} bytes left for additional info: ${key}`);
682
+ reader.logMissingFeatures && reader.log(`Unread ${left()} bytes left for additional info: ${key}`);
674
683
  skipBytes(reader, left());
675
684
  }
676
685
  }, false, u64);
@@ -688,7 +697,7 @@ function createImageDataBitDepth(width: number, height: number, bitDepth: number
688
697
  }
689
698
  }
690
699
 
691
- function readImageData(reader: PsdReader, psd: Psd, options: ReadOptionsExt) {
700
+ function readImageData(reader: PsdReader, psd: Psd) {
692
701
  const compression = readUint16(reader) as Compression;
693
702
  const bitsPerChannel = psd.bitsPerChannel ?? 8;
694
703
 
@@ -711,7 +720,7 @@ function readImageData(reader: PsdReader, psd: Psd, options: ReadOptionsExt) {
711
720
  bytes = readBytes(reader, Math.ceil(psd.width / 8) * psd.height);
712
721
  } else if (compression === Compression.RleCompressed) {
713
722
  bytes = new Uint8Array(psd.width * psd.height);
714
- readDataRLE(reader, { data: bytes, width: psd.width, height: psd.height }, psd.width, psd.height, 8, 1, [0], options.large);
723
+ readDataRLE(reader, { data: bytes, width: psd.width, height: psd.height }, psd.width, psd.height, 8, 1, [0], reader.large);
715
724
  } else {
716
725
  throw new Error(`Bitmap compression not supported: ${compression}`);
717
726
  }
@@ -728,7 +737,7 @@ function readImageData(reader: PsdReader, psd: Psd, options: ReadOptionsExt) {
728
737
  // TODO: store these channels in additional image data
729
738
  channels.push(i);
730
739
  }
731
- } else if (options.globalAlpha) {
740
+ } else if (reader.globalAlpha) {
732
741
  channels.push(3);
733
742
  }
734
743
 
@@ -738,7 +747,7 @@ function readImageData(reader: PsdReader, psd: Psd, options: ReadOptionsExt) {
738
747
  }
739
748
  } else if (compression === Compression.RleCompressed) {
740
749
  const start = reader.offset;
741
- readDataRLE(reader, imageData, psd.width, psd.height, bitsPerChannel, 4, channels, options.large);
750
+ readDataRLE(reader, imageData, psd.width, psd.height, bitsPerChannel, 4, channels, reader.large);
742
751
  if (RAW_IMAGE_DATA) (psd as any).imageDataRaw = new Uint8Array(reader.view.buffer, reader.view.byteOffset + start, reader.offset - start);
743
752
  }
744
753
 
@@ -752,7 +761,7 @@ function readImageData(reader: PsdReader, psd: Psd, options: ReadOptionsExt) {
752
761
  if (psd.channels !== 4) throw new Error(`Invalid channel count`);
753
762
 
754
763
  const channels = [0, 1, 2, 3];
755
- if (options.globalAlpha) channels.push(4);
764
+ if (reader.globalAlpha) channels.push(4);
756
765
 
757
766
  if (compression === Compression.RawData) {
758
767
  throw new Error(`Not implemented`);
@@ -768,7 +777,7 @@ function readImageData(reader: PsdReader, psd: Psd, options: ReadOptionsExt) {
768
777
  };
769
778
 
770
779
  const start = reader.offset;
771
- readDataRLE(reader, cmykImageData, psd.width, psd.height, psd.bitsPerChannel ?? 8, 5, channels, options.large);
780
+ readDataRLE(reader, cmykImageData, psd.width, psd.height, psd.bitsPerChannel ?? 8, 5, channels, reader.large);
772
781
  cmykToRgb(cmykImageData, imageData, true);
773
782
 
774
783
  if (RAW_IMAGE_DATA) (psd as any).imageDataRaw = new Uint8Array(reader.view.buffer, reader.view.byteOffset + start, reader.offset - start);
@@ -780,7 +789,7 @@ function readImageData(reader: PsdReader, psd: Psd, options: ReadOptionsExt) {
780
789
  }
781
790
 
782
791
  // remove weird white matte
783
- if (options.globalAlpha) {
792
+ if (reader.globalAlpha) {
784
793
  if (psd.bitsPerChannel !== 8) throw new Error('bitsPerChannel Not supproted');
785
794
  const p = imageData.data;
786
795
  const size = imageData.width * imageData.height * 4;
@@ -797,7 +806,7 @@ function readImageData(reader: PsdReader, psd: Psd, options: ReadOptionsExt) {
797
806
  }
798
807
  }
799
808
 
800
- if (options.useImageData) {
809
+ if (reader.useImageData) {
801
810
  psd.imageData = imageData;
802
811
  } else {
803
812
  psd.canvas = imageDataToCanvas(imageData);
@@ -1189,7 +1198,7 @@ export function readPattern(reader: PsdReader): PatternInfo {
1189
1198
  // console.log(data);
1190
1199
  // throw new Error('Zip compression not supported for pattern');
1191
1200
  // throw new Error('Unsupported pattern compression');
1192
- console.error('Unsupported pattern compression');
1201
+ reader.log('Unsupported pattern compression');
1193
1202
  name += ' (failed to decode)';
1194
1203
  } else {
1195
1204
  throw new Error('Invalid pattern compression mode');