ag-psd 21.0.2 → 22.0.1

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
 
@@ -369,6 +377,11 @@ export function readLayerInfo(reader: PsdReader, psd: Psd, options: ReadOptionsE
369
377
  if (type === SectionDividerType.OpenFolder || type === SectionDividerType.ClosedFolder) {
370
378
  l.opened = type === SectionDividerType.OpenFolder;
371
379
  l.children = [];
380
+
381
+ if (l.sectionDivider?.key) {
382
+ l.blendMode = toBlendMode[l.sectionDivider.key] ?? l.blendMode;
383
+ }
384
+
372
385
  stack[stack.length - 1].children!.unshift(l);
373
386
  stack.push(l);
374
387
  } else if (type === SectionDividerType.BoundingSectionDivider) {
@@ -383,7 +396,7 @@ export function readLayerInfo(reader: PsdReader, psd: Psd, options: ReadOptionsE
383
396
  }
384
397
  }
385
398
 
386
- function readLayerRecord(reader: PsdReader, psd: Psd, options: ReadOptionsExt) {
399
+ function readLayerRecord(reader: PsdReader, psd: Psd) {
387
400
  const layer: Layer = {};
388
401
  layer.top = readInt32(reader);
389
402
  layer.left = readInt32(reader);
@@ -397,7 +410,7 @@ function readLayerRecord(reader: PsdReader, psd: Psd, options: ReadOptionsExt) {
397
410
  let id = readInt16(reader) as ChannelID;
398
411
  let length = readUint32(reader);
399
412
 
400
- if (options.large) {
413
+ if (reader.large) {
401
414
  if (length !== 0) throw new Error('Sizes larger than 4GB are not supported');
402
415
  length = readUint32(reader);
403
416
  }
@@ -425,21 +438,22 @@ function readLayerRecord(reader: PsdReader, psd: Psd, options: ReadOptionsExt) {
425
438
  skipBytes(reader, 1);
426
439
 
427
440
  readSection(reader, 1, left => {
428
- const mask = readLayerMaskData(reader, options);
441
+ const mask = readLayerMaskData(reader);
429
442
  if (mask) layer.mask = mask;
430
443
 
431
444
  /*const blendingRanges =*/ readLayerBlendingRanges(reader);
432
- layer.name = readPascalString(reader, 4);
445
+ layer.name = readPascalString(reader, 1); // should be padded to 4, but is not sometimes
433
446
 
434
- while (left() > 0) {
435
- readAdditionalLayerInfo(reader, layer, psd, options);
436
- }
447
+ // HACK: fix for sometimes layer.name string not being padded correctly, just skip until we get valid signature
448
+ while (left() > 4 && !validSignatureAt(reader, reader.offset)) reader.offset++;
449
+
450
+ while (left() > 4) readAdditionalLayerInfo(reader, layer, psd);
437
451
  });
438
452
 
439
453
  return { layer, channels };
440
454
  }
441
455
 
442
- function readLayerMaskData(reader: PsdReader, options: ReadOptions) {
456
+ function readLayerMaskData(reader: PsdReader) {
443
457
  return readSection<LayerMaskData | undefined>(reader, 1, left => {
444
458
  if (!left()) return undefined;
445
459
 
@@ -475,8 +489,8 @@ function readLayerMaskData(reader: PsdReader, options: ReadOptions) {
475
489
  // TEMP
476
490
  (mask as any)._real = { realFlags, realUserMaskBackground, top2, left2, bottom2, right2 };*/
477
491
 
478
- if (options.logMissingFeatures) {
479
- console.log('Unhandled extra real user mask params');
492
+ if (reader.logMissingFeatures) {
493
+ reader.log('Unhandled extra real user mask params');
480
494
  }
481
495
  }
482
496
 
@@ -501,7 +515,7 @@ function readLayerBlendingRanges(reader: PsdReader) {
501
515
  });
502
516
  }
503
517
 
504
- function readLayerChannelImageData(reader: PsdReader, psd: Psd, layer: Layer, channels: ChannelInfo[], options: ReadOptionsExt) {
518
+ function readLayerChannelImageData(reader: PsdReader, psd: Psd, layer: Layer, channels: ChannelInfo[]) {
505
519
  const layerWidth = (layer.right || 0) - (layer.left || 0);
506
520
  const layerHeight = (layer.bottom || 0) - (layer.top || 0);
507
521
  const cmyk = psd.colorMode === ColorMode.CMYK;
@@ -556,7 +570,7 @@ function readLayerChannelImageData(reader: PsdReader, psd: Psd, layer: Layer, ch
556
570
  resetImageData(maskData);
557
571
 
558
572
  const start = reader.offset;
559
- readData(reader, channel.length, maskData, compression, maskWidth, maskHeight, psd.bitsPerChannel ?? 8, 0, options.large, 4);
573
+ readData(reader, channel.length, maskData, compression, maskWidth, maskHeight, psd.bitsPerChannel ?? 8, 0, reader.large, 4);
560
574
 
561
575
  if (RAW_IMAGE_DATA) {
562
576
  (layer as any).maskDataRaw = new Uint8Array(reader.view.buffer, reader.view.byteOffset + start, reader.offset - start);
@@ -564,15 +578,15 @@ function readLayerChannelImageData(reader: PsdReader, psd: Psd, layer: Layer, ch
564
578
 
565
579
  setupGrayscale(maskData);
566
580
 
567
- if (options.useImageData) {
581
+ if (reader.useImageData) {
568
582
  mask.imageData = maskData;
569
583
  } else {
570
584
  mask.canvas = imageDataToCanvas(maskData);
571
585
  }
572
586
  }
573
587
  } else if (channel.id === ChannelID.RealUserMask) {
574
- if (options.logMissingFeatures) {
575
- console.log(`RealUserMask not supported`);
588
+ if (reader.logMissingFeatures) {
589
+ reader.log(`RealUserMask not supported`);
576
590
  }
577
591
 
578
592
  reader.offset = start + channel.length;
@@ -583,12 +597,12 @@ function readLayerChannelImageData(reader: PsdReader, psd: Psd, layer: Layer, ch
583
597
  if (offset < 0) {
584
598
  targetData = undefined;
585
599
 
586
- if (options.throwForMissingFeatures) {
600
+ if (reader.throwForMissingFeatures) {
587
601
  throw new Error(`Channel not supported: ${channel.id}`);
588
602
  }
589
603
  }
590
604
 
591
- readData(reader, channel.length, targetData, compression, layerWidth, layerHeight, psd.bitsPerChannel ?? 8, offset, options.large, cmyk ? 5 : 4);
605
+ readData(reader, channel.length, targetData, compression, layerWidth, layerHeight, psd.bitsPerChannel ?? 8, offset, reader.large, cmyk ? 5 : 4);
592
606
 
593
607
  if (RAW_IMAGE_DATA) {
594
608
  (layer as any).imageDataRaw[channel.id] = new Uint8Array(reader.view.buffer, reader.view.byteOffset + start + 2, channel.length - 2);
@@ -609,7 +623,7 @@ function readLayerChannelImageData(reader: PsdReader, psd: Psd, layer: Layer, ch
609
623
  cmykToRgb(cmykData, imageData, false);
610
624
  }
611
625
 
612
- if (options.useImageData) {
626
+ if (reader.useImageData) {
613
627
  layer.imageData = imageData;
614
628
  } else {
615
629
  layer.canvas = imageDataToCanvas(imageData);
@@ -647,30 +661,30 @@ export function readGlobalLayerMaskInfo(reader: PsdReader) {
647
661
  });
648
662
  }
649
663
 
650
- export function readAdditionalLayerInfo(reader: PsdReader, target: LayerAdditionalInfo, psd: Psd, options: ReadOptionsExt) {
664
+ export function readAdditionalLayerInfo(reader: PsdReader, target: LayerAdditionalInfo, psd: Psd) {
651
665
  const sig = readSignature(reader);
652
666
  if (sig !== '8BIM' && sig !== '8B64') throw new Error(`Invalid signature: '${sig}' at 0x${(reader.offset - 4).toString(16)}`);
653
667
  const key = readSignature(reader);
654
668
 
655
669
  // `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);
670
+ const u64 = sig === '8B64' || (reader.large && largeAdditionalInfoKeys.indexOf(key) !== -1);
657
671
 
658
672
  readSection(reader, 2, left => {
659
673
  const handler = infoHandlersMap[key];
660
674
 
661
675
  if (handler) {
662
676
  try {
663
- handler.read(reader, target, left, psd, options);
677
+ handler.read(reader, target, left, psd);
664
678
  } catch (e) {
665
- if (options.throwForMissingFeatures) throw e;
679
+ if (reader.throwForMissingFeatures) throw e;
666
680
  }
667
681
  } else {
668
- options.logMissingFeatures && console.log(`Unhandled additional info: ${key}`);
682
+ reader.logMissingFeatures && reader.log(`Unhandled additional info: ${key}`);
669
683
  skipBytes(reader, left());
670
684
  }
671
685
 
672
686
  if (left()) {
673
- options.logMissingFeatures && console.log(`Unread ${left()} bytes left for additional info: ${key}`);
687
+ reader.logMissingFeatures && reader.log(`Unread ${left()} bytes left for additional info: ${key}`);
674
688
  skipBytes(reader, left());
675
689
  }
676
690
  }, false, u64);
@@ -688,7 +702,7 @@ function createImageDataBitDepth(width: number, height: number, bitDepth: number
688
702
  }
689
703
  }
690
704
 
691
- function readImageData(reader: PsdReader, psd: Psd, options: ReadOptionsExt) {
705
+ function readImageData(reader: PsdReader, psd: Psd) {
692
706
  const compression = readUint16(reader) as Compression;
693
707
  const bitsPerChannel = psd.bitsPerChannel ?? 8;
694
708
 
@@ -711,7 +725,7 @@ function readImageData(reader: PsdReader, psd: Psd, options: ReadOptionsExt) {
711
725
  bytes = readBytes(reader, Math.ceil(psd.width / 8) * psd.height);
712
726
  } else if (compression === Compression.RleCompressed) {
713
727
  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);
728
+ readDataRLE(reader, { data: bytes, width: psd.width, height: psd.height }, psd.width, psd.height, 8, 1, [0], reader.large);
715
729
  } else {
716
730
  throw new Error(`Bitmap compression not supported: ${compression}`);
717
731
  }
@@ -728,7 +742,7 @@ function readImageData(reader: PsdReader, psd: Psd, options: ReadOptionsExt) {
728
742
  // TODO: store these channels in additional image data
729
743
  channels.push(i);
730
744
  }
731
- } else if (options.globalAlpha) {
745
+ } else if (reader.globalAlpha) {
732
746
  channels.push(3);
733
747
  }
734
748
 
@@ -738,7 +752,7 @@ function readImageData(reader: PsdReader, psd: Psd, options: ReadOptionsExt) {
738
752
  }
739
753
  } else if (compression === Compression.RleCompressed) {
740
754
  const start = reader.offset;
741
- readDataRLE(reader, imageData, psd.width, psd.height, bitsPerChannel, 4, channels, options.large);
755
+ readDataRLE(reader, imageData, psd.width, psd.height, bitsPerChannel, 4, channels, reader.large);
742
756
  if (RAW_IMAGE_DATA) (psd as any).imageDataRaw = new Uint8Array(reader.view.buffer, reader.view.byteOffset + start, reader.offset - start);
743
757
  }
744
758
 
@@ -752,7 +766,7 @@ function readImageData(reader: PsdReader, psd: Psd, options: ReadOptionsExt) {
752
766
  if (psd.channels !== 4) throw new Error(`Invalid channel count`);
753
767
 
754
768
  const channels = [0, 1, 2, 3];
755
- if (options.globalAlpha) channels.push(4);
769
+ if (reader.globalAlpha) channels.push(4);
756
770
 
757
771
  if (compression === Compression.RawData) {
758
772
  throw new Error(`Not implemented`);
@@ -768,7 +782,7 @@ function readImageData(reader: PsdReader, psd: Psd, options: ReadOptionsExt) {
768
782
  };
769
783
 
770
784
  const start = reader.offset;
771
- readDataRLE(reader, cmykImageData, psd.width, psd.height, psd.bitsPerChannel ?? 8, 5, channels, options.large);
785
+ readDataRLE(reader, cmykImageData, psd.width, psd.height, psd.bitsPerChannel ?? 8, 5, channels, reader.large);
772
786
  cmykToRgb(cmykImageData, imageData, true);
773
787
 
774
788
  if (RAW_IMAGE_DATA) (psd as any).imageDataRaw = new Uint8Array(reader.view.buffer, reader.view.byteOffset + start, reader.offset - start);
@@ -780,7 +794,7 @@ function readImageData(reader: PsdReader, psd: Psd, options: ReadOptionsExt) {
780
794
  }
781
795
 
782
796
  // remove weird white matte
783
- if (options.globalAlpha) {
797
+ if (reader.globalAlpha) {
784
798
  if (psd.bitsPerChannel !== 8) throw new Error('bitsPerChannel Not supproted');
785
799
  const p = imageData.data;
786
800
  const size = imageData.width * imageData.height * 4;
@@ -797,7 +811,7 @@ function readImageData(reader: PsdReader, psd: Psd, options: ReadOptionsExt) {
797
811
  }
798
812
  }
799
813
 
800
- if (options.useImageData) {
814
+ if (reader.useImageData) {
801
815
  psd.imageData = imageData;
802
816
  } else {
803
817
  psd.canvas = imageDataToCanvas(imageData);
@@ -1189,7 +1203,7 @@ export function readPattern(reader: PsdReader): PatternInfo {
1189
1203
  // console.log(data);
1190
1204
  // throw new Error('Zip compression not supported for pattern');
1191
1205
  // throw new Error('Unsupported pattern compression');
1192
- console.error('Unsupported pattern compression');
1206
+ reader.log('Unsupported pattern compression');
1193
1207
  name += ' (failed to decode)';
1194
1208
  } else {
1195
1209
  throw new Error('Invalid pattern compression mode');
package/src/psdWriter.ts CHANGED
@@ -486,12 +486,13 @@ function addChildren(layers: Layer[], children: Layer[] | undefined) {
486
486
  });
487
487
  addChildren(layers, c.children);
488
488
  layers.push({
489
+ ...c,
490
+ blendMode: c.blendMode === 'pass through' ? 'normal' : c.blendMode,
489
491
  sectionDivider: {
490
492
  type: c.opened === false ? SectionDividerType.ClosedFolder : SectionDividerType.OpenFolder,
491
493
  key: fromBlendMode[c.blendMode!] || 'pass',
492
494
  subType: 0,
493
495
  },
494
- ...c,
495
496
  });
496
497
  } else {
497
498
  layers.push({ ...c });