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