ag-psd 31.0.3 → 32.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/00d0e0b4 +0 -0
- package/.v8-cache/v24.7.0-x64-ef5a0af0/05135617 +0 -0
- package/.v8-cache/v24.7.0-x64-ef5a0af0/0cc76730 +0 -0
- package/.v8-cache/v24.7.0-x64-ef5a0af0/1130885f +0 -0
- package/.v8-cache/v24.7.0-x64-ef5a0af0/20729831 +0 -0
- package/.v8-cache/v24.7.0-x64-ef5a0af0/214a593b +0 -0
- package/.v8-cache/v24.7.0-x64-ef5a0af0/226e2801 +0 -0
- package/.v8-cache/v24.7.0-x64-ef5a0af0/25f8b84a +0 -0
- package/.v8-cache/v24.7.0-x64-ef5a0af0/27518289 +0 -0
- package/.v8-cache/v24.7.0-x64-ef5a0af0/330ef2d9 +0 -0
- package/.v8-cache/v24.7.0-x64-ef5a0af0/37ff16b8 +0 -0
- package/.v8-cache/v24.7.0-x64-ef5a0af0/3f72a97c +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/75efccd4 +0 -0
- package/.v8-cache/v24.7.0-x64-ef5a0af0/762f9305 +0 -0
- package/.v8-cache/v24.7.0-x64-ef5a0af0/7bcfc50a +0 -0
- package/.v8-cache/v24.7.0-x64-ef5a0af0/7ea06cea +0 -0
- package/.v8-cache/v24.7.0-x64-ef5a0af0/825e9a05 +0 -0
- package/.v8-cache/v24.7.0-x64-ef5a0af0/87372817 +0 -0
- package/.v8-cache/v24.7.0-x64-ef5a0af0/87cf2209 +0 -0
- package/.v8-cache/v24.7.0-x64-ef5a0af0/891a7c73 +0 -0
- package/.v8-cache/v24.7.0-x64-ef5a0af0/8b68a3a2 +0 -0
- package/.v8-cache/v24.7.0-x64-ef5a0af0/932f0770 +0 -0
- package/.v8-cache/v24.7.0-x64-ef5a0af0/a1d12a93 +0 -0
- package/.v8-cache/v24.7.0-x64-ef5a0af0/a7e80865 +0 -0
- package/.v8-cache/v24.7.0-x64-ef5a0af0/ac317c9c +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/b8569d6b +0 -0
- package/.v8-cache/v24.7.0-x64-ef5a0af0/b8b073c4 +0 -0
- package/.v8-cache/v24.7.0-x64-ef5a0af0/bb489c89 +0 -0
- package/.v8-cache/v24.7.0-x64-ef5a0af0/c0b789d2 +0 -0
- package/.v8-cache/v24.7.0-x64-ef5a0af0/c314aece +0 -0
- package/.v8-cache/v24.7.0-x64-ef5a0af0/c46725e1 +0 -0
- package/.v8-cache/v24.7.0-x64-ef5a0af0/c794cb34 +0 -0
- package/.v8-cache/v24.7.0-x64-ef5a0af0/cd4ae980 +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/d02dd6f7 +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/e6735171 +0 -0
- package/.v8-cache/v24.7.0-x64-ef5a0af0/f5417276 +0 -0
- package/.v8-cache/v24.7.0-x64-ef5a0af0/f6fd8125 +0 -0
- package/.v8-cache/v24.7.0-x64-ef5a0af0/f74d3518 +0 -0
- package/.v8-cache/v24.7.0-x64-ef5a0af0/f886deac +0 -0
- package/.v8-cache/v24.7.0-x64-ef5a0af0/fd0a2987 +0 -0
- package/.v8-cache/v24.7.0-x64-ef5a0af0/fe349423 +0 -0
- package/CHANGELOG.md +29 -0
- package/dist/abr.js +12 -5
- package/dist/abr.js.map +1 -1
- package/dist/additionalInfo.d.ts +2 -0
- package/dist/additionalInfo.js +123 -68
- package/dist/additionalInfo.js.map +1 -1
- package/dist/bundle.js +1365 -632
- package/dist/descriptor.d.ts +15 -2
- package/dist/descriptor.js +329 -100
- package/dist/descriptor.js.map +1 -1
- package/dist/effectsHelpers.js +12 -12
- package/dist/effectsHelpers.js.map +1 -1
- package/dist/engineData.d.ts +3 -1
- package/dist/engineData.js +34 -13
- package/dist/engineData.js.map +1 -1
- package/dist/engineData2.js +50 -21
- package/dist/engineData2.js.map +1 -1
- package/dist/helpers.d.ts +5 -2
- package/dist/helpers.js +18 -4
- package/dist/helpers.js.map +1 -1
- package/dist/imageResources.js +45 -21
- package/dist/imageResources.js.map +1 -1
- package/dist/index.js +1 -0
- package/dist/index.js.map +1 -1
- package/dist/jpeg.js +27 -7
- package/dist/jpeg.js.map +1 -1
- package/dist/psd.d.ts +6 -1
- package/dist/psdReader.d.ts +2 -2
- package/dist/psdReader.js +457 -270
- package/dist/psdReader.js.map +1 -1
- package/dist/psdWriter.d.ts +2 -0
- package/dist/psdWriter.js +98 -76
- package/dist/psdWriter.js.map +1 -1
- package/dist/text.js +126 -23
- package/dist/text.js.map +1 -1
- package/dist/utf8.js +36 -15
- package/dist/utf8.js.map +1 -1
- package/dist-es/abr.js +12 -5
- package/dist-es/abr.js.map +1 -1
- package/dist-es/additionalInfo.d.ts +2 -0
- package/dist-es/additionalInfo.js +123 -68
- package/dist-es/additionalInfo.js.map +1 -1
- package/dist-es/descriptor.d.ts +15 -2
- package/dist-es/descriptor.js +329 -100
- package/dist-es/descriptor.js.map +1 -1
- package/dist-es/effectsHelpers.js +12 -12
- package/dist-es/effectsHelpers.js.map +1 -1
- package/dist-es/engineData.d.ts +3 -1
- package/dist-es/engineData.js +34 -13
- package/dist-es/engineData.js.map +1 -1
- package/dist-es/engineData2.js +50 -21
- package/dist-es/engineData2.js.map +1 -1
- package/dist-es/helpers.d.ts +5 -2
- package/dist-es/helpers.js +18 -4
- package/dist-es/helpers.js.map +1 -1
- package/dist-es/imageResources.js +45 -21
- package/dist-es/imageResources.js.map +1 -1
- package/dist-es/index.js +1 -0
- package/dist-es/index.js.map +1 -1
- package/dist-es/jpeg.js +27 -7
- package/dist-es/jpeg.js.map +1 -1
- package/dist-es/psd.d.ts +6 -1
- package/dist-es/psdReader.d.ts +2 -2
- package/dist-es/psdReader.js +457 -270
- package/dist-es/psdReader.js.map +1 -1
- package/dist-es/psdWriter.d.ts +2 -0
- package/dist-es/psdWriter.js +98 -76
- package/dist-es/psdWriter.js.map +1 -1
- package/dist-es/text.js +126 -23
- package/dist-es/text.js.map +1 -1
- package/dist-es/utf8.js +36 -15
- package/dist-es/utf8.js.map +1 -1
- package/package.json +1 -1
- package/src/abr.ts +14 -6
- package/src/additionalInfo.ts +127 -66
- package/src/descriptor.ts +347 -107
- package/src/effectsHelpers.ts +9 -9
- package/src/engineData.ts +36 -13
- package/src/engineData2.ts +468 -432
- package/src/helpers.ts +19 -4
- package/src/imageResources.ts +49 -22
- package/src/index.ts +1 -0
- package/src/jpeg.ts +24 -9
- package/src/psd.ts +3 -1
- package/src/psdReader.ts +477 -260
- package/src/psdWriter.ts +100 -83
- package/src/text.ts +122 -12
- package/src/utf8.ts +35 -17
package/src/psdReader.ts
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
import {
|
|
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, RAW_IMAGE_DATA, largeAdditionalInfoKeys, imageDataToCanvas } from './helpers';
|
|
3
|
+
import { resetImageData, offsetForChannel, decodeBitmap, createImageData, toBlendMode, LayerMaskFlags, MaskParams, ColorSpace, RAW_IMAGE_DATA, largeAdditionalInfoKeys, imageDataToCanvas, normalizeLabAB } from './helpers';
|
|
4
4
|
import { infoHandlersMap } from './additionalInfo';
|
|
5
5
|
import { InternalImageResources, resourceHandlersMap } from './imageResources';
|
|
6
6
|
|
|
@@ -99,20 +99,22 @@ export function readFixedPointPath32(reader: PsdReader): number {
|
|
|
99
99
|
return readInt32(reader) / (1 << 24);
|
|
100
100
|
}
|
|
101
101
|
|
|
102
|
+
// NOTE: returned array may alias the reader's underlying buffer, never write into it
|
|
102
103
|
export function readBytes(reader: PsdReader, length: number) {
|
|
103
|
-
const start = reader.
|
|
104
|
+
const start = reader.offset;
|
|
105
|
+
const viewLength = reader.view.byteLength;
|
|
104
106
|
reader.offset += length;
|
|
105
107
|
|
|
106
|
-
if ((start + length) >
|
|
108
|
+
if ((start + length) > viewLength) {
|
|
107
109
|
// fix for broken PSD files that are missing part of file at the end
|
|
108
110
|
warnOrThrow(reader, 'Reading bytes exceeding buffer length');
|
|
109
111
|
if (length > (100 * 1024 * 1024)) throw new Error('Reading past end of file'); // limit to 100MB
|
|
110
112
|
const result = new Uint8Array(length);
|
|
111
|
-
const len = Math.min(length,
|
|
112
|
-
if (len > 0) result.set(new Uint8Array(reader.view.buffer, start, len));
|
|
113
|
+
const len = Math.min(length, viewLength - start);
|
|
114
|
+
if (len > 0) result.set(new Uint8Array(reader.view.buffer, reader.view.byteOffset + start, len));
|
|
113
115
|
return result;
|
|
114
116
|
} else {
|
|
115
|
-
return new Uint8Array(reader.view.buffer, start, length);
|
|
117
|
+
return new Uint8Array(reader.view.buffer, reader.view.byteOffset + start, length);
|
|
116
118
|
}
|
|
117
119
|
}
|
|
118
120
|
|
|
@@ -144,41 +146,65 @@ export function readUnicodeString(reader: PsdReader) {
|
|
|
144
146
|
return readUnicodeStringWithLength(reader, length);
|
|
145
147
|
}
|
|
146
148
|
|
|
147
|
-
|
|
149
|
+
const STRING_CHUNK_SIZE = 0x2000;
|
|
150
|
+
|
|
151
|
+
function checkReadBounds(reader: PsdReader, size: number) {
|
|
152
|
+
if (reader.offset < 0 || (reader.offset + size) > reader.view.byteLength) {
|
|
153
|
+
throw new RangeError('Offset is outside the bounds of the DataView');
|
|
154
|
+
}
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
function readUtf16String(reader: PsdReader, length: number, littleEndian: boolean) {
|
|
158
|
+
if (!(length > 0)) return '';
|
|
159
|
+
|
|
160
|
+
checkReadBounds(reader, length * 2);
|
|
161
|
+
|
|
162
|
+
const view = reader.view;
|
|
163
|
+
const codes = new Uint16Array(Math.min(length, STRING_CHUNK_SIZE));
|
|
164
|
+
let offset = reader.offset;
|
|
148
165
|
let text = '';
|
|
149
166
|
|
|
150
|
-
|
|
151
|
-
const
|
|
167
|
+
for (let i = 0; i < length; i += STRING_CHUNK_SIZE) {
|
|
168
|
+
const count = Math.min(STRING_CHUNK_SIZE, length - i);
|
|
152
169
|
|
|
153
|
-
|
|
154
|
-
|
|
170
|
+
for (let j = 0; j < count; j++, offset += 2) {
|
|
171
|
+
codes[j] = view.getUint16(offset, littleEndian);
|
|
155
172
|
}
|
|
173
|
+
|
|
174
|
+
text += String.fromCharCode.apply(null, (count === codes.length ? codes : codes.subarray(0, count)) as any);
|
|
156
175
|
}
|
|
157
176
|
|
|
177
|
+
reader.offset = offset;
|
|
178
|
+
|
|
179
|
+
// remove trailing \0
|
|
180
|
+
if (text.charCodeAt(text.length - 1) === 0) text = text.substring(0, text.length - 1);
|
|
181
|
+
|
|
158
182
|
return text;
|
|
159
183
|
}
|
|
160
184
|
|
|
161
|
-
|
|
185
|
+
function bytesToString(bytes: Uint8Array) {
|
|
162
186
|
let text = '';
|
|
163
187
|
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
if (value || length > 0) { // remove trailing \0
|
|
168
|
-
text += String.fromCharCode(value);
|
|
169
|
-
}
|
|
188
|
+
for (let i = 0; i < bytes.length; i += STRING_CHUNK_SIZE) {
|
|
189
|
+
text += String.fromCharCode.apply(null, bytes.subarray(i, i + STRING_CHUNK_SIZE) as any);
|
|
170
190
|
}
|
|
171
191
|
|
|
172
192
|
return text;
|
|
173
193
|
}
|
|
174
194
|
|
|
175
|
-
export function
|
|
176
|
-
|
|
195
|
+
export function readUnicodeStringWithLength(reader: PsdReader, length: number) {
|
|
196
|
+
return readUtf16String(reader, length, false);
|
|
197
|
+
}
|
|
177
198
|
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
199
|
+
export function readUnicodeStringWithLengthLE(reader: PsdReader, length: number) {
|
|
200
|
+
return readUtf16String(reader, length, true);
|
|
201
|
+
}
|
|
181
202
|
|
|
203
|
+
export function readAsciiString(reader: PsdReader, length: number) {
|
|
204
|
+
if (!(length > 0)) return '';
|
|
205
|
+
checkReadBounds(reader, length);
|
|
206
|
+
const text = bytesToString(new Uint8Array(reader.view.buffer, reader.view.byteOffset + reader.offset, length));
|
|
207
|
+
reader.offset += length;
|
|
182
208
|
return text;
|
|
183
209
|
}
|
|
184
210
|
|
|
@@ -196,14 +222,7 @@ export function checkSignature(reader: PsdReader, a: string, b?: string) {
|
|
|
196
222
|
}
|
|
197
223
|
|
|
198
224
|
function readShortString(reader: PsdReader, length: number) {
|
|
199
|
-
|
|
200
|
-
let result = '';
|
|
201
|
-
|
|
202
|
-
for (let i = 0; i < buffer.length; i++) {
|
|
203
|
-
result += String.fromCharCode(buffer[i]);
|
|
204
|
-
}
|
|
205
|
-
|
|
206
|
-
return result;
|
|
225
|
+
return bytesToString(readBytes(reader, length));
|
|
207
226
|
}
|
|
208
227
|
|
|
209
228
|
function isValidSignature(sig: string) {
|
|
@@ -225,7 +244,7 @@ export function readPsd(reader: PsdReader, readOptions: ReadOptions = {}) {
|
|
|
225
244
|
const maxSize = version === 1 ? 30000 : 300000;
|
|
226
245
|
|
|
227
246
|
if (width > maxSize || height > maxSize) throw new Error(`Invalid size: ${width}x${height}`);
|
|
228
|
-
if (channels >
|
|
247
|
+
if (channels < 1 || channels > 56) throw new Error(`Invalid channel count: ${channels}`);
|
|
229
248
|
if (![1, 8, 16, 32].includes(bitsPerChannel)) throw new Error(`Invalid bitsPerChannel: ${bitsPerChannel}`);
|
|
230
249
|
if (supportedColorModes.indexOf(colorMode) === -1) throw new Error(`Color mode not supported: ${colorModes[colorMode] ?? colorMode}`);
|
|
231
250
|
|
|
@@ -331,7 +350,10 @@ export function readPsd(reader: PsdReader, readOptions: ReadOptions = {}) {
|
|
|
331
350
|
|
|
332
351
|
if (!skipComposite) {
|
|
333
352
|
if (reader.useRawData) {
|
|
334
|
-
|
|
353
|
+
const rawOffset = Math.min(reader.offset, reader.view.byteLength);
|
|
354
|
+
const rawCompositeData = new Uint8Array(reader.view.buffer, reader.view.byteOffset + rawOffset, reader.view.byteLength - rawOffset);
|
|
355
|
+
psd.rawCompositeData = rawCompositeData;
|
|
356
|
+
psd.rawCompositeDataInfo = { large: reader.large, globalAlpha: reader.globalAlpha };
|
|
335
357
|
} else {
|
|
336
358
|
const imageData = readImageData(reader, psd);
|
|
337
359
|
if (reader.useImageData) {
|
|
@@ -393,7 +415,8 @@ export function readLayerInfo(reader: PsdReader, psd: Psd, imageResources: Inter
|
|
|
393
415
|
stack[stack.length - 1].children!.unshift(l);
|
|
394
416
|
stack.push(l);
|
|
395
417
|
} else if (type === SectionDividerType.BoundingSectionDivider) {
|
|
396
|
-
|
|
418
|
+
// ignore unmatched section dividers, never pop the root
|
|
419
|
+
if (stack.length > 1) stack.pop();
|
|
397
420
|
// this was workaround because I didn't know what `lsdk` section was, now it's probably not needed anymore
|
|
398
421
|
// } else if (l.name === '</Layer group>' && !l.sectionDivider && !l.top && !l.left && !l.bottom && !l.right) {
|
|
399
422
|
// // sometimes layer group terminator doesn't have sectionDivider, so we just guess here (PS bug ?)
|
|
@@ -491,7 +514,7 @@ function readLayerMaskData(reader: PsdReader, layer: Layer) {
|
|
|
491
514
|
mask.disabled = (flags & LayerMaskFlags.LayerMaskDisabled) !== 0;
|
|
492
515
|
mask.fromVectorData = (flags & LayerMaskFlags.LayerMaskFromRenderingOtherData) !== 0;
|
|
493
516
|
|
|
494
|
-
if (
|
|
517
|
+
if (hasRealMaskData(reader, flags, left())) {
|
|
495
518
|
const realMask: LayerMaskData = {};
|
|
496
519
|
layer.realMask = realMask;
|
|
497
520
|
|
|
@@ -520,6 +543,44 @@ function readLayerMaskData(reader: PsdReader, layer: Layer) {
|
|
|
520
543
|
});
|
|
521
544
|
}
|
|
522
545
|
|
|
546
|
+
function maskParamsSize(params: number) {
|
|
547
|
+
return 1 +
|
|
548
|
+
((params & MaskParams.UserMaskDensity) ? 1 : 0) +
|
|
549
|
+
((params & MaskParams.UserMaskFeather) ? 8 : 0) +
|
|
550
|
+
((params & MaskParams.VectorMaskDensity) ? 1 : 0) +
|
|
551
|
+
((params & MaskParams.VectorMaskFeather) ? 8 : 0);
|
|
552
|
+
}
|
|
553
|
+
|
|
554
|
+
// checks if data at offset is mask parameters followed by up to 3 bytes of zero padding, ending exactly at offset + left
|
|
555
|
+
function isMaskParamsLayout(view: DataView, offset: number, left: number) {
|
|
556
|
+
if (left < 1) return false;
|
|
557
|
+
|
|
558
|
+
const params = view.getUint8(offset);
|
|
559
|
+
if (params === 0 || (params & ~0x0f)) return false; // only 4 parameter flags exist
|
|
560
|
+
|
|
561
|
+
const size = maskParamsSize(params);
|
|
562
|
+
if (left < size || left - size > 3) return false;
|
|
563
|
+
|
|
564
|
+
for (let i = size; i < left; i++) {
|
|
565
|
+
if (view.getUint8(offset + i) !== 0) return false;
|
|
566
|
+
}
|
|
567
|
+
|
|
568
|
+
return true;
|
|
569
|
+
}
|
|
570
|
+
|
|
571
|
+
// real mask data (18 bytes) is followed by mask parameters (if flag is set) and padding
|
|
572
|
+
function hasRealMaskData(reader: PsdReader, flags: number, left: number) {
|
|
573
|
+
if (left < 18) return false;
|
|
574
|
+
if (!(flags & LayerMaskFlags.MaskHasParametersAppliedToIt)) return true;
|
|
575
|
+
|
|
576
|
+
const { view, offset } = reader;
|
|
577
|
+
if (isMaskParamsLayout(view, offset, left)) return false;
|
|
578
|
+
if (isMaskParamsLayout(view, offset + 18, left - 18)) return true;
|
|
579
|
+
|
|
580
|
+
// unknown layout, assume real mask if there's enough data for it (and mask parameters)
|
|
581
|
+
return left === 18 || left - 18 >= maskParamsSize(view.getUint8(offset + 18));
|
|
582
|
+
}
|
|
583
|
+
|
|
523
584
|
function readBlendingRange(reader: PsdReader) {
|
|
524
585
|
return [readUint8(reader), readUint8(reader), readUint8(reader), readUint8(reader)];
|
|
525
586
|
}
|
|
@@ -599,8 +660,14 @@ export function getCompositeImageData(psd: Psd) {
|
|
|
599
660
|
if (!data) return undefined;
|
|
600
661
|
|
|
601
662
|
const reader = createReader(data.buffer, data.byteOffset, data.byteLength);
|
|
602
|
-
const
|
|
603
|
-
|
|
663
|
+
const info = psd.rawCompositeDataInfo;
|
|
664
|
+
|
|
665
|
+
if (info) {
|
|
666
|
+
reader.large = info.large;
|
|
667
|
+
reader.globalAlpha = info.globalAlpha;
|
|
668
|
+
}
|
|
669
|
+
|
|
670
|
+
return readImageData(reader, psd);
|
|
604
671
|
}
|
|
605
672
|
|
|
606
673
|
export function getCompositeCanvas(psd: Psd) {
|
|
@@ -649,21 +716,23 @@ export function decodeLayerPixels(layer: Layer, useImageData?: boolean) {
|
|
|
649
716
|
decodeLayerImageData(layer, { useImageData });
|
|
650
717
|
}
|
|
651
718
|
|
|
652
|
-
|
|
719
|
+
type LogOptions = Pick<ReadOptions, 'debug' | 'log'>;
|
|
720
|
+
|
|
721
|
+
function decodeLayerImageData(layer: Layer, options: Pick<ReadOptions, 'useImageData' | 'throwForMissingFeatures' | 'totalMemoryLimit' | 'debug' | 'log'>) {
|
|
653
722
|
let { throwForMissingFeatures, useImageData } = options;
|
|
654
723
|
|
|
655
|
-
const imageData = getDataFromLayer(layer, LayerDataType.Layer, throwForMissingFeatures, options.totalMemoryLimit);
|
|
724
|
+
const imageData = getDataFromLayer(layer, LayerDataType.Layer, throwForMissingFeatures, options.totalMemoryLimit, options);
|
|
656
725
|
setImageDataOrCanvas(layer, imageData, useImageData);
|
|
657
726
|
if (options.totalMemoryLimit !== undefined) options.totalMemoryLimit -= imageData?.data.byteLength ?? 0;
|
|
658
727
|
|
|
659
728
|
if (layer.mask) {
|
|
660
|
-
const maskData = getDataFromLayer(layer, LayerDataType.Mask, throwForMissingFeatures, options.totalMemoryLimit);
|
|
729
|
+
const maskData = getDataFromLayer(layer, LayerDataType.Mask, throwForMissingFeatures, options.totalMemoryLimit, options);
|
|
661
730
|
setImageDataOrCanvas(layer.mask, maskData, useImageData);
|
|
662
731
|
if (options.totalMemoryLimit !== undefined) options.totalMemoryLimit -= maskData?.data.byteLength ?? 0;
|
|
663
732
|
}
|
|
664
733
|
|
|
665
734
|
if (layer.realMask) {
|
|
666
|
-
const maskData = getDataFromLayer(layer, LayerDataType.RealMask, throwForMissingFeatures, options.totalMemoryLimit);
|
|
735
|
+
const maskData = getDataFromLayer(layer, LayerDataType.RealMask, throwForMissingFeatures, options.totalMemoryLimit, options);
|
|
667
736
|
setImageDataOrCanvas(layer.realMask, maskData, useImageData);
|
|
668
737
|
if (options.totalMemoryLimit !== undefined) options.totalMemoryLimit -= maskData?.data.byteLength ?? 0;
|
|
669
738
|
}
|
|
@@ -677,7 +746,7 @@ enum LayerDataType {
|
|
|
677
746
|
RealMask,
|
|
678
747
|
}
|
|
679
748
|
|
|
680
|
-
function getDataFromLayer(layer: Layer, read: LayerDataType, throwForMissingFeatures?: boolean, memoryLimit?: number) {
|
|
749
|
+
function getDataFromLayer(layer: Layer, read: LayerDataType, throwForMissingFeatures?: boolean, memoryLimit?: number, logOptions?: LogOptions) {
|
|
681
750
|
if (!layer.rawData) return undefined;
|
|
682
751
|
|
|
683
752
|
const { colorMode, bitsPerChannel, channels, large } = layer.rawData;
|
|
@@ -691,7 +760,7 @@ function getDataFromLayer(layer: Layer, read: LayerDataType, throwForMissingFeat
|
|
|
691
760
|
|
|
692
761
|
if (layerWidth && layerHeight && read === LayerDataType.Layer) {
|
|
693
762
|
if (cmyk) {
|
|
694
|
-
if (bitsPerChannel !== 8) throw new Error('bitsPerChannel
|
|
763
|
+
if (bitsPerChannel !== 8) throw new Error('bitsPerChannel not supported');
|
|
695
764
|
imageData = { width: layerWidth, height: layerHeight, data: new Uint8ClampedArray(layerWidth * layerHeight * 5) } as any as ImageData;
|
|
696
765
|
} else {
|
|
697
766
|
imageData = createImageDataBitDepth(layerWidth, layerHeight, bitsPerChannel, 4, memoryLimit);
|
|
@@ -707,6 +776,9 @@ function getDataFromLayer(layer: Layer, read: LayerDataType, throwForMissingFeat
|
|
|
707
776
|
if (!data) continue;
|
|
708
777
|
|
|
709
778
|
const dataReader = createReader(data.buffer, data.byteOffset, data.byteLength);
|
|
779
|
+
dataReader.debug = !!logOptions?.debug;
|
|
780
|
+
if (logOptions?.log) dataReader.log = logOptions.log;
|
|
781
|
+
if (memoryLimit !== undefined) dataReader.totalMemoryLimit = Math.max(0, memoryLimit - (imageData?.data.byteLength ?? 0));
|
|
710
782
|
|
|
711
783
|
if (id === ChannelID.UserMask || id === ChannelID.RealUserMask) {
|
|
712
784
|
if (id === ChannelID.UserMask && read !== LayerDataType.Mask) continue;
|
|
@@ -720,6 +792,7 @@ function getDataFromLayer(layer: Layer, read: LayerDataType, throwForMissingFeat
|
|
|
720
792
|
|
|
721
793
|
if (maskWidth && maskHeight) {
|
|
722
794
|
maskData = createImageDataBitDepth(maskWidth, maskHeight, bitsPerChannel, 4, memoryLimit);
|
|
795
|
+
if (memoryLimit !== undefined) dataReader.totalMemoryLimit = Math.max(0, dataReader.totalMemoryLimit! - maskData.data.byteLength);
|
|
723
796
|
readData(dataReader, data.byteLength, maskData, compression, maskWidth, maskHeight, bitsPerChannel, 0, large, 4);
|
|
724
797
|
if (RAW_IMAGE_DATA) { // TODO: use layer.rawData instead
|
|
725
798
|
if (id === ChannelID.UserMask) {
|
|
@@ -754,9 +827,6 @@ function getDataFromLayer(layer: Layer, read: LayerDataType, throwForMissingFeat
|
|
|
754
827
|
(layer as any).imageDataRaw[id] = data;
|
|
755
828
|
}
|
|
756
829
|
|
|
757
|
-
if (targetData && colorMode === ColorMode.Grayscale) {
|
|
758
|
-
setupGrayscale(targetData);
|
|
759
|
-
}
|
|
760
830
|
}
|
|
761
831
|
|
|
762
832
|
if (id === ChannelID.Transparency) {
|
|
@@ -765,6 +835,7 @@ function getDataFromLayer(layer: Layer, read: LayerDataType, throwForMissingFeat
|
|
|
765
835
|
}
|
|
766
836
|
|
|
767
837
|
if (imageData) {
|
|
838
|
+
if (colorMode === ColorMode.Grayscale) setupGrayscale(imageData);
|
|
768
839
|
if (!initializedAlpha) resetAlpha(imageData, cmyk);
|
|
769
840
|
|
|
770
841
|
if (cmyk) {
|
|
@@ -782,7 +853,7 @@ function readData(reader: PsdReader, length: number, pixels: PixelData | undefin
|
|
|
782
853
|
|
|
783
854
|
if (compression === Compression.RawData) {
|
|
784
855
|
if (length !== (width * height * Math.floor(bitDepth / 8))) {
|
|
785
|
-
reader
|
|
856
|
+
warnOrThrow(reader, `Invalid length (${length}, ${width * height * Math.floor(bitDepth / 8)})`);
|
|
786
857
|
}
|
|
787
858
|
const data = readBytes(reader, length);
|
|
788
859
|
readDataRaw(data, pixels, bitDepth, step, offset);
|
|
@@ -791,10 +862,10 @@ function readData(reader: PsdReader, length: number, pixels: PixelData | undefin
|
|
|
791
862
|
readDataRLE(reader, pixels, width, height, bitDepth, step, [offset], large);
|
|
792
863
|
} else if (compression === Compression.ZipWithoutPrediction) {
|
|
793
864
|
const data = readBytes(reader, length);
|
|
794
|
-
readDataZip(data, pixels, width, height, bitDepth, step, offset, false);
|
|
865
|
+
readDataZip(data, pixels, width, height, bitDepth, step, offset, false, reader);
|
|
795
866
|
} else if (compression === Compression.ZipWithPrediction) {
|
|
796
867
|
const data = readBytes(reader, length);
|
|
797
|
-
readDataZip(data, pixels, width, height, bitDepth, step, offset, true);
|
|
868
|
+
readDataZip(data, pixels, width, height, bitDepth, step, offset, true, reader);
|
|
798
869
|
} else {
|
|
799
870
|
throw new Error(`Invalid Compression type: ${compression}`);
|
|
800
871
|
}
|
|
@@ -809,7 +880,7 @@ export function readGlobalLayerMaskInfo(reader: PsdReader) {
|
|
|
809
880
|
const colorSpace2 = readUint16(reader);
|
|
810
881
|
const colorSpace3 = readUint16(reader);
|
|
811
882
|
const colorSpace4 = readUint16(reader);
|
|
812
|
-
const opacity = readUint16(reader) /
|
|
883
|
+
const opacity = readUint16(reader) / 100; // 0 - 100
|
|
813
884
|
const kind = readUint8(reader);
|
|
814
885
|
skipBytes(reader, left()); // 3 bytes of padding ?
|
|
815
886
|
return { overlayColorSpace, colorSpace1, colorSpace2, colorSpace3, colorSpace4, opacity, kind };
|
|
@@ -892,6 +963,7 @@ function createImageDataBitDepth(width: number, height: number, bitDepth: number
|
|
|
892
963
|
function readImageData(reader: PsdReader, psd: Psd) {
|
|
893
964
|
const compression = readUint16(reader) as Compression;
|
|
894
965
|
const bitsPerChannel = psd.bitsPerChannel ?? 8;
|
|
966
|
+
const { width, height } = psd;
|
|
895
967
|
|
|
896
968
|
if (supportedColorModes.indexOf(psd.colorMode!) === -1)
|
|
897
969
|
throw new Error(`Color mode not supported: ${psd.colorMode}`);
|
|
@@ -899,7 +971,7 @@ function readImageData(reader: PsdReader, psd: Psd) {
|
|
|
899
971
|
if (compression !== Compression.RawData && compression !== Compression.RleCompressed)
|
|
900
972
|
throw new Error(`Compression type not supported: ${compression}`);
|
|
901
973
|
|
|
902
|
-
const imageData = createImageDataBitDepth(
|
|
974
|
+
const imageData = createImageDataBitDepth(width, height, bitsPerChannel, 4, reader.totalMemoryLimit);
|
|
903
975
|
if (reader.totalMemoryLimit !== undefined) reader.totalMemoryLimit -= imageData.data.byteLength;
|
|
904
976
|
resetImageData(imageData);
|
|
905
977
|
|
|
@@ -907,44 +979,29 @@ function readImageData(reader: PsdReader, psd: Psd) {
|
|
|
907
979
|
case ColorMode.Bitmap: {
|
|
908
980
|
if (bitsPerChannel !== 1) throw new Error('Invalid bitsPerChannel for bitmap color mode');
|
|
909
981
|
|
|
982
|
+
const rowBytes = Math.ceil(width / 8);
|
|
910
983
|
let bytes: Uint8Array;
|
|
911
984
|
|
|
912
985
|
if (compression === Compression.RawData) {
|
|
913
|
-
bytes = readBytes(reader,
|
|
914
|
-
} else if (compression === Compression.RleCompressed) {
|
|
915
|
-
bytes = new Uint8Array(psd.width * psd.height);
|
|
916
|
-
readDataRLE(reader, { data: bytes, width: psd.width, height: psd.height }, psd.width, psd.height, 8, 1, [0], reader.large);
|
|
986
|
+
bytes = readBytes(reader, rowBytes * height);
|
|
917
987
|
} else {
|
|
918
|
-
|
|
988
|
+
bytes = new Uint8Array(rowBytes * height);
|
|
989
|
+
readDataRLE(reader, { data: bytes, width: rowBytes, height }, rowBytes, height, 8, 1, [0], reader.large);
|
|
919
990
|
}
|
|
920
991
|
|
|
921
|
-
decodeBitmap(bytes, imageData.data,
|
|
992
|
+
decodeBitmap(bytes, imageData.data, width, height);
|
|
922
993
|
break;
|
|
923
994
|
}
|
|
924
995
|
case ColorMode.RGB:
|
|
925
996
|
case ColorMode.Grayscale: {
|
|
926
|
-
const
|
|
997
|
+
const colorChannels = psd.colorMode === ColorMode.Grayscale ? 1 : 3;
|
|
998
|
+
const offsets = compositeChannelOffsets(reader, psd, colorChannels, 3);
|
|
999
|
+
const start = reader.offset;
|
|
927
1000
|
|
|
928
|
-
|
|
929
|
-
for (let i = 3; i < psd.channels; i++) {
|
|
930
|
-
// TODO: store these channels in additional image data
|
|
931
|
-
channels.push(i);
|
|
932
|
-
}
|
|
933
|
-
} else if (reader.globalAlpha) {
|
|
934
|
-
channels.push(3);
|
|
935
|
-
}
|
|
1001
|
+
readCompositeChannels(reader, compression, imageData, bitsPerChannel, 4, offsets);
|
|
936
1002
|
|
|
937
|
-
if (compression === Compression.
|
|
938
|
-
|
|
939
|
-
const data = readBytes(reader, psd.width * psd.height * Math.floor(bitsPerChannel / 8));
|
|
940
|
-
readDataRaw(data, imageData, bitsPerChannel, 4, channels[i]);
|
|
941
|
-
}
|
|
942
|
-
} else if (compression === Compression.RleCompressed) {
|
|
943
|
-
const start = reader.offset;
|
|
944
|
-
readDataRLE(reader, imageData, psd.width, psd.height, bitsPerChannel, 4, channels, reader.large);
|
|
945
|
-
if (RAW_IMAGE_DATA) (psd as any).imageDataRaw = new Uint8Array(reader.view.buffer, reader.view.byteOffset + start, reader.offset - start);
|
|
946
|
-
} else {
|
|
947
|
-
throw new Error(`Compression not supported: ${compression}`);
|
|
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);
|
|
948
1005
|
}
|
|
949
1006
|
|
|
950
1007
|
if (psd.colorMode === ColorMode.Grayscale) {
|
|
@@ -953,55 +1010,28 @@ function readImageData(reader: PsdReader, psd: Psd) {
|
|
|
953
1010
|
break;
|
|
954
1011
|
}
|
|
955
1012
|
case ColorMode.Indexed: {
|
|
956
|
-
if (bitsPerChannel !== 8) throw new Error('bitsPerChannel
|
|
957
|
-
if (psd.channels !== 1) throw new Error('Invalid channel count');
|
|
1013
|
+
if (bitsPerChannel !== 8) throw new Error('bitsPerChannel not supported');
|
|
958
1014
|
if (!psd.palette) throw new Error('Missing color palette');
|
|
959
1015
|
|
|
960
|
-
|
|
961
|
-
|
|
962
|
-
|
|
963
|
-
|
|
964
|
-
width: imageData.width,
|
|
965
|
-
height: imageData.height,
|
|
966
|
-
data: new Uint8Array(imageData.width * imageData.height),
|
|
967
|
-
};
|
|
968
|
-
readDataRLE(reader, indexedImageData, psd.width, psd.height, bitsPerChannel, 1, [0], reader.large);
|
|
969
|
-
indexedToRgb(indexedImageData, imageData, psd.palette);
|
|
970
|
-
} else {
|
|
971
|
-
throw new Error(`Compression not supported: ${compression}`);
|
|
972
|
-
}
|
|
973
|
-
|
|
1016
|
+
const offsets = compositeChannelOffsets(reader, psd, 1, -1);
|
|
1017
|
+
const indexedImageData: PixelData = { width, height, data: new Uint8Array(width * height) };
|
|
1018
|
+
readCompositeChannels(reader, compression, indexedImageData, 8, 1, offsets);
|
|
1019
|
+
indexedToRgb(indexedImageData, imageData, psd.palette);
|
|
974
1020
|
break;
|
|
975
1021
|
}
|
|
976
1022
|
case ColorMode.CMYK: {
|
|
977
|
-
if (bitsPerChannel !== 8) throw new Error('bitsPerChannel
|
|
978
|
-
if (psd.channels !== 4) throw new Error(`Invalid channel count`);
|
|
1023
|
+
if (bitsPerChannel !== 8) throw new Error('bitsPerChannel not supported');
|
|
979
1024
|
|
|
980
|
-
const
|
|
981
|
-
|
|
1025
|
+
const offsets = compositeChannelOffsets(reader, psd, 4, 4);
|
|
1026
|
+
const cmykImageData: PixelData = { width, height, data: new Uint8Array(width * height * 5) };
|
|
1027
|
+
const start = reader.offset;
|
|
982
1028
|
|
|
983
|
-
|
|
984
|
-
|
|
985
|
-
// TODO: ...
|
|
986
|
-
// for (let i = 0; i < channels.length; i++) {
|
|
987
|
-
// readDataRaw(reader, imageData, channels[i], psd.width, psd.height);
|
|
988
|
-
// }
|
|
989
|
-
} else if (compression === Compression.RleCompressed) {
|
|
990
|
-
const cmykImageData: PixelData = {
|
|
991
|
-
width: imageData.width,
|
|
992
|
-
height: imageData.height,
|
|
993
|
-
data: new Uint8Array(imageData.width * imageData.height * 5),
|
|
994
|
-
};
|
|
995
|
-
|
|
996
|
-
const start = reader.offset;
|
|
997
|
-
readDataRLE(reader, cmykImageData, psd.width, psd.height, bitsPerChannel, 5, channels, reader.large);
|
|
998
|
-
cmykToRgb(cmykImageData, imageData, true);
|
|
999
|
-
|
|
1000
|
-
if (RAW_IMAGE_DATA) (psd as any).imageDataRaw = new Uint8Array(reader.view.buffer, reader.view.byteOffset + start, reader.offset - start);
|
|
1001
|
-
} else {
|
|
1002
|
-
throw new Error(`Compression not supported: ${compression}`);
|
|
1003
|
-
}
|
|
1029
|
+
readCompositeChannels(reader, compression, cmykImageData, 8, 5, offsets);
|
|
1030
|
+
cmykToRgb(cmykImageData, imageData, true);
|
|
1004
1031
|
|
|
1032
|
+
if (RAW_IMAGE_DATA && compression === Compression.RleCompressed) {
|
|
1033
|
+
(psd as any).imageDataRaw = new Uint8Array(reader.view.buffer, reader.view.byteOffset + start, reader.offset - start);
|
|
1034
|
+
}
|
|
1005
1035
|
break;
|
|
1006
1036
|
}
|
|
1007
1037
|
default: throw new Error(`Color mode not supported: ${psd.colorMode}`);
|
|
@@ -1009,23 +1039,103 @@ function readImageData(reader: PsdReader, psd: Psd) {
|
|
|
1009
1039
|
|
|
1010
1040
|
// remove weird white matte
|
|
1011
1041
|
if (reader.globalAlpha) {
|
|
1012
|
-
|
|
1013
|
-
|
|
1014
|
-
|
|
1042
|
+
removeWhiteMatte(imageData);
|
|
1043
|
+
}
|
|
1044
|
+
|
|
1045
|
+
return imageData;
|
|
1046
|
+
}
|
|
1047
|
+
|
|
1048
|
+
// returns target offset for each of the composite image channels stored in the file, -1 for channels that are skipped
|
|
1049
|
+
function compositeChannelOffsets(reader: PsdReader, psd: Psd, colorChannels: number, alphaOffset: number) {
|
|
1050
|
+
const channelCount = psd.channels ?? (colorChannels + (reader.globalAlpha ? 1 : 0));
|
|
1051
|
+
if (channelCount < colorChannels) throw new Error(`Invalid channel count: ${channelCount}`);
|
|
1052
|
+
|
|
1053
|
+
// First extra channel is transparency if document has global alpha (negative layer count). Some non-Photoshop
|
|
1054
|
+
// applications write transparency channel without setting global alpha, in that case extra channel is not
|
|
1055
|
+
// listed in alpha channel names (Photoshop names all user alpha channels / saved selections).
|
|
1056
|
+
const namedChannels = psd.imageResources?.alphaChannelNames?.length ?? 0;
|
|
1057
|
+
const hasTransparency = reader.globalAlpha || (channelCount - colorChannels) > namedChannels;
|
|
1058
|
+
const offsets: number[] = [];
|
|
1059
|
+
|
|
1060
|
+
for (let i = 0; i < channelCount; i++) {
|
|
1061
|
+
if (i < colorChannels) {
|
|
1062
|
+
offsets.push(i);
|
|
1063
|
+
} else if (i === colorChannels && hasTransparency && alphaOffset >= 0) {
|
|
1064
|
+
offsets.push(alphaOffset);
|
|
1065
|
+
} else {
|
|
1066
|
+
// TODO: store these channels (alpha channels / saved selections, spot colors) in additional image data
|
|
1067
|
+
offsets.push(-1);
|
|
1068
|
+
}
|
|
1069
|
+
}
|
|
1070
|
+
|
|
1071
|
+
return offsets;
|
|
1072
|
+
}
|
|
1073
|
+
|
|
1074
|
+
function readCompositeChannels(reader: PsdReader, compression: Compression, pixelData: PixelData, bitsPerChannel: number, step: number, offsets: number[]) {
|
|
1075
|
+
if (compression === Compression.RawData) {
|
|
1076
|
+
const channelSize = pixelData.width * pixelData.height * bytesPerValue(bitsPerChannel);
|
|
1077
|
+
|
|
1078
|
+
for (const offset of offsets) {
|
|
1079
|
+
if (offset < 0) {
|
|
1080
|
+
skipBytes(reader, channelSize);
|
|
1081
|
+
} else {
|
|
1082
|
+
readDataRaw(readBytes(reader, channelSize), pixelData, bitsPerChannel, step, offset);
|
|
1083
|
+
}
|
|
1084
|
+
}
|
|
1085
|
+
} else if (compression === Compression.RleCompressed) {
|
|
1086
|
+
readDataRLE(reader, pixelData, pixelData.width, pixelData.height, bitsPerChannel, step, offsets, reader.large);
|
|
1087
|
+
} else {
|
|
1088
|
+
throw new Error(`Compression not supported: ${compression}`);
|
|
1089
|
+
}
|
|
1090
|
+
}
|
|
1091
|
+
|
|
1092
|
+
// un-premultiplies colors from white matte, used for composite image of documents with global alpha
|
|
1093
|
+
function removeWhiteMatte({ data, width, height }: PixelData) {
|
|
1094
|
+
const size = width * height * 4;
|
|
1095
|
+
|
|
1096
|
+
if (data instanceof Float32Array) {
|
|
1097
|
+
for (let i = 0; i < size; i += 4) {
|
|
1098
|
+
const a = data[i + 3];
|
|
1099
|
+
|
|
1100
|
+
if (a > 0 && a < 1) {
|
|
1101
|
+
const ra = 1 / a;
|
|
1102
|
+
const invA = 1 - ra;
|
|
1103
|
+
|
|
1104
|
+
for (let j = 0; j < 3; j++) {
|
|
1105
|
+
const v = data[i + j] * ra + invA;
|
|
1106
|
+
data[i + j] = v < 0 ? 0 : v;
|
|
1107
|
+
}
|
|
1108
|
+
}
|
|
1109
|
+
}
|
|
1110
|
+
} else if (data instanceof Uint16Array) {
|
|
1015
1111
|
for (let i = 0; i < size; i += 4) {
|
|
1016
|
-
const pa =
|
|
1112
|
+
const pa = data[i + 3];
|
|
1113
|
+
|
|
1114
|
+
if (pa !== 0 && pa !== 0xffff) {
|
|
1115
|
+
const ra = 0xffff / pa;
|
|
1116
|
+
const invA = 0xffff * (1 - ra);
|
|
1117
|
+
|
|
1118
|
+
for (let j = 0; j < 3; j++) {
|
|
1119
|
+
const v = Math.round(data[i + j] * ra + invA);
|
|
1120
|
+
data[i + j] = v < 0 ? 0 : (v > 0xffff ? 0xffff : v);
|
|
1121
|
+
}
|
|
1122
|
+
}
|
|
1123
|
+
}
|
|
1124
|
+
} else {
|
|
1125
|
+
// Uint8ClampedArray clamps and rounds values on assignment
|
|
1126
|
+
for (let i = 0; i < size; i += 4) {
|
|
1127
|
+
const pa = data[i + 3];
|
|
1128
|
+
|
|
1017
1129
|
if (pa != 0 && pa != 255) {
|
|
1018
1130
|
const a = pa / 255;
|
|
1019
1131
|
const ra = 1 / a;
|
|
1020
1132
|
const invA = 255 * (1 - ra);
|
|
1021
|
-
|
|
1022
|
-
|
|
1023
|
-
|
|
1133
|
+
data[i + 0] = data[i + 0] * ra + invA;
|
|
1134
|
+
data[i + 1] = data[i + 1] * ra + invA;
|
|
1135
|
+
data[i + 2] = data[i + 2] * ra + invA;
|
|
1024
1136
|
}
|
|
1025
1137
|
}
|
|
1026
1138
|
}
|
|
1027
|
-
|
|
1028
|
-
return imageData;
|
|
1029
1139
|
}
|
|
1030
1140
|
|
|
1031
1141
|
function cmykToRgb(cmyk: PixelData, rgb: PixelData, reverseAlpha: boolean) {
|
|
@@ -1070,186 +1180,293 @@ function indexedToRgb(indexed: PixelData, rgb: PixelData, palette: RGB[]) {
|
|
|
1070
1180
|
}
|
|
1071
1181
|
}
|
|
1072
1182
|
|
|
1073
|
-
|
|
1074
|
-
|
|
1075
|
-
|
|
1183
|
+
// number of bytes used to store single channel value, 1-bit data is handled as bytes
|
|
1184
|
+
function bytesPerValue(bitDepth: number) {
|
|
1185
|
+
switch (bitDepth) {
|
|
1186
|
+
case 1:
|
|
1187
|
+
case 8: return 1;
|
|
1188
|
+
case 16: return 2;
|
|
1189
|
+
case 32: return 4;
|
|
1190
|
+
default: throw new Error(`Invalid bitDepth (${bitDepth})`);
|
|
1076
1191
|
}
|
|
1077
1192
|
}
|
|
1078
1193
|
|
|
1079
|
-
function
|
|
1080
|
-
if (
|
|
1081
|
-
|
|
1082
|
-
} else if (bitDepth === 16) {
|
|
1083
|
-
// PSD files store 16-bit channel data in big-endian byte order.
|
|
1084
|
-
// Swap each pair of bytes so that Uint16Array (native-endian) reads the correct values.
|
|
1085
|
-
for (let i = 0; i < bytes.byteLength; i += 2) {
|
|
1086
|
-
const tmp = bytes[i];
|
|
1087
|
-
bytes[i] = bytes[i + 1];
|
|
1088
|
-
bytes[i + 1] = tmp;
|
|
1089
|
-
}
|
|
1090
|
-
if (bytes.byteOffset % 2) {
|
|
1091
|
-
const result = new Uint16Array(bytes.byteLength / 2);
|
|
1092
|
-
new Uint8Array(result.buffer, result.byteOffset, result.byteLength).set(bytes);
|
|
1093
|
-
return result;
|
|
1094
|
-
} else {
|
|
1095
|
-
return new Uint16Array(bytes.buffer, bytes.byteOffset, bytes.byteLength / 2);
|
|
1096
|
-
}
|
|
1097
|
-
} else if (bitDepth === 32) {
|
|
1098
|
-
if (bytes.byteOffset % 4) {
|
|
1099
|
-
const result = new Float32Array(bytes.byteLength / 4);
|
|
1100
|
-
new Uint8Array(result.buffer, result.byteOffset, result.byteLength).set(bytes);
|
|
1101
|
-
return result;
|
|
1102
|
-
} else {
|
|
1103
|
-
return new Float32Array(bytes.buffer, bytes.byteOffset, bytes.byteLength / 4);
|
|
1104
|
-
}
|
|
1105
|
-
} else {
|
|
1106
|
-
throw new Error(`Invalid bitDepth (${bitDepth})`)
|
|
1194
|
+
function verifyArrayType(data: PixelArray, bytesPerValue: number) {
|
|
1195
|
+
if (data.BYTES_PER_ELEMENT !== bytesPerValue) {
|
|
1196
|
+
throw new Error('Invalid array types');
|
|
1107
1197
|
}
|
|
1108
1198
|
}
|
|
1109
1199
|
|
|
1110
|
-
|
|
1111
|
-
|
|
1112
|
-
const
|
|
1200
|
+
// NOTE: `buffer` may alias input file data, it must not be modified here
|
|
1201
|
+
function readDataRaw(buffer: Uint8Array, pixelData: PixelData | undefined, bitDepth: number, step: number, offset: number) {
|
|
1202
|
+
const bpv = bytesPerValue(bitDepth);
|
|
1203
|
+
|
|
1204
|
+
if (!pixelData || offset < 0 || offset >= step) return;
|
|
1205
|
+
|
|
1113
1206
|
const data = pixelData.data;
|
|
1114
|
-
|
|
1115
|
-
|
|
1116
|
-
}
|
|
1117
|
-
}
|
|
1207
|
+
verifyArrayType(data, bpv);
|
|
1208
|
+
const size = Math.min(pixelData.width * pixelData.height, Math.floor(buffer.byteLength / bpv)) | 0;
|
|
1118
1209
|
|
|
1119
|
-
|
|
1120
|
-
|
|
1121
|
-
|
|
1122
|
-
const a = buffer[i + 0];
|
|
1123
|
-
const b = buffer[i + 1];
|
|
1124
|
-
const c = buffer[i + 2];
|
|
1125
|
-
const d = buffer[i + 3];
|
|
1126
|
-
buffer[i + 0] = d;
|
|
1127
|
-
buffer[i + 1] = c;
|
|
1128
|
-
buffer[i + 2] = b;
|
|
1129
|
-
buffer[i + 3] = a;
|
|
1210
|
+
if (bpv === 1) {
|
|
1211
|
+
for (let i = 0, p = offset | 0; i < size; i++, p = (p + step) | 0) {
|
|
1212
|
+
data[p] = buffer[i];
|
|
1130
1213
|
}
|
|
1131
|
-
}
|
|
1132
|
-
|
|
1133
|
-
|
|
1214
|
+
} else if (bpv === 2) {
|
|
1215
|
+
for (let i = 0, j = 0, p = offset | 0; i < size; i++, j += 2, p = (p + step) | 0) {
|
|
1216
|
+
data[p] = (buffer[j] << 8) | buffer[j + 1];
|
|
1217
|
+
}
|
|
1218
|
+
} else {
|
|
1219
|
+
const view = new DataView(buffer.buffer, buffer.byteOffset, buffer.byteLength);
|
|
1134
1220
|
|
|
1135
|
-
|
|
1136
|
-
|
|
1221
|
+
for (let i = 0, j = 0, p = offset | 0; i < size; i++, j += 4, p = (p + step) | 0) {
|
|
1222
|
+
data[p] = view.getFloat32(j, false);
|
|
1223
|
+
}
|
|
1137
1224
|
}
|
|
1138
1225
|
}
|
|
1139
1226
|
|
|
1140
|
-
function decodePredicted(data: Uint8Array
|
|
1227
|
+
function decodePredicted(data: Uint8Array, width: number, height: number) {
|
|
1141
1228
|
for (let y = 0; y < height; y++) {
|
|
1142
1229
|
const offset = y * width;
|
|
1143
1230
|
|
|
1144
1231
|
for (let x = 1, o = offset + 1; x < width; x++, o++) {
|
|
1145
|
-
data[o] =
|
|
1232
|
+
data[o] = data[o - 1] + data[o]; // Uint8Array wraps values on assignment
|
|
1146
1233
|
}
|
|
1147
1234
|
}
|
|
1148
1235
|
}
|
|
1149
1236
|
|
|
1150
|
-
|
|
1151
|
-
const decompressed = inflateSync(compressed);
|
|
1237
|
+
const zipOverflow = new Error('Decompressed data exceeds expected size');
|
|
1152
1238
|
|
|
1153
|
-
|
|
1154
|
-
|
|
1239
|
+
// inflates data, stopping when output exceeds expected size (protects against zip bombs)
|
|
1240
|
+
function inflateLimited(compressed: Uint8Array, expectedSize: number, reader: PsdReader | undefined) {
|
|
1241
|
+
const output = new Uint8Array(expectedSize);
|
|
1242
|
+
let position = 0;
|
|
1243
|
+
let overflow = false;
|
|
1155
1244
|
|
|
1156
|
-
|
|
1157
|
-
|
|
1158
|
-
|
|
1159
|
-
|
|
1160
|
-
|
|
1161
|
-
|
|
1162
|
-
} else if (bitDepth === 32) {
|
|
1163
|
-
if (prediction) decodePredicted(decompressed, width * 4, height, 0x100);
|
|
1245
|
+
const inflator = new Inflate({ chunkSize: Math.max(1024, Math.min(expectedSize, 0x10000)) });
|
|
1246
|
+
inflator.onData = (chunk: any) => {
|
|
1247
|
+
const data = chunk as Uint8Array;
|
|
1248
|
+
const count = Math.min(data.length, expectedSize - position);
|
|
1249
|
+
if (count > 0) output.set(count === data.length ? data : data.subarray(0, count), position);
|
|
1250
|
+
position += count;
|
|
1164
1251
|
|
|
1165
|
-
|
|
1166
|
-
|
|
1252
|
+
if (count < data.length) {
|
|
1253
|
+
overflow = true;
|
|
1254
|
+
throw zipOverflow;
|
|
1255
|
+
}
|
|
1256
|
+
};
|
|
1257
|
+
inflator.onEnd = (status: number) => {
|
|
1258
|
+
// default handler collects all the chunks into result, we only need the error status
|
|
1259
|
+
inflator.err = status;
|
|
1260
|
+
inflator.msg = (inflator as any).strm?.msg || '';
|
|
1261
|
+
};
|
|
1262
|
+
|
|
1263
|
+
try {
|
|
1264
|
+
inflator.push(compressed, true);
|
|
1265
|
+
} catch (e) {
|
|
1266
|
+
if (e !== zipOverflow) throw e;
|
|
1267
|
+
}
|
|
1167
1268
|
|
|
1168
|
-
|
|
1169
|
-
|
|
1269
|
+
if (overflow) {
|
|
1270
|
+
if (reader) warnOrThrow(reader, zipOverflow.message);
|
|
1271
|
+
} else if (inflator.err) {
|
|
1272
|
+
throw new Error(`Invalid zip data: ${inflator.msg}`);
|
|
1273
|
+
}
|
|
1170
1274
|
|
|
1171
|
-
|
|
1172
|
-
|
|
1173
|
-
|
|
1174
|
-
|
|
1175
|
-
|
|
1275
|
+
return output;
|
|
1276
|
+
}
|
|
1277
|
+
|
|
1278
|
+
export function readDataZip(
|
|
1279
|
+
compressed: Uint8Array, pixelData: PixelData | undefined, width: number, height: number, bitDepth: number, step: number,
|
|
1280
|
+
offset: number, prediction: boolean, reader?: PsdReader
|
|
1281
|
+
) {
|
|
1282
|
+
const bpv = bytesPerValue(bitDepth);
|
|
1283
|
+
|
|
1284
|
+
if (!pixelData || offset < 0 || offset >= step) return;
|
|
1285
|
+
|
|
1286
|
+
const data = pixelData.data;
|
|
1287
|
+
verifyArrayType(data, bpv);
|
|
1288
|
+
|
|
1289
|
+
const expectedSize = width * height * bpv;
|
|
1290
|
+
if (reader) consumeMemory(reader, expectedSize);
|
|
1291
|
+
|
|
1292
|
+
try {
|
|
1293
|
+
const decompressed = inflateLimited(compressed, expectedSize, reader);
|
|
1294
|
+
|
|
1295
|
+
if (bpv === 1) {
|
|
1296
|
+
for (let y = 0, i = 0; y < height; y++) {
|
|
1297
|
+
let prev = 0;
|
|
1298
|
+
|
|
1299
|
+
for (let x = 0, p = offset + y * width * step; x < width; x++, i++, p += step) {
|
|
1300
|
+
let v = decompressed[i];
|
|
1301
|
+
if (prediction) v = prev = (prev + v) & 0xff;
|
|
1302
|
+
data[p] = v;
|
|
1303
|
+
}
|
|
1304
|
+
}
|
|
1305
|
+
} else if (bpv === 2) {
|
|
1306
|
+
for (let y = 0, i = 0; y < height; y++) {
|
|
1307
|
+
let prev = 0;
|
|
1308
|
+
|
|
1309
|
+
for (let x = 0, p = offset + y * width * step; x < width; x++, i += 2, p += step) {
|
|
1310
|
+
let v = (decompressed[i] << 8) | decompressed[i + 1];
|
|
1311
|
+
if (prediction) v = prev = (prev + v) & 0xffff;
|
|
1312
|
+
data[p] = v;
|
|
1176
1313
|
}
|
|
1177
1314
|
}
|
|
1178
1315
|
} else {
|
|
1179
|
-
|
|
1316
|
+
const dst = new Uint32Array(data.buffer, data.byteOffset, data.length);
|
|
1317
|
+
|
|
1318
|
+
if (prediction) {
|
|
1319
|
+
// bytes are delta encoded per row and stored in planar order (all 1st bytes, all 2nd bytes, ...)
|
|
1320
|
+
decodePredicted(decompressed, width * 4, height);
|
|
1321
|
+
|
|
1322
|
+
for (let y = 0, di = offset; y < height; y++) {
|
|
1323
|
+
let a = width * 4 * y;
|
|
1324
|
+
|
|
1325
|
+
for (let x = 0; x < width; x++, a++, di += step) {
|
|
1326
|
+
const b = a + width;
|
|
1327
|
+
const c = b + width;
|
|
1328
|
+
const d = c + width;
|
|
1329
|
+
dst[di] = ((decompressed[a] << 24) | (decompressed[b] << 16) | (decompressed[c] << 8) | decompressed[d]) >>> 0;
|
|
1330
|
+
}
|
|
1331
|
+
}
|
|
1332
|
+
} else {
|
|
1333
|
+
const view = new DataView(decompressed.buffer, decompressed.byteOffset, decompressed.byteLength);
|
|
1334
|
+
|
|
1335
|
+
for (let i = 0, j = 0, di = offset, size = width * height; i < size; i++, j += 4, di += step) {
|
|
1336
|
+
dst[di] = view.getUint32(j, false);
|
|
1337
|
+
}
|
|
1338
|
+
}
|
|
1180
1339
|
}
|
|
1340
|
+
} finally {
|
|
1341
|
+
if (reader) recoverMemory(reader, expectedSize);
|
|
1181
1342
|
}
|
|
1182
1343
|
}
|
|
1183
1344
|
|
|
1184
|
-
|
|
1185
|
-
|
|
1186
|
-
let
|
|
1345
|
+
// decodes single PackBits row, returns number of decoded values
|
|
1346
|
+
function decodeRLERow(src: Uint8Array, i: number, end: number, dst: PixelArray, p: number, step: number, count: number) {
|
|
1347
|
+
let x = 0;
|
|
1187
1348
|
|
|
1188
|
-
|
|
1189
|
-
|
|
1190
|
-
lengths = new Uint32Array(offsets.length * height);
|
|
1349
|
+
while (i < end && x < count) {
|
|
1350
|
+
const header = src[i++];
|
|
1191
1351
|
|
|
1192
|
-
|
|
1193
|
-
|
|
1194
|
-
|
|
1352
|
+
if (header > 128) {
|
|
1353
|
+
if (i >= end) break;
|
|
1354
|
+
|
|
1355
|
+
const value = src[i++];
|
|
1356
|
+
let n = 257 - header;
|
|
1357
|
+
if (n > count - x) n = count - x;
|
|
1358
|
+
x += n;
|
|
1359
|
+
|
|
1360
|
+
for (; n > 0; n--, p += step) {
|
|
1361
|
+
dst[p] = value;
|
|
1362
|
+
}
|
|
1363
|
+
} else if (header < 128) {
|
|
1364
|
+
let n = header + 1;
|
|
1365
|
+
if (n > count - x) n = count - x;
|
|
1366
|
+
if (n > end - i) n = end - i;
|
|
1367
|
+
x += n;
|
|
1368
|
+
|
|
1369
|
+
for (; n > 0; n--, p += step) {
|
|
1370
|
+
dst[p] = src[i++];
|
|
1195
1371
|
}
|
|
1372
|
+
} else {
|
|
1373
|
+
// ignore 128
|
|
1374
|
+
}
|
|
1375
|
+
}
|
|
1376
|
+
|
|
1377
|
+
return x;
|
|
1378
|
+
}
|
|
1379
|
+
|
|
1380
|
+
// converts big-endian 16/32-bit values from bytes and writes them to `data` starting at `p` with `step`
|
|
1381
|
+
function scatterValues(bytes: Uint8Array, count: number, bpv: number, data: PixelArray, p: number, step: number) {
|
|
1382
|
+
if (bpv === 2) {
|
|
1383
|
+
for (let x = 0, i = 0; x < count; x++, i += 2, p += step) {
|
|
1384
|
+
data[p] = (bytes[i] << 8) | bytes[i + 1];
|
|
1196
1385
|
}
|
|
1197
1386
|
} else {
|
|
1198
|
-
|
|
1199
|
-
lengths = new Uint16Array(offsets.length * height);
|
|
1387
|
+
const view = new DataView(bytes.buffer, bytes.byteOffset, bytes.byteLength);
|
|
1200
1388
|
|
|
1201
|
-
for (let
|
|
1202
|
-
|
|
1203
|
-
lengths[li] = readUint16(reader);
|
|
1204
|
-
}
|
|
1389
|
+
for (let x = 0, i = 0; x < count; x++, i += 4, p += step) {
|
|
1390
|
+
data[p] = view.getFloat32(i, false);
|
|
1205
1391
|
}
|
|
1206
1392
|
}
|
|
1393
|
+
}
|
|
1207
1394
|
|
|
1208
|
-
|
|
1395
|
+
// decodes RLE rows of single channel, each row starts at the beginning of the image row
|
|
1396
|
+
function decodeRLEChannel(
|
|
1397
|
+
src: Uint8Array, lengths: Uint16Array | Uint32Array, li: number, pos: number, width: number, height: number,
|
|
1398
|
+
data: PixelArray, offset: number, step: number, bpv: number, row: Uint8Array | undefined
|
|
1399
|
+
) {
|
|
1400
|
+
const srcEnd = src.length;
|
|
1401
|
+
const rowSize = width * bpv;
|
|
1209
1402
|
|
|
1210
|
-
|
|
1403
|
+
for (let y = 0; y < height; y++, li++) {
|
|
1404
|
+
const rowStart = pos < srcEnd ? pos : srcEnd;
|
|
1405
|
+
pos += lengths[li];
|
|
1406
|
+
const rowEnd = pos < srcEnd ? pos : srcEnd;
|
|
1211
1407
|
|
|
1212
|
-
|
|
1213
|
-
const offset = offsets[c] | 0;
|
|
1214
|
-
const extra = c > extraLimit || offset > extraLimit;
|
|
1408
|
+
if (rowStart >= rowEnd) continue;
|
|
1215
1409
|
|
|
1216
|
-
if (
|
|
1217
|
-
|
|
1218
|
-
skipBytes(reader, lengths[li]);
|
|
1219
|
-
}
|
|
1410
|
+
if (bpv === 1) {
|
|
1411
|
+
decodeRLERow(src, rowStart, rowEnd, data, offset + y * width * step, step, width);
|
|
1220
1412
|
} else {
|
|
1221
|
-
|
|
1222
|
-
|
|
1223
|
-
|
|
1224
|
-
|
|
1225
|
-
|
|
1226
|
-
let header = buffer[i];
|
|
1227
|
-
|
|
1228
|
-
if (header > 128) {
|
|
1229
|
-
const value = buffer[++i];
|
|
1230
|
-
header = (256 - header) | 0;
|
|
1231
|
-
|
|
1232
|
-
for (let j = 0; j <= header && x < width; j = (j + 1) | 0, x = (x + 1) | 0) {
|
|
1233
|
-
data[p] = value;
|
|
1234
|
-
p = (p + step) | 0;
|
|
1235
|
-
}
|
|
1236
|
-
} else if (header < 128) {
|
|
1237
|
-
for (let j = 0; j <= header && x < width; j = (j + 1) | 0, x = (x + 1) | 0) {
|
|
1238
|
-
data[p] = buffer[++i];
|
|
1239
|
-
p = (p + step) | 0;
|
|
1240
|
-
}
|
|
1241
|
-
} else {
|
|
1242
|
-
// ignore 128
|
|
1243
|
-
}
|
|
1413
|
+
const decoded = decodeRLERow(src, rowStart, rowEnd, row!, 0, 1, rowSize);
|
|
1414
|
+
scatterValues(row!, Math.floor(decoded / bpv), bpv, data, offset + y * width * step, step);
|
|
1415
|
+
}
|
|
1416
|
+
}
|
|
1417
|
+
}
|
|
1244
1418
|
|
|
1245
|
-
|
|
1246
|
-
|
|
1247
|
-
|
|
1419
|
+
export function readDataRLE(reader: PsdReader, pixelData: PixelData | undefined, width: number, height: number, bitDepth: number, step: number, offsets: number[], large: boolean) {
|
|
1420
|
+
const data = pixelData && pixelData.data;
|
|
1421
|
+
const rows = offsets.length * height;
|
|
1422
|
+
const lengthSize = large ? 4 : 2;
|
|
1423
|
+
|
|
1424
|
+
consumeMemory(reader, rows * lengthSize);
|
|
1425
|
+
|
|
1426
|
+
try {
|
|
1427
|
+
if ((reader.offset + rows * lengthSize) > reader.view.byteLength) {
|
|
1428
|
+
throw new Error('RLE row lengths exceed buffer length');
|
|
1429
|
+
}
|
|
1430
|
+
|
|
1431
|
+
const lengths = large ? new Uint32Array(rows) : new Uint16Array(rows);
|
|
1432
|
+
let total = 0;
|
|
1433
|
+
|
|
1434
|
+
if (large) {
|
|
1435
|
+
for (let i = 0; i < rows; i++) total += (lengths[i] = readUint32(reader));
|
|
1436
|
+
} else {
|
|
1437
|
+
for (let i = 0; i < rows; i++) total += (lengths[i] = readUint16(reader));
|
|
1438
|
+
}
|
|
1439
|
+
|
|
1440
|
+
const start = reader.offset;
|
|
1441
|
+
const srcEnd = reader.view.byteLength;
|
|
1442
|
+
|
|
1443
|
+
if (total > (srcEnd - start)) {
|
|
1444
|
+
// broken or truncated file, decode only data that is available
|
|
1445
|
+
warnOrThrow(reader, 'RLE data exceeds buffer length');
|
|
1446
|
+
}
|
|
1447
|
+
|
|
1448
|
+
const bpv = bytesPerValue(bitDepth);
|
|
1449
|
+
if (data) verifyArrayType(data, bpv);
|
|
1450
|
+
|
|
1451
|
+
const src = new Uint8Array(reader.view.buffer, reader.view.byteOffset, srcEnd);
|
|
1452
|
+
const row = (data && bpv > 1) ? new Uint8Array(width * bpv) : undefined;
|
|
1453
|
+
let pos = start;
|
|
1454
|
+
|
|
1455
|
+
for (let c = 0, li = 0; c < offsets.length; c++, li += height) {
|
|
1456
|
+
const offset = offsets[c] | 0;
|
|
1457
|
+
const channelStart = pos;
|
|
1458
|
+
|
|
1459
|
+
for (let y = 0; y < height; y++) pos += lengths[li + y];
|
|
1460
|
+
|
|
1461
|
+
if (data && offset >= 0 && offset < step) {
|
|
1462
|
+
decodeRLEChannel(src, lengths, li, channelStart, width, height, data, offset, step, bpv, row);
|
|
1248
1463
|
}
|
|
1249
1464
|
}
|
|
1250
|
-
}
|
|
1251
1465
|
|
|
1252
|
-
|
|
1466
|
+
reader.offset = pos;
|
|
1467
|
+
} finally {
|
|
1468
|
+
recoverMemory(reader, rows * lengthSize);
|
|
1469
|
+
}
|
|
1253
1470
|
}
|
|
1254
1471
|
|
|
1255
1472
|
export function readSection<T>(
|
|
@@ -1314,8 +1531,8 @@ export function readColor(reader: PsdReader): Color {
|
|
|
1314
1531
|
const l = readInt16(reader) / 10000;
|
|
1315
1532
|
const ta = readInt16(reader);
|
|
1316
1533
|
const tb = readInt16(reader);
|
|
1317
|
-
const a =
|
|
1318
|
-
const b =
|
|
1534
|
+
const a = normalizeLabAB(ta, 100);
|
|
1535
|
+
const b = normalizeLabAB(tb, 100);
|
|
1319
1536
|
skipBytes(reader, 2);
|
|
1320
1537
|
return { l, a, b };
|
|
1321
1538
|
}
|