ag-psd 19.0.1 → 20.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 (53) hide show
  1. package/CHANGELOG.md +5 -0
  2. package/README_PSD.md +6 -0
  3. package/TODO +5 -0
  4. package/dist/abr.js +4 -4
  5. package/dist/abr.js.map +1 -1
  6. package/dist/additionalInfo.d.ts +3 -3
  7. package/dist/additionalInfo.js +99 -10
  8. package/dist/additionalInfo.js.map +1 -1
  9. package/dist/bundle.js +405 -142
  10. package/dist/descriptor.js +21 -16
  11. package/dist/descriptor.js.map +1 -1
  12. package/dist/helpers.d.ts +2 -7
  13. package/dist/helpers.js +33 -10
  14. package/dist/helpers.js.map +1 -1
  15. package/dist/imageResources.js +37 -0
  16. package/dist/imageResources.js.map +1 -1
  17. package/dist/psd.d.ts +46 -3
  18. package/dist/psd.js +8 -1
  19. package/dist/psd.js.map +1 -1
  20. package/dist/psdReader.d.ts +11 -5
  21. package/dist/psdReader.js +179 -97
  22. package/dist/psdReader.js.map +1 -1
  23. package/dist/psdWriter.js +24 -4
  24. package/dist/psdWriter.js.map +1 -1
  25. package/dist-es/abr.js +4 -4
  26. package/dist-es/abr.js.map +1 -1
  27. package/dist-es/additionalInfo.d.ts +3 -3
  28. package/dist-es/additionalInfo.js +102 -13
  29. package/dist-es/additionalInfo.js.map +1 -1
  30. package/dist-es/descriptor.js +21 -16
  31. package/dist-es/descriptor.js.map +1 -1
  32. package/dist-es/helpers.d.ts +2 -7
  33. package/dist-es/helpers.js +31 -9
  34. package/dist-es/helpers.js.map +1 -1
  35. package/dist-es/imageResources.js +37 -0
  36. package/dist-es/imageResources.js.map +1 -1
  37. package/dist-es/psd.d.ts +46 -3
  38. package/dist-es/psd.js +7 -0
  39. package/dist-es/psd.js.map +1 -1
  40. package/dist-es/psdReader.d.ts +11 -5
  41. package/dist-es/psdReader.js +178 -99
  42. package/dist-es/psdReader.js.map +1 -1
  43. package/dist-es/psdWriter.js +25 -5
  44. package/dist-es/psdWriter.js.map +1 -1
  45. package/package.json +1 -1
  46. package/src/abr.ts +4 -4
  47. package/src/additionalInfo.ts +142 -49
  48. package/src/descriptor.ts +14 -9
  49. package/src/helpers.ts +35 -18
  50. package/src/imageResources.ts +53 -0
  51. package/src/psd.ts +41 -5
  52. package/src/psdReader.ts +170 -126
  53. package/src/psdWriter.ts +33 -14
package/src/psdReader.ts CHANGED
@@ -1,12 +1,6 @@
1
1
  import { inflate } from 'pako';
2
- import {
3
- Psd, Layer, ColorMode, SectionDividerType, LayerAdditionalInfo, ReadOptions, LayerMaskData, Color,
4
- PatternInfo, GlobalLayerMaskInfo, RGB
5
- } from './psd';
6
- import {
7
- resetImageData, offsetForChannel, decodeBitmap, PixelData, createCanvas, createImageData,
8
- toBlendMode, ChannelID, Compression, LayerMaskFlags, MaskParams, ColorSpace, RAW_IMAGE_DATA, largeAdditionalInfoKeys
9
- } from './helpers';
2
+ import { Psd, Layer, ColorMode, SectionDividerType, LayerAdditionalInfo, ReadOptions, LayerMaskData, Color, PatternInfo, GlobalLayerMaskInfo, RGB, PixelData, PixelArray } from './psd';
3
+ import { resetImageData, offsetForChannel, decodeBitmap, createImageData, toBlendMode, ChannelID, Compression, LayerMaskFlags, MaskParams, ColorSpace, RAW_IMAGE_DATA, largeAdditionalInfoKeys, imageDataToCanvas } from './helpers';
10
4
  import { infoHandlersMap } from './additionalInfo';
11
5
  import { resourceHandlersMap } from './imageResources';
12
6
 
@@ -15,8 +9,9 @@ interface ChannelInfo {
15
9
  length: number;
16
10
  }
17
11
 
