ag-psd 19.0.0 → 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.
- package/CHANGELOG.md +8 -0
- package/README_PSD.md +6 -0
- package/TODO +5 -0
- package/dist/abr.js +4 -4
- package/dist/abr.js.map +1 -1
- package/dist/additionalInfo.d.ts +3 -3
- package/dist/additionalInfo.js +99 -10
- package/dist/additionalInfo.js.map +1 -1
- package/dist/bundle.js +408 -151
- package/dist/descriptor.js +21 -16
- package/dist/descriptor.js.map +1 -1
- package/dist/helpers.d.ts +2 -7
- package/dist/helpers.js +33 -10
- package/dist/helpers.js.map +1 -1
- package/dist/imageResources.js +37 -0
- package/dist/imageResources.js.map +1 -1
- package/dist/psd.d.ts +46 -3
- package/dist/psd.js +8 -1
- package/dist/psd.js.map +1 -1
- package/dist/psdReader.d.ts +11 -5
- package/dist/psdReader.js +179 -97
- package/dist/psdReader.js.map +1 -1
- package/dist/psdWriter.js +27 -13
- package/dist/psdWriter.js.map +1 -1
- package/dist-es/abr.js +4 -4
- package/dist-es/abr.js.map +1 -1
- package/dist-es/additionalInfo.d.ts +3 -3
- package/dist-es/additionalInfo.js +102 -13
- package/dist-es/additionalInfo.js.map +1 -1
- package/dist-es/descriptor.js +21 -16
- package/dist-es/descriptor.js.map +1 -1
- package/dist-es/helpers.d.ts +2 -7
- package/dist-es/helpers.js +31 -9
- package/dist-es/helpers.js.map +1 -1
- package/dist-es/imageResources.js +37 -0
- package/dist-es/imageResources.js.map +1 -1
- package/dist-es/psd.d.ts +46 -3
- package/dist-es/psd.js +7 -0
- package/dist-es/psd.js.map +1 -1
- package/dist-es/psdReader.d.ts +11 -5
- package/dist-es/psdReader.js +178 -99
- package/dist-es/psdReader.js.map +1 -1
- package/dist-es/psdWriter.js +28 -14
- package/dist-es/psdWriter.js.map +1 -1
- package/package.json +1 -1
- package/src/abr.ts +4 -4
- package/src/additionalInfo.ts +142 -49
- package/src/descriptor.ts +14 -9
- package/src/helpers.ts +35 -18
- package/src/imageResources.ts +53 -0
- package/src/psd.ts +41 -5
- package/src/psdReader.ts +170 -126
- package/src/psdWriter.ts +36 -23
package/src/psdReader.ts
CHANGED
|
@@ -1,12 +1,6 @@
|
|
|
1
1
|
import { inflate } from 'pako';
|
|
2
|
-
import {
|
|
3
|
-
|
|
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,
|
|
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 (
|
|
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
|
|
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 (
|
|
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 && !!
|
|
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,
|
|
269
|
+
handler.read(reader, psd.imageResources, left, options);
|
|
276
270
|
} catch (e) {
|
|
277
|
-
if (
|
|
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
|
-
|
|
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,
|
|
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,
|
|
313
|
+
}, undefined, options.large);
|
|
319
314
|
|
|
320
315
|
const hasChildren = psd.children && psd.children.length;
|
|
321
|
-
const skipComposite =
|
|
316
|
+
const skipComposite = options.skipCompositeImageData && (options.skipLayerImageData || hasChildren);
|
|
322
317
|
|
|
323
318
|
if (!skipComposite) {
|
|
324
|
-
readImageData(reader, psd,
|
|
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
|
|
329
|
+
export function readLayerInfo(reader: PsdReader, psd: Psd, options: ReadOptionsExt) {
|
|
330
|
+
let layerCount = readInt16(reader);
|
|
336
331
|
|
|
337
|
-
|
|
338
|
-
|
|
332
|
+
if (layerCount < 0) {
|
|
333
|
+
options.globalAlpha = true;
|
|
334
|
+
layerCount = -layerCount;
|
|
335
|
+
}
|
|
339
336
|
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
layerCount = -layerCount;
|
|
343
|
-
}
|
|
337
|
+
const layers: Layer[] = [];
|
|
338
|
+
const layerChannels: ChannelInfo[][] = [];
|
|
344
339
|
|
|
345
|
-
|
|
346
|
-
const
|
|
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
|
-
|
|
350
|
-
layers.push(layer);
|
|
351
|
-
layerChannels.push(channels);
|
|
348
|
+
readLayerChannelImageData(reader, psd, layers[i], layerChannels[i], options);
|
|
352
349
|
}
|
|
350
|
+
}
|
|
353
351
|
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
if (
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
if (
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
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
|
-
}
|
|
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:
|
|
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 =
|
|
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 =
|
|
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 =
|
|
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 =
|
|
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
|
-
|
|
605
|
+
readDataZip(reader, length, data, width, height, bitDepth, step, offset, false);
|
|
625
606
|
} else if (compression === Compression.ZipWithPrediction) {
|
|
626
|
-
|
|
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
|
|
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 =
|
|
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 =
|
|
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
|
|
818
|
-
|
|
819
|
-
|
|
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
|
-
|
|
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
|
|
825
|
-
data[
|
|
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
|
|
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
|
|
840
|
-
|
|
841
|
-
|
|
842
|
-
|
|
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,
|
|
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
|
-
|
|
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,14 +607,18 @@ 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:
|
|
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;
|
|
597
618
|
|
|
598
619
|
for (let y = 0; y < height; y++) {
|
|
599
620
|
for (let x = 0; x < width; x++) {
|
|
600
|
-
let src = ((x + left) + (y + top) * width) * 4;
|
|
621
|
+
let src = ((x + left) + (y + top) * data.width) * 4;
|
|
601
622
|
let dst = (x + y * width) * 4;
|
|
602
623
|
dstData[dst] = srcData[src];
|
|
603
624
|
dstData[dst + 1] = srcData[src + 1];
|
|
@@ -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;
|
|
@@ -646,15 +665,9 @@ function getLayerChannels(
|
|
|
646
665
|
width = right - left;
|
|
647
666
|
height = bottom - top;
|
|
648
667
|
|
|
649
|
-
if (!width || !height) {
|
|
650
|
-
return { layer, top, left, right, bottom, channels };
|
|
651
|
-
}
|
|
668
|
+
if (!width || !height) return { layer, top, left, right, bottom, channels };
|
|
652
669
|
|
|
653
|
-
|
|
654
|
-
data = cropImageData(data, trimmed.left, trimmed.top, width, height);
|
|
655
|
-
} else {
|
|
656
|
-
data = layer.canvas!.getContext('2d')!.getImageData(trimmed.left, trimmed.top, width, height);
|
|
657
|
-
}
|
|
670
|
+
data = cropImageData(data, trimmed.left, trimmed.top, width, height);
|
|
658
671
|
}
|
|
659
672
|
}
|
|
660
673
|
|