ag-psd 32.0.0 → 34.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.v8-cache/v24.7.0-x64-ef5a0af0/03e19709 +0 -0
- package/.v8-cache/v24.7.0-x64-ef5a0af0/05135617 +0 -0
- package/.v8-cache/v24.7.0-x64-ef5a0af0/1130885f +0 -0
- package/.v8-cache/v24.7.0-x64-ef5a0af0/226e2801 +0 -0
- package/.v8-cache/v24.7.0-x64-ef5a0af0/2a19133e +0 -0
- package/.v8-cache/v24.7.0-x64-ef5a0af0/4511bacf +0 -0
- package/.v8-cache/v24.7.0-x64-ef5a0af0/68416cb2 +0 -0
- package/.v8-cache/v24.7.0-x64-ef5a0af0/71df9cb3 +0 -0
- package/.v8-cache/v24.7.0-x64-ef5a0af0/7ea06cea +0 -0
- package/.v8-cache/v24.7.0-x64-ef5a0af0/87cf2209 +0 -0
- package/.v8-cache/v24.7.0-x64-ef5a0af0/932f0770 +0 -0
- package/.v8-cache/v24.7.0-x64-ef5a0af0/acc36e66 +0 -0
- package/.v8-cache/v24.7.0-x64-ef5a0af0/b3c2fab7 +0 -0
- package/.v8-cache/v24.7.0-x64-ef5a0af0/c0b789d2 +0 -0
- package/.v8-cache/v24.7.0-x64-ef5a0af0/c314aece +0 -0
- package/.v8-cache/v24.7.0-x64-ef5a0af0/cfc49f4f +0 -0
- package/.v8-cache/v24.7.0-x64-ef5a0af0/cff9434a +0 -0
- package/.v8-cache/v24.7.0-x64-ef5a0af0/d13d7610 +0 -0
- package/.v8-cache/v24.7.0-x64-ef5a0af0/dc86f000 +0 -0
- package/.v8-cache/v24.7.0-x64-ef5a0af0/e03e2acd +0 -0
- package/.v8-cache/v24.7.0-x64-ef5a0af0/e6745c2b +0 -0
- package/.v8-cache/v24.7.0-x64-ef5a0af0/e6b99945 +0 -0
- package/.v8-cache/v24.7.0-x64-ef5a0af0/eeeebdfe +0 -0
- package/.v8-cache/v24.7.0-x64-ef5a0af0/f6fd8125 +0 -0
- package/CHANGELOG.md +100 -0
- package/LICENSE +5 -0
- package/README.md +4 -7
- package/README_PSD.md +261 -84
- package/TODO.md +13 -84
- package/dist/abr.d.ts +11 -4
- package/dist/abr.js +362 -230
- package/dist/abr.js.map +1 -1
- package/dist/additionalInfo.d.ts +56 -2
- package/dist/additionalInfo.js +828 -361
- 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 +3321 -1364
- package/dist/descriptor.d.ts +28 -13
- package/dist/descriptor.js +295 -241
- package/dist/descriptor.js.map +1 -1
- package/dist/helpers.d.ts +7 -1
- package/dist/helpers.js +246 -4
- package/dist/helpers.js.map +1 -1
- package/dist/imageResources.d.ts +15 -3
- package/dist/imageResources.js +586 -144
- package/dist/imageResources.js.map +1 -1
- package/dist/index.d.ts +1 -0
- package/dist/index.js +1 -0
- package/dist/index.js.map +1 -1
- package/dist/jpeg.js +8 -8
- package/dist/jpeg.js.map +1 -1
- package/dist/psd.d.ts +292 -19
- package/dist/psd.js.map +1 -1
- package/dist/psdReader.d.ts +10 -0
- package/dist/psdReader.js +424 -216
- package/dist/psdReader.js.map +1 -1
- package/dist/psdWriter.d.ts +2 -0
- package/dist/psdWriter.js +373 -113
- package/dist/psdWriter.js.map +1 -1
- package/dist/text.d.ts +32 -1
- package/dist/text.js +75 -6
- package/dist/text.js.map +1 -1
- package/dist/utf8.js +0 -3
- package/dist/utf8.js.map +1 -1
- package/dist-es/abr.d.ts +11 -4
- package/dist-es/abr.js +362 -230
- package/dist-es/abr.js.map +1 -1
- package/dist-es/additionalInfo.d.ts +56 -2
- package/dist-es/additionalInfo.js +828 -361
- 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 +28 -13
- package/dist-es/descriptor.js +295 -241
- package/dist-es/descriptor.js.map +1 -1
- package/dist-es/helpers.d.ts +7 -1
- package/dist-es/helpers.js +246 -4
- package/dist-es/helpers.js.map +1 -1
- package/dist-es/imageResources.d.ts +15 -3
- package/dist-es/imageResources.js +586 -144
- package/dist-es/imageResources.js.map +1 -1
- package/dist-es/index.d.ts +1 -0
- package/dist-es/index.js +1 -0
- package/dist-es/index.js.map +1 -1
- package/dist-es/jpeg.js +8 -8
- package/dist-es/jpeg.js.map +1 -1
- package/dist-es/psd.d.ts +292 -19
- package/dist-es/psd.js.map +1 -1
- package/dist-es/psdReader.d.ts +10 -0
- package/dist-es/psdReader.js +424 -216
- package/dist-es/psdReader.js.map +1 -1
- package/dist-es/psdWriter.d.ts +2 -0
- package/dist-es/psdWriter.js +373 -113
- package/dist-es/psdWriter.js.map +1 -1
- package/dist-es/text.d.ts +32 -1
- package/dist-es/text.js +75 -6
- package/dist-es/text.js.map +1 -1
- package/dist-es/utf8.js +0 -3
- package/dist-es/utf8.js.map +1 -1
- package/package.json +2 -2
- package/perf.js +312 -9
- package/src/abr.ts +382 -235
- package/src/additionalInfo.ts +1071 -482
- package/src/ase.ts +66 -61
- package/src/descriptor.ts +291 -242
- package/src/helpers.ts +266 -3
- package/src/imageResources.ts +736 -169
- package/src/index.ts +1 -0
- package/src/jpeg.ts +8 -8
- package/src/psd.ts +271 -32
- package/src/psdReader.ts +440 -221
- package/src/psdWriter.ts +402 -110
- package/src/text.ts +93 -7
- package/src/utf8.ts +0 -4
package/dist/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 };
|
|
@@ -168,6 +206,8 @@ function readUnicodeString(reader) {
|
|
|
168
206
|
return readUnicodeStringWithLength(reader, length);
|
|
169
207
|
}
|
|
170
208
|
const STRING_CHUNK_SIZE = 0x2000;
|
|
209
|
+
// shorter strings (most descriptor keys, OSTypes and names) are faster to build with simple loop than String.fromCharCode.apply
|
|
210
|
+
const SHORT_STRING_LENGTH = 64;
|
|
171
211
|
function checkReadBounds(reader, size) {
|
|
172
212
|
if (reader.offset < 0 || (reader.offset + size) > reader.view.byteLength) {
|
|
173
213
|
throw new RangeError('Offset is outside the bounds of the DataView');
|
|
@@ -178,9 +218,18 @@ function readUtf16String(reader, length, littleEndian) {
|
|
|
178
218
|
return '';
|
|
179
219
|
checkReadBounds(reader, length * 2);
|
|
180
220
|
const view = reader.view;
|
|
181
|
-
const codes = new Uint16Array(Math.min(length, STRING_CHUNK_SIZE));
|
|
182
221
|
let offset = reader.offset;
|
|
183
222
|
let text = '';
|
|
223
|
+
if (length <= SHORT_STRING_LENGTH) {
|
|
224
|
+
for (let i = 0; i < length; i++, offset += 2) {
|
|
225
|
+
text += String.fromCharCode(view.getUint16(offset, littleEndian));
|
|
226
|
+
}
|
|
227
|
+
reader.offset = offset;
|
|
228
|
+
if (text.charCodeAt(text.length - 1) === 0)
|
|
229
|
+
text = text.substring(0, text.length - 1);
|
|
230
|
+
return text;
|
|
231
|
+
}
|
|
232
|
+
const codes = new Uint16Array(Math.min(length, STRING_CHUNK_SIZE));
|
|
184
233
|
for (let i = 0; i < length; i += STRING_CHUNK_SIZE) {
|
|
185
234
|
const count = Math.min(STRING_CHUNK_SIZE, length - i);
|
|
186
235
|
for (let j = 0; j < count; j++, offset += 2) {
|
|
@@ -211,6 +260,15 @@ function readAsciiString(reader, length) {
|
|
|
211
260
|
if (!(length > 0))
|
|
212
261
|
return '';
|
|
213
262
|
checkReadBounds(reader, length);
|
|
263
|
+
if (length <= SHORT_STRING_LENGTH) {
|
|
264
|
+
const view = reader.view;
|
|
265
|
+
let text = '';
|
|
266
|
+
for (let i = 0, offset = reader.offset; i < length; i++, offset++) {
|
|
267
|
+
text += String.fromCharCode(view.getUint8(offset));
|
|
268
|
+
}
|
|
269
|
+
reader.offset += length;
|
|
270
|
+
return text;
|
|
271
|
+
}
|
|
214
272
|
const text = bytesToString(new Uint8Array(reader.view.buffer, reader.view.byteOffset + reader.offset, length));
|
|
215
273
|
reader.offset += length;
|
|
216
274
|
return text;
|
|
@@ -226,7 +284,14 @@ function checkSignature(reader, a, b) {
|
|
|
226
284
|
}
|
|
227
285
|
}
|
|
228
286
|
function readShortString(reader, length) {
|
|
229
|
-
|
|
287
|
+
const bytes = readBytes(reader, length);
|
|
288
|
+
if (length > SHORT_STRING_LENGTH)
|
|
289
|
+
return bytesToString(bytes);
|
|
290
|
+
let text = '';
|
|
291
|
+
for (let i = 0; i < bytes.length; i++) {
|
|
292
|
+
text += String.fromCharCode(bytes[i]);
|
|
293
|
+
}
|
|
294
|
+
return text;
|
|
230
295
|
}
|
|
231
296
|
function isValidSignature(sig) {
|
|
232
297
|
return sig === '8BIM' || sig === 'MeSa' || sig === 'AgHg' || sig === 'PHUT' || sig === 'DCSR';
|
|
@@ -247,7 +312,8 @@ function readPsd(reader, readOptions = {}) {
|
|
|
247
312
|
const maxSize = version === 1 ? 30000 : 300000;
|
|
248
313
|
if (width > maxSize || height > maxSize)
|
|
249
314
|
throw new Error(`Invalid size: ${width}x${height}`);
|
|
250
|
-
|
|
315
|
+
// up to 56 channels plus transparency of composite image (Photoshop CS3 writes 57 channels for 53 alpha channels)
|
|
316
|
+
if (channels < 1 || channels > 57)
|
|
251
317
|
throw new Error(`Invalid channel count: ${channels}`);
|
|
252
318
|
if (![1, 8, 16, 32].includes(bitsPerChannel))
|
|
253
319
|
throw new Error(`Invalid bitsPerChannel: ${bitsPerChannel}`);
|
|
@@ -257,6 +323,7 @@ function readPsd(reader, readOptions = {}) {
|
|
|
257
323
|
Object.assign(reader, readOptions);
|
|
258
324
|
reader.large = version === 2;
|
|
259
325
|
reader.globalAlpha = false;
|
|
326
|
+
reader.layersInGlobalInfo = false;
|
|
260
327
|
if (!('totalMemoryLimit' in reader)) { // setting totalMemoryLimit to undefined explicitly disables memory limit
|
|
261
328
|
reader.totalMemoryLimit = 2 * 1024 * 1024 * 1024; // default 2GB memory limit
|
|
262
329
|
}
|
|
@@ -284,17 +351,21 @@ function readPsd(reader, readOptions = {}) {
|
|
|
284
351
|
});
|
|
285
352
|
// image resources
|
|
286
353
|
const imageResources = {};
|
|
354
|
+
let unreadResourceBytes = 0;
|
|
287
355
|
readSection(reader, 1, left => {
|
|
288
|
-
|
|
289
|
-
|
|
356
|
+
// stop if there's not enough data left for resource header (some files have few trailing bytes in this section)
|
|
357
|
+
while (left() >= 12) {
|
|
358
|
+
const signature = realignWithSignature(reader, isValidSignature);
|
|
290
359
|
const id = readUint16(reader);
|
|
291
|
-
|
|
360
|
+
const nameLength = readUint8(reader);
|
|
361
|
+
const name = readBytes(reader, nameLength);
|
|
362
|
+
skipBytes(reader, (nameLength + 1) % 2); // pad to even size
|
|
292
363
|
readSection(reader, 2, left => {
|
|
293
364
|
const handler = imageResources_1.resourceHandlersMap[id];
|
|
294
365
|
const skip = id === 1036 && !!reader.skipThumbnail;
|
|
295
366
|
if (handler && !skip) {
|
|
296
367
|
try {
|
|
297
|
-
handler.read(reader, imageResources, left);
|
|
368
|
+
handler.read(reader, imageResources, left, { signature, id, name, width, height });
|
|
298
369
|
}
|
|
299
370
|
catch (e) {
|
|
300
371
|
if (reader.throwForMissingFeatures)
|
|
@@ -303,13 +374,20 @@ function readPsd(reader, readOptions = {}) {
|
|
|
303
374
|
}
|
|
304
375
|
}
|
|
305
376
|
else {
|
|
306
|
-
|
|
377
|
+
reader.logMissingFeatures && reader.log(`Unhandled image resource: ${id} (${left()})`);
|
|
307
378
|
skipBytes(reader, left());
|
|
308
379
|
}
|
|
309
380
|
});
|
|
310
381
|
}
|
|
382
|
+
unreadResourceBytes = left();
|
|
383
|
+
skipBytes(reader, left());
|
|
311
384
|
});
|
|
312
|
-
|
|
385
|
+
// some files have image resources section length few bytes larger than the data, layer and mask info section
|
|
386
|
+
// starts right after the last resource in that case
|
|
387
|
+
if (unreadResourceBytes && !sectionFits(reader, reader.offset) && sectionFits(reader, reader.offset - unreadResourceBytes)) {
|
|
388
|
+
reader.offset -= unreadResourceBytes;
|
|
389
|
+
}
|
|
390
|
+
const { layersGroup, layerGroupsEnabledId, savedPathsOrigination } = imageResources, rest = __rest(imageResources, ["layersGroup", "layerGroupsEnabledId", "savedPathsOrigination"]);
|
|
313
391
|
if (Object.keys(rest).length) {
|
|
314
392
|
psd.imageResources = rest;
|
|
315
393
|
}
|
|
@@ -353,6 +431,8 @@ function readPsd(reader, readOptions = {}) {
|
|
|
353
431
|
const rawCompositeData = new Uint8Array(reader.view.buffer, reader.view.byteOffset + rawOffset, reader.view.byteLength - rawOffset);
|
|
354
432
|
psd.rawCompositeData = rawCompositeData;
|
|
355
433
|
psd.rawCompositeDataInfo = { large: reader.large, globalAlpha: reader.globalAlpha };
|
|
434
|
+
if (reader.layersInGlobalInfo)
|
|
435
|
+
psd.rawCompositeDataInfo.layersInGlobalInfo = true;
|
|
356
436
|
}
|
|
357
437
|
else {
|
|
358
438
|
const imageData = readImageData(reader, psd);
|
|
@@ -364,9 +444,6 @@ function readPsd(reader, readOptions = {}) {
|
|
|
364
444
|
}
|
|
365
445
|
}
|
|
366
446
|
}
|
|
367
|
-
// TODO: show converted color mode instead of original PSD file color mode
|
|
368
|
-
// but add option to preserve file color mode (need to return image data instead of canvas in that case)
|
|
369
|
-
// psd.colorMode = ColorMode.RGB; // we convert all color modes to RGB
|
|
370
447
|
return psd;
|
|
371
448
|
}
|
|
372
449
|
function readLayerInfo(reader, psd, imageResources) {
|
|
@@ -400,6 +477,9 @@ function readLayerInfo(reader, psd, imageResources) {
|
|
|
400
477
|
if (type === 1 /* SectionDividerType.OpenFolder */ || type === 2 /* SectionDividerType.ClosedFolder */) {
|
|
401
478
|
l.opened = type === 1 /* SectionDividerType.OpenFolder */;
|
|
402
479
|
l.children = [];
|
|
480
|
+
// group layers can't have image data, some broken files have it, ignore it like Photoshop does
|
|
481
|
+
delete l.canvas;
|
|
482
|
+
delete l.imageData;
|
|
403
483
|
if ((_a = l.sectionDivider) === null || _a === void 0 ? void 0 : _a.key) {
|
|
404
484
|
l.blendMode = (_b = helpers_1.toBlendMode[l.sectionDivider.key]) !== null && _b !== void 0 ? _b : l.blendMode;
|
|
405
485
|
}
|
|
@@ -442,9 +522,12 @@ function readLayerRecord(reader, psd, imageResources) {
|
|
|
442
522
|
}
|
|
443
523
|
checkSignature(reader, '8BIM');
|
|
444
524
|
const blendMode = readSignature(reader);
|
|
445
|
-
if (!helpers_1.toBlendMode[blendMode])
|
|
446
|
-
|
|
447
|
-
|
|
525
|
+
if (!helpers_1.toBlendMode[blendMode]) {
|
|
526
|
+
if (reader.throwForMissingFeatures)
|
|
527
|
+
throw new Error(`Invalid blend mode: '${blendMode}'`);
|
|
528
|
+
reader.logMissingFeatures && reader.log(`Unknown blend mode: '${blendMode}', using normal blend mode`);
|
|
529
|
+
}
|
|
530
|
+
layer.blendMode = helpers_1.toBlendMode[blendMode] || 'normal';
|
|
448
531
|
layer.opacity = readUint8(reader) / 0xff;
|
|
449
532
|
layer.clipping = readUint8(reader) === 1;
|
|
450
533
|
const flags = readUint8(reader);
|
|
@@ -472,11 +555,20 @@ function readLayerRecord(reader, psd, imageResources) {
|
|
|
472
555
|
});
|
|
473
556
|
return { layer, channels };
|
|
474
557
|
}
|
|
558
|
+
// negative sizes are treated as empty (Photoshop writes them for some empty layers, like 0,0,-1,0 bounds of fill layer)
|
|
559
|
+
// checks if length of section (layer and mask info) at given offset fits in the file
|
|
560
|
+
function sectionFits(reader, offset) {
|
|
561
|
+
const lengthSize = reader.large ? 8 : 4;
|
|
562
|
+
if (offset < 0 || offset + lengthSize > reader.view.byteLength)
|
|
563
|
+
return false;
|
|
564
|
+
const length = reader.large ? reader.view.getUint32(offset) * 0x100000000 + reader.view.getUint32(offset + 4) : reader.view.getUint32(offset);
|
|
565
|
+
return offset + lengthSize + length <= reader.view.byteLength;
|
|
566
|
+
}
|
|
475
567
|
function isValidBoxSize(box, reader) {
|
|
476
568
|
const width = (box.right || 0) - (box.left || 0);
|
|
477
569
|
const height = (box.bottom || 0) - (box.top || 0);
|
|
478
570
|
const maxSize = reader.large ? 300000 : 30000;
|
|
479
|
-
return width
|
|
571
|
+
return width <= maxSize && height <= maxSize;
|
|
480
572
|
}
|
|
481
573
|
function readLayerMaskData(reader, layer) {
|
|
482
574
|
return readSection(reader, 1, left => {
|
|
@@ -586,18 +678,17 @@ function readLayerChannelImageData(reader, psd, layer, channels) {
|
|
|
586
678
|
const start = reader.offset;
|
|
587
679
|
let compression = 0 /* Compression.RawData */;
|
|
588
680
|
let data = undefined;
|
|
589
|
-
|
|
590
|
-
|
|
591
|
-
if (channel.length) {
|
|
681
|
+
// channel length of 1 is invalid (should be 0 or at least 2), skip the byte and treat channel as empty
|
|
682
|
+
if (channel.length >= 2) {
|
|
592
683
|
compression = readUint16(reader);
|
|
593
684
|
// try to fix broken files where there's 1 byte shift of channel
|
|
594
685
|
if (compression > 3) {
|
|
595
686
|
reader.offset -= 1;
|
|
596
687
|
compression = readUint16(reader);
|
|
597
688
|
}
|
|
598
|
-
// try to fix broken files where there's 1 byte shift of channel
|
|
689
|
+
// try to fix broken files where there's 1 byte shift of channel (in the other direction)
|
|
599
690
|
if (compression > 3) {
|
|
600
|
-
reader.offset -=
|
|
691
|
+
reader.offset -= 4;
|
|
601
692
|
compression = readUint16(reader);
|
|
602
693
|
}
|
|
603
694
|
if (compression > 3)
|
|
@@ -610,7 +701,16 @@ function readLayerChannelImageData(reader, psd, layer, channels) {
|
|
|
610
701
|
layer.rawData.channels.push({ id: channel.id, compression, data });
|
|
611
702
|
}
|
|
612
703
|
if (!reader.useRawData) {
|
|
613
|
-
|
|
704
|
+
try {
|
|
705
|
+
decodeLayerImageData(layer, reader);
|
|
706
|
+
}
|
|
707
|
+
catch (e) {
|
|
708
|
+
// corrupted image data of one layer shouldn't prevent reading rest of the file
|
|
709
|
+
if (reader.throwForMissingFeatures || e.message === 'Exceeded memory limit')
|
|
710
|
+
throw e;
|
|
711
|
+
reader.logMissingFeatures && reader.log(`Failed to decode layer image data: ${e.message}`);
|
|
712
|
+
delete layer.rawData;
|
|
713
|
+
}
|
|
614
714
|
}
|
|
615
715
|
}
|
|
616
716
|
function resetAlpha({ data }, cmyk) {
|
|
@@ -631,6 +731,7 @@ function getCompositeImageData(psd) {
|
|
|
631
731
|
if (info) {
|
|
632
732
|
reader.large = info.large;
|
|
633
733
|
reader.globalAlpha = info.globalAlpha;
|
|
734
|
+
reader.layersInGlobalInfo = !!info.layersInGlobalInfo;
|
|
634
735
|
}
|
|
635
736
|
return readImageData(reader, psd);
|
|
636
737
|
}
|
|
@@ -711,18 +812,14 @@ function getDataFromLayer(layer, read, throwForMissingFeatures, memoryLimit, log
|
|
|
711
812
|
let initializedAlpha = false;
|
|
712
813
|
if (layerWidth && layerHeight && read === LayerDataType.Layer) {
|
|
713
814
|
if (cmyk) {
|
|
714
|
-
if (bitsPerChannel !== 8)
|
|
815
|
+
if (bitsPerChannel !== 8 && bitsPerChannel !== 16)
|
|
715
816
|
throw new Error('bitsPerChannel not supported');
|
|
716
|
-
imageData =
|
|
817
|
+
imageData = createImageDataBitDepth(layerWidth, layerHeight, bitsPerChannel, 5, memoryLimit);
|
|
717
818
|
}
|
|
718
819
|
else {
|
|
719
820
|
imageData = createImageDataBitDepth(layerWidth, layerHeight, bitsPerChannel, 4, memoryLimit);
|
|
720
821
|
}
|
|
721
822
|
}
|
|
722
|
-
if (helpers_1.RAW_IMAGE_DATA) { // TODO: use layer.rawData instead
|
|
723
|
-
layer.imageDataRaw = [];
|
|
724
|
-
layer.imageDataRawCompression = [];
|
|
725
|
-
}
|
|
726
823
|
for (const { id, compression, data } of channels) {
|
|
727
824
|
if (!data)
|
|
728
825
|
continue;
|
|
@@ -747,16 +844,6 @@ function getDataFromLayer(layer, read, throwForMissingFeatures, memoryLimit, log
|
|
|
747
844
|
if (memoryLimit !== undefined)
|
|
748
845
|
dataReader.totalMemoryLimit = Math.max(0, dataReader.totalMemoryLimit - maskData.data.byteLength);
|
|
749
846
|
readData(dataReader, data.byteLength, maskData, compression, maskWidth, maskHeight, bitsPerChannel, 0, large, 4);
|
|
750
|
-
if (helpers_1.RAW_IMAGE_DATA) { // TODO: use layer.rawData instead
|
|
751
|
-
if (id === -2 /* ChannelID.UserMask */) {
|
|
752
|
-
layer.maskDataRawCompression = compression;
|
|
753
|
-
layer.maskDataRaw = data;
|
|
754
|
-
}
|
|
755
|
-
else {
|
|
756
|
-
layer.realMaskDataRawCompression = compression;
|
|
757
|
-
layer.realMaskDataRaw = data;
|
|
758
|
-
}
|
|
759
|
-
}
|
|
760
847
|
setupGrayscale(maskData);
|
|
761
848
|
resetAlpha(maskData, false);
|
|
762
849
|
}
|
|
@@ -773,10 +860,6 @@ function getDataFromLayer(layer, read, throwForMissingFeatures, memoryLimit, log
|
|
|
773
860
|
}
|
|
774
861
|
}
|
|
775
862
|
readData(dataReader, data.byteLength, targetData, compression, layerWidth, layerHeight, bitsPerChannel, offset, large, cmyk ? 5 : 4);
|
|
776
|
-
if (helpers_1.RAW_IMAGE_DATA) { // TODO: use layer.rawData instead
|
|
777
|
-
layer.imageDataRawCompression[id] = compression;
|
|
778
|
-
layer.imageDataRaw[id] = data;
|
|
779
|
-
}
|
|
780
863
|
}
|
|
781
864
|
if (id === -1 /* ChannelID.Transparency */) {
|
|
782
865
|
initializedAlpha = true;
|
|
@@ -789,7 +872,8 @@ function getDataFromLayer(layer, read, throwForMissingFeatures, memoryLimit, log
|
|
|
789
872
|
resetAlpha(imageData, cmyk);
|
|
790
873
|
if (cmyk) {
|
|
791
874
|
const cmykData = imageData;
|
|
792
|
-
|
|
875
|
+
const rgbMemoryLimit = memoryLimit === undefined ? undefined : Math.max(0, memoryLimit - cmykData.data.byteLength);
|
|
876
|
+
imageData = createImageDataBitDepth(cmykData.width, cmykData.height, bitsPerChannel, 4, rgbMemoryLimit);
|
|
793
877
|
cmykToRgb(cmykData, imageData, false);
|
|
794
878
|
}
|
|
795
879
|
}
|
|
@@ -830,7 +914,7 @@ function readGlobalLayerMaskInfo(reader) {
|
|
|
830
914
|
const colorSpace2 = readUint16(reader);
|
|
831
915
|
const colorSpace3 = readUint16(reader);
|
|
832
916
|
const colorSpace4 = readUint16(reader);
|
|
833
|
-
const opacity = readUint16(reader) / 100; // 0 - 100
|
|
917
|
+
const opacity = Math.min(readUint16(reader), 100) / 100; // 0 - 100 (some files have values out of range)
|
|
834
918
|
const kind = readUint8(reader);
|
|
835
919
|
skipBytes(reader, left()); // 3 bytes of padding ?
|
|
836
920
|
return { overlayColorSpace, colorSpace1, colorSpace2, colorSpace3, colorSpace4, opacity, kind };
|
|
@@ -862,7 +946,15 @@ function readAdditionalLayerInfo(reader, target, psd, imageResources) {
|
|
|
862
946
|
const sig = realignWithSignature(reader, isValidAdditionalInfoSignature);
|
|
863
947
|
const key = readSignature(reader);
|
|
864
948
|
// `largeAdditionalInfoKeys` fallback, because some keys don't have 8B64 signature even when they are 64bit
|
|
865
|
-
|
|
949
|
+
let u64 = sig === '8B64' || (reader.large && helpers_1.largeAdditionalInfoKeys.indexOf(key) !== -1);
|
|
950
|
+
// these keys were written by older versions of this library with 32bit length and 8BIM signature,
|
|
951
|
+
// 64bit length always has high 32bits set to 0 (lengths over 4GB are not supported)
|
|
952
|
+
if (reader.large && sig === '8BIM' && helpers_1.largeAdditionalInfoKeysObserved.indexOf(key) !== -1) {
|
|
953
|
+
// 32bit length of 0 is followed by signature of the next block, 64bit length would continue with its low 32bits
|
|
954
|
+
const next = reader.offset + 8 <= reader.view.byteLength ? reader.view.getUint32(reader.offset + 4) : 0;
|
|
955
|
+
const nextIsSignature = next === 0x3842494d /* 8BIM */ || next === 0x38423634 /* 8B64 */;
|
|
956
|
+
u64 = reader.view.getUint32(reader.offset) === 0 && !nextIsSignature;
|
|
957
|
+
}
|
|
866
958
|
readSection(reader, 2, left => {
|
|
867
959
|
const handler = additionalInfo_1.infoHandlersMap[key];
|
|
868
960
|
if (handler) {
|
|
@@ -875,7 +967,7 @@ function readAdditionalLayerInfo(reader, target, psd, imageResources) {
|
|
|
875
967
|
}
|
|
876
968
|
}
|
|
877
969
|
else {
|
|
878
|
-
reader.logMissingFeatures && reader.log(`Unhandled additional info: ${key}`);
|
|
970
|
+
reader.logMissingFeatures && key !== 'GenI' && key !== 'OCIO' && key !== 'CAI ' && reader.log(`Unhandled additional info: ${key}`);
|
|
879
971
|
skipBytes(reader, left());
|
|
880
972
|
}
|
|
881
973
|
if (left()) {
|
|
@@ -907,7 +999,7 @@ function createImageDataBitDepth(width, height, bitDepth, channels, memoryLimit)
|
|
|
907
999
|
}
|
|
908
1000
|
}
|
|
909
1001
|
function readImageData(reader, psd) {
|
|
910
|
-
var _a;
|
|
1002
|
+
var _a, _b, _c;
|
|
911
1003
|
const compression = readUint16(reader);
|
|
912
1004
|
const bitsPerChannel = (_a = psd.bitsPerChannel) !== null && _a !== void 0 ? _a : 8;
|
|
913
1005
|
const { width, height } = psd;
|
|
@@ -939,11 +1031,7 @@ function readImageData(reader, psd) {
|
|
|
939
1031
|
case 1 /* ColorMode.Grayscale */: {
|
|
940
1032
|
const colorChannels = psd.colorMode === 1 /* ColorMode.Grayscale */ ? 1 : 3;
|
|
941
1033
|
const offsets = compositeChannelOffsets(reader, psd, colorChannels, 3);
|
|
942
|
-
const start = reader.offset;
|
|
943
1034
|
readCompositeChannels(reader, compression, imageData, bitsPerChannel, 4, offsets);
|
|
944
|
-
if (helpers_1.RAW_IMAGE_DATA && compression === 1 /* Compression.RleCompressed */) {
|
|
945
|
-
psd.imageDataRaw = new Uint8Array(reader.view.buffer, reader.view.byteOffset + start, reader.offset - start);
|
|
946
|
-
}
|
|
947
1035
|
if (psd.colorMode === 1 /* ColorMode.Grayscale */) {
|
|
948
1036
|
setupGrayscale(imageData);
|
|
949
1037
|
}
|
|
@@ -957,41 +1045,42 @@ function readImageData(reader, psd) {
|
|
|
957
1045
|
const offsets = compositeChannelOffsets(reader, psd, 1, -1);
|
|
958
1046
|
const indexedImageData = { width, height, data: new Uint8Array(width * height) };
|
|
959
1047
|
readCompositeChannels(reader, compression, indexedImageData, 8, 1, offsets);
|
|
960
|
-
indexedToRgb(indexedImageData, imageData, psd.palette);
|
|
1048
|
+
indexedToRgb(indexedImageData, imageData, psd.palette, (_c = (_b = psd.imageResources) === null || _b === void 0 ? void 0 : _b.transparencyIndex) !== null && _c !== void 0 ? _c : -1);
|
|
961
1049
|
break;
|
|
962
1050
|
}
|
|
963
1051
|
case 4 /* ColorMode.CMYK */: {
|
|
964
|
-
if (bitsPerChannel !== 8)
|
|
1052
|
+
if (bitsPerChannel !== 8 && bitsPerChannel !== 16)
|
|
965
1053
|
throw new Error('bitsPerChannel not supported');
|
|
966
1054
|
const offsets = compositeChannelOffsets(reader, psd, 4, 4);
|
|
967
|
-
const cmykImageData =
|
|
968
|
-
|
|
969
|
-
readCompositeChannels(reader, compression, cmykImageData,
|
|
970
|
-
cmykToRgb(cmykImageData, imageData,
|
|
971
|
-
if (helpers_1.RAW_IMAGE_DATA && compression === 1 /* Compression.RleCompressed */) {
|
|
972
|
-
psd.imageDataRaw = new Uint8Array(reader.view.buffer, reader.view.byteOffset + start, reader.offset - start);
|
|
973
|
-
}
|
|
1055
|
+
const cmykImageData = createImageDataBitDepth(width, height, bitsPerChannel, 5, reader.totalMemoryLimit);
|
|
1056
|
+
resetAlpha(cmykImageData, true);
|
|
1057
|
+
readCompositeChannels(reader, compression, cmykImageData, bitsPerChannel, 5, offsets);
|
|
1058
|
+
cmykToRgb(cmykImageData, imageData, !!reader.globalAlpha);
|
|
974
1059
|
break;
|
|
975
1060
|
}
|
|
976
1061
|
default: throw new Error(`Color mode not supported: ${psd.colorMode}`);
|
|
977
1062
|
}
|
|
978
|
-
// remove weird white matte
|
|
979
|
-
if (reader.globalAlpha) {
|
|
1063
|
+
// remove weird white matte (already removed when converting from CMYK)
|
|
1064
|
+
if (reader.globalAlpha && psd.colorMode !== 4 /* ColorMode.CMYK */) {
|
|
980
1065
|
removeWhiteMatte(imageData);
|
|
981
1066
|
}
|
|
982
1067
|
return imageData;
|
|
983
1068
|
}
|
|
984
1069
|
// returns target offset for each of the composite image channels stored in the file, -1 for channels that are skipped
|
|
985
1070
|
function compositeChannelOffsets(reader, psd, colorChannels, alphaOffset) {
|
|
986
|
-
var _a, _b, _c
|
|
1071
|
+
var _a, _b, _c;
|
|
987
1072
|
const channelCount = (_a = psd.channels) !== null && _a !== void 0 ? _a : (colorChannels + (reader.globalAlpha ? 1 : 0));
|
|
988
1073
|
if (channelCount < colorChannels)
|
|
989
1074
|
throw new Error(`Invalid channel count: ${channelCount}`);
|
|
990
1075
|
// First extra channel is transparency if document has global alpha (negative layer count). Some non-Photoshop
|
|
991
1076
|
// applications write transparency channel without setting global alpha, in that case extra channel is not
|
|
992
1077
|
// listed in alpha channel names (Photoshop names all user alpha channels / saved selections).
|
|
993
|
-
|
|
994
|
-
|
|
1078
|
+
// Photoshop also lists merged transparency as first alpha channel named "Transparency" (in documents marked with
|
|
1079
|
+
// Mtrn/Mt16/Mt32 additional info, or in some 16bit documents with layers stored in Lr16 without any other mark)
|
|
1080
|
+
const alphaNames = (_c = (_b = psd.imageResources) === null || _b === void 0 ? void 0 : _b.alphaChannelNames) !== null && _c !== void 0 ? _c : [];
|
|
1081
|
+
const namedChannels = alphaNames.length;
|
|
1082
|
+
const namedTransparency = !!reader.layersInGlobalInfo && alphaNames[0] === 'Transparency' && namedChannels === channelCount - colorChannels;
|
|
1083
|
+
const hasTransparency = reader.globalAlpha || namedTransparency || (channelCount - colorChannels) > namedChannels;
|
|
995
1084
|
const offsets = [];
|
|
996
1085
|
for (let i = 0; i < channelCount; i++) {
|
|
997
1086
|
if (i < colorChannels) {
|
|
@@ -1070,41 +1159,54 @@ function removeWhiteMatte({ data, width, height }) {
|
|
|
1070
1159
|
}
|
|
1071
1160
|
}
|
|
1072
1161
|
}
|
|
1073
|
-
|
|
1162
|
+
// converts value in 0-255 range to target range, rounding and clamping it
|
|
1163
|
+
function toRange(value, max) {
|
|
1164
|
+
value = Math.round(value * (max / 255));
|
|
1165
|
+
return value <= 0 ? 0 : (value >= max ? max : value);
|
|
1166
|
+
}
|
|
1167
|
+
// converts CMYK data to RGB using approximation of U.S. Web Coated (SWOP) v2 profile (see cmykToRgbValues)
|
|
1168
|
+
// cmyk - 8 or 16 bit data with 5 channels (cmyka), rgb - 8 or 16 bit data with 4 channels (rgba)
|
|
1169
|
+
// removeMatte - colors are blended with white (no ink), used for composite image of documents with global alpha
|
|
1170
|
+
function cmykToRgb(cmyk, rgb, removeMatte) {
|
|
1074
1171
|
const size = rgb.width * rgb.height * 4;
|
|
1075
1172
|
const srcData = cmyk.data;
|
|
1076
1173
|
const dstData = rgb.data;
|
|
1174
|
+
const srcMax = srcData instanceof Uint16Array ? 0xffff : 0xff;
|
|
1175
|
+
const dstMax = dstData instanceof Uint16Array ? 0xffff : 0xff;
|
|
1176
|
+
const out = [0, 0, 0];
|
|
1077
1177
|
for (let src = 0, dst = 0; dst < size; src += 5, dst += 4) {
|
|
1078
|
-
const
|
|
1079
|
-
|
|
1080
|
-
|
|
1081
|
-
|
|
1082
|
-
|
|
1083
|
-
|
|
1084
|
-
|
|
1085
|
-
|
|
1086
|
-
|
|
1087
|
-
|
|
1088
|
-
|
|
1089
|
-
|
|
1090
|
-
|
|
1091
|
-
|
|
1092
|
-
|
|
1093
|
-
|
|
1094
|
-
|
|
1095
|
-
|
|
1096
|
-
|
|
1097
|
-
}
|
|
1098
|
-
|
|
1178
|
+
const a = srcData[src + 4];
|
|
1179
|
+
// image data stores inverted values (0 = 100% ink)
|
|
1180
|
+
let c = 1 - srcData[src] / srcMax;
|
|
1181
|
+
let m = 1 - srcData[src + 1] / srcMax;
|
|
1182
|
+
let y = 1 - srcData[src + 2] / srcMax;
|
|
1183
|
+
let k = 1 - srcData[src + 3] / srcMax;
|
|
1184
|
+
if (removeMatte && a > 0 && a < srcMax) {
|
|
1185
|
+
const ra = srcMax / a;
|
|
1186
|
+
c = Math.min(1, c * ra);
|
|
1187
|
+
m = Math.min(1, m * ra);
|
|
1188
|
+
y = Math.min(1, y * ra);
|
|
1189
|
+
k = Math.min(1, k * ra);
|
|
1190
|
+
}
|
|
1191
|
+
(0, helpers_1.cmykToRgbValues)(c, m, y, k, out);
|
|
1192
|
+
dstData[dst] = toRange(out[0], dstMax);
|
|
1193
|
+
dstData[dst + 1] = toRange(out[1], dstMax);
|
|
1194
|
+
dstData[dst + 2] = toRange(out[2], dstMax);
|
|
1195
|
+
dstData[dst + 3] = srcMax === dstMax ? a : Math.round(a * dstMax / srcMax);
|
|
1196
|
+
}
|
|
1197
|
+
}
|
|
1198
|
+
// transparencyIndex - palette index of transparent color (-1 if there's none)
|
|
1199
|
+
function indexedToRgb(indexed, rgb, palette, transparencyIndex) {
|
|
1099
1200
|
const size = indexed.width * indexed.height;
|
|
1100
1201
|
const srcData = indexed.data;
|
|
1101
1202
|
const dstData = rgb.data;
|
|
1102
1203
|
for (let src = 0, dst = 0; src < size; src++, dst += 4) {
|
|
1103
|
-
const
|
|
1204
|
+
const index = srcData[src];
|
|
1205
|
+
const c = palette[index];
|
|
1104
1206
|
dstData[dst + 0] = c.r;
|
|
1105
1207
|
dstData[dst + 1] = c.g;
|
|
1106
1208
|
dstData[dst + 2] = c.b;
|
|
1107
|
-
dstData[dst + 3] = 255;
|
|
1209
|
+
dstData[dst + 3] = index === transparencyIndex ? 0 : 255;
|
|
1108
1210
|
}
|
|
1109
1211
|
}
|
|
1110
1212
|
// number of bytes used to store single channel value, 1-bit data is handled as bytes
|
|
@@ -1256,8 +1358,8 @@ function readDataZip(compressed, pixelData, width, height, bitDepth, step, offse
|
|
|
1256
1358
|
recoverMemory(reader, expectedSize);
|
|
1257
1359
|
}
|
|
1258
1360
|
}
|
|
1259
|
-
// decodes single PackBits row, returns number of decoded
|
|
1260
|
-
function decodeRLERow(src, i, end, dst,
|
|
1361
|
+
// decodes single PackBits row into byte buffer, returns number of decoded bytes
|
|
1362
|
+
function decodeRLERow(src, i, end, dst, count) {
|
|
1261
1363
|
let x = 0;
|
|
1262
1364
|
while (i < end && x < count) {
|
|
1263
1365
|
const header = src[i++];
|
|
@@ -1268,10 +1370,8 @@ function decodeRLERow(src, i, end, dst, p, step, count) {
|
|
|
1268
1370
|
let n = 257 - header;
|
|
1269
1371
|
if (n > count - x)
|
|
1270
1372
|
n = count - x;
|
|
1373
|
+
dst.fill(value, x, x + n);
|
|
1271
1374
|
x += n;
|
|
1272
|
-
for (; n > 0; n--, p += step) {
|
|
1273
|
-
dst[p] = value;
|
|
1274
|
-
}
|
|
1275
1375
|
}
|
|
1276
1376
|
else if (header < 128) {
|
|
1277
1377
|
let n = header + 1;
|
|
@@ -1279,9 +1379,8 @@ function decodeRLERow(src, i, end, dst, p, step, count) {
|
|
|
1279
1379
|
n = count - x;
|
|
1280
1380
|
if (n > end - i)
|
|
1281
1381
|
n = end - i;
|
|
1282
|
-
|
|
1283
|
-
|
|
1284
|
-
dst[p] = src[i++];
|
|
1382
|
+
for (; n > 0; n--) {
|
|
1383
|
+
dst[x++] = src[i++];
|
|
1285
1384
|
}
|
|
1286
1385
|
}
|
|
1287
1386
|
else {
|
|
@@ -1290,6 +1389,47 @@ function decodeRLERow(src, i, end, dst, p, step, count) {
|
|
|
1290
1389
|
}
|
|
1291
1390
|
return x;
|
|
1292
1391
|
}
|
|
1392
|
+
// decodes RLE rows of single 8bit channel, writing values to `data` with `step`
|
|
1393
|
+
// (kept as single function with the loops, inlining row decoder into readDataRLE makes V8 generate much slower code)
|
|
1394
|
+
function decodeRLEChannel8(src, lengths, li, pos, width, height, data, offset, step) {
|
|
1395
|
+
const srcEnd = src.length | 0;
|
|
1396
|
+
for (let y = 0; y < height; y++, li++) {
|
|
1397
|
+
let i = (pos < srcEnd ? pos : srcEnd) | 0;
|
|
1398
|
+
pos += lengths[li];
|
|
1399
|
+
const end = (pos < srcEnd ? pos : srcEnd) | 0;
|
|
1400
|
+
let p = (offset + y * width * step) | 0;
|
|
1401
|
+
let x = 0;
|
|
1402
|
+
while (i < end && x < width) {
|
|
1403
|
+
const header = src[i++] | 0;
|
|
1404
|
+
if (header > 128) {
|
|
1405
|
+
if (i >= end)
|
|
1406
|
+
break;
|
|
1407
|
+
const value = src[i++] | 0;
|
|
1408
|
+
let n = (257 - header) | 0;
|
|
1409
|
+
if (n > width - x)
|
|
1410
|
+
n = (width - x) | 0;
|
|
1411
|
+
x = (x + n) | 0;
|
|
1412
|
+
for (const pe = (p + n * step) | 0; p < pe; p = (p + step) | 0) {
|
|
1413
|
+
data[p] = value;
|
|
1414
|
+
}
|
|
1415
|
+
}
|
|
1416
|
+
else if (header < 128) {
|
|
1417
|
+
let n = (header + 1) | 0;
|
|
1418
|
+
if (n > width - x)
|
|
1419
|
+
n = (width - x) | 0;
|
|
1420
|
+
if (n > end - i)
|
|
1421
|
+
n = (end - i) | 0;
|
|
1422
|
+
x = (x + n) | 0;
|
|
1423
|
+
for (const ie = (i + n) | 0; i < ie; i = (i + 1) | 0, p = (p + step) | 0) {
|
|
1424
|
+
data[p] = src[i];
|
|
1425
|
+
}
|
|
1426
|
+
}
|
|
1427
|
+
else {
|
|
1428
|
+
// ignore 128
|
|
1429
|
+
}
|
|
1430
|
+
}
|
|
1431
|
+
}
|
|
1432
|
+
}
|
|
1293
1433
|
// converts big-endian 16/32-bit values from bytes and writes them to `data` starting at `p` with `step`
|
|
1294
1434
|
function scatterValues(bytes, count, bpv, data, p, step) {
|
|
1295
1435
|
if (bpv === 2) {
|
|
@@ -1304,8 +1444,8 @@ function scatterValues(bytes, count, bpv, data, p, step) {
|
|
|
1304
1444
|
}
|
|
1305
1445
|
}
|
|
1306
1446
|
}
|
|
1307
|
-
// decodes RLE rows of single channel, each row
|
|
1308
|
-
function
|
|
1447
|
+
// decodes RLE rows of single 16bit or 32bit channel, each row is decoded into `row` buffer and then converted to values
|
|
1448
|
+
function decodeRLEChannelWide(src, lengths, li, pos, width, height, data, offset, step, bpv, row) {
|
|
1309
1449
|
const srcEnd = src.length;
|
|
1310
1450
|
const rowSize = width * bpv;
|
|
1311
1451
|
for (let y = 0; y < height; y++, li++) {
|
|
@@ -1314,25 +1454,24 @@ function decodeRLEChannel(src, lengths, li, pos, width, height, data, offset, st
|
|
|
1314
1454
|
const rowEnd = pos < srcEnd ? pos : srcEnd;
|
|
1315
1455
|
if (rowStart >= rowEnd)
|
|
1316
1456
|
continue;
|
|
1317
|
-
|
|
1318
|
-
|
|
1319
|
-
}
|
|
1320
|
-
else {
|
|
1321
|
-
const decoded = decodeRLERow(src, rowStart, rowEnd, row, 0, 1, rowSize);
|
|
1322
|
-
scatterValues(row, Math.floor(decoded / bpv), bpv, data, offset + y * width * step, step);
|
|
1323
|
-
}
|
|
1457
|
+
const decoded = decodeRLERow(src, rowStart, rowEnd, row, rowSize);
|
|
1458
|
+
scatterValues(row, Math.floor(decoded / bpv), bpv, data, offset + y * width * step, step);
|
|
1324
1459
|
}
|
|
1325
1460
|
}
|
|
1326
1461
|
function readDataRLE(reader, pixelData, width, height, bitDepth, step, offsets, large) {
|
|
1327
1462
|
const data = pixelData && pixelData.data;
|
|
1328
1463
|
const rows = offsets.length * height;
|
|
1329
1464
|
const lengthSize = large ? 4 : 2;
|
|
1330
|
-
|
|
1465
|
+
const lengthsMemory = rows * lengthSize;
|
|
1466
|
+
const srcEnd = reader.view.byteLength;
|
|
1467
|
+
const bpv = bytesPerValue(bitDepth); // throws before consuming memory
|
|
1468
|
+
let lengths;
|
|
1469
|
+
consumeMemory(reader, lengthsMemory);
|
|
1331
1470
|
try {
|
|
1332
|
-
if ((reader.offset +
|
|
1471
|
+
if ((reader.offset + lengthsMemory) > srcEnd) {
|
|
1333
1472
|
throw new Error('RLE row lengths exceed buffer length');
|
|
1334
1473
|
}
|
|
1335
|
-
|
|
1474
|
+
lengths = large ? new Uint32Array(rows) : new Uint16Array(rows);
|
|
1336
1475
|
let total = 0;
|
|
1337
1476
|
if (large) {
|
|
1338
1477
|
for (let i = 0; i < rows; i++)
|
|
@@ -1342,32 +1481,37 @@ function readDataRLE(reader, pixelData, width, height, bitDepth, step, offsets,
|
|
|
1342
1481
|
for (let i = 0; i < rows; i++)
|
|
1343
1482
|
total += (lengths[i] = readUint16(reader));
|
|
1344
1483
|
}
|
|
1345
|
-
|
|
1346
|
-
const srcEnd = reader.view.byteLength;
|
|
1347
|
-
if (total > (srcEnd - start)) {
|
|
1484
|
+
if (total > (srcEnd - reader.offset)) {
|
|
1348
1485
|
// broken or truncated file, decode only data that is available
|
|
1349
1486
|
warnOrThrow(reader, 'RLE data exceeds buffer length');
|
|
1350
1487
|
}
|
|
1351
|
-
const bpv = bytesPerValue(bitDepth);
|
|
1352
1488
|
if (data)
|
|
1353
1489
|
verifyArrayType(data, bpv);
|
|
1354
|
-
|
|
1355
|
-
|
|
1356
|
-
|
|
1357
|
-
|
|
1358
|
-
|
|
1359
|
-
|
|
1360
|
-
|
|
1361
|
-
|
|
1362
|
-
|
|
1363
|
-
|
|
1490
|
+
}
|
|
1491
|
+
catch (e) {
|
|
1492
|
+
recoverMemory(reader, lengthsMemory);
|
|
1493
|
+
throw e;
|
|
1494
|
+
}
|
|
1495
|
+
// decoding can't throw, it's kept outside of try block because V8 generates much slower code for loops inlined into try block
|
|
1496
|
+
const src = new Uint8Array(reader.view.buffer, reader.view.byteOffset, srcEnd);
|
|
1497
|
+
const row = (data && bpv > 1) ? new Uint8Array(width * bpv) : undefined;
|
|
1498
|
+
let pos = reader.offset;
|
|
1499
|
+
for (let c = 0, li = 0; c < offsets.length; c++, li += height) {
|
|
1500
|
+
const offset = offsets[c] | 0;
|
|
1501
|
+
const channelStart = pos;
|
|
1502
|
+
for (let y = 0; y < height; y++)
|
|
1503
|
+
pos += lengths[li + y];
|
|
1504
|
+
if (data && offset >= 0 && offset < step) {
|
|
1505
|
+
if (bpv === 1) {
|
|
1506
|
+
decodeRLEChannel8(src, lengths, li, channelStart, width, height, data, offset, step);
|
|
1507
|
+
}
|
|
1508
|
+
else {
|
|
1509
|
+
decodeRLEChannelWide(src, lengths, li, channelStart, width, height, data, offset, step, bpv, row);
|
|
1364
1510
|
}
|
|
1365
1511
|
}
|
|
1366
|
-
reader.offset = pos;
|
|
1367
|
-
}
|
|
1368
|
-
finally {
|
|
1369
|
-
recoverMemory(reader, rows * lengthSize);
|
|
1370
1512
|
}
|
|
1513
|
+
reader.offset = pos;
|
|
1514
|
+
recoverMemory(reader, lengthsMemory);
|
|
1371
1515
|
}
|
|
1372
1516
|
function readSection(reader, round, func, skipEmpty = true, eightBytes = false) {
|
|
1373
1517
|
let length = readUint32(reader);
|
|
@@ -1415,11 +1559,11 @@ function readColor(reader) {
|
|
|
1415
1559
|
skipBytes(reader, 2);
|
|
1416
1560
|
return { h, s, b };
|
|
1417
1561
|
}
|
|
1418
|
-
case 2 /* ColorSpace.CMYK */: {
|
|
1419
|
-
const c = readUint16(reader) / 257;
|
|
1420
|
-
const m = readUint16(reader) / 257;
|
|
1421
|
-
const y = readUint16(reader) / 257;
|
|
1422
|
-
const k = readUint16(reader) / 257;
|
|
1562
|
+
case 2 /* ColorSpace.CMYK */: { // 0 = 100% ink
|
|
1563
|
+
const c = (0xffff - readUint16(reader)) / 257;
|
|
1564
|
+
const m = (0xffff - readUint16(reader)) / 257;
|
|
1565
|
+
const y = (0xffff - readUint16(reader)) / 257;
|
|
1566
|
+
const k = (0xffff - readUint16(reader)) / 257;
|
|
1423
1567
|
return { c, m, y, k };
|
|
1424
1568
|
}
|
|
1425
1569
|
case 7 /* ColorSpace.Lab */: {
|
|
@@ -1440,6 +1584,24 @@ function readColor(reader) {
|
|
|
1440
1584
|
throw new Error('Invalid color space');
|
|
1441
1585
|
}
|
|
1442
1586
|
}
|
|
1587
|
+
const MAX_PATTERN_SIZE = 30000;
|
|
1588
|
+
// reads pattern, unsupported or invalid patterns are skipped (returns undefined) unless throwForMissingFeatures is set
|
|
1589
|
+
function readPatternSafe(reader) {
|
|
1590
|
+
let length = reader.view.getUint32(reader.offset);
|
|
1591
|
+
while (length % 4)
|
|
1592
|
+
length++;
|
|
1593
|
+
const end = reader.offset + 4 + length;
|
|
1594
|
+
try {
|
|
1595
|
+
return readPattern(reader);
|
|
1596
|
+
}
|
|
1597
|
+
catch (e) {
|
|
1598
|
+
if (reader.throwForMissingFeatures)
|
|
1599
|
+
throw e;
|
|
1600
|
+
reader.logMissingFeatures && reader.log(`Skipped pattern: ${e.message}`);
|
|
1601
|
+
reader.offset = end;
|
|
1602
|
+
return undefined;
|
|
1603
|
+
}
|
|
1604
|
+
}
|
|
1443
1605
|
function readPattern(reader) {
|
|
1444
1606
|
let length = readUint32(reader);
|
|
1445
1607
|
while (length % 4)
|
|
@@ -1451,8 +1613,8 @@ function readPattern(reader) {
|
|
|
1451
1613
|
const colorMode = readUint32(reader);
|
|
1452
1614
|
const x = readInt16(reader);
|
|
1453
1615
|
const y = readInt16(reader);
|
|
1454
|
-
// we only support RGB and
|
|
1455
|
-
if (colorMode !== 3 /* ColorMode.RGB */ && colorMode !== 1 /* ColorMode.Grayscale */ && colorMode !== 2 /* ColorMode.Indexed */) {
|
|
1616
|
+
// we only support RGB, grayscale, indexed and CMYK for now
|
|
1617
|
+
if (colorMode !== 3 /* ColorMode.RGB */ && colorMode !== 1 /* ColorMode.Grayscale */ && colorMode !== 2 /* ColorMode.Indexed */ && colorMode !== 4 /* ColorMode.CMYK */) {
|
|
1456
1618
|
throw new Error(`Unsupported pattern color mode: ${colorMode}`);
|
|
1457
1619
|
}
|
|
1458
1620
|
let name = readUnicodeString(reader);
|
|
@@ -1466,7 +1628,9 @@ function readPattern(reader) {
|
|
|
1466
1628
|
b: readUint8(reader),
|
|
1467
1629
|
});
|
|
1468
1630
|
}
|
|
1469
|
-
|
|
1631
|
+
// some writers follow the palette with 4 extra bytes (unknown)
|
|
1632
|
+
if (reader.view.getUint32(reader.offset) !== 3)
|
|
1633
|
+
skipBytes(reader, 4);
|
|
1470
1634
|
}
|
|
1471
1635
|
// virtual memory array list
|
|
1472
1636
|
const version2 = readUint32(reader);
|
|
@@ -1480,17 +1644,31 @@ function readPattern(reader) {
|
|
|
1480
1644
|
const channelsCount = readUint32(reader);
|
|
1481
1645
|
const width = right - left;
|
|
1482
1646
|
const height = bottom - top;
|
|
1483
|
-
|
|
1484
|
-
|
|
1485
|
-
|
|
1486
|
-
|
|
1487
|
-
|
|
1488
|
-
|
|
1489
|
-
|
|
1647
|
+
if (width < 0 || height < 0)
|
|
1648
|
+
throw new Error('Invalid pattern bounds');
|
|
1649
|
+
if (width > MAX_PATTERN_SIZE || height > MAX_PATTERN_SIZE)
|
|
1650
|
+
throw new Error(`Pattern too large (${width}x${height})`);
|
|
1651
|
+
if (channelsCount > 64)
|
|
1652
|
+
throw new Error(`Invalid pattern channel count: ${channelsCount}`);
|
|
1653
|
+
// channels cover whole pattern, each 2 bytes of RLE data can produce at most 128 bytes
|
|
1654
|
+
if (width * height > (Math.min(end, reader.view.byteLength) - reader.offset) * 64)
|
|
1655
|
+
throw new Error('Pattern data out of bounds');
|
|
1656
|
+
// first written arrays are color channels, next one is transparency (Photoshop stores it in the last, sheet mask, array)
|
|
1657
|
+
const colorChannels = colorMode === 3 /* ColorMode.RGB */ ? 3 : (colorMode === 4 /* ColorMode.CMYK */ ? 4 : 1);
|
|
1658
|
+
const isCmyk = colorMode === 4 /* ColorMode.CMYK */;
|
|
1659
|
+
const alphaTarget = isCmyk ? 4 : 3;
|
|
1660
|
+
const channels = [];
|
|
1661
|
+
let maxDecodeSize = 0;
|
|
1662
|
+
// validate all channels before allocating any image data
|
|
1663
|
+
for (let i = 0, ch = 0; i < (channelsCount + 2) && reader.offset + 4 <= end; i++) {
|
|
1490
1664
|
const has = readUint32(reader);
|
|
1491
1665
|
if (!has)
|
|
1492
1666
|
continue;
|
|
1493
1667
|
const length = readUint32(reader);
|
|
1668
|
+
if (!length)
|
|
1669
|
+
continue;
|
|
1670
|
+
if (length < 23)
|
|
1671
|
+
throw new Error('Invalid pattern channel length');
|
|
1494
1672
|
const pixelDepth = readUint32(reader);
|
|
1495
1673
|
const ctop = readUint32(reader);
|
|
1496
1674
|
const cleft = readUint32(reader);
|
|
@@ -1498,97 +1676,127 @@ function readPattern(reader) {
|
|
|
1498
1676
|
const cright = readUint32(reader);
|
|
1499
1677
|
const pixelDepth2 = readUint16(reader);
|
|
1500
1678
|
const compressionMode = readUint8(reader); // 0 - raw, 1 - rle
|
|
1501
|
-
const
|
|
1502
|
-
const
|
|
1503
|
-
if (
|
|
1504
|
-
throw new Error(
|
|
1505
|
-
|
|
1679
|
+
const cdata = readBytes(reader, length - 23);
|
|
1680
|
+
const depth = (pixelDepth2 === 8 || pixelDepth2 === 16 || pixelDepth2 === 32) ? pixelDepth2 : pixelDepth;
|
|
1681
|
+
if (depth !== 8 && depth !== 16 && depth !== 32)
|
|
1682
|
+
throw new Error(`Unsupported pattern pixel depth: ${depth}`);
|
|
1683
|
+
if (ctop < top || cleft < left || cbottom > bottom || cright > right || cbottom < ctop || cright < cleft) {
|
|
1684
|
+
throw new Error('Pattern channel bounds outside of pattern bounds');
|
|
1685
|
+
}
|
|
1686
|
+
const target = ch < colorChannels ? ch : (ch === colorChannels ? alphaTarget : -1);
|
|
1687
|
+
ch++;
|
|
1688
|
+
if (target === -1)
|
|
1689
|
+
continue;
|
|
1506
1690
|
const w = cright - cleft;
|
|
1507
1691
|
const h = cbottom - ctop;
|
|
1508
|
-
const
|
|
1509
|
-
const
|
|
1692
|
+
const rowBytes = w * (depth / 8);
|
|
1693
|
+
const channelSize = rowBytes * h;
|
|
1510
1694
|
if (compressionMode === 0) {
|
|
1511
|
-
if (
|
|
1512
|
-
|
|
1513
|
-
for (let x = 0; x < w; x++) {
|
|
1514
|
-
const src = x + y * w;
|
|
1515
|
-
const dst = (ox + x + (y + oy) * width) * 4;
|
|
1516
|
-
data[dst + ch] = cdata[src];
|
|
1517
|
-
}
|
|
1518
|
-
}
|
|
1519
|
-
}
|
|
1520
|
-
else if (colorMode === 1 /* ColorMode.Grayscale */ && ch < 1) {
|
|
1521
|
-
for (let y = 0; y < h; y++) {
|
|
1522
|
-
for (let x = 0; x < w; x++) {
|
|
1523
|
-
const src = x + y * w;
|
|
1524
|
-
const dst = (ox + x + (y + oy) * width) * 4;
|
|
1525
|
-
const value = cdata[src];
|
|
1526
|
-
data[dst + 0] = value;
|
|
1527
|
-
data[dst + 1] = value;
|
|
1528
|
-
data[dst + 2] = value;
|
|
1529
|
-
}
|
|
1530
|
-
}
|
|
1531
|
-
}
|
|
1532
|
-
else if (colorMode === 2 /* ColorMode.Indexed */) {
|
|
1533
|
-
for (let y = 0; y < h; y++) {
|
|
1534
|
-
for (let x = 0; x < w; x++) {
|
|
1535
|
-
const src = x + y * w;
|
|
1536
|
-
const dst = (ox + x + (y + oy) * width) * 4;
|
|
1537
|
-
const index = cdata[src];
|
|
1538
|
-
const color = palette[index];
|
|
1539
|
-
data[dst + 0] = color.r;
|
|
1540
|
-
data[dst + 1] = color.g;
|
|
1541
|
-
data[dst + 2] = color.b;
|
|
1542
|
-
}
|
|
1543
|
-
}
|
|
1544
|
-
}
|
|
1545
|
-
else {
|
|
1546
|
-
if (reader.throwForMissingFeatures)
|
|
1547
|
-
throw new Error('Invalid color pattern');
|
|
1548
|
-
}
|
|
1695
|
+
if (cdata.byteLength < channelSize)
|
|
1696
|
+
throw new Error('Pattern channel data out of bounds');
|
|
1549
1697
|
}
|
|
1550
1698
|
else if (compressionMode === 1) {
|
|
1551
|
-
|
|
1552
|
-
|
|
1553
|
-
|
|
1699
|
+
// each 2 bytes of RLE data can produce at most 128 bytes
|
|
1700
|
+
if (channelSize > cdata.byteLength * 64)
|
|
1701
|
+
throw new Error('Pattern channel data out of bounds');
|
|
1702
|
+
maxDecodeSize = Math.max(maxDecodeSize, channelSize);
|
|
1703
|
+
}
|
|
1704
|
+
else {
|
|
1705
|
+
throw new Error('Invalid pattern compression mode');
|
|
1706
|
+
}
|
|
1707
|
+
channels.push({ target, depth, compressionMode, cdata, ctop, cleft, w, h, rowBytes, channelSize });
|
|
1708
|
+
}
|
|
1709
|
+
// check peak memory up front (image data, CMYK buffer and largest RLE decode buffer),
|
|
1710
|
+
// image data is consumed only after successful decoding so failed patterns don't reduce the limit
|
|
1711
|
+
const size = width * height * 4;
|
|
1712
|
+
const cmykSize = isCmyk ? width * height * 5 : 0;
|
|
1713
|
+
consumeMemory(reader, size + cmykSize + maxDecodeSize);
|
|
1714
|
+
recoverMemory(reader, size + cmykSize + maxDecodeSize);
|
|
1715
|
+
const data = new Uint8Array(size);
|
|
1716
|
+
for (let i = 3; i < data.byteLength; i += 4) {
|
|
1717
|
+
data[i] = 255;
|
|
1718
|
+
}
|
|
1719
|
+
// CMYK channels are decoded into separate buffer (5 values per pixel, inverted, 0 = 100% ink) and converted to RGB at the end
|
|
1720
|
+
const cmyk = isCmyk ? new Uint8Array(cmykSize).fill(255) : undefined;
|
|
1721
|
+
for (const { target, depth, compressionMode, cdata, ctop, cleft, w, h, rowBytes, channelSize } of channels) {
|
|
1722
|
+
let bytes = cdata;
|
|
1723
|
+
if (compressionMode === 1) {
|
|
1724
|
+
bytes = new Uint8Array(channelSize);
|
|
1554
1725
|
const cdataReader = createReader(cdata.buffer, cdata.byteOffset, cdata.byteLength);
|
|
1555
|
-
|
|
1556
|
-
|
|
1557
|
-
|
|
1558
|
-
|
|
1559
|
-
|
|
1560
|
-
|
|
1561
|
-
|
|
1562
|
-
|
|
1726
|
+
readDataRLE(cdataReader, { data: bytes, width: rowBytes, height: h }, rowBytes, h, 8, 1, [0], false);
|
|
1727
|
+
}
|
|
1728
|
+
if (depth !== 8)
|
|
1729
|
+
bytes = patternChannelTo8bit(bytes, w * h, depth);
|
|
1730
|
+
const ox = cleft - left;
|
|
1731
|
+
const oy = ctop - top;
|
|
1732
|
+
if (cmyk) {
|
|
1733
|
+
copyPatternChannel(bytes, cmyk, w, h, (ox + oy * width) * 5 + target, width * 5, 5);
|
|
1734
|
+
}
|
|
1735
|
+
else if (target === 3 || colorMode === 3 /* ColorMode.RGB */) {
|
|
1736
|
+
copyPatternChannel(bytes, data, w, h, (ox + oy * width) * 4 + target, width * 4, 4);
|
|
1737
|
+
}
|
|
1738
|
+
else if (colorMode === 2 /* ColorMode.Indexed */) {
|
|
1739
|
+
const colors = new Uint8Array(256 * 3);
|
|
1740
|
+
for (let i = 0; i < 256; i++) {
|
|
1741
|
+
colors[i * 3 + 0] = palette[i].r;
|
|
1742
|
+
colors[i * 3 + 1] = palette[i].g;
|
|
1743
|
+
colors[i * 3 + 2] = palette[i].b;
|
|
1563
1744
|
}
|
|
1564
|
-
|
|
1565
|
-
|
|
1566
|
-
|
|
1745
|
+
for (let y = 0; y < h; y++) {
|
|
1746
|
+
for (let x = 0, src = y * w, dst = (ox + (y + oy) * width) * 4; x < w; x++, src++, dst += 4) {
|
|
1747
|
+
const index = bytes[src] * 3;
|
|
1748
|
+
data[dst + 0] = colors[index + 0];
|
|
1749
|
+
data[dst + 1] = colors[index + 1];
|
|
1750
|
+
data[dst + 2] = colors[index + 2];
|
|
1751
|
+
}
|
|
1567
1752
|
}
|
|
1568
|
-
recoverMemory(reader, w * h);
|
|
1569
1753
|
}
|
|
1570
|
-
else {
|
|
1571
|
-
|
|
1754
|
+
else { // grayscale
|
|
1755
|
+
for (let y = 0; y < h; y++) {
|
|
1756
|
+
for (let x = 0, src = y * w, dst = (ox + (y + oy) * width) * 4; x < w; x++, src++, dst += 4) {
|
|
1757
|
+
const value = bytes[src];
|
|
1758
|
+
data[dst + 0] = value;
|
|
1759
|
+
data[dst + 1] = value;
|
|
1760
|
+
data[dst + 2] = value;
|
|
1761
|
+
}
|
|
1762
|
+
}
|
|
1572
1763
|
}
|
|
1573
|
-
ch++;
|
|
1574
1764
|
}
|
|
1765
|
+
if (cmyk) {
|
|
1766
|
+
cmykToRgb({ width, height, data: cmyk }, { width, height, data }, false);
|
|
1767
|
+
}
|
|
1768
|
+
consumeMemory(reader, size);
|
|
1575
1769
|
reader.offset = end;
|
|
1576
1770
|
return { id, name, x, y, bounds: { x: left, y: top, w: width, h: height }, data };
|
|
1577
1771
|
}
|
|
1578
|
-
|
|
1579
|
-
|
|
1580
|
-
const
|
|
1581
|
-
|
|
1582
|
-
|
|
1583
|
-
|
|
1584
|
-
|
|
1585
|
-
|
|
1586
|
-
|
|
1587
|
-
|
|
1772
|
+
// converts big-endian 16bit or 32bit (float) values to 8bit values
|
|
1773
|
+
function patternChannelTo8bit(bytes, count, depth) {
|
|
1774
|
+
const result = new Uint8Array(count);
|
|
1775
|
+
if (depth === 16) {
|
|
1776
|
+
for (let i = 0; i < count; i++) {
|
|
1777
|
+
result[i] = Math.round(((bytes[i * 2] << 8) | bytes[i * 2 + 1]) / 257);
|
|
1778
|
+
}
|
|
1779
|
+
}
|
|
1780
|
+
else {
|
|
1781
|
+
const view = new DataView(bytes.buffer, bytes.byteOffset, bytes.byteLength);
|
|
1782
|
+
for (let i = 0; i < count; i++) {
|
|
1783
|
+
const value = view.getFloat32(i * 4, false);
|
|
1784
|
+
result[i] = value > 0 ? (value < 1 ? Math.round(value * 255) : 255) : 0;
|
|
1785
|
+
}
|
|
1786
|
+
}
|
|
1787
|
+
return result;
|
|
1788
|
+
}
|
|
1789
|
+
// copies w x h channel values into interleaved data starting at `offset`
|
|
1790
|
+
function copyPatternChannel(bytes, data, w, h, offset, stride, step) {
|
|
1791
|
+
for (let y = 0, src = 0; y < h; y++) {
|
|
1792
|
+
for (let x = 0, dst = offset + y * stride; x < w; x++, src++, dst += step) {
|
|
1793
|
+
data[dst] = bytes[src];
|
|
1588
1794
|
}
|
|
1589
1795
|
}
|
|
1590
1796
|
}
|
|
1591
1797
|
function consumeMemory(reader, size) {
|
|
1798
|
+
if (!(size >= 0))
|
|
1799
|
+
throw new Error('Invalid size');
|
|
1592
1800
|
if (reader.totalMemoryLimit !== undefined) {
|
|
1593
1801
|
if (reader.totalMemoryLimit < size)
|
|
1594
1802
|
throw new Error('Exceeded memory limit');
|