18
- interface ReadOptionsExt extends ReadOptions {
12
+ export interface ReadOptionsExt extends ReadOptions {
19
13
  large: boolean;
14
+ globalAlpha: boolean;
20
15
  }
21
16
 
22
17
  export const supportedColorModes = [ColorMode.Bitmap, ColorMode.Grayscale, ColorMode.RGB];
@@ -210,7 +205,7 @@ function isValidSignature(sig: string) {
210
205
  return sig === '8BIM' || sig === 'MeSa' || sig === 'AgHg' || sig === 'PHUT' || sig === 'DCSR';
211
206
  }
212
207
 
213
- export function readPsd(reader: PsdReader, options: ReadOptions = {}) {
208
+ export function readPsd(reader: PsdReader, readOptions: ReadOptions = {}) {
214
209
  // header
215
210
  checkSignature(reader, '8BPS');
216
211
  const version = readUint16(reader);
@@ -224,19 +219,18 @@ export function readPsd(reader: PsdReader, options: ReadOptions = {}) {
224
219
  const colorMode = readUint16(reader);
225
220
  const maxSize = version === 1 ? 30000 : 300000;
226
221
 
227
- if (width > maxSize || height > maxSize) throw new Error(`Invalid size`);
228
- if (channels > 16) throw new Error(`Invalid channel count`);
229
- if (bitsPerChannel > 32) throw new Error(`Invalid bitsPerChannel count`);
230
- if (supportedColorModes.indexOf(colorMode) === -1)
231
- throw new Error(`Color mode not supported: ${colorModes[colorMode] ?? colorMode}`);
222
+ if (width > maxSize || height > maxSize) throw new Error(`Invalid size: ${width}x${height}`);
223
+ if (channels > 16) throw new Error(`Invalid channel count: ${channels}`);
224
+ if (![1, 8, 16, 32].includes(bitsPerChannel)) throw new Error(`Invalid bitsPerChannel: ${bitsPerChannel}`);
225
+ if (supportedColorModes.indexOf(colorMode) === -1) throw new Error(`Color mode not supported: ${colorModes[colorMode] ?? colorMode}`);
232
226
 
233
227
  const psd: Psd = { width, height, channels, bitsPerChannel, colorMode };
234
- const opt: ReadOptionsExt = { ...options, large: version === 2 };
228
+ const options: ReadOptionsExt = { ...readOptions, large: version === 2, globalAlpha: false };
235
229
  const fixOffsets = [0, 1, -1, 2, -2, 3, -3, 4, -4];
236
230
 
237
231
  // color mode data
238
232
  readSection(reader, 1, left => {
239
- if (opt.throwForMissingFeatures) throw new Error('Color mode data not supported');
233
+ if (options.throwForMissingFeatures) throw new Error('Color mode data not supported');
240
234
  skipBytes(reader, left());
241
235
  });
242
236
 
@@ -264,7 +258,7 @@ export function readPsd(reader: PsdReader, options: ReadOptions = {}) {
264
258
 
265
259
  readSection(reader, 2, left => {
266
260
  const handler = resourceHandlersMap[id];
267
- const skip = id === 1036 && !!opt.skipThumbnail;
261
+ const skip = id === 1036 && !!options.skipThumbnail;
268
262
 
269
263
  if (!psd.imageResources) {
270
264
  psd.imageResources = {};
@@ -272,13 +266,13 @@ export function readPsd(reader: PsdReader, options: ReadOptions = {}) {
272
266
 
273
267
  if (handler && !skip) {
274
268
  try {
275
- handler.read(reader, psd.imageResources, left, opt);
269
+ handler.read(reader, psd.imageResources, left, options);
276
270
  } catch (e) {
277
- if (opt.throwForMissingFeatures) throw e;
271
+ if (options.throwForMissingFeatures) throw e;
278
272
  skipBytes(reader, left());
279
273
  }
280
274
  } else {
281
- // options.logMissingFeatures && console.log(`Unhandled image resource: ${id}`);
275
+ // options.logMissingFeatures && console.log(`Unhandled image resource: ${id} (${left()})`);
282
276
  skipBytes(reader, left());
283
277
  }
284
278
  });
@@ -286,10 +280,11 @@ export function readPsd(reader: PsdReader, options: ReadOptions = {}) {
286
280
  });
287
281
 
288
282
  // layer and mask info
289
- let globalAlpha = false;
290
-
291
283
  readSection(reader, 1, left => {
292
- globalAlpha = readLayerInfo(reader, psd, opt);
284
+ readSection(reader, 2, left => {
285
+ readLayerInfo(reader, psd, options);
286
+ skipBytes(reader, left());
287
+ }, undefined, options.large);
293
288
 
294
289
  // SAI does not include this section
295
290
  if (left() > 0) {
@@ -309,19 +304,19 @@ export function readPsd(reader: PsdReader, options: ReadOptions = {}) {
309
304
  }
310
305
 
311
306
  if (left() >= 12) {
312
- readAdditionalLayerInfo(reader, psd, psd, opt);
307
+ readAdditionalLayerInfo(reader, psd, psd, options);
313
308
  } else {
314
309
  // opt.logMissingFeatures && console.log('skipping leftover bytes', left());
315
310
  skipBytes(reader, left());
316
311
  }
317
312
  }
318
- }, undefined, opt.large);
313
+ }, undefined, options.large);
319
314
 
320
315
  const hasChildren = psd.children && psd.children.length;
321
- const skipComposite = opt.skipCompositeImageData && (opt.skipLayerImageData || hasChildren);
316
+ const skipComposite = options.skipCompositeImageData && (options.skipLayerImageData || hasChildren);
322
317
 
323
318
  if (!skipComposite) {
324
- readImageData(reader, psd, globalAlpha, opt);
319
+ readImageData(reader, psd, options);
325
320
  }
326
321
 
327
322
  // TODO: show converted color mode instead of original PSD file color mode
@@ -331,60 +326,52 @@ export function readPsd(reader: PsdReader, options: ReadOptions = {}) {
331
326
  return psd;
332
327
  }
333
328
 
334
- function readLayerInfo(reader: PsdReader, psd: Psd, options: ReadOptionsExt) {
335
- let globalAlpha = false;
329
+ export function readLayerInfo(reader: PsdReader, psd: Psd, options: ReadOptionsExt) {
330
+ let layerCount = readInt16(reader);
336
331
 
337
- readSection(reader, 2, left => {
338
- let layerCount = readInt16(reader);
332
+ if (layerCount < 0) {
333
+ options.globalAlpha = true;
334
+ layerCount = -layerCount;
335
+ }
339
336
 
340
- if (layerCount < 0) {
341
- globalAlpha = true;
342
- layerCount = -layerCount;
343
- }
337
+ const layers: Layer[] = [];
338
+ const layerChannels: ChannelInfo[][] = [];
344
339
 
345
- const layers: Layer[] = [];
346
- const layerChannels: ChannelInfo[][] = [];
340
+ for (let i = 0; i < layerCount; i++) {
341
+ const { layer, channels } = readLayerRecord(reader, psd, options);
342
+ layers.push(layer);
343
+ layerChannels.push(channels);
344
+ }
347
345
 
346
+ if (!options.skipLayerImageData) {
348
347
  for (let i = 0; i < layerCount; i++) {
349
- const { layer, channels } = readLayerRecord(reader, psd, options);
350
- layers.push(layer);
351
- layerChannels.push(channels);
348
+ readLayerChannelImageData(reader, psd, layers[i], layerChannels[i], options);
352
349
  }
350
+ }
353
351
 
354
- if (!options.skipLayerImageData) {
355
- for (let i = 0; i < layerCount; i++) {
356
- readLayerChannelImageData(reader, psd, layers[i], layerChannels[i], options);
357
- }
358
- }
359
-
360
- skipBytes(reader, left());
361
-
362
- if (!psd.children) psd.children = [];
363
-
364
- const stack: (Layer | Psd)[] = [psd];
365
-
366
- for (let i = layers.length - 1; i >= 0; i--) {
367
- const l = layers[i];
368
- const type = l.sectionDivider ? l.sectionDivider.type : SectionDividerType.Other;
369
-
370
- if (type === SectionDividerType.OpenFolder || type === SectionDividerType.ClosedFolder) {
371
- l.opened = type === SectionDividerType.OpenFolder;
372
- l.children = [];
373
- stack[stack.length - 1].children!.unshift(l);
374
- stack.push(l);
375
- } else if (type === SectionDividerType.BoundingSectionDivider) {
376
- stack.pop();
377
- // this was workaround because I didn't know what `lsdk` section was, now it's probably not needed anymore
378
- // } else if (l.name === '</Layer group>' && !l.sectionDivider && !l.top && !l.left && !l.bottom && !l.right) {
379
- // // sometimes layer group terminator doesn't have sectionDivider, so we just guess here (PS bug ?)
380
- // stack.pop();
381
- } else {
382
- stack[stack.length - 1].children!.unshift(l);
383
- }
352
+ if (!psd.children) psd.children = [];
353
+
354
+ const stack: (Layer | Psd)[] = [psd];
355
+
356
+ for (let i = layers.length - 1; i >= 0; i--) {
357
+ const l = layers[i];
358
+ const type = l.sectionDivider ? l.sectionDivider.type : SectionDividerType.Other;
359
+
360
+ if (type === SectionDividerType.OpenFolder || type === SectionDividerType.ClosedFolder) {
361
+ l.opened = type === SectionDividerType.OpenFolder;
362
+ l.children = [];
363
+ stack[stack.length - 1].children!.unshift(l);
364
+ stack.push(l);
365
+ } else if (type === SectionDividerType.BoundingSectionDivider) {
366
+ stack.pop();
367
+ // this was workaround because I didn't know what `lsdk` section was, now it's probably not needed anymore
368
+ // } else if (l.name === '</Layer group>' && !l.sectionDivider && !l.top && !l.left && !l.bottom && !l.right) {
369
+ // // sometimes layer group terminator doesn't have sectionDivider, so we just guess here (PS bug ?)
370
+ // stack.pop();
371
+ } else {
372
+ stack[stack.length - 1].children!.unshift(l);
384
373
  }
385
- }, undefined, options.large);
386
-
387
- return globalAlpha;
374
+ }
388
375
  }
389
376
 
390
377
  function readLayerRecord(reader: PsdReader, psd: Psd, options: ReadOptionsExt) {
@@ -499,21 +486,20 @@ function readLayerBlendingRanges(reader: PsdReader) {
499
486
  });
500
487
  }
501
488
 
502
- function readLayerChannelImageData(
503
- reader: PsdReader, psd: Psd, layer: Layer, channels: ChannelInfo[], options: ReadOptionsExt
504
- ) {
489
+ function readLayerChannelImageData(reader: PsdReader, psd: Psd, layer: Layer, channels: ChannelInfo[], options: ReadOptionsExt) {
505
490
  const layerWidth = (layer.right || 0) - (layer.left || 0);
506
491
  const layerHeight = (layer.bottom || 0) - (layer.top || 0);
507
492
  const cmyk = psd.colorMode === ColorMode.CMYK;
508
493
 
509
- let imageData: ImageData | undefined;
494
+ let imageData: PixelData | undefined;
510
495
 
511
496
  if (layerWidth && layerHeight) {
512
497
  if (cmyk) {
498
+ if (psd.bitsPerChannel !== 8) throw new Error('bitsPerChannel Not supproted');
513
499
  imageData = { width: layerWidth, height: layerHeight, data: new Uint8ClampedArray(layerWidth * layerHeight * 5) } as any as ImageData;
514
500
  for (let p = 4; p < imageData.data.byteLength; p += 5) imageData.data[p] = 255;
515
501
  } else {
516
- imageData = createImageData(layerWidth, layerHeight);
502
+ imageData = createImageDataBitDepth(layerWidth, layerHeight, psd.bitsPerChannel ?? 8);
517
503
  resetImageData(imageData);
518
504
  }
519
505
  }
@@ -551,11 +537,11 @@ function readLayerChannelImageData(
551
537
  const maskHeight = (mask.bottom || 0) - (mask.top || 0);
552
538
 
553
539
  if (maskWidth && maskHeight) {
554
- const maskData = createImageData(maskWidth, maskHeight);
540
+ const maskData = createImageDataBitDepth(maskWidth, maskHeight, psd.bitsPerChannel ?? 8);
555
541
  resetImageData(maskData);
556
542
 
557
543
  const start = reader.offset;
558
- readData(reader, channel.length, maskData, compression, maskWidth, maskHeight, 0, options.large, 4);
544
+ readData(reader, channel.length, maskData, compression, maskWidth, maskHeight, psd.bitsPerChannel ?? 8, 0, options.large, 4);
559
545
 
560
546
  if (RAW_IMAGE_DATA) {
561
547
  (layer as any).maskDataRaw = new Uint8Array(reader.view.buffer, reader.view.byteOffset + start, reader.offset - start);
@@ -566,8 +552,7 @@ function readLayerChannelImageData(
566
552
  if (options.useImageData) {
567
553
  mask.imageData = maskData;
568
554
  } else {
569
- mask.canvas = createCanvas(maskWidth, maskHeight);
570
- mask.canvas.getContext('2d')!.putImageData(maskData, 0, 0);
555
+ mask.canvas = imageDataToCanvas(maskData);
571
556
  }
572
557
  }
573
558
  } else {
@@ -582,7 +567,7 @@ function readLayerChannelImageData(
582
567
  }
583
568
  }
584
569
 
585
- readData(reader, channel.length, targetData, compression, layerWidth, layerHeight, offset, options.large, cmyk ? 5 : 4);
570
+ readData(reader, channel.length, targetData, compression, layerWidth, layerHeight, psd.bitsPerChannel ?? 8, offset, options.large, cmyk ? 5 : 4);
586
571
 
587
572
  if (RAW_IMAGE_DATA) {
588
573
  (layer as any).imageDataRaw[channel.id] = new Uint8Array(reader.view.buffer, reader.view.byteOffset + start + 2, channel.length - 2);
@@ -606,30 +591,26 @@ function readLayerChannelImageData(
606
591
  if (options.useImageData) {
607
592
  layer.imageData = imageData;
608
593
  } else {
609
- layer.canvas = createCanvas(layerWidth, layerHeight);
610
- layer.canvas.getContext('2d')!.putImageData(imageData, 0, 0);
594
+ layer.canvas = imageDataToCanvas(imageData);
611
595
  }
612
596
  }
613
597
  }
614
598
 
615
- function readData(
616
- reader: PsdReader, length: number, data: ImageData | undefined, compression: Compression, width: number, height: number,
617
- offset: number, large: boolean, step: number
618
- ) {
599
+ function readData(reader: PsdReader, length: number, data: PixelData | undefined, compression: Compression, width: number, height: number, bitDepth: number, offset: number, large: boolean, step: number) {
619
600
  if (compression === Compression.RawData) {
620
- readDataRaw(reader, data, width, height, step, offset);
601
+ readDataRaw(reader, data, width, height, bitDepth, step, offset);
621
602
  } else if (compression === Compression.RleCompressed) {
622
- readDataRLE(reader, data, width, height, step, [offset], large);
603
+ readDataRLE(reader, data, width, height, bitDepth, step, [offset], large);
623
604
  } else if (compression === Compression.ZipWithoutPrediction) {
624
- readDataZipWithoutPrediction(reader, length, data, width, height, step, offset);
605
+ readDataZip(reader, length, data, width, height, bitDepth, step, offset, false);
625
606
  } else if (compression === Compression.ZipWithPrediction) {
626
- throw new Error(`Compression type not supported: ${compression}`);
607
+ readDataZip(reader, length, data, width, height, bitDepth, step, offset, true);
627
608
  } else {
628
609
  throw new Error(`Invalid Compression type: ${compression}`);
629
610
  }
630
611
  }
631
612
 
632
- function readGlobalLayerMaskInfo(reader: PsdReader) {
613
+ export function readGlobalLayerMaskInfo(reader: PsdReader) {
633
614
  return readSection<GlobalLayerMaskInfo | undefined>(reader, 1, left => {
634
615
  if (!left()) return undefined;
635
616
 
@@ -645,7 +626,7 @@ function readGlobalLayerMaskInfo(reader: PsdReader) {
645
626
  });
646
627
  }
647
628
 
648
- function readAdditionalLayerInfo(reader: PsdReader, target: LayerAdditionalInfo, psd: Psd, options: ReadOptionsExt) {
629
+ export function readAdditionalLayerInfo(reader: PsdReader, target: LayerAdditionalInfo, psd: Psd, options: ReadOptionsExt) {
649
630
  const sig = readSignature(reader);
650
631
  if (sig !== '8BIM' && sig !== '8B64') throw new Error(`Invalid signature: '${sig}' at 0x${(reader.offset - 4).toString(16)}`);
651
632
  const key = readSignature(reader);
@@ -674,8 +655,21 @@ function readAdditionalLayerInfo(reader: PsdReader, target: LayerAdditionalInfo,
674
655
  }, false, u64);
675
656
  }
676
657
 
677
- function readImageData(reader: PsdReader, psd: Psd, globalAlpha: boolean, options: ReadOptionsExt) {
658
+ function createImageDataBitDepth(width: number, height: number, bitDepth: number): PixelData {
659
+ if (bitDepth === 1 || bitDepth === 8) {
660
+ return createImageData(width, height);
661
+ } else if (bitDepth === 16) {
662
+ return { width, height, data: new Uint16Array(width * height * 4) };
663
+ } else if (bitDepth === 32) {
664
+ return { width, height, data: new Uint32Array(width * height * 4) };
665
+ } else {
666
+ throw new Error(`Invalid bitDepth (${bitDepth})`);
667
+ }
668
+ }
669
+
670
+ function readImageData(reader: PsdReader, psd: Psd, options: ReadOptionsExt) {
678
671
  const compression = readUint16(reader) as Compression;
672
+ const bitsPerChannel = psd.bitsPerChannel ?? 8;
679
673
 
680
674
  if (supportedColorModes.indexOf(psd.colorMode!) === -1)
681
675
  throw new Error(`Color mode not supported: ${psd.colorMode}`);
@@ -683,7 +677,7 @@ function readImageData(reader: PsdReader, psd: Psd, globalAlpha: boolean, option
683
677
  if (compression !== Compression.RawData && compression !== Compression.RleCompressed)
684
678
  throw new Error(`Compression type not supported: ${compression}`);
685
679
 
686
- const imageData = createImageData(psd.width, psd.height);
680
+ const imageData = createImageDataBitDepth(psd.width, psd.height, bitsPerChannel);
687
681
  resetImageData(imageData);
688
682
 
689
683
  switch (psd.colorMode) {
@@ -694,7 +688,7 @@ function readImageData(reader: PsdReader, psd: Psd, globalAlpha: boolean, option
694
688
  bytes = readBytes(reader, Math.ceil(psd.width / 8) * psd.height);
695
689
  } else if (compression === Compression.RleCompressed) {
696
690
  bytes = new Uint8Array(psd.width * psd.height);
697
- readDataRLE(reader, { data: bytes, width: psd.width, height: psd.height }, psd.width, psd.height, 1, [0], options.large);
691
+ readDataRLE(reader, { data: bytes, width: psd.width, height: psd.height }, psd.width, psd.height, bitsPerChannel, 1, [0], options.large);
698
692
  } else {
699
693
  throw new Error(`Bitmap compression not supported: ${compression}`);
700
694
  }
@@ -711,17 +705,17 @@ function readImageData(reader: PsdReader, psd: Psd, globalAlpha: boolean, option
711
705
  // TODO: store these channels in additional image data
712
706
  channels.push(i);
713
707
  }
714
- } else if (globalAlpha) {
708
+ } else if (options.globalAlpha) {
715
709
  channels.push(3);
716
710
  }
717
711
 
718
712
  if (compression === Compression.RawData) {
719
713
  for (let i = 0; i < channels.length; i++) {
720
- readDataRaw(reader, imageData, psd.width, psd.height, 4, channels[i]);
714
+ readDataRaw(reader, imageData, psd.width, psd.height, bitsPerChannel, 4, channels[i]);
721
715
  }
722
716
  } else if (compression === Compression.RleCompressed) {
723
717
  const start = reader.offset;
724
- readDataRLE(reader, imageData, psd.width, psd.height, 4, channels, options.large);
718
+ readDataRLE(reader, imageData, psd.width, psd.height, bitsPerChannel, 4, channels, options.large);
725
719
  if (RAW_IMAGE_DATA) (psd as any).imageDataRaw = new Uint8Array(reader.view.buffer, reader.view.byteOffset + start, reader.offset - start);
726
720
  }
727
721
 
@@ -731,10 +725,11 @@ function readImageData(reader: PsdReader, psd: Psd, globalAlpha: boolean, option
731
725
  break;
732
726
  }
733
727
  case ColorMode.CMYK: {
728
+ if (psd.bitsPerChannel !== 8) throw new Error('bitsPerChannel Not supproted');
734
729
  if (psd.channels !== 4) throw new Error(`Invalid channel count`);
735
730
 
736
731
  const channels = [0, 1, 2, 3];
737
- if (globalAlpha) channels.push(4);
732
+ if (options.globalAlpha) channels.push(4);
738
733
 
739
734
  if (compression === Compression.RawData) {
740
735
  throw new Error(`Not implemented`);
@@ -750,7 +745,7 @@ function readImageData(reader: PsdReader, psd: Psd, globalAlpha: boolean, option
750
745
  };
751
746
 
752
747
  const start = reader.offset;
753
- readDataRLE(reader, cmykImageData, psd.width, psd.height, 5, channels, options.large);
748
+ readDataRLE(reader, cmykImageData, psd.width, psd.height, psd.bitsPerChannel ?? 8, 5, channels, options.large);
754
749
  cmykToRgb(cmykImageData, imageData, true);
755
750
 
756
751
  if (RAW_IMAGE_DATA) (psd as any).imageDataRaw = new Uint8Array(reader.view.buffer, reader.view.byteOffset + start, reader.offset - start);
@@ -762,7 +757,8 @@ function readImageData(reader: PsdReader, psd: Psd, globalAlpha: boolean, option
762
757
  }
763
758
 
764
759
  // remove weird white matte
765
- if (globalAlpha) {
760
+ if (options.globalAlpha) {
761
+ if (psd.bitsPerChannel !== 8) throw new Error('bitsPerChannel Not supproted');
766
762
  const p = imageData.data;
767
763
  const size = imageData.width * imageData.height * 4;
768
764
  for (let i = 0; i < size; i += 4) {
@@ -781,8 +777,7 @@ function readImageData(reader: PsdReader, psd: Psd, globalAlpha: boolean, option
781
777
  if (options.useImageData) {
782
778
  psd.imageData = imageData;
783
779
  } else {
784
- psd.canvas = createCanvas(psd.width, psd.height);
785
- psd.canvas.getContext('2d')!.putImageData(imageData, 0, 0);
780
+ psd.canvas = imageDataToCanvas(imageData);
786
781
  }
787
782
  }
788
783
 
@@ -814,40 +809,87 @@ function cmykToRgb(cmyk: PixelData, rgb: PixelData, reverseAlpha: boolean) {
814
809
  // }
815
810
  }
816
811
 
817
- function readDataRaw(reader: PsdReader, pixelData: PixelData | undefined, width: number, height: number, step: number, offset: number) {
818
- const size = width * height;
819
- const buffer = readBytes(reader, size);
812
+ function verifyCompatible(a: PixelArray, b: PixelArray) {
813
+ if ((a.byteLength / a.length) !== (b.byteLength / b.length)) {
814
+ throw new Error('Invalid array types');
815
+ }
816
+ }
817
+
818
+ function bytesToArray(bytes: Uint8Array, bitDepth: number) {
819
+ if (bitDepth === 8) {
820
+ return bytes;
821
+ } else if (bitDepth === 16) {
822
+ if (bytes.byteOffset % 2) {
823
+ const result = new Uint16Array(bytes.byteLength / 2);
824
+ new Uint8Array(result.buffer, result.byteOffset, result.byteLength).set(bytes);
825
+ return result;
826
+ } else {
827
+ return new Uint16Array(bytes.buffer, bytes.byteOffset, bytes.byteLength / 2);
828
+ }
829
+ } else if (bitDepth === 32) {
830
+ if (bytes.byteOffset % 4) {
831
+ const result = new Uint32Array(bytes.byteLength / 4);
832
+ new Uint8Array(result.buffer, result.byteOffset, result.byteLength).set(bytes);
833
+ return result;
834
+ } else {
835
+ return new Uint32Array(bytes.buffer, bytes.byteOffset, bytes.byteLength / 4);
836
+ }
837
+ } else {
838
+ throw new Error(`Invalid bitDepth (${bitDepth})`)
839
+ }
840
+ }
841
+
842
+ function copyChannelToPixelData(pixelData: PixelData, channel: PixelArray, offset: number, step: number) {
843
+ verifyCompatible(pixelData.data, channel);
844
+ const size = pixelData.width * pixelData.height;
845
+ const data = pixelData.data;
846
+ for (let i = 0, p = offset | 0; i < size; i++, p = (p + step) | 0) {
847
+ data[p] = channel[i];
848
+ }
849
+ }
850
+
851
+ function readDataRaw(reader: PsdReader, pixelData: PixelData | undefined, width: number, height: number, bitDepth: number, step: number, offset: number) {
852
+ const buffer = readBytes(reader, width * height * Math.floor(bitDepth / 8));
853
+ const array = bytesToArray(buffer, bitDepth);
820
854
 
821
855
  if (pixelData && offset < step) {
822
- const data = pixelData.data;
856
+ copyChannelToPixelData(pixelData, array, offset, step);
857
+ }
858
+ }
859
+
860
+ function decodePredicted(data: Uint8Array | Uint16Array, width: number, height: number, mod: number) {
861
+ for (let y = 0; y < height; y++) {
862
+ const offset = y * width;
823
863
 
824
- for (let i = 0, p = offset | 0; i < size; i++, p = (p + step) | 0) {
825
- data[p] = buffer[i];
864
+ for (let x = 1, o = offset + 1; x < width; x++, o++) {
865
+ data[o] = (data[o - 1] + data[o]) % mod;
826
866
  }
827
867
  }
828
868
  }
829
869
 
830
- export function readDataZipWithoutPrediction(
831
- reader: PsdReader, length: number, pixelData: PixelData | undefined, width: number, height: number,
832
- step: number, offset: number
833
- ) {
870
+ export function readDataZip(reader: PsdReader, length: number, pixelData: PixelData | undefined, width: number, height: number, bitDepth: number, step: number, offset: number, prediction: boolean) {
834
871
  const compressed = readBytes(reader, length);
835
872
  const decompressed = inflate(compressed);
836
- const size = width * height;
837
873
 
838
874
  if (pixelData && offset < step) {
839
- const data = pixelData.data;
840
-
841
- for (let i = 0, p = offset | 0; i < size; i++, p = (p + step) | 0) {
842
- data[p] = decompressed[i];
875
+ const array = bytesToArray(decompressed, bitDepth);
876
+
877
+ if (bitDepth === 8) {
878
+ if (prediction) decodePredicted(decompressed, width, height, 0x100);
879
+ copyChannelToPixelData(pixelData, decompressed, offset, step);
880
+ } else if (bitDepth === 16) {
881
+ if (prediction) decodePredicted(array as Uint16Array, width, height, 0x10000);
882
+ copyChannelToPixelData(pixelData, array, offset, step);
883
+ } else if (bitDepth === 32) {
884
+ if (prediction) decodePredicted(decompressed, width, height, 0x100);
885
+ copyChannelToPixelData(pixelData, array, offset, step);
886
+ } else {
887
+ throw new Error('Invalid bitDepth');
843
888
  }
844
889
  }
845
890
  }
846
891
 
847
- export function readDataRLE(
848
- reader: PsdReader, pixelData: PixelData | undefined, _width: number, height: number, step: number, offsets: number[],
849
- large: boolean
850
- ) {
892
+ export function readDataRLE(reader: PsdReader, pixelData: PixelData | undefined, _width: number, height: number, bitDepth: number, step: number, offsets: number[], large: boolean) {
851
893
  const data = pixelData && pixelData.data;
852
894
  let lengths: Uint16Array | Uint32Array;
853
895
 
@@ -869,6 +911,8 @@ export function readDataRLE(
869
911
  }
870
912
  }
871
913
 
914
+ if (bitDepth !== 1 && bitDepth !== 8) throw new Error(`Invalid bit depth (${bitDepth})`);
915
+
872
916
  const extraLimit = (step - 1) | 0; // 3 for rgb, 4 for cmyk
873
917
 
874
918
  for (let c = 0, li = 0; c < offsets.length; c++) {
package/src/psdWriter.ts CHANGED
@@ -1,9 +1,5 @@
1
- import { Psd, Layer, LayerAdditionalInfo, ColorMode, SectionDividerType, WriteOptions, Color, GlobalLayerMaskInfo } from './psd';
2
- import {
3
- hasAlpha, createCanvas, writeDataRLE, PixelData, LayerChannelData, ChannelData,
4
- offsetForChannel, createImageData, fromBlendMode, ChannelID, Compression, clamp,
5
- LayerMaskFlags, MaskParams, ColorSpace, Bounds, largeAdditionalInfoKeys, RAW_IMAGE_DATA, writeDataZipWithoutPrediction
6
- } from './helpers';
1
+ import { Psd, Layer, LayerAdditionalInfo, ColorMode, SectionDividerType, WriteOptions, Color, GlobalLayerMaskInfo, PixelData } from './psd';
2
+ import { hasAlpha, createCanvas, writeDataRLE, LayerChannelData, ChannelData, offsetForChannel, createImageData, fromBlendMode, ChannelID, Compression, clamp, LayerMaskFlags, MaskParams, ColorSpace, Bounds, largeAdditionalInfoKeys, RAW_IMAGE_DATA, writeDataZipWithoutPrediction, imageDataToCanvas } from './helpers';
7
3
  import { ExtendedWriteOptions, infoHandlers } from './additionalInfo';
8
4
  import { resourceHandlers } from './imageResources';
9
5
 
@@ -189,6 +185,22 @@ export function writeSection(writer: PsdWriter, round: number, func: () => void,
189
185
  writer.view.setUint32(offset, length, false);
190
186
  }
191
187
 
188
+ function verifyBitCount(target: Psd | Layer) {
189
+ target.children?.forEach(verifyBitCount);
190
+
191
+ const data = target.imageData;
192
+ if (data && (data.data instanceof Uint32Array || data.data instanceof Uint16Array)) {
193
+ throw new Error('imageData has incorrect bitDepth');
194
+ }
195
+
196
+ if ('mask' in target && target.mask) {
197
+ const data = target.mask.imageData;
198
+ if (data && (data.data instanceof Uint32Array || data.data instanceof Uint16Array)) {
199
+ throw new Error('mask imageData has incorrect bitDepth');
200
+ }
201
+ }
202
+ }
203
+
192
204
  export function writePsd(writer: PsdWriter, psd: Psd, options: WriteOptions = {}) {
193
205
  if (!(+psd.width > 0 && +psd.height > 0))
194
206
  throw new Error('Invalid document size');
@@ -196,6 +208,13 @@ export function writePsd(writer: PsdWriter, psd: Psd, options: WriteOptions = {}
196
208
  if ((psd.width > 30000 || psd.height > 30000) && !options.psb)
197
209
  throw new Error('Document size is too large (max is 30000x30000, use PSB format instead)');
198
210
 
211
+ const bitsPerChannel = psd.bitsPerChannel ?? 8;
212
+
213
+ if (bitsPerChannel !== 8)
214
+ throw new Error('bitsPerChannel other than 8 are not supported for writing');
215
+
216
+ verifyBitCount(psd);
217
+
199
218
  let imageResources = psd.imageResources || {};
200
219
 
201
220
  const opt: ExtendedWriteOptions = { ...options, layerIds: new Set(), layerToId: new Map() };
@@ -224,7 +243,7 @@ export function writePsd(writer: PsdWriter, psd: Psd, options: WriteOptions = {}
224
243
  writeUint16(writer, globalAlpha ? 4 : 3); // channels
225
244
  writeUint32(writer, psd.height);
226
245
  writeUint32(writer, psd.width);
227
- writeUint16(writer, 8); // bits per channel
246
+ writeUint16(writer, bitsPerChannel); // bits per channel
228
247
  writeUint16(writer, ColorMode.RGB); // we only support saving RGB right now
229
248
 
230
249
  // color mode data
@@ -525,9 +544,7 @@ function createThumbnail(psd: Psd) {
525
544
  context.scale(scale, scale);
526
545
 
527
546
  if (psd.imageData) {
528
- const temp = createCanvas(psd.imageData.width, psd.imageData.height);
529
- temp.getContext('2d')!.putImageData(psd.imageData, 0, 0);
530
- context.drawImage(temp, 0, 0);
547
+ context.drawImage(imageDataToCanvas(psd.imageData), 0, 0);
531
548
  } else if (psd.canvas) {
532
549
  context.drawImage(psd.canvas, 0, 0);
533
550
  }
@@ -590,7 +607,11 @@ function getLayerDimentions({ canvas, imageData }: Layer): { width: number; heig
590
607
  return imageData || canvas || { width: 0, height: 0 };
591
608
  }
592
609
 
593
- function cropImageData(data: ImageData, left: number, top: number, width: number, height: number) {
610
+ function cropImageData(data: PixelData, left: number, top: number, width: number, height: number) {
611
+ if (data.data instanceof Uint32Array || data.data instanceof Uint16Array) {
612
+ throw new Error('imageData has incorrect bit depth');
613
+ }
614
+
594
615
  const croppedData = createImageData(width, height);
595
616
  const srcData = data.data;
596
617
  const dstData = croppedData.data;
@@ -609,9 +630,7 @@ function cropImageData(data: ImageData, left: number, top: number, width: number
609
630
  return croppedData;
610
631
  }
611
632
 
612
- function getLayerChannels(
613
- tempBuffer: Uint8Array, layer: Layer, background: boolean, options: WriteOptions
614
- ): LayerChannelData {
633
+ function getLayerChannels(tempBuffer: Uint8Array, layer: Layer, background: boolean, options: WriteOptions): LayerChannelData {
615
634
  let top = (layer.top as any) | 0;
616
635
  let left = (layer.left as any) | 0;
617
636
  let right = (layer.right as any) | 0;