ag-psd 31.0.3 → 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/00d0e0b4 +0 -0
- 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/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/2a19133e +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/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/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 +107 -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 +363 -229
- package/dist/abr.js.map +1 -1
- package/dist/additionalInfo.d.ts +56 -2
- package/dist/additionalInfo.js +600 -364
- 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 +3096 -1541
- package/dist/descriptor.d.ts +25 -6
- package/dist/descriptor.js +380 -135
- 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 +6 -3
- package/dist/helpers.js +22 -6
- package/dist/helpers.js.map +1 -1
- package/dist/imageResources.d.ts +15 -3
- package/dist/imageResources.js +529 -144
- package/dist/imageResources.js.map +1 -1
- package/dist/index.js +1 -0
- package/dist/index.js.map +1 -1
- package/dist/jpeg.js +30 -10
- package/dist/jpeg.js.map +1 -1
- package/dist/psd.d.ts +154 -18
- package/dist/psd.js.map +1 -1
- package/dist/psdReader.d.ts +12 -2
- package/dist/psdReader.js +720 -421
- package/dist/psdReader.js.map +1 -1
- package/dist/psdWriter.d.ts +3 -0
- package/dist/psdWriter.js +167 -130
- package/dist/psdWriter.js.map +1 -1
- package/dist/text.js +143 -29
- package/dist/text.js.map +1 -1
- package/dist/utf8.js +36 -18
- package/dist/utf8.js.map +1 -1
- package/dist-es/abr.d.ts +11 -4
- package/dist-es/abr.js +363 -229
- package/dist-es/abr.js.map +1 -1
- package/dist-es/additionalInfo.d.ts +56 -2
- package/dist-es/additionalInfo.js +600 -364
- 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 +25 -6
- package/dist-es/descriptor.js +380 -135
- 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 +6 -3
- package/dist-es/helpers.js +22 -6
- package/dist-es/helpers.js.map +1 -1
- package/dist-es/imageResources.d.ts +15 -3
- package/dist-es/imageResources.js +529 -144
- 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 +30 -10
- package/dist-es/jpeg.js.map +1 -1
- package/dist-es/psd.d.ts +154 -18
- package/dist-es/psd.js.map +1 -1
- package/dist-es/psdReader.d.ts +12 -2
- package/dist-es/psdReader.js +720 -421
- package/dist-es/psdReader.js.map +1 -1
- package/dist-es/psdWriter.d.ts +3 -0
- package/dist-es/psdWriter.js +167 -130
- package/dist-es/psdWriter.js.map +1 -1
- package/dist-es/text.js +143 -29
- package/dist-es/text.js.map +1 -1
- package/dist-es/utf8.js +36 -18
- package/dist-es/utf8.js.map +1 -1
- package/package.json +2 -2
- package/src/abr.ts +380 -232
- package/src/additionalInfo.ts +781 -477
- package/src/ase.ts +66 -61
- package/src/descriptor.ts +386 -142
- package/src/effectsHelpers.ts +9 -9
- package/src/engineData.ts +36 -13
- package/src/engineData2.ts +468 -432
- package/src/helpers.ts +23 -5
- package/src/imageResources.ts +658 -167
- package/src/index.ts +1 -0
- package/src/jpeg.ts +27 -12
- package/src/psd.ts +158 -30
- package/src/psdReader.ts +732 -414
- package/src/psdWriter.ts +186 -132
- package/src/text.ts +139 -18
- package/src/utf8.ts +35 -21
package/dist-es/psdReader.js
CHANGED
|
@@ -12,6 +12,10 @@ var __rest = (this && this.__rest) || function (s, e) {
|
|
|
12
12
|
};
|
|
13
13
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
14
14
|
exports.supportedColorModes = void 0;
|
|
15
|
+
exports.getUtf8Decoder = getUtf8Decoder;
|
|
16
|
+
exports.getUtf8StrictDecoder = getUtf8StrictDecoder;
|
|
17
|
+
exports.getGbkStrictDecoder = getGbkStrictDecoder;
|
|
18
|
+
exports.decodeUtf8 = decodeUtf8;
|
|
15
19
|
exports.createReader = createReader;
|
|
16
20
|
exports.warnOrThrow = warnOrThrow;
|
|
17
21
|
exports.readUint8 = readUint8;
|
|
@@ -53,12 +57,15 @@ exports.readDataZip = readDataZip;
|
|
|
53
57
|
exports.readDataRLE = readDataRLE;
|
|
54
58
|
exports.readSection = readSection;
|
|
55
59
|
exports.readColor = readColor;
|
|
60
|
+
exports.readPatternSafe = readPatternSafe;
|
|
56
61
|
exports.readPattern = readPattern;
|
|
62
|
+
exports.consumeMemory = consumeMemory;
|
|
57
63
|
const pako_1 = require("pako");
|
|
58
64
|
const helpers_1 = require("./helpers");
|
|
59
65
|
const additionalInfo_1 = require("./additionalInfo");
|
|
66
|
+
const utf8_1 = require("./utf8");
|
|
60
67
|
const imageResources_1 = require("./imageResources");
|
|
61
|
-
exports.supportedColorModes = [0 /* ColorMode.Bitmap */, 1 /* ColorMode.Grayscale */, 3 /* ColorMode.RGB */, 2 /* ColorMode.Indexed */];
|
|
68
|
+
exports.supportedColorModes = [0 /* ColorMode.Bitmap */, 1 /* ColorMode.Grayscale */, 3 /* ColorMode.RGB */, 2 /* ColorMode.Indexed */, 4 /* ColorMode.CMYK */];
|
|
62
69
|
const colorModes = ['bitmap', 'grayscale', 'indexed', 'RGB', 'CMYK', '', '', 'multichannel', 'duotone', 'lab'];
|
|
63
70
|
function setupGrayscale(data) {
|
|
64
71
|
const size = data.width * data.height * 4;
|
|
@@ -68,6 +75,37 @@ function setupGrayscale(data) {
|
|
|
68
75
|
data.data[i + 2] = c;
|
|
69
76
|
}
|
|
70
77
|
}
|
|
78
|
+
function createTextDecoder(label, options) {
|
|
79
|
+
try {
|
|
80
|
+
return typeof TextDecoder !== 'undefined' ? new TextDecoder(label, options) : null;
|
|
81
|
+
}
|
|
82
|
+
catch (_a) {
|
|
83
|
+
return null; // encoding not supported
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
// non-fatal UTF-8 decoder, malformed byte sequences are replaced with U+FFFD
|
|
87
|
+
function getUtf8Decoder(reader) {
|
|
88
|
+
if (reader.utf8Decoder === undefined)
|
|
89
|
+
reader.utf8Decoder = createTextDecoder('utf-8', { ignoreBOM: true });
|
|
90
|
+
return reader.utf8Decoder;
|
|
91
|
+
}
|
|
92
|
+
// UTF-8 decoder that throws on malformed byte sequences
|
|
93
|
+
function getUtf8StrictDecoder(reader) {
|
|
94
|
+
if (reader.utf8StrictDecoder === undefined)
|
|
95
|
+
reader.utf8StrictDecoder = createTextDecoder('utf-8', { ignoreBOM: true, fatal: true });
|
|
96
|
+
return reader.utf8StrictDecoder;
|
|
97
|
+
}
|
|
98
|
+
// GBK decoder that throws on malformed byte sequences
|
|
99
|
+
function getGbkStrictDecoder(reader) {
|
|
100
|
+
if (reader.gbkDecoder === undefined)
|
|
101
|
+
reader.gbkDecoder = createTextDecoder('gbk', { fatal: true });
|
|
102
|
+
return reader.gbkDecoder;
|
|
103
|
+
}
|
|
104
|
+
function decodeUtf8(reader, buffer) {
|
|
105
|
+
// native decoder is faster only for longer strings
|
|
106
|
+
const decoder = buffer.byteLength > 1000 ? getUtf8Decoder(reader) : null;
|
|
107
|
+
return decoder ? decoder.decode(buffer) : (0, utf8_1.decodeString)(buffer);
|
|
108
|
+
}
|
|
71
109
|
function createReader(buffer, offset, length) {
|
|
72
110
|
const view = new DataView(buffer, offset, length);
|
|
73
111
|
return { view, offset: 0, strict: false, debug: false, large: false, globalAlpha: false, log: console.log };
|
|
@@ -125,22 +163,24 @@ function readFixedPoint32(reader) {
|
|
|
125
163
|
function readFixedPointPath32(reader) {
|
|
126
164
|
return readInt32(reader) / (1 << 24);
|
|
127
165
|
}
|
|
166
|
+
// NOTE: returned array may alias the reader's underlying buffer, never write into it
|
|
128
167
|
function readBytes(reader, length) {
|
|
129
|
-
const start = reader.
|
|
168
|
+
const start = reader.offset;
|
|
169
|
+
const viewLength = reader.view.byteLength;
|
|
130
170
|
reader.offset += length;
|
|
131
|
-
if ((start + length) >
|
|
171
|
+
if ((start + length) > viewLength) {
|
|
132
172
|
// fix for broken PSD files that are missing part of file at the end
|
|
133
173
|
warnOrThrow(reader, 'Reading bytes exceeding buffer length');
|
|
134
174
|
if (length > (100 * 1024 * 1024))
|
|
135
175
|
throw new Error('Reading past end of file'); // limit to 100MB
|
|
136
176
|
const result = new Uint8Array(length);
|
|
137
|
-
const len = Math.min(length,
|
|
177
|
+
const len = Math.min(length, viewLength - start);
|
|
138
178
|
if (len > 0)
|
|
139
|
-
result.set(new Uint8Array(reader.view.buffer, start, len));
|
|
179
|
+
result.set(new Uint8Array(reader.view.buffer, reader.view.byteOffset + start, len));
|
|
140
180
|
return result;
|
|
141
181
|
}
|
|
142
182
|
else {
|
|
143
|
-
return new Uint8Array(reader.view.buffer, start, length);
|
|
183
|
+
return new Uint8Array(reader.view.buffer, reader.view.byteOffset + start, length);
|
|
144
184
|
}
|
|
145
185
|
}
|
|
146
186
|
function readSignature(reader) {
|
|
@@ -165,31 +205,52 @@ function readUnicodeString(reader) {
|
|
|
165
205
|
const length = readUint32(reader);
|
|
166
206
|
return readUnicodeStringWithLength(reader, length);
|
|
167
207
|
}
|
|
168
|
-
|
|
208
|
+
const STRING_CHUNK_SIZE = 0x2000;
|
|
209
|
+
function checkReadBounds(reader, size) {
|
|
210
|
+
if (reader.offset < 0 || (reader.offset + size) > reader.view.byteLength) {
|
|
211
|
+
throw new RangeError('Offset is outside the bounds of the DataView');
|
|
212
|
+
}
|
|
213
|
+
}
|
|
214
|
+
function readUtf16String(reader, length, littleEndian) {
|
|
215
|
+
if (!(length > 0))
|
|
216
|
+
return '';
|
|
217
|
+
checkReadBounds(reader, length * 2);
|
|
218
|
+
const view = reader.view;
|
|
219
|
+
const codes = new Uint16Array(Math.min(length, STRING_CHUNK_SIZE));
|
|
220
|
+
let offset = reader.offset;
|
|
169
221
|
let text = '';
|
|
170
|
-
|
|
171
|
-
const
|
|
172
|
-
|
|
173
|
-
|
|
222
|
+
for (let i = 0; i < length; i += STRING_CHUNK_SIZE) {
|
|
223
|
+
const count = Math.min(STRING_CHUNK_SIZE, length - i);
|
|
224
|
+
for (let j = 0; j < count; j++, offset += 2) {
|
|
225
|
+
codes[j] = view.getUint16(offset, littleEndian);
|
|
174
226
|
}
|
|
227
|
+
text += String.fromCharCode.apply(null, (count === codes.length ? codes : codes.subarray(0, count)));
|
|
175
228
|
}
|
|
229
|
+
reader.offset = offset;
|
|
230
|
+
// remove trailing \0
|
|
231
|
+
if (text.charCodeAt(text.length - 1) === 0)
|
|
232
|
+
text = text.substring(0, text.length - 1);
|
|
176
233
|
return text;
|
|
177
234
|
}
|
|
178
|
-
function
|
|
235
|
+
function bytesToString(bytes) {
|
|
179
236
|
let text = '';
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
if (value || length > 0) { // remove trailing \0
|
|
183
|
-
text += String.fromCharCode(value);
|
|
184
|
-
}
|
|
237
|
+
for (let i = 0; i < bytes.length; i += STRING_CHUNK_SIZE) {
|
|
238
|
+
text += String.fromCharCode.apply(null, bytes.subarray(i, i + STRING_CHUNK_SIZE));
|
|
185
239
|
}
|
|
186
240
|
return text;
|
|
187
241
|
}
|
|
242
|
+
function readUnicodeStringWithLength(reader, length) {
|
|
243
|
+
return readUtf16String(reader, length, false);
|
|
244
|
+
}
|
|
245
|
+
function readUnicodeStringWithLengthLE(reader, length) {
|
|
246
|
+
return readUtf16String(reader, length, true);
|
|
247
|
+
}
|
|
188
248
|
function readAsciiString(reader, length) {
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
249
|
+
if (!(length > 0))
|
|
250
|
+
return '';
|
|
251
|
+
checkReadBounds(reader, length);
|
|
252
|
+
const text = bytesToString(new Uint8Array(reader.view.buffer, reader.view.byteOffset + reader.offset, length));
|
|
253
|
+
reader.offset += length;
|
|
193
254
|
return text;
|
|
194
255
|
}
|
|
195
256
|
function skipBytes(reader, count) {
|
|
@@ -203,12 +264,7 @@ function checkSignature(reader, a, b) {
|
|
|
203
264
|
}
|
|
204
265
|
}
|
|
205
266
|
function readShortString(reader, length) {
|
|
206
|
-
|
|
207
|
-
let result = '';
|
|
208
|
-
for (let i = 0; i < buffer.length; i++) {
|
|
209
|
-
result += String.fromCharCode(buffer[i]);
|
|
210
|
-
}
|
|
211
|
-
return result;
|
|
267
|
+
return bytesToString(readBytes(reader, length));
|
|
212
268
|
}
|
|
213
269
|
function isValidSignature(sig) {
|
|
214
270
|
return sig === '8BIM' || sig === 'MeSa' || sig === 'AgHg' || sig === 'PHUT' || sig === 'DCSR';
|
|
@@ -229,7 +285,8 @@ function readPsd(reader, readOptions = {}) {
|
|
|
229
285
|
const maxSize = version === 1 ? 30000 : 300000;
|
|
230
286
|
if (width > maxSize || height > maxSize)
|
|
231
287
|
throw new Error(`Invalid size: ${width}x${height}`);
|
|
232
|
-
|
|
288
|
+
// up to 56 channels plus transparency of composite image (Photoshop CS3 writes 57 channels for 53 alpha channels)
|
|
289
|
+
if (channels < 1 || channels > 57)
|
|
233
290
|
throw new Error(`Invalid channel count: ${channels}`);
|
|
234
291
|
if (![1, 8, 16, 32].includes(bitsPerChannel))
|
|
235
292
|
throw new Error(`Invalid bitsPerChannel: ${bitsPerChannel}`);
|
|
@@ -239,6 +296,7 @@ function readPsd(reader, readOptions = {}) {
|
|
|
239
296
|
Object.assign(reader, readOptions);
|
|
240
297
|
reader.large = version === 2;
|
|
241
298
|
reader.globalAlpha = false;
|
|
299
|
+
reader.layersInGlobalInfo = false;
|
|
242
300
|
if (!('totalMemoryLimit' in reader)) { // setting totalMemoryLimit to undefined explicitly disables memory limit
|
|
243
301
|
reader.totalMemoryLimit = 2 * 1024 * 1024 * 1024; // default 2GB memory limit
|
|
244
302
|
}
|
|
@@ -266,17 +324,21 @@ function readPsd(reader, readOptions = {}) {
|
|
|
266
324
|
});
|
|
267
325
|
// image resources
|
|
268
326
|
const imageResources = {};
|
|
327
|
+
let unreadResourceBytes = 0;
|
|
269
328
|
readSection(reader, 1, left => {
|
|
270
|
-
|
|
271
|
-
|
|
329
|
+
// stop if there's not enough data left for resource header (some files have few trailing bytes in this section)
|
|
330
|
+
while (left() >= 12) {
|
|
331
|
+
const signature = realignWithSignature(reader, isValidSignature);
|
|
272
332
|
const id = readUint16(reader);
|
|
273
|
-
|
|
333
|
+
const nameLength = readUint8(reader);
|
|
334
|
+
const name = readBytes(reader, nameLength);
|
|
335
|
+
skipBytes(reader, (nameLength + 1) % 2); // pad to even size
|
|
274
336
|
readSection(reader, 2, left => {
|
|
275
337
|
const handler = imageResources_1.resourceHandlersMap[id];
|
|
276
338
|
const skip = id === 1036 && !!reader.skipThumbnail;
|
|
277
339
|
if (handler && !skip) {
|
|
278
340
|
try {
|
|
279
|
-
handler.read(reader, imageResources, left);
|
|
341
|
+
handler.read(reader, imageResources, left, { signature, id, name, width, height });
|
|
280
342
|
}
|
|
281
343
|
catch (e) {
|
|
282
344
|
if (reader.throwForMissingFeatures)
|
|
@@ -285,13 +347,20 @@ function readPsd(reader, readOptions = {}) {
|
|
|
285
347
|
}
|
|
286
348
|
}
|
|
287
349
|
else {
|
|
288
|
-
|
|
350
|
+
reader.logMissingFeatures && reader.log(`Unhandled image resource: ${id} (${left()})`);
|
|
289
351
|
skipBytes(reader, left());
|
|
290
352
|
}
|
|
291
353
|
});
|
|
292
354
|
}
|
|
355
|
+
unreadResourceBytes = left();
|
|
356
|
+
skipBytes(reader, left());
|
|
293
357
|
});
|
|
294
|
-
|
|
358
|
+
// some files have image resources section length few bytes larger than the data, layer and mask info section
|
|
359
|
+
// starts right after the last resource in that case
|
|
360
|
+
if (unreadResourceBytes && !sectionFits(reader, reader.offset) && sectionFits(reader, reader.offset - unreadResourceBytes)) {
|
|
361
|
+
reader.offset -= unreadResourceBytes;
|
|
362
|
+
}
|
|
363
|
+
const { layersGroup, layerGroupsEnabledId, savedPathsOrigination } = imageResources, rest = __rest(imageResources, ["layersGroup", "layerGroupsEnabledId", "savedPathsOrigination"]);
|
|
295
364
|
if (Object.keys(rest).length) {
|
|
296
365
|
psd.imageResources = rest;
|
|
297
366
|
}
|
|
@@ -331,7 +400,12 @@ function readPsd(reader, readOptions = {}) {
|
|
|
331
400
|
const skipComposite = reader.skipCompositeImageData && (reader.skipLayerImageData || hasChildren);
|
|
332
401
|
if (!skipComposite) {
|
|
333
402
|
if (reader.useRawData) {
|
|
334
|
-
|
|
403
|
+
const rawOffset = Math.min(reader.offset, reader.view.byteLength);
|
|
404
|
+
const rawCompositeData = new Uint8Array(reader.view.buffer, reader.view.byteOffset + rawOffset, reader.view.byteLength - rawOffset);
|
|
405
|
+
psd.rawCompositeData = rawCompositeData;
|
|
406
|
+
psd.rawCompositeDataInfo = { large: reader.large, globalAlpha: reader.globalAlpha };
|
|
407
|
+
if (reader.layersInGlobalInfo)
|
|
408
|
+
psd.rawCompositeDataInfo.layersInGlobalInfo = true;
|
|
335
409
|
}
|
|
336
410
|
else {
|
|
337
411
|
const imageData = readImageData(reader, psd);
|
|
@@ -343,9 +417,6 @@ function readPsd(reader, readOptions = {}) {
|
|
|
343
417
|
}
|
|
344
418
|
}
|
|
345
419
|
}
|
|
346
|
-
// TODO: show converted color mode instead of original PSD file color mode
|
|
347
|
-
// but add option to preserve file color mode (need to return image data instead of canvas in that case)
|
|
348
|
-
// psd.colorMode = ColorMode.RGB; // we convert all color modes to RGB
|
|
349
420
|
return psd;
|
|
350
421
|
}
|
|
351
422
|
function readLayerInfo(reader, psd, imageResources) {
|
|
@@ -386,7 +457,9 @@ function readLayerInfo(reader, psd, imageResources) {
|
|
|
386
457
|
stack.push(l);
|
|
387
458
|
}
|
|
388
459
|
else if (type === 3 /* SectionDividerType.BoundingSectionDivider */) {
|
|
389
|
-
|
|
460
|
+
// ignore unmatched section dividers, never pop the root
|
|
461
|
+
if (stack.length > 1)
|
|
462
|
+
stack.pop();
|
|
390
463
|
// this was workaround because I didn't know what `lsdk` section was, now it's probably not needed anymore
|
|
391
464
|
// } else if (l.name === '</Layer group>' && !l.sectionDivider && !l.top && !l.left && !l.bottom && !l.right) {
|
|
392
465
|
// // sometimes layer group terminator doesn't have sectionDivider, so we just guess here (PS bug ?)
|
|
@@ -419,9 +492,12 @@ function readLayerRecord(reader, psd, imageResources) {
|
|
|
419
492
|
}
|
|
420
493
|
checkSignature(reader, '8BIM');
|
|
421
494
|
const blendMode = readSignature(reader);
|
|
422
|
-
if (!helpers_1.toBlendMode[blendMode])
|
|
423
|
-
|
|
424
|
-
|
|
495
|
+
if (!helpers_1.toBlendMode[blendMode]) {
|
|
496
|
+
if (reader.throwForMissingFeatures)
|
|
497
|
+
throw new Error(`Invalid blend mode: '${blendMode}'`);
|
|
498
|
+
reader.logMissingFeatures && reader.log(`Unknown blend mode: '${blendMode}', using normal blend mode`);
|
|
499
|
+
}
|
|
500
|
+
layer.blendMode = helpers_1.toBlendMode[blendMode] || 'normal';
|
|
425
501
|
layer.opacity = readUint8(reader) / 0xff;
|
|
426
502
|
layer.clipping = readUint8(reader) === 1;
|
|
427
503
|
const flags = readUint8(reader);
|
|
@@ -449,11 +525,20 @@ function readLayerRecord(reader, psd, imageResources) {
|
|
|
449
525
|
});
|
|
450
526
|
return { layer, channels };
|
|
451
527
|
}
|
|
528
|
+
// negative sizes are treated as empty (Photoshop writes them for some empty layers, like 0,0,-1,0 bounds of fill layer)
|
|
529
|
+
// checks if length of section (layer and mask info) at given offset fits in the file
|
|
530
|
+
function sectionFits(reader, offset) {
|
|
531
|
+
const lengthSize = reader.large ? 8 : 4;
|
|
532
|
+
if (offset < 0 || offset + lengthSize > reader.view.byteLength)
|
|
533
|
+
return false;
|
|
534
|
+
const length = reader.large ? reader.view.getUint32(offset) * 0x100000000 + reader.view.getUint32(offset + 4) : reader.view.getUint32(offset);
|
|
535
|
+
return offset + lengthSize + length <= reader.view.byteLength;
|
|
536
|
+
}
|
|
452
537
|
function isValidBoxSize(box, reader) {
|
|
453
538
|
const width = (box.right || 0) - (box.left || 0);
|
|
454
539
|
const height = (box.bottom || 0) - (box.top || 0);
|
|
455
540
|
const maxSize = reader.large ? 300000 : 30000;
|
|
456
|
-
return width
|
|
541
|
+
return width <= maxSize && height <= maxSize;
|
|
457
542
|
}
|
|
458
543
|
function readLayerMaskData(reader, layer) {
|
|
459
544
|
return readSection(reader, 1, left => {
|
|
@@ -472,7 +557,7 @@ function readLayerMaskData(reader, layer) {
|
|
|
472
557
|
mask.positionRelativeToLayer = (flags & 1 /* LayerMaskFlags.PositionRelativeToLayer */) !== 0;
|
|
473
558
|
mask.disabled = (flags & 2 /* LayerMaskFlags.LayerMaskDisabled */) !== 0;
|
|
474
559
|
mask.fromVectorData = (flags & 8 /* LayerMaskFlags.LayerMaskFromRenderingOtherData */) !== 0;
|
|
475
|
-
if (
|
|
560
|
+
if (hasRealMaskData(reader, flags, left())) {
|
|
476
561
|
const realMask = {};
|
|
477
562
|
layer.realMask = realMask;
|
|
478
563
|
const realFlags = readUint8(reader);
|
|
@@ -501,6 +586,43 @@ function readLayerMaskData(reader, layer) {
|
|
|
501
586
|
skipBytes(reader, left());
|
|
502
587
|
});
|
|
503
588
|
}
|
|
589
|
+
function maskParamsSize(params) {
|
|
590
|
+
return 1 +
|
|
591
|
+
((params & 1 /* MaskParams.UserMaskDensity */) ? 1 : 0) +
|
|
592
|
+
((params & 2 /* MaskParams.UserMaskFeather */) ? 8 : 0) +
|
|
593
|
+
((params & 4 /* MaskParams.VectorMaskDensity */) ? 1 : 0) +
|
|
594
|
+
((params & 8 /* MaskParams.VectorMaskFeather */) ? 8 : 0);
|
|
595
|
+
}
|
|
596
|
+
// checks if data at offset is mask parameters followed by up to 3 bytes of zero padding, ending exactly at offset + left
|
|
597
|
+
function isMaskParamsLayout(view, offset, left) {
|
|
598
|
+
if (left < 1)
|
|
599
|
+
return false;
|
|
600
|
+
const params = view.getUint8(offset);
|
|
601
|
+
if (params === 0 || (params & ~0x0f))
|
|
602
|
+
return false; // only 4 parameter flags exist
|
|
603
|
+
const size = maskParamsSize(params);
|
|
604
|
+
if (left < size || left - size > 3)
|
|
605
|
+
return false;
|
|
606
|
+
for (let i = size; i < left; i++) {
|
|
607
|
+
if (view.getUint8(offset + i) !== 0)
|
|
608
|
+
return false;
|
|
609
|
+
}
|
|
610
|
+
return true;
|
|
611
|
+
}
|
|
612
|
+
// real mask data (18 bytes) is followed by mask parameters (if flag is set) and padding
|
|
613
|
+
function hasRealMaskData(reader, flags, left) {
|
|
614
|
+
if (left < 18)
|
|
615
|
+
return false;
|
|
616
|
+
if (!(flags & 16 /* LayerMaskFlags.MaskHasParametersAppliedToIt */))
|
|
617
|
+
return true;
|
|
618
|
+
const { view, offset } = reader;
|
|
619
|
+
if (isMaskParamsLayout(view, offset, left))
|
|
620
|
+
return false;
|
|
621
|
+
if (isMaskParamsLayout(view, offset + 18, left - 18))
|
|
622
|
+
return true;
|
|
623
|
+
// unknown layout, assume real mask if there's enough data for it (and mask parameters)
|
|
624
|
+
return left === 18 || left - 18 >= maskParamsSize(view.getUint8(offset + 18));
|
|
625
|
+
}
|
|
504
626
|
function readBlendingRange(reader) {
|
|
505
627
|
return [readUint8(reader), readUint8(reader), readUint8(reader), readUint8(reader)];
|
|
506
628
|
}
|
|
@@ -535,9 +657,9 @@ function readLayerChannelImageData(reader, psd, layer, channels) {
|
|
|
535
657
|
reader.offset -= 1;
|
|
536
658
|
compression = readUint16(reader);
|
|
537
659
|
}
|
|
538
|
-
// try to fix broken files where there's 1 byte shift of channel
|
|
660
|
+
// try to fix broken files where there's 1 byte shift of channel (in the other direction)
|
|
539
661
|
if (compression > 3) {
|
|
540
|
-
reader.offset -=
|
|
662
|
+
reader.offset -= 4;
|
|
541
663
|
compression = readUint16(reader);
|
|
542
664
|
}
|
|
543
665
|
if (compression > 3)
|
|
@@ -567,8 +689,13 @@ function getCompositeImageData(psd) {
|
|
|
567
689
|
if (!data)
|
|
568
690
|
return undefined;
|
|
569
691
|
const reader = createReader(data.buffer, data.byteOffset, data.byteLength);
|
|
570
|
-
const
|
|
571
|
-
|
|
692
|
+
const info = psd.rawCompositeDataInfo;
|
|
693
|
+
if (info) {
|
|
694
|
+
reader.large = info.large;
|
|
695
|
+
reader.globalAlpha = info.globalAlpha;
|
|
696
|
+
reader.layersInGlobalInfo = !!info.layersInGlobalInfo;
|
|
697
|
+
}
|
|
698
|
+
return readImageData(reader, psd);
|
|
572
699
|
}
|
|
573
700
|
function getCompositeCanvas(psd) {
|
|
574
701
|
return imageDataToCanvasSafe(getCompositeImageData(psd));
|
|
@@ -610,18 +737,18 @@ function decodeLayerPixels(layer, useImageData) {
|
|
|
610
737
|
function decodeLayerImageData(layer, options) {
|
|
611
738
|
var _a, _b, _c;
|
|
612
739
|
let { throwForMissingFeatures, useImageData } = options;
|
|
613
|
-
const imageData = getDataFromLayer(layer, LayerDataType.Layer, throwForMissingFeatures, options.totalMemoryLimit);
|
|
740
|
+
const imageData = getDataFromLayer(layer, LayerDataType.Layer, throwForMissingFeatures, options.totalMemoryLimit, options);
|
|
614
741
|
setImageDataOrCanvas(layer, imageData, useImageData);
|
|
615
742
|
if (options.totalMemoryLimit !== undefined)
|
|
616
743
|
options.totalMemoryLimit -= (_a = imageData === null || imageData === void 0 ? void 0 : imageData.data.byteLength) !== null && _a !== void 0 ? _a : 0;
|
|
617
744
|
if (layer.mask) {
|
|
618
|
-
const maskData = getDataFromLayer(layer, LayerDataType.Mask, throwForMissingFeatures, options.totalMemoryLimit);
|
|
745
|
+
const maskData = getDataFromLayer(layer, LayerDataType.Mask, throwForMissingFeatures, options.totalMemoryLimit, options);
|
|
619
746
|
setImageDataOrCanvas(layer.mask, maskData, useImageData);
|
|
620
747
|
if (options.totalMemoryLimit !== undefined)
|
|
621
748
|
options.totalMemoryLimit -= (_b = maskData === null || maskData === void 0 ? void 0 : maskData.data.byteLength) !== null && _b !== void 0 ? _b : 0;
|
|
622
749
|
}
|
|
623
750
|
if (layer.realMask) {
|
|
624
|
-
const maskData = getDataFromLayer(layer, LayerDataType.RealMask, throwForMissingFeatures, options.totalMemoryLimit);
|
|
751
|
+
const maskData = getDataFromLayer(layer, LayerDataType.RealMask, throwForMissingFeatures, options.totalMemoryLimit, options);
|
|
625
752
|
setImageDataOrCanvas(layer.realMask, maskData, useImageData);
|
|
626
753
|
if (options.totalMemoryLimit !== undefined)
|
|
627
754
|
options.totalMemoryLimit -= (_c = maskData === null || maskData === void 0 ? void 0 : maskData.data.byteLength) !== null && _c !== void 0 ? _c : 0;
|
|
@@ -634,7 +761,8 @@ var LayerDataType;
|
|
|
634
761
|
LayerDataType[LayerDataType["Mask"] = 1] = "Mask";
|
|
635
762
|
LayerDataType[LayerDataType["RealMask"] = 2] = "RealMask";
|
|
636
763
|
})(LayerDataType || (LayerDataType = {}));
|
|
637
|
-
function getDataFromLayer(layer, read, throwForMissingFeatures, memoryLimit) {
|
|
764
|
+
function getDataFromLayer(layer, read, throwForMissingFeatures, memoryLimit, logOptions) {
|
|
765
|
+
var _a;
|
|
638
766
|
if (!layer.rawData)
|
|
639
767
|
return undefined;
|
|
640
768
|
const { colorMode, bitsPerChannel, channels, large } = layer.rawData;
|
|
@@ -646,22 +774,23 @@ function getDataFromLayer(layer, read, throwForMissingFeatures, memoryLimit) {
|
|
|
646
774
|
let initializedAlpha = false;
|
|
647
775
|
if (layerWidth && layerHeight && read === LayerDataType.Layer) {
|
|
648
776
|
if (cmyk) {
|
|
649
|
-
if (bitsPerChannel !== 8)
|
|
650
|
-
throw new Error('bitsPerChannel
|
|
651
|
-
imageData =
|
|
777
|
+
if (bitsPerChannel !== 8 && bitsPerChannel !== 16)
|
|
778
|
+
throw new Error('bitsPerChannel not supported');
|
|
779
|
+
imageData = createImageDataBitDepth(layerWidth, layerHeight, bitsPerChannel, 5, memoryLimit);
|
|
652
780
|
}
|
|
653
781
|
else {
|
|
654
782
|
imageData = createImageDataBitDepth(layerWidth, layerHeight, bitsPerChannel, 4, memoryLimit);
|
|
655
783
|
}
|
|
656
784
|
}
|
|
657
|
-
if (helpers_1.RAW_IMAGE_DATA) { // TODO: use layer.rawData instead
|
|
658
|
-
layer.imageDataRaw = [];
|
|
659
|
-
layer.imageDataRawCompression = [];
|
|
660
|
-
}
|
|
661
785
|
for (const { id, compression, data } of channels) {
|
|
662
786
|
if (!data)
|
|
663
787
|
continue;
|
|
664
788
|
const dataReader = createReader(data.buffer, data.byteOffset, data.byteLength);
|
|
789
|
+
dataReader.debug = !!(logOptions === null || logOptions === void 0 ? void 0 : logOptions.debug);
|
|
790
|
+
if (logOptions === null || logOptions === void 0 ? void 0 : logOptions.log)
|
|
791
|
+
dataReader.log = logOptions.log;
|
|
792
|
+
if (memoryLimit !== undefined)
|
|
793
|
+
dataReader.totalMemoryLimit = Math.max(0, memoryLimit - ((_a = imageData === null || imageData === void 0 ? void 0 : imageData.data.byteLength) !== null && _a !== void 0 ? _a : 0));
|
|
665
794
|
if (id === -2 /* ChannelID.UserMask */ || id === -3 /* ChannelID.RealUserMask */) {
|
|
666
795
|
if (id === -2 /* ChannelID.UserMask */ && read !== LayerDataType.Mask)
|
|
667
796
|
continue;
|
|
@@ -674,17 +803,9 @@ function getDataFromLayer(layer, read, throwForMissingFeatures, memoryLimit) {
|
|
|
674
803
|
const maskHeight = Math.max(0, (mask.bottom || 0) - (mask.top || 0));
|
|
675
804
|
if (maskWidth && maskHeight) {
|
|
676
805
|
maskData = createImageDataBitDepth(maskWidth, maskHeight, bitsPerChannel, 4, memoryLimit);
|
|
806
|
+
if (memoryLimit !== undefined)
|
|
807
|
+
dataReader.totalMemoryLimit = Math.max(0, dataReader.totalMemoryLimit - maskData.data.byteLength);
|
|
677
808
|
readData(dataReader, data.byteLength, maskData, compression, maskWidth, maskHeight, bitsPerChannel, 0, large, 4);
|
|
678
|
-
if (helpers_1.RAW_IMAGE_DATA) { // TODO: use layer.rawData instead
|
|
679
|
-
if (id === -2 /* ChannelID.UserMask */) {
|
|
680
|
-
layer.maskDataRawCompression = compression;
|
|
681
|
-
layer.maskDataRaw = data;
|
|
682
|
-
}
|
|
683
|
-
else {
|
|
684
|
-
layer.realMaskDataRawCompression = compression;
|
|
685
|
-
layer.realMaskDataRaw = data;
|
|
686
|
-
}
|
|
687
|
-
}
|
|
688
809
|
setupGrayscale(maskData);
|
|
689
810
|
resetAlpha(maskData, false);
|
|
690
811
|
}
|
|
@@ -701,24 +822,20 @@ function getDataFromLayer(layer, read, throwForMissingFeatures, memoryLimit) {
|
|
|
701
822
|
}
|
|
702
823
|
}
|
|
703
824
|
readData(dataReader, data.byteLength, targetData, compression, layerWidth, layerHeight, bitsPerChannel, offset, large, cmyk ? 5 : 4);
|
|
704
|
-
if (helpers_1.RAW_IMAGE_DATA) { // TODO: use layer.rawData instead
|
|
705
|
-
layer.imageDataRawCompression[id] = compression;
|
|
706
|
-
layer.imageDataRaw[id] = data;
|
|
707
|
-
}
|
|
708
|
-
if (targetData && colorMode === 1 /* ColorMode.Grayscale */) {
|
|
709
|
-
setupGrayscale(targetData);
|
|
710
|
-
}
|
|
711
825
|
}
|
|
712
826
|
if (id === -1 /* ChannelID.Transparency */) {
|
|
713
827
|
initializedAlpha = true;
|
|
714
828
|
}
|
|
715
829
|
}
|
|
716
830
|
if (imageData) {
|
|
831
|
+
if (colorMode === 1 /* ColorMode.Grayscale */)
|
|
832
|
+
setupGrayscale(imageData);
|
|
717
833
|
if (!initializedAlpha)
|
|
718
834
|
resetAlpha(imageData, cmyk);
|
|
719
835
|
if (cmyk) {
|
|
720
836
|
const cmykData = imageData;
|
|
721
|
-
|
|
837
|
+
const rgbMemoryLimit = memoryLimit === undefined ? undefined : Math.max(0, memoryLimit - cmykData.data.byteLength);
|
|
838
|
+
imageData = createImageDataBitDepth(cmykData.width, cmykData.height, bitsPerChannel, 4, rgbMemoryLimit);
|
|
722
839
|
cmykToRgb(cmykData, imageData, false);
|
|
723
840
|
}
|
|
724
841
|
}
|
|
@@ -729,7 +846,7 @@ function readData(reader, length, pixels, compression, width, height, bitDepth,
|
|
|
729
846
|
return;
|
|
730
847
|
if (compression === 0 /* Compression.RawData */) {
|
|
731
848
|
if (length !== (width * height * Math.floor(bitDepth / 8))) {
|
|
732
|
-
reader
|
|
849
|
+
warnOrThrow(reader, `Invalid length (${length}, ${width * height * Math.floor(bitDepth / 8)})`);
|
|
733
850
|
}
|
|
734
851
|
const data = readBytes(reader, length);
|
|
735
852
|
readDataRaw(data, pixels, bitDepth, step, offset);
|
|
@@ -740,11 +857,11 @@ function readData(reader, length, pixels, compression, width, height, bitDepth,
|
|
|
740
857
|
}
|
|
741
858
|
else if (compression === 2 /* Compression.ZipWithoutPrediction */) {
|
|
742
859
|
const data = readBytes(reader, length);
|
|
743
|
-
readDataZip(data, pixels, width, height, bitDepth, step, offset, false);
|
|
860
|
+
readDataZip(data, pixels, width, height, bitDepth, step, offset, false, reader);
|
|
744
861
|
}
|
|
745
862
|
else if (compression === 3 /* Compression.ZipWithPrediction */) {
|
|
746
863
|
const data = readBytes(reader, length);
|
|
747
|
-
readDataZip(data, pixels, width, height, bitDepth, step, offset, true);
|
|
864
|
+
readDataZip(data, pixels, width, height, bitDepth, step, offset, true, reader);
|
|
748
865
|
}
|
|
749
866
|
else {
|
|
750
867
|
throw new Error(`Invalid Compression type: ${compression}`);
|
|
@@ -759,7 +876,7 @@ function readGlobalLayerMaskInfo(reader) {
|
|
|
759
876
|
const colorSpace2 = readUint16(reader);
|
|
760
877
|
const colorSpace3 = readUint16(reader);
|
|
761
878
|
const colorSpace4 = readUint16(reader);
|
|
762
|
-
const opacity = readUint16(reader) /
|
|
879
|
+
const opacity = readUint16(reader) / 100; // 0 - 100
|
|
763
880
|
const kind = readUint8(reader);
|
|
764
881
|
skipBytes(reader, left()); // 3 bytes of padding ?
|
|
765
882
|
return { overlayColorSpace, colorSpace1, colorSpace2, colorSpace3, colorSpace4, opacity, kind };
|
|
@@ -791,7 +908,15 @@ function readAdditionalLayerInfo(reader, target, psd, imageResources) {
|
|
|
791
908
|
const sig = realignWithSignature(reader, isValidAdditionalInfoSignature);
|
|
792
909
|
const key = readSignature(reader);
|
|
793
910
|
// `largeAdditionalInfoKeys` fallback, because some keys don't have 8B64 signature even when they are 64bit
|
|
794
|
-
|
|
911
|
+
let u64 = sig === '8B64' || (reader.large && helpers_1.largeAdditionalInfoKeys.indexOf(key) !== -1);
|
|
912
|
+
// these keys were written by older versions of this library with 32bit length and 8BIM signature,
|
|
913
|
+
// 64bit length always has high 32bits set to 0 (lengths over 4GB are not supported)
|
|
914
|
+
if (reader.large && sig === '8BIM' && helpers_1.largeAdditionalInfoKeysObserved.indexOf(key) !== -1) {
|
|
915
|
+
// 32bit length of 0 is followed by signature of the next block, 64bit length would continue with its low 32bits
|
|
916
|
+
const next = reader.offset + 8 <= reader.view.byteLength ? reader.view.getUint32(reader.offset + 4) : 0;
|
|
917
|
+
const nextIsSignature = next === 0x3842494d /* 8BIM */ || next === 0x38423634 /* 8B64 */;
|
|
918
|
+
u64 = reader.view.getUint32(reader.offset) === 0 && !nextIsSignature;
|
|
919
|
+
}
|
|
795
920
|
readSection(reader, 2, left => {
|
|
796
921
|
const handler = additionalInfo_1.infoHandlersMap[key];
|
|
797
922
|
if (handler) {
|
|
@@ -804,7 +929,7 @@ function readAdditionalLayerInfo(reader, target, psd, imageResources) {
|
|
|
804
929
|
}
|
|
805
930
|
}
|
|
806
931
|
else {
|
|
807
|
-
reader.logMissingFeatures && reader.log(`Unhandled additional info: ${key}`);
|
|
932
|
+
reader.logMissingFeatures && key !== 'GenI' && key !== 'OCIO' && key !== 'CAI ' && reader.log(`Unhandled additional info: ${key}`);
|
|
808
933
|
skipBytes(reader, left());
|
|
809
934
|
}
|
|
810
935
|
if (left()) {
|
|
@@ -836,14 +961,15 @@ function createImageDataBitDepth(width, height, bitDepth, channels, memoryLimit)
|
|
|
836
961
|
}
|
|
837
962
|
}
|
|
838
963
|
function readImageData(reader, psd) {
|
|
839
|
-
var _a;
|
|
964
|
+
var _a, _b, _c;
|
|
840
965
|
const compression = readUint16(reader);
|
|
841
966
|
const bitsPerChannel = (_a = psd.bitsPerChannel) !== null && _a !== void 0 ? _a : 8;
|
|
967
|
+
const { width, height } = psd;
|
|
842
968
|
if (exports.supportedColorModes.indexOf(psd.colorMode) === -1)
|
|
843
969
|
throw new Error(`Color mode not supported: ${psd.colorMode}`);
|
|
844
970
|
if (compression !== 0 /* Compression.RawData */ && compression !== 1 /* Compression.RleCompressed */)
|
|
845
971
|
throw new Error(`Compression type not supported: ${compression}`);
|
|
846
|
-
const imageData = createImageDataBitDepth(
|
|
972
|
+
const imageData = createImageDataBitDepth(width, height, bitsPerChannel, 4, reader.totalMemoryLimit);
|
|
847
973
|
if (reader.totalMemoryLimit !== undefined)
|
|
848
974
|
reader.totalMemoryLimit -= imageData.data.byteLength;
|
|
849
975
|
(0, helpers_1.resetImageData)(imageData);
|
|
@@ -851,47 +977,23 @@ function readImageData(reader, psd) {
|
|
|
851
977
|
case 0 /* ColorMode.Bitmap */: {
|
|
852
978
|
if (bitsPerChannel !== 1)
|
|
853
979
|
throw new Error('Invalid bitsPerChannel for bitmap color mode');
|
|
980
|
+
const rowBytes = Math.ceil(width / 8);
|
|
854
981
|
let bytes;
|
|
855
982
|
if (compression === 0 /* Compression.RawData */) {
|
|
856
|
-
bytes = readBytes(reader,
|
|
857
|
-
}
|
|
858
|
-
else if (compression === 1 /* Compression.RleCompressed */) {
|
|
859
|
-
bytes = new Uint8Array(psd.width * psd.height);
|
|
860
|
-
readDataRLE(reader, { data: bytes, width: psd.width, height: psd.height }, psd.width, psd.height, 8, 1, [0], reader.large);
|
|
983
|
+
bytes = readBytes(reader, rowBytes * height);
|
|
861
984
|
}
|
|
862
985
|
else {
|
|
863
|
-
|
|
986
|
+
bytes = new Uint8Array(rowBytes * height);
|
|
987
|
+
readDataRLE(reader, { data: bytes, width: rowBytes, height }, rowBytes, height, 8, 1, [0], reader.large);
|
|
864
988
|
}
|
|
865
|
-
(0, helpers_1.decodeBitmap)(bytes, imageData.data,
|
|
989
|
+
(0, helpers_1.decodeBitmap)(bytes, imageData.data, width, height);
|
|
866
990
|
break;
|
|
867
991
|
}
|
|
868
992
|
case 3 /* ColorMode.RGB */:
|
|
869
993
|
case 1 /* ColorMode.Grayscale */: {
|
|
870
|
-
const
|
|
871
|
-
|
|
872
|
-
|
|
873
|
-
// TODO: store these channels in additional image data
|
|
874
|
-
channels.push(i);
|
|
875
|
-
}
|
|
876
|
-
}
|
|
877
|
-
else if (reader.globalAlpha) {
|
|
878
|
-
channels.push(3);
|
|
879
|
-
}
|
|
880
|
-
if (compression === 0 /* Compression.RawData */) {
|
|
881
|
-
for (let i = 0; i < channels.length; i++) {
|
|
882
|
-
const data = readBytes(reader, psd.width * psd.height * Math.floor(bitsPerChannel / 8));
|
|
883
|
-
readDataRaw(data, imageData, bitsPerChannel, 4, channels[i]);
|
|
884
|
-
}
|
|
885
|
-
}
|
|
886
|
-
else if (compression === 1 /* Compression.RleCompressed */) {
|
|
887
|
-
const start = reader.offset;
|
|
888
|
-
readDataRLE(reader, imageData, psd.width, psd.height, bitsPerChannel, 4, channels, reader.large);
|
|
889
|
-
if (helpers_1.RAW_IMAGE_DATA)
|
|
890
|
-
psd.imageDataRaw = new Uint8Array(reader.view.buffer, reader.view.byteOffset + start, reader.offset - start);
|
|
891
|
-
}
|
|
892
|
-
else {
|
|
893
|
-
throw new Error(`Compression not supported: ${compression}`);
|
|
894
|
-
}
|
|
994
|
+
const colorChannels = psd.colorMode === 1 /* ColorMode.Grayscale */ ? 1 : 3;
|
|
995
|
+
const offsets = compositeChannelOffsets(reader, psd, colorChannels, 3);
|
|
996
|
+
readCompositeChannels(reader, compression, imageData, bitsPerChannel, 4, offsets);
|
|
895
997
|
if (psd.colorMode === 1 /* ColorMode.Grayscale */) {
|
|
896
998
|
setupGrayscale(imageData);
|
|
897
999
|
}
|
|
@@ -899,288 +1001,450 @@ function readImageData(reader, psd) {
|
|
|
899
1001
|
}
|
|
900
1002
|
case 2 /* ColorMode.Indexed */: {
|
|
901
1003
|
if (bitsPerChannel !== 8)
|
|
902
|
-
throw new Error('bitsPerChannel
|
|
903
|
-
if (psd.channels !== 1)
|
|
904
|
-
throw new Error('Invalid channel count');
|
|
1004
|
+
throw new Error('bitsPerChannel not supported');
|
|
905
1005
|
if (!psd.palette)
|
|
906
1006
|
throw new Error('Missing color palette');
|
|
907
|
-
|
|
908
|
-
|
|
909
|
-
|
|
910
|
-
|
|
911
|
-
const indexedImageData = {
|
|
912
|
-
width: imageData.width,
|
|
913
|
-
height: imageData.height,
|
|
914
|
-
data: new Uint8Array(imageData.width * imageData.height),
|
|
915
|
-
};
|
|
916
|
-
readDataRLE(reader, indexedImageData, psd.width, psd.height, bitsPerChannel, 1, [0], reader.large);
|
|
917
|
-
indexedToRgb(indexedImageData, imageData, psd.palette);
|
|
918
|
-
}
|
|
919
|
-
else {
|
|
920
|
-
throw new Error(`Compression not supported: ${compression}`);
|
|
921
|
-
}
|
|
1007
|
+
const offsets = compositeChannelOffsets(reader, psd, 1, -1);
|
|
1008
|
+
const indexedImageData = { width, height, data: new Uint8Array(width * height) };
|
|
1009
|
+
readCompositeChannels(reader, compression, indexedImageData, 8, 1, offsets);
|
|
1010
|
+
indexedToRgb(indexedImageData, imageData, psd.palette, (_c = (_b = psd.imageResources) === null || _b === void 0 ? void 0 : _b.transparencyIndex) !== null && _c !== void 0 ? _c : -1);
|
|
922
1011
|
break;
|
|
923
1012
|
}
|
|
924
1013
|
case 4 /* ColorMode.CMYK */: {
|
|
925
|
-
if (bitsPerChannel !== 8)
|
|
926
|
-
throw new Error('bitsPerChannel
|
|
927
|
-
|
|
928
|
-
|
|
929
|
-
|
|
930
|
-
|
|
931
|
-
|
|
932
|
-
|
|
933
|
-
|
|
934
|
-
|
|
935
|
-
|
|
936
|
-
|
|
937
|
-
|
|
938
|
-
|
|
939
|
-
|
|
940
|
-
|
|
941
|
-
|
|
942
|
-
|
|
943
|
-
|
|
944
|
-
|
|
945
|
-
|
|
946
|
-
|
|
947
|
-
|
|
948
|
-
|
|
949
|
-
|
|
1014
|
+
if (bitsPerChannel !== 8 && bitsPerChannel !== 16)
|
|
1015
|
+
throw new Error('bitsPerChannel not supported');
|
|
1016
|
+
const offsets = compositeChannelOffsets(reader, psd, 4, 4);
|
|
1017
|
+
const cmykImageData = createImageDataBitDepth(width, height, bitsPerChannel, 5, reader.totalMemoryLimit);
|
|
1018
|
+
resetAlpha(cmykImageData, true);
|
|
1019
|
+
readCompositeChannels(reader, compression, cmykImageData, bitsPerChannel, 5, offsets);
|
|
1020
|
+
cmykToRgb(cmykImageData, imageData, !!reader.globalAlpha);
|
|
1021
|
+
break;
|
|
1022
|
+
}
|
|
1023
|
+
default: throw new Error(`Color mode not supported: ${psd.colorMode}`);
|
|
1024
|
+
}
|
|
1025
|
+
// remove weird white matte (already removed when converting from CMYK)
|
|
1026
|
+
if (reader.globalAlpha && psd.colorMode !== 4 /* ColorMode.CMYK */) {
|
|
1027
|
+
removeWhiteMatte(imageData);
|
|
1028
|
+
}
|
|
1029
|
+
return imageData;
|
|
1030
|
+
}
|
|
1031
|
+
// returns target offset for each of the composite image channels stored in the file, -1 for channels that are skipped
|
|
1032
|
+
function compositeChannelOffsets(reader, psd, colorChannels, alphaOffset) {
|
|
1033
|
+
var _a, _b, _c;
|
|
1034
|
+
const channelCount = (_a = psd.channels) !== null && _a !== void 0 ? _a : (colorChannels + (reader.globalAlpha ? 1 : 0));
|
|
1035
|
+
if (channelCount < colorChannels)
|
|
1036
|
+
throw new Error(`Invalid channel count: ${channelCount}`);
|
|
1037
|
+
// First extra channel is transparency if document has global alpha (negative layer count). Some non-Photoshop
|
|
1038
|
+
// applications write transparency channel without setting global alpha, in that case extra channel is not
|
|
1039
|
+
// listed in alpha channel names (Photoshop names all user alpha channels / saved selections).
|
|
1040
|
+
// Photoshop also lists merged transparency as first alpha channel named "Transparency" (in documents marked with
|
|
1041
|
+
// Mtrn/Mt16/Mt32 additional info, or in some 16bit documents with layers stored in Lr16 without any other mark)
|
|
1042
|
+
const alphaNames = (_c = (_b = psd.imageResources) === null || _b === void 0 ? void 0 : _b.alphaChannelNames) !== null && _c !== void 0 ? _c : [];
|
|
1043
|
+
const namedChannels = alphaNames.length;
|
|
1044
|
+
const namedTransparency = !!reader.layersInGlobalInfo && alphaNames[0] === 'Transparency' && namedChannels === channelCount - colorChannels;
|
|
1045
|
+
const hasTransparency = reader.globalAlpha || namedTransparency || (channelCount - colorChannels) > namedChannels;
|
|
1046
|
+
const offsets = [];
|
|
1047
|
+
for (let i = 0; i < channelCount; i++) {
|
|
1048
|
+
if (i < colorChannels) {
|
|
1049
|
+
offsets.push(i);
|
|
1050
|
+
}
|
|
1051
|
+
else if (i === colorChannels && hasTransparency && alphaOffset >= 0) {
|
|
1052
|
+
offsets.push(alphaOffset);
|
|
1053
|
+
}
|
|
1054
|
+
else {
|
|
1055
|
+
// TODO: store these channels (alpha channels / saved selections, spot colors) in additional image data
|
|
1056
|
+
offsets.push(-1);
|
|
1057
|
+
}
|
|
1058
|
+
}
|
|
1059
|
+
return offsets;
|
|
1060
|
+
}
|
|
1061
|
+
function readCompositeChannels(reader, compression, pixelData, bitsPerChannel, step, offsets) {
|
|
1062
|
+
if (compression === 0 /* Compression.RawData */) {
|
|
1063
|
+
const channelSize = pixelData.width * pixelData.height * bytesPerValue(bitsPerChannel);
|
|
1064
|
+
for (const offset of offsets) {
|
|
1065
|
+
if (offset < 0) {
|
|
1066
|
+
skipBytes(reader, channelSize);
|
|
950
1067
|
}
|
|
951
1068
|
else {
|
|
952
|
-
|
|
1069
|
+
readDataRaw(readBytes(reader, channelSize), pixelData, bitsPerChannel, step, offset);
|
|
1070
|
+
}
|
|
1071
|
+
}
|
|
1072
|
+
}
|
|
1073
|
+
else if (compression === 1 /* Compression.RleCompressed */) {
|
|
1074
|
+
readDataRLE(reader, pixelData, pixelData.width, pixelData.height, bitsPerChannel, step, offsets, reader.large);
|
|
1075
|
+
}
|
|
1076
|
+
else {
|
|
1077
|
+
throw new Error(`Compression not supported: ${compression}`);
|
|
1078
|
+
}
|
|
1079
|
+
}
|
|
1080
|
+
// un-premultiplies colors from white matte, used for composite image of documents with global alpha
|
|
1081
|
+
function removeWhiteMatte({ data, width, height }) {
|
|
1082
|
+
const size = width * height * 4;
|
|
1083
|
+
if (data instanceof Float32Array) {
|
|
1084
|
+
for (let i = 0; i < size; i += 4) {
|
|
1085
|
+
const a = data[i + 3];
|
|
1086
|
+
if (a > 0 && a < 1) {
|
|
1087
|
+
const ra = 1 / a;
|
|
1088
|
+
const invA = 1 - ra;
|
|
1089
|
+
for (let j = 0; j < 3; j++) {
|
|
1090
|
+
const v = data[i + j] * ra + invA;
|
|
1091
|
+
data[i + j] = v < 0 ? 0 : v;
|
|
1092
|
+
}
|
|
1093
|
+
}
|
|
1094
|
+
}
|
|
1095
|
+
}
|
|
1096
|
+
else if (data instanceof Uint16Array) {
|
|
1097
|
+
for (let i = 0; i < size; i += 4) {
|
|
1098
|
+
const pa = data[i + 3];
|
|
1099
|
+
if (pa !== 0 && pa !== 0xffff) {
|
|
1100
|
+
const ra = 0xffff / pa;
|
|
1101
|
+
const invA = 0xffff * (1 - ra);
|
|
1102
|
+
for (let j = 0; j < 3; j++) {
|
|
1103
|
+
const v = Math.round(data[i + j] * ra + invA);
|
|
1104
|
+
data[i + j] = v < 0 ? 0 : (v > 0xffff ? 0xffff : v);
|
|
1105
|
+
}
|
|
953
1106
|
}
|
|
954
|
-
break;
|
|
955
1107
|
}
|
|
956
|
-
default: throw new Error(`Color mode not supported: ${psd.colorMode}`);
|
|
957
1108
|
}
|
|
958
|
-
|
|
959
|
-
|
|
960
|
-
if (psd.bitsPerChannel !== 8)
|
|
961
|
-
throw new Error('bitsPerChannel Not supproted');
|
|
962
|
-
const p = imageData.data;
|
|
963
|
-
const size = imageData.width * imageData.height * 4;
|
|
1109
|
+
else {
|
|
1110
|
+
// Uint8ClampedArray clamps and rounds values on assignment
|
|
964
1111
|
for (let i = 0; i < size; i += 4) {
|
|
965
|
-
const pa =
|
|
1112
|
+
const pa = data[i + 3];
|
|
966
1113
|
if (pa != 0 && pa != 255) {
|
|
967
1114
|
const a = pa / 255;
|
|
968
1115
|
const ra = 1 / a;
|
|
969
1116
|
const invA = 255 * (1 - ra);
|
|
970
|
-
|
|
971
|
-
|
|
972
|
-
|
|
1117
|
+
data[i + 0] = data[i + 0] * ra + invA;
|
|
1118
|
+
data[i + 1] = data[i + 1] * ra + invA;
|
|
1119
|
+
data[i + 2] = data[i + 2] * ra + invA;
|
|
973
1120
|
}
|
|
974
1121
|
}
|
|
975
1122
|
}
|
|
976
|
-
return imageData;
|
|
977
1123
|
}
|
|
978
|
-
|
|
1124
|
+
// converts value in 0-255 range to target range, rounding and clamping it
|
|
1125
|
+
function toRange(value, max) {
|
|
1126
|
+
value = Math.round(value * (max / 255));
|
|
1127
|
+
return value <= 0 ? 0 : (value >= max ? max : value);
|
|
1128
|
+
}
|
|
1129
|
+
// Polynomial approximation of U.S. Web Coated (SWOP) v2 CMYK to RGB conversion (default CMYK profile of Photoshop).
|
|
1130
|
+
// Coefficients are taken from pdf.js (DeviceCmykCS in src/core/colorspace.js, https://github.com/mozilla/pdf.js),
|
|
1131
|
+
// Copyright 2012 Mozilla Foundation, licensed under the Apache License, Version 2.0
|
|
1132
|
+
// (http://www.apache.org/licenses/LICENSE-2.0)
|
|
1133
|
+
// cmyk - 8 or 16 bit data with 5 channels (cmyka), rgb - 8 or 16 bit data with 4 channels (rgba)
|
|
1134
|
+
// removeMatte - colors are blended with white (no ink), used for composite image of documents with global alpha
|
|
1135
|
+
function cmykToRgb(cmyk, rgb, removeMatte) {
|
|
979
1136
|
const size = rgb.width * rgb.height * 4;
|
|
980
1137
|
const srcData = cmyk.data;
|
|
981
1138
|
const dstData = rgb.data;
|
|
1139
|
+
const srcMax = srcData instanceof Uint16Array ? 0xffff : 0xff;
|
|
1140
|
+
const dstMax = dstData instanceof Uint16Array ? 0xffff : 0xff;
|
|
982
1141
|
for (let src = 0, dst = 0; dst < size; src += 5, dst += 4) {
|
|
983
|
-
const
|
|
984
|
-
|
|
985
|
-
|
|
986
|
-
|
|
987
|
-
|
|
988
|
-
|
|
989
|
-
|
|
990
|
-
|
|
991
|
-
|
|
992
|
-
|
|
993
|
-
|
|
994
|
-
|
|
995
|
-
|
|
996
|
-
|
|
997
|
-
|
|
998
|
-
|
|
999
|
-
|
|
1000
|
-
|
|
1001
|
-
|
|
1002
|
-
|
|
1003
|
-
|
|
1142
|
+
const a = srcData[src + 4];
|
|
1143
|
+
// image data stores inverted values (0 = 100% ink)
|
|
1144
|
+
let c = 1 - srcData[src] / srcMax;
|
|
1145
|
+
let m = 1 - srcData[src + 1] / srcMax;
|
|
1146
|
+
let y = 1 - srcData[src + 2] / srcMax;
|
|
1147
|
+
let k = 1 - srcData[src + 3] / srcMax;
|
|
1148
|
+
if (removeMatte && a > 0 && a < srcMax) {
|
|
1149
|
+
const ra = srcMax / a;
|
|
1150
|
+
c = Math.min(1, c * ra);
|
|
1151
|
+
m = Math.min(1, m * ra);
|
|
1152
|
+
y = Math.min(1, y * ra);
|
|
1153
|
+
k = Math.min(1, k * ra);
|
|
1154
|
+
}
|
|
1155
|
+
dstData[dst] = toRange(255 +
|
|
1156
|
+
c * (-4.387332384609988 * c + 54.48615194189176 * m + 18.82290502165302 * y + 212.25662451639585 * k - 285.2331026137004) +
|
|
1157
|
+
m * (1.7149763477362134 * m - 5.6096736904047315 * y - 17.873870861415444 * k - 5.497006427196366) +
|
|
1158
|
+
y * (-2.5217340131683033 * y - 21.248923337353073 * k + 17.5119270841813) +
|
|
1159
|
+
k * (-21.86122147463605 * k - 189.48180835922747), dstMax);
|
|
1160
|
+
dstData[dst + 1] = toRange(255 +
|
|
1161
|
+
c * (8.841041422036149 * c + 60.118027045597366 * m + 6.871425592049007 * y + 31.159100130055922 * k - 79.2970844816548) +
|
|
1162
|
+
m * (-15.310361306967817 * m + 17.575251261109482 * y + 131.35250912493976 * k - 190.9453302588951) +
|
|
1163
|
+
y * (4.444339102852739 * y + 9.8632861493405 * k - 24.86741582555878) +
|
|
1164
|
+
k * (-20.737325471181034 * k - 187.80453709719578), dstMax);
|
|
1165
|
+
dstData[dst + 2] = toRange(255 +
|
|
1166
|
+
c * (0.8842522430003296 * c + 8.078677503112928 * m + 30.89978309703729 * y - 0.23883238689178934 * k - 14.183576799673286) +
|
|
1167
|
+
m * (10.49593273432072 * m + 63.02378494754052 * y + 50.606957656360734 * k - 112.23884253719248) +
|
|
1168
|
+
y * (0.03296041114873217 * y + 115.60384449646641 * k - 193.58209356861505) +
|
|
1169
|
+
k * (-22.33816807309886 * k - 180.12613974708367), dstMax);
|
|
1170
|
+
dstData[dst + 3] = srcMax === dstMax ? a : Math.round(a * dstMax / srcMax);
|
|
1171
|
+
}
|
|
1172
|
+
}
|
|
1173
|
+
// transparencyIndex - palette index of transparent color (-1 if there's none)
|
|
1174
|
+
function indexedToRgb(indexed, rgb, palette, transparencyIndex) {
|
|
1004
1175
|
const size = indexed.width * indexed.height;
|
|
1005
1176
|
const srcData = indexed.data;
|
|
1006
1177
|
const dstData = rgb.data;
|
|
1007
1178
|
for (let src = 0, dst = 0; src < size; src++, dst += 4) {
|
|
1008
|
-
const
|
|
1179
|
+
const index = srcData[src];
|
|
1180
|
+
const c = palette[index];
|
|
1009
1181
|
dstData[dst + 0] = c.r;
|
|
1010
1182
|
dstData[dst + 1] = c.g;
|
|
1011
1183
|
dstData[dst + 2] = c.b;
|
|
1012
|
-
dstData[dst + 3] = 255;
|
|
1184
|
+
dstData[dst + 3] = index === transparencyIndex ? 0 : 255;
|
|
1013
1185
|
}
|
|
1014
1186
|
}
|
|
1015
|
-
|
|
1016
|
-
|
|
1017
|
-
|
|
1187
|
+
// number of bytes used to store single channel value, 1-bit data is handled as bytes
|
|
1188
|
+
function bytesPerValue(bitDepth) {
|
|
1189
|
+
switch (bitDepth) {
|
|
1190
|
+
case 1:
|
|
1191
|
+
case 8: return 1;
|
|
1192
|
+
case 16: return 2;
|
|
1193
|
+
case 32: return 4;
|
|
1194
|
+
default: throw new Error(`Invalid bitDepth (${bitDepth})`);
|
|
1018
1195
|
}
|
|
1019
1196
|
}
|
|
1020
|
-
function
|
|
1021
|
-
if (
|
|
1022
|
-
|
|
1197
|
+
function verifyArrayType(data, bytesPerValue) {
|
|
1198
|
+
if (data.BYTES_PER_ELEMENT !== bytesPerValue) {
|
|
1199
|
+
throw new Error('Invalid array types');
|
|
1023
1200
|
}
|
|
1024
|
-
|
|
1025
|
-
|
|
1026
|
-
|
|
1027
|
-
|
|
1028
|
-
|
|
1029
|
-
|
|
1030
|
-
|
|
1031
|
-
|
|
1032
|
-
|
|
1033
|
-
|
|
1034
|
-
|
|
1035
|
-
|
|
1036
|
-
}
|
|
1037
|
-
else {
|
|
1038
|
-
return new Uint16Array(bytes.buffer, bytes.byteOffset, bytes.byteLength / 2);
|
|
1201
|
+
}
|
|
1202
|
+
// NOTE: `buffer` may alias input file data, it must not be modified here
|
|
1203
|
+
function readDataRaw(buffer, pixelData, bitDepth, step, offset) {
|
|
1204
|
+
const bpv = bytesPerValue(bitDepth);
|
|
1205
|
+
if (!pixelData || offset < 0 || offset >= step)
|
|
1206
|
+
return;
|
|
1207
|
+
const data = pixelData.data;
|
|
1208
|
+
verifyArrayType(data, bpv);
|
|
1209
|
+
const size = Math.min(pixelData.width * pixelData.height, Math.floor(buffer.byteLength / bpv)) | 0;
|
|
1210
|
+
if (bpv === 1) {
|
|
1211
|
+
for (let i = 0, p = offset | 0; i < size; i++, p = (p + step) | 0) {
|
|
1212
|
+
data[p] = buffer[i];
|
|
1039
1213
|
}
|
|
1040
1214
|
}
|
|
1041
|
-
else if (
|
|
1042
|
-
|
|
1043
|
-
|
|
1044
|
-
new Uint8Array(result.buffer, result.byteOffset, result.byteLength).set(bytes);
|
|
1045
|
-
return result;
|
|
1046
|
-
}
|
|
1047
|
-
else {
|
|
1048
|
-
return new Float32Array(bytes.buffer, bytes.byteOffset, bytes.byteLength / 4);
|
|
1215
|
+
else if (bpv === 2) {
|
|
1216
|
+
for (let i = 0, j = 0, p = offset | 0; i < size; i++, j += 2, p = (p + step) | 0) {
|
|
1217
|
+
data[p] = (buffer[j] << 8) | buffer[j + 1];
|
|
1049
1218
|
}
|
|
1050
1219
|
}
|
|
1051
1220
|
else {
|
|
1052
|
-
|
|
1053
|
-
|
|
1054
|
-
|
|
1055
|
-
function copyChannelToPixelData(pixelData, channel, offset, step) {
|
|
1056
|
-
verifyCompatible(pixelData.data, channel);
|
|
1057
|
-
const size = pixelData.width * pixelData.height;
|
|
1058
|
-
const data = pixelData.data;
|
|
1059
|
-
for (let i = 0, p = offset | 0; i < size; i++, p = (p + step) | 0) {
|
|
1060
|
-
data[p] = channel[i];
|
|
1061
|
-
}
|
|
1062
|
-
}
|
|
1063
|
-
function readDataRaw(buffer, pixelData, bitDepth, step, offset) {
|
|
1064
|
-
if (bitDepth == 32) {
|
|
1065
|
-
for (let i = 0; i < buffer.byteLength; i += 4) {
|
|
1066
|
-
const a = buffer[i + 0];
|
|
1067
|
-
const b = buffer[i + 1];
|
|
1068
|
-
const c = buffer[i + 2];
|
|
1069
|
-
const d = buffer[i + 3];
|
|
1070
|
-
buffer[i + 0] = d;
|
|
1071
|
-
buffer[i + 1] = c;
|
|
1072
|
-
buffer[i + 2] = b;
|
|
1073
|
-
buffer[i + 3] = a;
|
|
1221
|
+
const view = new DataView(buffer.buffer, buffer.byteOffset, buffer.byteLength);
|
|
1222
|
+
for (let i = 0, j = 0, p = offset | 0; i < size; i++, j += 4, p = (p + step) | 0) {
|
|
1223
|
+
data[p] = view.getFloat32(j, false);
|
|
1074
1224
|
}
|
|
1075
1225
|
}
|
|
1076
|
-
const array = bytesToArray(buffer, bitDepth);
|
|
1077
|
-
if (pixelData && offset < step) {
|
|
1078
|
-
copyChannelToPixelData(pixelData, array, offset, step);
|
|
1079
|
-
}
|
|
1080
1226
|
}
|
|
1081
|
-
function decodePredicted(data, width, height
|
|
1227
|
+
function decodePredicted(data, width, height) {
|
|
1082
1228
|
for (let y = 0; y < height; y++) {
|
|
1083
1229
|
const offset = y * width;
|
|
1084
1230
|
for (let x = 1, o = offset + 1; x < width; x++, o++) {
|
|
1085
|
-
data[o] =
|
|
1086
|
-
}
|
|
1087
|
-
}
|
|
1088
|
-
}
|
|
1089
|
-
|
|
1090
|
-
|
|
1091
|
-
|
|
1092
|
-
|
|
1093
|
-
|
|
1094
|
-
|
|
1095
|
-
|
|
1096
|
-
|
|
1097
|
-
|
|
1098
|
-
|
|
1099
|
-
|
|
1100
|
-
|
|
1101
|
-
|
|
1102
|
-
|
|
1103
|
-
|
|
1104
|
-
|
|
1105
|
-
|
|
1106
|
-
|
|
1107
|
-
|
|
1108
|
-
|
|
1109
|
-
|
|
1110
|
-
|
|
1111
|
-
|
|
1112
|
-
|
|
1113
|
-
|
|
1114
|
-
|
|
1231
|
+
data[o] = data[o - 1] + data[o]; // Uint8Array wraps values on assignment
|
|
1232
|
+
}
|
|
1233
|
+
}
|
|
1234
|
+
}
|
|
1235
|
+
const zipOverflow = new Error('Decompressed data exceeds expected size');
|
|
1236
|
+
// inflates data, stopping when output exceeds expected size (protects against zip bombs)
|
|
1237
|
+
function inflateLimited(compressed, expectedSize, reader) {
|
|
1238
|
+
const output = new Uint8Array(expectedSize);
|
|
1239
|
+
let position = 0;
|
|
1240
|
+
let overflow = false;
|
|
1241
|
+
const inflator = new pako_1.Inflate({ chunkSize: Math.max(1024, Math.min(expectedSize, 0x10000)) });
|
|
1242
|
+
inflator.onData = (chunk) => {
|
|
1243
|
+
const data = chunk;
|
|
1244
|
+
const count = Math.min(data.length, expectedSize - position);
|
|
1245
|
+
if (count > 0)
|
|
1246
|
+
output.set(count === data.length ? data : data.subarray(0, count), position);
|
|
1247
|
+
position += count;
|
|
1248
|
+
if (count < data.length) {
|
|
1249
|
+
overflow = true;
|
|
1250
|
+
throw zipOverflow;
|
|
1251
|
+
}
|
|
1252
|
+
};
|
|
1253
|
+
inflator.onEnd = (status) => {
|
|
1254
|
+
var _a;
|
|
1255
|
+
// default handler collects all the chunks into result, we only need the error status
|
|
1256
|
+
inflator.err = status;
|
|
1257
|
+
inflator.msg = ((_a = inflator.strm) === null || _a === void 0 ? void 0 : _a.msg) || '';
|
|
1258
|
+
};
|
|
1259
|
+
try {
|
|
1260
|
+
inflator.push(compressed, true);
|
|
1261
|
+
}
|
|
1262
|
+
catch (e) {
|
|
1263
|
+
if (e !== zipOverflow)
|
|
1264
|
+
throw e;
|
|
1265
|
+
}
|
|
1266
|
+
if (overflow) {
|
|
1267
|
+
if (reader)
|
|
1268
|
+
warnOrThrow(reader, zipOverflow.message);
|
|
1269
|
+
}
|
|
1270
|
+
else if (inflator.err) {
|
|
1271
|
+
throw new Error(`Invalid zip data: ${inflator.msg}`);
|
|
1272
|
+
}
|
|
1273
|
+
return output;
|
|
1274
|
+
}
|
|
1275
|
+
function readDataZip(compressed, pixelData, width, height, bitDepth, step, offset, prediction, reader) {
|
|
1276
|
+
const bpv = bytesPerValue(bitDepth);
|
|
1277
|
+
if (!pixelData || offset < 0 || offset >= step)
|
|
1278
|
+
return;
|
|
1279
|
+
const data = pixelData.data;
|
|
1280
|
+
verifyArrayType(data, bpv);
|
|
1281
|
+
const expectedSize = width * height * bpv;
|
|
1282
|
+
if (reader)
|
|
1283
|
+
consumeMemory(reader, expectedSize);
|
|
1284
|
+
try {
|
|
1285
|
+
const decompressed = inflateLimited(compressed, expectedSize, reader);
|
|
1286
|
+
if (bpv === 1) {
|
|
1287
|
+
for (let y = 0, i = 0; y < height; y++) {
|
|
1288
|
+
let prev = 0;
|
|
1289
|
+
for (let x = 0, p = offset + y * width * step; x < width; x++, i++, p += step) {
|
|
1290
|
+
let v = decompressed[i];
|
|
1291
|
+
if (prediction)
|
|
1292
|
+
v = prev = (prev + v) & 0xff;
|
|
1293
|
+
data[p] = v;
|
|
1294
|
+
}
|
|
1295
|
+
}
|
|
1296
|
+
}
|
|
1297
|
+
else if (bpv === 2) {
|
|
1298
|
+
for (let y = 0, i = 0; y < height; y++) {
|
|
1299
|
+
let prev = 0;
|
|
1300
|
+
for (let x = 0, p = offset + y * width * step; x < width; x++, i += 2, p += step) {
|
|
1301
|
+
let v = (decompressed[i] << 8) | decompressed[i + 1];
|
|
1302
|
+
if (prediction)
|
|
1303
|
+
v = prev = (prev + v) & 0xffff;
|
|
1304
|
+
data[p] = v;
|
|
1115
1305
|
}
|
|
1116
1306
|
}
|
|
1117
1307
|
}
|
|
1118
1308
|
else {
|
|
1119
|
-
|
|
1309
|
+
const dst = new Uint32Array(data.buffer, data.byteOffset, data.length);
|
|
1310
|
+
if (prediction) {
|
|
1311
|
+
// bytes are delta encoded per row and stored in planar order (all 1st bytes, all 2nd bytes, ...)
|
|
1312
|
+
decodePredicted(decompressed, width * 4, height);
|
|
1313
|
+
for (let y = 0, di = offset; y < height; y++) {
|
|
1314
|
+
let a = width * 4 * y;
|
|
1315
|
+
for (let x = 0; x < width; x++, a++, di += step) {
|
|
1316
|
+
const b = a + width;
|
|
1317
|
+
const c = b + width;
|
|
1318
|
+
const d = c + width;
|
|
1319
|
+
dst[di] = ((decompressed[a] << 24) | (decompressed[b] << 16) | (decompressed[c] << 8) | decompressed[d]) >>> 0;
|
|
1320
|
+
}
|
|
1321
|
+
}
|
|
1322
|
+
}
|
|
1323
|
+
else {
|
|
1324
|
+
const view = new DataView(decompressed.buffer, decompressed.byteOffset, decompressed.byteLength);
|
|
1325
|
+
for (let i = 0, j = 0, di = offset, size = width * height; i < size; i++, j += 4, di += step) {
|
|
1326
|
+
dst[di] = view.getUint32(j, false);
|
|
1327
|
+
}
|
|
1328
|
+
}
|
|
1120
1329
|
}
|
|
1121
1330
|
}
|
|
1122
|
-
|
|
1123
|
-
|
|
1124
|
-
|
|
1125
|
-
|
|
1126
|
-
|
|
1127
|
-
|
|
1128
|
-
|
|
1129
|
-
|
|
1130
|
-
|
|
1131
|
-
|
|
1331
|
+
finally {
|
|
1332
|
+
if (reader)
|
|
1333
|
+
recoverMemory(reader, expectedSize);
|
|
1334
|
+
}
|
|
1335
|
+
}
|
|
1336
|
+
// decodes single PackBits row, returns number of decoded values
|
|
1337
|
+
function decodeRLERow(src, i, end, dst, p, step, count) {
|
|
1338
|
+
let x = 0;
|
|
1339
|
+
while (i < end && x < count) {
|
|
1340
|
+
const header = src[i++];
|
|
1341
|
+
if (header > 128) {
|
|
1342
|
+
if (i >= end)
|
|
1343
|
+
break;
|
|
1344
|
+
const value = src[i++];
|
|
1345
|
+
let n = 257 - header;
|
|
1346
|
+
if (n > count - x)
|
|
1347
|
+
n = count - x;
|
|
1348
|
+
x += n;
|
|
1349
|
+
for (; n > 0; n--, p += step) {
|
|
1350
|
+
dst[p] = value;
|
|
1351
|
+
}
|
|
1352
|
+
}
|
|
1353
|
+
else if (header < 128) {
|
|
1354
|
+
let n = header + 1;
|
|
1355
|
+
if (n > count - x)
|
|
1356
|
+
n = count - x;
|
|
1357
|
+
if (n > end - i)
|
|
1358
|
+
n = end - i;
|
|
1359
|
+
x += n;
|
|
1360
|
+
for (; n > 0; n--, p += step) {
|
|
1361
|
+
dst[p] = src[i++];
|
|
1132
1362
|
}
|
|
1133
1363
|
}
|
|
1364
|
+
else {
|
|
1365
|
+
// ignore 128
|
|
1366
|
+
}
|
|
1367
|
+
}
|
|
1368
|
+
return x;
|
|
1369
|
+
}
|
|
1370
|
+
// converts big-endian 16/32-bit values from bytes and writes them to `data` starting at `p` with `step`
|
|
1371
|
+
function scatterValues(bytes, count, bpv, data, p, step) {
|
|
1372
|
+
if (bpv === 2) {
|
|
1373
|
+
for (let x = 0, i = 0; x < count; x++, i += 2, p += step) {
|
|
1374
|
+
data[p] = (bytes[i] << 8) | bytes[i + 1];
|
|
1375
|
+
}
|
|
1134
1376
|
}
|
|
1135
1377
|
else {
|
|
1136
|
-
|
|
1137
|
-
|
|
1138
|
-
|
|
1139
|
-
for (let y = 0; y < height; y++, li++) {
|
|
1140
|
-
lengths[li] = readUint16(reader);
|
|
1141
|
-
}
|
|
1378
|
+
const view = new DataView(bytes.buffer, bytes.byteOffset, bytes.byteLength);
|
|
1379
|
+
for (let x = 0, i = 0; x < count; x++, i += 4, p += step) {
|
|
1380
|
+
data[p] = view.getFloat32(i, false);
|
|
1142
1381
|
}
|
|
1143
1382
|
}
|
|
1144
|
-
|
|
1145
|
-
|
|
1146
|
-
|
|
1147
|
-
|
|
1148
|
-
|
|
1149
|
-
|
|
1150
|
-
|
|
1151
|
-
|
|
1152
|
-
|
|
1383
|
+
}
|
|
1384
|
+
// decodes RLE rows of single channel, each row starts at the beginning of the image row
|
|
1385
|
+
function decodeRLEChannel(src, lengths, li, pos, width, height, data, offset, step, bpv, row) {
|
|
1386
|
+
const srcEnd = src.length;
|
|
1387
|
+
const rowSize = width * bpv;
|
|
1388
|
+
for (let y = 0; y < height; y++, li++) {
|
|
1389
|
+
const rowStart = pos < srcEnd ? pos : srcEnd;
|
|
1390
|
+
pos += lengths[li];
|
|
1391
|
+
const rowEnd = pos < srcEnd ? pos : srcEnd;
|
|
1392
|
+
if (rowStart >= rowEnd)
|
|
1393
|
+
continue;
|
|
1394
|
+
if (bpv === 1) {
|
|
1395
|
+
decodeRLERow(src, rowStart, rowEnd, data, offset + y * width * step, step, width);
|
|
1153
1396
|
}
|
|
1154
1397
|
else {
|
|
1155
|
-
|
|
1156
|
-
|
|
1157
|
-
|
|
1158
|
-
|
|
1159
|
-
|
|
1160
|
-
|
|
1161
|
-
|
|
1162
|
-
|
|
1163
|
-
|
|
1164
|
-
|
|
1165
|
-
|
|
1166
|
-
|
|
1167
|
-
|
|
1168
|
-
|
|
1169
|
-
|
|
1170
|
-
|
|
1171
|
-
|
|
1172
|
-
|
|
1173
|
-
|
|
1174
|
-
|
|
1175
|
-
|
|
1176
|
-
|
|
1177
|
-
|
|
1178
|
-
|
|
1179
|
-
|
|
1398
|
+
const decoded = decodeRLERow(src, rowStart, rowEnd, row, 0, 1, rowSize);
|
|
1399
|
+
scatterValues(row, Math.floor(decoded / bpv), bpv, data, offset + y * width * step, step);
|
|
1400
|
+
}
|
|
1401
|
+
}
|
|
1402
|
+
}
|
|
1403
|
+
function readDataRLE(reader, pixelData, width, height, bitDepth, step, offsets, large) {
|
|
1404
|
+
const data = pixelData && pixelData.data;
|
|
1405
|
+
const rows = offsets.length * height;
|
|
1406
|
+
const lengthSize = large ? 4 : 2;
|
|
1407
|
+
consumeMemory(reader, rows * lengthSize);
|
|
1408
|
+
try {
|
|
1409
|
+
if ((reader.offset + rows * lengthSize) > reader.view.byteLength) {
|
|
1410
|
+
throw new Error('RLE row lengths exceed buffer length');
|
|
1411
|
+
}
|
|
1412
|
+
const lengths = large ? new Uint32Array(rows) : new Uint16Array(rows);
|
|
1413
|
+
let total = 0;
|
|
1414
|
+
if (large) {
|
|
1415
|
+
for (let i = 0; i < rows; i++)
|
|
1416
|
+
total += (lengths[i] = readUint32(reader));
|
|
1417
|
+
}
|
|
1418
|
+
else {
|
|
1419
|
+
for (let i = 0; i < rows; i++)
|
|
1420
|
+
total += (lengths[i] = readUint16(reader));
|
|
1421
|
+
}
|
|
1422
|
+
const start = reader.offset;
|
|
1423
|
+
const srcEnd = reader.view.byteLength;
|
|
1424
|
+
if (total > (srcEnd - start)) {
|
|
1425
|
+
// broken or truncated file, decode only data that is available
|
|
1426
|
+
warnOrThrow(reader, 'RLE data exceeds buffer length');
|
|
1427
|
+
}
|
|
1428
|
+
const bpv = bytesPerValue(bitDepth);
|
|
1429
|
+
if (data)
|
|
1430
|
+
verifyArrayType(data, bpv);
|
|
1431
|
+
const src = new Uint8Array(reader.view.buffer, reader.view.byteOffset, srcEnd);
|
|
1432
|
+
const row = (data && bpv > 1) ? new Uint8Array(width * bpv) : undefined;
|
|
1433
|
+
let pos = start;
|
|
1434
|
+
for (let c = 0, li = 0; c < offsets.length; c++, li += height) {
|
|
1435
|
+
const offset = offsets[c] | 0;
|
|
1436
|
+
const channelStart = pos;
|
|
1437
|
+
for (let y = 0; y < height; y++)
|
|
1438
|
+
pos += lengths[li + y];
|
|
1439
|
+
if (data && offset >= 0 && offset < step) {
|
|
1440
|
+
decodeRLEChannel(src, lengths, li, channelStart, width, height, data, offset, step, bpv, row);
|
|
1180
1441
|
}
|
|
1181
1442
|
}
|
|
1443
|
+
reader.offset = pos;
|
|
1444
|
+
}
|
|
1445
|
+
finally {
|
|
1446
|
+
recoverMemory(reader, rows * lengthSize);
|
|
1182
1447
|
}
|
|
1183
|
-
recoverMemory(reader, lengths.byteLength);
|
|
1184
1448
|
}
|
|
1185
1449
|
function readSection(reader, round, func, skipEmpty = true, eightBytes = false) {
|
|
1186
1450
|
let length = readUint32(reader);
|
|
@@ -1228,19 +1492,19 @@ function readColor(reader) {
|
|
|
1228
1492
|
skipBytes(reader, 2);
|
|
1229
1493
|
return { h, s, b };
|
|
1230
1494
|
}
|
|
1231
|
-
case 2 /* ColorSpace.CMYK */: {
|
|
1232
|
-
const c = readUint16(reader) / 257;
|
|
1233
|
-
const m = readUint16(reader) / 257;
|
|
1234
|
-
const y = readUint16(reader) / 257;
|
|
1235
|
-
const k = readUint16(reader) / 257;
|
|
1495
|
+
case 2 /* ColorSpace.CMYK */: { // 0 = 100% ink
|
|
1496
|
+
const c = (0xffff - readUint16(reader)) / 257;
|
|
1497
|
+
const m = (0xffff - readUint16(reader)) / 257;
|
|
1498
|
+
const y = (0xffff - readUint16(reader)) / 257;
|
|
1499
|
+
const k = (0xffff - readUint16(reader)) / 257;
|
|
1236
1500
|
return { c, m, y, k };
|
|
1237
1501
|
}
|
|
1238
1502
|
case 7 /* ColorSpace.Lab */: {
|
|
1239
1503
|
const l = readInt16(reader) / 10000;
|
|
1240
1504
|
const ta = readInt16(reader);
|
|
1241
1505
|
const tb = readInt16(reader);
|
|
1242
|
-
const a =
|
|
1243
|
-
const b =
|
|
1506
|
+
const a = (0, helpers_1.normalizeLabAB)(ta, 100);
|
|
1507
|
+
const b = (0, helpers_1.normalizeLabAB)(tb, 100);
|
|
1244
1508
|
skipBytes(reader, 2);
|
|
1245
1509
|
return { l, a, b };
|
|
1246
1510
|
}
|
|
@@ -1253,6 +1517,24 @@ function readColor(reader) {
|
|
|
1253
1517
|
throw new Error('Invalid color space');
|
|
1254
1518
|
}
|
|
1255
1519
|
}
|
|
1520
|
+
const MAX_PATTERN_SIZE = 30000;
|
|
1521
|
+
// reads pattern, unsupported or invalid patterns are skipped (returns undefined) unless throwForMissingFeatures is set
|
|
1522
|
+
function readPatternSafe(reader) {
|
|
1523
|
+
let length = reader.view.getUint32(reader.offset);
|
|
1524
|
+
while (length % 4)
|
|
1525
|
+
length++;
|
|
1526
|
+
const end = reader.offset + 4 + length;
|
|
1527
|
+
try {
|
|
1528
|
+
return readPattern(reader);
|
|
1529
|
+
}
|
|
1530
|
+
catch (e) {
|
|
1531
|
+
if (reader.throwForMissingFeatures)
|
|
1532
|
+
throw e;
|
|
1533
|
+
reader.logMissingFeatures && reader.log(`Skipped pattern: ${e.message}`);
|
|
1534
|
+
reader.offset = end;
|
|
1535
|
+
return undefined;
|
|
1536
|
+
}
|
|
1537
|
+
}
|
|
1256
1538
|
function readPattern(reader) {
|
|
1257
1539
|
let length = readUint32(reader);
|
|
1258
1540
|
while (length % 4)
|
|
@@ -1264,8 +1546,8 @@ function readPattern(reader) {
|
|
|
1264
1546
|
const colorMode = readUint32(reader);
|
|
1265
1547
|
const x = readInt16(reader);
|
|
1266
1548
|
const y = readInt16(reader);
|
|
1267
|
-
// we only support RGB and
|
|
1268
|
-
if (colorMode !== 3 /* ColorMode.RGB */ && colorMode !== 1 /* ColorMode.Grayscale */ && colorMode !== 2 /* ColorMode.Indexed */) {
|
|
1549
|
+
// we only support RGB, grayscale, indexed and CMYK for now
|
|
1550
|
+
if (colorMode !== 3 /* ColorMode.RGB */ && colorMode !== 1 /* ColorMode.Grayscale */ && colorMode !== 2 /* ColorMode.Indexed */ && colorMode !== 4 /* ColorMode.CMYK */) {
|
|
1269
1551
|
throw new Error(`Unsupported pattern color mode: ${colorMode}`);
|
|
1270
1552
|
}
|
|
1271
1553
|
let name = readUnicodeString(reader);
|
|
@@ -1279,7 +1561,9 @@ function readPattern(reader) {
|
|
|
1279
1561
|
b: readUint8(reader),
|
|
1280
1562
|
});
|
|
1281
1563
|
}
|
|
1282
|
-
|
|
1564
|
+
// some writers follow the palette with 4 extra bytes (unknown)
|
|
1565
|
+
if (reader.view.getUint32(reader.offset) !== 3)
|
|
1566
|
+
skipBytes(reader, 4);
|
|
1283
1567
|
}
|
|
1284
1568
|
// virtual memory array list
|
|
1285
1569
|
const version2 = readUint32(reader);
|
|
@@ -1293,17 +1577,37 @@ function readPattern(reader) {
|
|
|
1293
1577
|
const channelsCount = readUint32(reader);
|
|
1294
1578
|
const width = right - left;
|
|
1295
1579
|
const height = bottom - top;
|
|
1580
|
+
if (width < 0 || height < 0)
|
|
1581
|
+
throw new Error('Invalid pattern bounds');
|
|
1582
|
+
if (width > MAX_PATTERN_SIZE || height > MAX_PATTERN_SIZE)
|
|
1583
|
+
throw new Error(`Pattern too large (${width}x${height})`);
|
|
1584
|
+
if (channelsCount > 64)
|
|
1585
|
+
throw new Error(`Invalid pattern channel count: ${channelsCount}`);
|
|
1586
|
+
// channels cover whole pattern, each 2 bytes of RLE data can produce at most 128 bytes
|
|
1587
|
+
if (width * height > (Math.min(end, reader.view.byteLength) - reader.offset) * 64)
|
|
1588
|
+
throw new Error('Pattern data out of bounds');
|
|
1296
1589
|
const size = width * height * 4;
|
|
1297
1590
|
consumeMemory(reader, size);
|
|
1298
1591
|
const data = new Uint8Array(size);
|
|
1299
1592
|
for (let i = 3; i < data.byteLength; i += 4) {
|
|
1300
1593
|
data[i] = 255;
|
|
1301
1594
|
}
|
|
1302
|
-
|
|
1595
|
+
// first written arrays are color channels, next one is transparency (Photoshop stores it in the last, sheet mask, array)
|
|
1596
|
+
const colorChannels = colorMode === 3 /* ColorMode.RGB */ ? 3 : (colorMode === 4 /* ColorMode.CMYK */ ? 4 : 1);
|
|
1597
|
+
// CMYK channels are decoded into separate buffer (5 values per pixel, inverted, 0 = 100% ink) and converted to RGB at the end
|
|
1598
|
+
const cmyk = colorMode === 4 /* ColorMode.CMYK */ ? new Uint8Array(width * height * 5).fill(255) : undefined;
|
|
1599
|
+
if (cmyk)
|
|
1600
|
+
consumeMemory(reader, cmyk.byteLength);
|
|
1601
|
+
const alphaTarget = cmyk ? 4 : 3;
|
|
1602
|
+
for (let i = 0, ch = 0; i < (channelsCount + 2) && reader.offset + 4 <= end; i++) {
|
|
1303
1603
|
const has = readUint32(reader);
|
|
1304
1604
|
if (!has)
|
|
1305
1605
|
continue;
|
|
1306
1606
|
const length = readUint32(reader);
|
|
1607
|
+
if (!length)
|
|
1608
|
+
continue;
|
|
1609
|
+
if (length < 23)
|
|
1610
|
+
throw new Error('Invalid pattern channel length');
|
|
1307
1611
|
const pixelDepth = readUint32(reader);
|
|
1308
1612
|
const ctop = readUint32(reader);
|
|
1309
1613
|
const cleft = readUint32(reader);
|
|
@@ -1311,97 +1615,92 @@ function readPattern(reader) {
|
|
|
1311
1615
|
const cright = readUint32(reader);
|
|
1312
1616
|
const pixelDepth2 = readUint16(reader);
|
|
1313
1617
|
const compressionMode = readUint8(reader); // 0 - raw, 1 - rle
|
|
1314
|
-
const
|
|
1315
|
-
const
|
|
1316
|
-
if (
|
|
1317
|
-
throw new Error(
|
|
1318
|
-
|
|
1618
|
+
const cdata = readBytes(reader, length - 23);
|
|
1619
|
+
const depth = (pixelDepth2 === 8 || pixelDepth2 === 16 || pixelDepth2 === 32) ? pixelDepth2 : pixelDepth;
|
|
1620
|
+
if (depth !== 8 && depth !== 16 && depth !== 32)
|
|
1621
|
+
throw new Error(`Unsupported pattern pixel depth: ${depth}`);
|
|
1622
|
+
if (ctop < top || cleft < left || cbottom > bottom || cright > right || cbottom < ctop || cright < cleft) {
|
|
1623
|
+
throw new Error('Pattern channel bounds outside of pattern bounds');
|
|
1624
|
+
}
|
|
1625
|
+
const target = ch < colorChannels ? ch : (ch === colorChannels ? alphaTarget : -1);
|
|
1626
|
+
ch++;
|
|
1627
|
+
if (target === -1)
|
|
1628
|
+
continue;
|
|
1319
1629
|
const w = cright - cleft;
|
|
1320
1630
|
const h = cbottom - ctop;
|
|
1321
|
-
const
|
|
1322
|
-
const
|
|
1631
|
+
const rowBytes = w * (depth / 8);
|
|
1632
|
+
const channelSize = rowBytes * h;
|
|
1633
|
+
let bytes;
|
|
1323
1634
|
if (compressionMode === 0) {
|
|
1324
|
-
if (
|
|
1325
|
-
|
|
1326
|
-
|
|
1327
|
-
const src = x + y * w;
|
|
1328
|
-
const dst = (ox + x + (y + oy) * width) * 4;
|
|
1329
|
-
data[dst + ch] = cdata[src];
|
|
1330
|
-
}
|
|
1331
|
-
}
|
|
1332
|
-
}
|
|
1333
|
-
else if (colorMode === 1 /* ColorMode.Grayscale */ && ch < 1) {
|
|
1334
|
-
for (let y = 0; y < h; y++) {
|
|
1335
|
-
for (let x = 0; x < w; x++) {
|
|
1336
|
-
const src = x + y * w;
|
|
1337
|
-
const dst = (ox + x + (y + oy) * width) * 4;
|
|
1338
|
-
const value = cdata[src];
|
|
1339
|
-
data[dst + 0] = value;
|
|
1340
|
-
data[dst + 1] = value;
|
|
1341
|
-
data[dst + 2] = value;
|
|
1342
|
-
}
|
|
1343
|
-
}
|
|
1344
|
-
}
|
|
1345
|
-
else if (colorMode === 2 /* ColorMode.Indexed */) {
|
|
1346
|
-
for (let y = 0; y < h; y++) {
|
|
1347
|
-
for (let x = 0; x < w; x++) {
|
|
1348
|
-
const src = x + y * w;
|
|
1349
|
-
const dst = (ox + x + (y + oy) * width) * 4;
|
|
1350
|
-
const index = cdata[src];
|
|
1351
|
-
const color = palette[index];
|
|
1352
|
-
data[dst + 0] = color.r;
|
|
1353
|
-
data[dst + 1] = color.g;
|
|
1354
|
-
data[dst + 2] = color.b;
|
|
1355
|
-
}
|
|
1356
|
-
}
|
|
1357
|
-
}
|
|
1358
|
-
else {
|
|
1359
|
-
if (reader.throwForMissingFeatures)
|
|
1360
|
-
throw new Error('Invalid color pattern');
|
|
1361
|
-
}
|
|
1635
|
+
if (cdata.byteLength < channelSize)
|
|
1636
|
+
throw new Error('Pattern channel data out of bounds');
|
|
1637
|
+
bytes = cdata;
|
|
1362
1638
|
}
|
|
1363
1639
|
else if (compressionMode === 1) {
|
|
1364
|
-
|
|
1365
|
-
|
|
1366
|
-
|
|
1640
|
+
// each 2 bytes of RLE data can produce at most 128 bytes
|
|
1641
|
+
if (channelSize > cdata.byteLength * 64)
|
|
1642
|
+
throw new Error('Pattern channel data out of bounds');
|
|
1643
|
+
consumeMemory(reader, channelSize);
|
|
1644
|
+
bytes = new Uint8Array(channelSize);
|
|
1367
1645
|
const cdataReader = createReader(cdata.buffer, cdata.byteOffset, cdata.byteLength);
|
|
1368
|
-
|
|
1369
|
-
readDataRLE(cdataReader, tempData, w, h, 8, 1, [0], false);
|
|
1370
|
-
copyChannelToRGBA(tempData, pixelData, ox, oy, ch);
|
|
1371
|
-
}
|
|
1372
|
-
if (colorMode === 1 /* ColorMode.Grayscale */ && ch < 1) {
|
|
1373
|
-
readDataRLE(cdataReader, tempData, w, h, 8, 1, [0], false);
|
|
1374
|
-
copyChannelToRGBA(tempData, pixelData, ox, oy, 0);
|
|
1375
|
-
setupGrayscale(pixelData);
|
|
1376
|
-
}
|
|
1377
|
-
if (colorMode === 2 /* ColorMode.Indexed */) {
|
|
1378
|
-
// TODO:
|
|
1379
|
-
throw new Error('Indexed pattern color mode not implemented');
|
|
1380
|
-
}
|
|
1381
|
-
recoverMemory(reader, w * h);
|
|
1646
|
+
readDataRLE(cdataReader, { data: bytes, width: rowBytes, height: h }, rowBytes, h, 8, 1, [0], false);
|
|
1382
1647
|
}
|
|
1383
1648
|
else {
|
|
1384
1649
|
throw new Error('Invalid pattern compression mode');
|
|
1385
1650
|
}
|
|
1386
|
-
|
|
1651
|
+
const ox = cleft - left;
|
|
1652
|
+
const oy = ctop - top;
|
|
1653
|
+
for (let y = 0; y < h; y++) {
|
|
1654
|
+
for (let x = 0; x < w; x++) {
|
|
1655
|
+
const src = x + y * w;
|
|
1656
|
+
const dst = (ox + x + (y + oy) * width) * 4;
|
|
1657
|
+
const value = depth === 8 ? bytes[src] : patternValueTo8bit(bytes, src, depth);
|
|
1658
|
+
if (cmyk) {
|
|
1659
|
+
cmyk[(ox + x + (y + oy) * width) * 5 + target] = value;
|
|
1660
|
+
}
|
|
1661
|
+
else if (target === 3) {
|
|
1662
|
+
data[dst + 3] = value;
|
|
1663
|
+
}
|
|
1664
|
+
else if (colorMode === 2 /* ColorMode.Indexed */) {
|
|
1665
|
+
const color = palette[value];
|
|
1666
|
+
data[dst + 0] = color.r;
|
|
1667
|
+
data[dst + 1] = color.g;
|
|
1668
|
+
data[dst + 2] = color.b;
|
|
1669
|
+
}
|
|
1670
|
+
else if (colorMode === 1 /* ColorMode.Grayscale */) {
|
|
1671
|
+
data[dst + 0] = value;
|
|
1672
|
+
data[dst + 1] = value;
|
|
1673
|
+
data[dst + 2] = value;
|
|
1674
|
+
}
|
|
1675
|
+
else {
|
|
1676
|
+
data[dst + target] = value;
|
|
1677
|
+
}
|
|
1678
|
+
}
|
|
1679
|
+
}
|
|
1680
|
+
if (compressionMode === 1)
|
|
1681
|
+
recoverMemory(reader, channelSize);
|
|
1682
|
+
}
|
|
1683
|
+
if (cmyk) {
|
|
1684
|
+
cmykToRgb({ width, height, data: cmyk }, { width, height, data }, false);
|
|
1685
|
+
recoverMemory(reader, cmyk.byteLength);
|
|
1387
1686
|
}
|
|
1388
1687
|
reader.offset = end;
|
|
1389
1688
|
return { id, name, x, y, bounds: { x: left, y: top, w: width, h: height }, data };
|
|
1390
1689
|
}
|
|
1391
|
-
|
|
1392
|
-
|
|
1393
|
-
|
|
1394
|
-
|
|
1395
|
-
|
|
1396
|
-
|
|
1397
|
-
|
|
1398
|
-
|
|
1399
|
-
|
|
1400
|
-
dstData.data[dst + offset] = value;
|
|
1401
|
-
}
|
|
1690
|
+
// converts big-endian 16bit or 32bit (float) value at given index to 8bit value
|
|
1691
|
+
function patternValueTo8bit(bytes, index, depth) {
|
|
1692
|
+
if (depth === 16) {
|
|
1693
|
+
return Math.round(((bytes[index * 2] << 8) | bytes[index * 2 + 1]) / 257);
|
|
1694
|
+
}
|
|
1695
|
+
else {
|
|
1696
|
+
const view = new DataView(bytes.buffer, bytes.byteOffset + index * 4, 4);
|
|
1697
|
+
const value = view.getFloat32(0, false);
|
|
1698
|
+
return value > 0 ? (value < 1 ? Math.round(value * 255) : 255) : 0;
|
|
1402
1699
|
}
|
|
1403
1700
|
}
|
|
1404
1701
|
function consumeMemory(reader, size) {
|
|
1702
|
+
if (!(size >= 0))
|
|
1703
|
+
throw new Error('Invalid size');
|
|
1405
1704
|
if (reader.totalMemoryLimit !== undefined) {
|
|
1406
1705
|
if (reader.totalMemoryLimit < size)
|
|
1407
1706
|
throw new Error('Exceeded memory limit');
|