ag-psd 33.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/05135617 +0 -0
- package/.v8-cache/v24.7.0-x64-ef5a0af0/226e2801 +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/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/e6b99945 +0 -0
- package/CHANGELOG.md +22 -0
- package/README.md +1 -1
- package/README_PSD.md +25 -9
- package/TODO.md +13 -84
- package/dist/abr.js +12 -7
- package/dist/abr.js.map +1 -1
- package/dist/additionalInfo.d.ts +2 -0
- package/dist/additionalInfo.js +361 -75
- package/dist/additionalInfo.js.map +1 -1
- package/dist/bundle.js +1524 -389
- package/dist/descriptor.d.ts +14 -5
- package/dist/descriptor.js +102 -64
- package/dist/descriptor.js.map +1 -1
- package/dist/helpers.d.ts +6 -0
- package/dist/helpers.js +242 -2
- package/dist/helpers.js.map +1 -1
- package/dist/imageResources.js +84 -3
- 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/psd.d.ts +144 -2
- package/dist/psd.js.map +1 -1
- package/dist/psdReader.js +206 -110
- package/dist/psdReader.js.map +1 -1
- package/dist/psdWriter.d.ts +1 -0
- package/dist/psdWriter.js +336 -91
- package/dist/psdWriter.js.map +1 -1
- package/dist/text.d.ts +32 -1
- package/dist/text.js +58 -0
- package/dist/text.js.map +1 -1
- package/dist-es/abr.js +12 -7
- package/dist-es/abr.js.map +1 -1
- package/dist-es/additionalInfo.d.ts +2 -0
- package/dist-es/additionalInfo.js +361 -75
- package/dist-es/additionalInfo.js.map +1 -1
- package/dist-es/descriptor.d.ts +14 -5
- package/dist-es/descriptor.js +102 -64
- package/dist-es/descriptor.js.map +1 -1
- package/dist-es/helpers.d.ts +6 -0
- package/dist-es/helpers.js +242 -2
- package/dist-es/helpers.js.map +1 -1
- package/dist-es/imageResources.js +84 -3
- 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/psd.d.ts +144 -2
- package/dist-es/psd.js.map +1 -1
- package/dist-es/psdReader.js +206 -110
- package/dist-es/psdReader.js.map +1 -1
- package/dist-es/psdWriter.d.ts +1 -0
- package/dist-es/psdWriter.js +336 -91
- package/dist-es/psdWriter.js.map +1 -1
- package/dist-es/text.d.ts +32 -1
- package/dist-es/text.js +58 -0
- package/dist-es/text.js.map +1 -1
- package/package.json +1 -1
- package/perf.js +312 -9
- package/src/abr.ts +14 -7
- package/src/additionalInfo.ts +433 -87
- package/src/descriptor.ts +103 -58
- package/src/helpers.ts +262 -2
- package/src/imageResources.ts +107 -4
- package/src/index.ts +1 -0
- package/src/psd.ts +115 -2
- package/src/psdReader.ts +226 -108
- package/src/psdWriter.ts +345 -90
- package/src/text.ts +76 -1
package/dist/psdReader.js
CHANGED
|
@@ -206,6 +206,8 @@ function readUnicodeString(reader) {
|
|
|
206
206
|
return readUnicodeStringWithLength(reader, length);
|
|
207
207
|
}
|
|
208
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;
|
|
209
211
|
function checkReadBounds(reader, size) {
|
|
210
212
|
if (reader.offset < 0 || (reader.offset + size) > reader.view.byteLength) {
|
|
211
213
|
throw new RangeError('Offset is outside the bounds of the DataView');
|
|
@@ -216,9 +218,18 @@ function readUtf16String(reader, length, littleEndian) {
|
|
|
216
218
|
return '';
|
|
217
219
|
checkReadBounds(reader, length * 2);
|
|
218
220
|
const view = reader.view;
|
|
219
|
-
const codes = new Uint16Array(Math.min(length, STRING_CHUNK_SIZE));
|
|
220
221
|
let offset = reader.offset;
|
|
221
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));
|
|
222
233
|
for (let i = 0; i < length; i += STRING_CHUNK_SIZE) {
|
|
223
234
|
const count = Math.min(STRING_CHUNK_SIZE, length - i);
|
|
224
235
|
for (let j = 0; j < count; j++, offset += 2) {
|
|
@@ -249,6 +260,15 @@ function readAsciiString(reader, length) {
|
|
|
249
260
|
if (!(length > 0))
|
|
250
261
|
return '';
|
|
251
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
|
+
}
|
|
252
272
|
const text = bytesToString(new Uint8Array(reader.view.buffer, reader.view.byteOffset + reader.offset, length));
|
|
253
273
|
reader.offset += length;
|
|
254
274
|
return text;
|
|
@@ -264,7 +284,14 @@ function checkSignature(reader, a, b) {
|
|
|
264
284
|
}
|
|
265
285
|
}
|
|
266
286
|
function readShortString(reader, length) {
|
|
267
|
-
|
|
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;
|
|
268
295
|
}
|
|
269
296
|
function isValidSignature(sig) {
|
|
270
297
|
return sig === '8BIM' || sig === 'MeSa' || sig === 'AgHg' || sig === 'PHUT' || sig === 'DCSR';
|
|
@@ -450,6 +477,9 @@ function readLayerInfo(reader, psd, imageResources) {
|
|
|
450
477
|
if (type === 1 /* SectionDividerType.OpenFolder */ || type === 2 /* SectionDividerType.ClosedFolder */) {
|
|
451
478
|
l.opened = type === 1 /* SectionDividerType.OpenFolder */;
|
|
452
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;
|
|
453
483
|
if ((_a = l.sectionDivider) === null || _a === void 0 ? void 0 : _a.key) {
|
|
454
484
|
l.blendMode = (_b = helpers_1.toBlendMode[l.sectionDivider.key]) !== null && _b !== void 0 ? _b : l.blendMode;
|
|
455
485
|
}
|
|
@@ -648,9 +678,8 @@ function readLayerChannelImageData(reader, psd, layer, channels) {
|
|
|
648
678
|
const start = reader.offset;
|
|
649
679
|
let compression = 0 /* Compression.RawData */;
|
|
650
680
|
let data = undefined;
|
|
651
|
-
|
|
652
|
-
|
|
653
|
-
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) {
|
|
654
683
|
compression = readUint16(reader);
|
|
655
684
|
// try to fix broken files where there's 1 byte shift of channel
|
|
656
685
|
if (compression > 3) {
|
|
@@ -672,7 +701,16 @@ function readLayerChannelImageData(reader, psd, layer, channels) {
|
|
|
672
701
|
layer.rawData.channels.push({ id: channel.id, compression, data });
|
|
673
702
|
}
|
|
674
703
|
if (!reader.useRawData) {
|
|
675
|
-
|
|
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
|
+
}
|
|
676
714
|
}
|
|
677
715
|
}
|
|
678
716
|
function resetAlpha({ data }, cmyk) {
|
|
@@ -876,7 +914,7 @@ function readGlobalLayerMaskInfo(reader) {
|
|
|
876
914
|
const colorSpace2 = readUint16(reader);
|
|
877
915
|
const colorSpace3 = readUint16(reader);
|
|
878
916
|
const colorSpace4 = readUint16(reader);
|
|
879
|
-
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)
|
|
880
918
|
const kind = readUint8(reader);
|
|
881
919
|
skipBytes(reader, left()); // 3 bytes of padding ?
|
|
882
920
|
return { overlayColorSpace, colorSpace1, colorSpace2, colorSpace3, colorSpace4, opacity, kind };
|
|
@@ -1126,10 +1164,7 @@ function toRange(value, max) {
|
|
|
1126
1164
|
value = Math.round(value * (max / 255));
|
|
1127
1165
|
return value <= 0 ? 0 : (value >= max ? max : value);
|
|
1128
1166
|
}
|
|
1129
|
-
//
|
|
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)
|
|
1167
|
+
// converts CMYK data to RGB using approximation of U.S. Web Coated (SWOP) v2 profile (see cmykToRgbValues)
|
|
1133
1168
|
// cmyk - 8 or 16 bit data with 5 channels (cmyka), rgb - 8 or 16 bit data with 4 channels (rgba)
|
|
1134
1169
|
// removeMatte - colors are blended with white (no ink), used for composite image of documents with global alpha
|
|
1135
1170
|
function cmykToRgb(cmyk, rgb, removeMatte) {
|
|
@@ -1138,6 +1173,7 @@ function cmykToRgb(cmyk, rgb, removeMatte) {
|
|
|
1138
1173
|
const dstData = rgb.data;
|
|
1139
1174
|
const srcMax = srcData instanceof Uint16Array ? 0xffff : 0xff;
|
|
1140
1175
|
const dstMax = dstData instanceof Uint16Array ? 0xffff : 0xff;
|
|
1176
|
+
const out = [0, 0, 0];
|
|
1141
1177
|
for (let src = 0, dst = 0; dst < size; src += 5, dst += 4) {
|
|
1142
1178
|
const a = srcData[src + 4];
|
|
1143
1179
|
// image data stores inverted values (0 = 100% ink)
|
|
@@ -1152,21 +1188,10 @@ function cmykToRgb(cmyk, rgb, removeMatte) {
|
|
|
1152
1188
|
y = Math.min(1, y * ra);
|
|
1153
1189
|
k = Math.min(1, k * ra);
|
|
1154
1190
|
}
|
|
1155
|
-
|
|
1156
|
-
|
|
1157
|
-
|
|
1158
|
-
|
|
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);
|
|
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);
|
|
1170
1195
|
dstData[dst + 3] = srcMax === dstMax ? a : Math.round(a * dstMax / srcMax);
|
|
1171
1196
|
}
|
|
1172
1197
|
}
|
|
@@ -1333,8 +1358,8 @@ function readDataZip(compressed, pixelData, width, height, bitDepth, step, offse
|
|
|
1333
1358
|
recoverMemory(reader, expectedSize);
|
|
1334
1359
|
}
|
|
1335
1360
|
}
|
|
1336
|
-
// decodes single PackBits row, returns number of decoded
|
|
1337
|
-
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) {
|
|
1338
1363
|
let x = 0;
|
|
1339
1364
|
while (i < end && x < count) {
|
|
1340
1365
|
const header = src[i++];
|
|
@@ -1345,10 +1370,8 @@ function decodeRLERow(src, i, end, dst, p, step, count) {
|
|
|
1345
1370
|
let n = 257 - header;
|
|
1346
1371
|
if (n > count - x)
|
|
1347
1372
|
n = count - x;
|
|
1373
|
+
dst.fill(value, x, x + n);
|
|
1348
1374
|
x += n;
|
|
1349
|
-
for (; n > 0; n--, p += step) {
|
|
1350
|
-
dst[p] = value;
|
|
1351
|
-
}
|
|
1352
1375
|
}
|
|
1353
1376
|
else if (header < 128) {
|
|
1354
1377
|
let n = header + 1;
|
|
@@ -1356,9 +1379,8 @@ function decodeRLERow(src, i, end, dst, p, step, count) {
|
|
|
1356
1379
|
n = count - x;
|
|
1357
1380
|
if (n > end - i)
|
|
1358
1381
|
n = end - i;
|
|
1359
|
-
|
|
1360
|
-
|
|
1361
|
-
dst[p] = src[i++];
|
|
1382
|
+
for (; n > 0; n--) {
|
|
1383
|
+
dst[x++] = src[i++];
|
|
1362
1384
|
}
|
|
1363
1385
|
}
|
|
1364
1386
|
else {
|
|
@@ -1367,6 +1389,47 @@ function decodeRLERow(src, i, end, dst, p, step, count) {
|
|
|
1367
1389
|
}
|
|
1368
1390
|
return x;
|
|
1369
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
|
+
}
|
|
1370
1433
|
// converts big-endian 16/32-bit values from bytes and writes them to `data` starting at `p` with `step`
|
|
1371
1434
|
function scatterValues(bytes, count, bpv, data, p, step) {
|
|
1372
1435
|
if (bpv === 2) {
|
|
@@ -1381,8 +1444,8 @@ function scatterValues(bytes, count, bpv, data, p, step) {
|
|
|
1381
1444
|
}
|
|
1382
1445
|
}
|
|
1383
1446
|
}
|
|
1384
|
-
// decodes RLE rows of single channel, each row
|
|
1385
|
-
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) {
|
|
1386
1449
|
const srcEnd = src.length;
|
|
1387
1450
|
const rowSize = width * bpv;
|
|
1388
1451
|
for (let y = 0; y < height; y++, li++) {
|
|
@@ -1391,25 +1454,24 @@ function decodeRLEChannel(src, lengths, li, pos, width, height, data, offset, st
|
|
|
1391
1454
|
const rowEnd = pos < srcEnd ? pos : srcEnd;
|
|
1392
1455
|
if (rowStart >= rowEnd)
|
|
1393
1456
|
continue;
|
|
1394
|
-
|
|
1395
|
-
|
|
1396
|
-
}
|
|
1397
|
-
else {
|
|
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
|
-
}
|
|
1457
|
+
const decoded = decodeRLERow(src, rowStart, rowEnd, row, rowSize);
|
|
1458
|
+
scatterValues(row, Math.floor(decoded / bpv), bpv, data, offset + y * width * step, step);
|
|
1401
1459
|
}
|
|
1402
1460
|
}
|
|
1403
1461
|
function readDataRLE(reader, pixelData, width, height, bitDepth, step, offsets, large) {
|
|
1404
1462
|
const data = pixelData && pixelData.data;
|
|
1405
1463
|
const rows = offsets.length * height;
|
|
1406
1464
|
const lengthSize = large ? 4 : 2;
|
|
1407
|
-
|
|
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);
|
|
1408
1470
|
try {
|
|
1409
|
-
if ((reader.offset +
|
|
1471
|
+
if ((reader.offset + lengthsMemory) > srcEnd) {
|
|
1410
1472
|
throw new Error('RLE row lengths exceed buffer length');
|
|
1411
1473
|
}
|
|
1412
|
-
|
|
1474
|
+
lengths = large ? new Uint32Array(rows) : new Uint16Array(rows);
|
|
1413
1475
|
let total = 0;
|
|
1414
1476
|
if (large) {
|
|
1415
1477
|
for (let i = 0; i < rows; i++)
|
|
@@ -1419,32 +1481,37 @@ function readDataRLE(reader, pixelData, width, height, bitDepth, step, offsets,
|
|
|
1419
1481
|
for (let i = 0; i < rows; i++)
|
|
1420
1482
|
total += (lengths[i] = readUint16(reader));
|
|
1421
1483
|
}
|
|
1422
|
-
|
|
1423
|
-
const srcEnd = reader.view.byteLength;
|
|
1424
|
-
if (total > (srcEnd - start)) {
|
|
1484
|
+
if (total > (srcEnd - reader.offset)) {
|
|
1425
1485
|
// broken or truncated file, decode only data that is available
|
|
1426
1486
|
warnOrThrow(reader, 'RLE data exceeds buffer length');
|
|
1427
1487
|
}
|
|
1428
|
-
const bpv = bytesPerValue(bitDepth);
|
|
1429
1488
|
if (data)
|
|
1430
1489
|
verifyArrayType(data, bpv);
|
|
1431
|
-
|
|
1432
|
-
|
|
1433
|
-
|
|
1434
|
-
|
|
1435
|
-
|
|
1436
|
-
|
|
1437
|
-
|
|
1438
|
-
|
|
1439
|
-
|
|
1440
|
-
|
|
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);
|
|
1441
1510
|
}
|
|
1442
1511
|
}
|
|
1443
|
-
reader.offset = pos;
|
|
1444
|
-
}
|
|
1445
|
-
finally {
|
|
1446
|
-
recoverMemory(reader, rows * lengthSize);
|
|
1447
1512
|
}
|
|
1513
|
+
reader.offset = pos;
|
|
1514
|
+
recoverMemory(reader, lengthsMemory);
|
|
1448
1515
|
}
|
|
1449
1516
|
function readSection(reader, round, func, skipEmpty = true, eightBytes = false) {
|
|
1450
1517
|
let length = readUint32(reader);
|
|
@@ -1586,19 +1653,13 @@ function readPattern(reader) {
|
|
|
1586
1653
|
// channels cover whole pattern, each 2 bytes of RLE data can produce at most 128 bytes
|
|
1587
1654
|
if (width * height > (Math.min(end, reader.view.byteLength) - reader.offset) * 64)
|
|
1588
1655
|
throw new Error('Pattern data out of bounds');
|
|
1589
|
-
const size = width * height * 4;
|
|
1590
|
-
consumeMemory(reader, size);
|
|
1591
|
-
const data = new Uint8Array(size);
|
|
1592
|
-
for (let i = 3; i < data.byteLength; i += 4) {
|
|
1593
|
-
data[i] = 255;
|
|
1594
|
-
}
|
|
1595
1656
|
// first written arrays are color channels, next one is transparency (Photoshop stores it in the last, sheet mask, array)
|
|
1596
1657
|
const colorChannels = colorMode === 3 /* ColorMode.RGB */ ? 3 : (colorMode === 4 /* ColorMode.CMYK */ ? 4 : 1);
|
|
1597
|
-
|
|
1598
|
-
const
|
|
1599
|
-
|
|
1600
|
-
|
|
1601
|
-
|
|
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
|
|
1602
1663
|
for (let i = 0, ch = 0; i < (channelsCount + 2) && reader.offset + 4 <= end; i++) {
|
|
1603
1664
|
const has = readUint32(reader);
|
|
1604
1665
|
if (!has)
|
|
@@ -1630,72 +1691,107 @@ function readPattern(reader) {
|
|
|
1630
1691
|
const h = cbottom - ctop;
|
|
1631
1692
|
const rowBytes = w * (depth / 8);
|
|
1632
1693
|
const channelSize = rowBytes * h;
|
|
1633
|
-
let bytes;
|
|
1634
1694
|
if (compressionMode === 0) {
|
|
1635
1695
|
if (cdata.byteLength < channelSize)
|
|
1636
1696
|
throw new Error('Pattern channel data out of bounds');
|
|
1637
|
-
bytes = cdata;
|
|
1638
1697
|
}
|
|
1639
1698
|
else if (compressionMode === 1) {
|
|
1640
1699
|
// each 2 bytes of RLE data can produce at most 128 bytes
|
|
1641
1700
|
if (channelSize > cdata.byteLength * 64)
|
|
1642
1701
|
throw new Error('Pattern channel data out of bounds');
|
|
1643
|
-
|
|
1644
|
-
bytes = new Uint8Array(channelSize);
|
|
1645
|
-
const cdataReader = createReader(cdata.buffer, cdata.byteOffset, cdata.byteLength);
|
|
1646
|
-
readDataRLE(cdataReader, { data: bytes, width: rowBytes, height: h }, rowBytes, h, 8, 1, [0], false);
|
|
1702
|
+
maxDecodeSize = Math.max(maxDecodeSize, channelSize);
|
|
1647
1703
|
}
|
|
1648
1704
|
else {
|
|
1649
1705
|
throw new Error('Invalid pattern compression mode');
|
|
1650
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);
|
|
1725
|
+
const cdataReader = createReader(cdata.buffer, cdata.byteOffset, cdata.byteLength);
|
|
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);
|
|
1651
1730
|
const ox = cleft - left;
|
|
1652
1731
|
const oy = ctop - top;
|
|
1653
|
-
|
|
1654
|
-
|
|
1655
|
-
|
|
1656
|
-
|
|
1657
|
-
|
|
1658
|
-
|
|
1659
|
-
|
|
1660
|
-
|
|
1661
|
-
|
|
1662
|
-
|
|
1663
|
-
|
|
1664
|
-
|
|
1665
|
-
|
|
1666
|
-
|
|
1667
|
-
|
|
1668
|
-
|
|
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;
|
|
1744
|
+
}
|
|
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];
|
|
1669
1751
|
}
|
|
1670
|
-
|
|
1752
|
+
}
|
|
1753
|
+
}
|
|
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];
|
|
1671
1758
|
data[dst + 0] = value;
|
|
1672
1759
|
data[dst + 1] = value;
|
|
1673
1760
|
data[dst + 2] = value;
|
|
1674
1761
|
}
|
|
1675
|
-
else {
|
|
1676
|
-
data[dst + target] = value;
|
|
1677
|
-
}
|
|
1678
1762
|
}
|
|
1679
1763
|
}
|
|
1680
|
-
if (compressionMode === 1)
|
|
1681
|
-
recoverMemory(reader, channelSize);
|
|
1682
1764
|
}
|
|
1683
1765
|
if (cmyk) {
|
|
1684
1766
|
cmykToRgb({ width, height, data: cmyk }, { width, height, data }, false);
|
|
1685
|
-
recoverMemory(reader, cmyk.byteLength);
|
|
1686
1767
|
}
|
|
1768
|
+
consumeMemory(reader, size);
|
|
1687
1769
|
reader.offset = end;
|
|
1688
1770
|
return { id, name, x, y, bounds: { x: left, y: top, w: width, h: height }, data };
|
|
1689
1771
|
}
|
|
1690
|
-
// converts big-endian 16bit or 32bit (float)
|
|
1691
|
-
function
|
|
1772
|
+
// converts big-endian 16bit or 32bit (float) values to 8bit values
|
|
1773
|
+
function patternChannelTo8bit(bytes, count, depth) {
|
|
1774
|
+
const result = new Uint8Array(count);
|
|
1692
1775
|
if (depth === 16) {
|
|
1693
|
-
|
|
1776
|
+
for (let i = 0; i < count; i++) {
|
|
1777
|
+
result[i] = Math.round(((bytes[i * 2] << 8) | bytes[i * 2 + 1]) / 257);
|
|
1778
|
+
}
|
|
1694
1779
|
}
|
|
1695
1780
|
else {
|
|
1696
|
-
const view = new DataView(bytes.buffer, bytes.byteOffset
|
|
1697
|
-
|
|
1698
|
-
|
|
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];
|
|
1794
|
+
}
|
|
1699
1795
|
}
|
|
1700
1796
|
}
|
|
1701
1797
|
function consumeMemory(reader, size) {
|