@yft-design/psd-lib 1.0.1

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.
@@ -0,0 +1,1188 @@
1
+ import { inflate as inflateSync } from 'pako';
2
+ import { infoHandlersMap } from './additionalInfo';
3
+ import { RAW_IMAGE_DATA, createImageData, decodeBitmap, imageDataToCanvas, largeAdditionalInfoKeys, offsetForChannel, resetImageData, toBlendMode } from './helpers';
4
+ import { resourceHandlersMap } from './imageResources';
5
+ export const supportedColorModes = [0 /* ColorMode.Bitmap */, 1 /* ColorMode.Grayscale */, 3 /* ColorMode.RGB */, 2 /* ColorMode.Indexed */, 4 /* ColorMode.CMYK */];
6
+ const colorModes = ['bitmap', 'grayscale', 'indexed', 'RGB', 'CMYK', 'multichannel', 'duotone', 'lab'];
7
+ function setupGrayscale(data) {
8
+ const size = data.width * data.height * 4;
9
+ for (let i = 0; i < size; i += 4) {
10
+ const c = data.data[i];
11
+ data.data[i + 1] = c;
12
+ data.data[i + 2] = c;
13
+ }
14
+ }
15
+ export function createReader(buffer, offset, length) {
16
+ const view = new DataView(buffer, offset, length);
17
+ return { view, offset: 0, strict: false, debug: false, large: false, globalAlpha: false, log: console.log };
18
+ }
19
+ export function warnOrThrow(reader, message) {
20
+ if (reader.strict)
21
+ throw new Error(message);
22
+ if (reader.debug)
23
+ reader.log(message);
24
+ }
25
+ export function readUint8(reader) {
26
+ reader.offset += 1;
27
+ return reader.view.getUint8(reader.offset - 1);
28
+ }
29
+ export function peekUint8(reader) {
30
+ return reader.view.getUint8(reader.offset);
31
+ }
32
+ export function readInt16(reader) {
33
+ reader.offset += 2;
34
+ return reader.view.getInt16(reader.offset - 2, false);
35
+ }
36
+ export function readUint16(reader) {
37
+ reader.offset += 2;
38
+ return reader.view.getUint16(reader.offset - 2, false);
39
+ }
40
+ export function readUint16LE(reader) {
41
+ reader.offset += 2;
42
+ return reader.view.getUint16(reader.offset - 2, true);
43
+ }
44
+ export function readInt32(reader) {
45
+ reader.offset += 4;
46
+ return reader.view.getInt32(reader.offset - 4, false);
47
+ }
48
+ export function readInt32LE(reader) {
49
+ reader.offset += 4;
50
+ return reader.view.getInt32(reader.offset - 4, true);
51
+ }
52
+ export function readUint32(reader) {
53
+ reader.offset += 4;
54
+ return reader.view.getUint32(reader.offset - 4, false);
55
+ }
56
+ export function readFloat32(reader) {
57
+ reader.offset += 4;
58
+ return reader.view.getFloat32(reader.offset - 4, false);
59
+ }
60
+ export function readFloat64(reader) {
61
+ reader.offset += 8;
62
+ return reader.view.getFloat64(reader.offset - 8, false);
63
+ }
64
+ // 32-bit fixed-point number 16.16
65
+ export function readFixedPoint32(reader) {
66
+ return readInt32(reader) / (1 << 16);
67
+ }
68
+ // 32-bit fixed-point number 8.24
69
+ export function readFixedPointPath32(reader) {
70
+ return readInt32(reader) / (1 << 24);
71
+ }
72
+ export function readBytes(reader, length) {
73
+ const start = reader.view.byteOffset + reader.offset;
74
+ reader.offset += length;
75
+ if (start + length > reader.view.buffer.byteLength) {
76
+ // fix for broken PSD files that are missing part of file at the end
77
+ warnOrThrow(reader, 'Reading bytes exceeding buffer length');
78
+ if (length > 100 * 1024 * 1024)
79
+ throw new Error('Reading past end of file'); // limit to 100MB
80
+ const result = new Uint8Array(length);
81
+ const len = Math.min(length, reader.view.byteLength - start);
82
+ if (len > 0)
83
+ result.set(new Uint8Array(reader.view.buffer, start, len));
84
+ return result;
85
+ }
86
+ else {
87
+ return new Uint8Array(reader.view.buffer, start, length);
88
+ }
89
+ }
90
+ export function readSignature(reader) {
91
+ return readShortString(reader, 4);
92
+ }
93
+ export function validSignatureAt(reader, offset) {
94
+ const sig = String.fromCharCode(reader.view.getUint8(offset)) +
95
+ String.fromCharCode(reader.view.getUint8(offset + 1)) +
96
+ String.fromCharCode(reader.view.getUint8(offset + 2)) +
97
+ String.fromCharCode(reader.view.getUint8(offset + 3));
98
+ return sig == '8BIM' || sig == '8B64';
99
+ }
100
+ export function readPascalString(reader, padTo) {
101
+ let length = readUint8(reader);
102
+ const text = length ? readShortString(reader, length) : '';
103
+ while (++length % padTo) {
104
+ // starts with length + 1 so we count the size byte too
105
+ reader.offset++;
106
+ }
107
+ return text;
108
+ }
109
+ export function readUnicodeString(reader) {
110
+ const length = readUint32(reader);
111
+ return readUnicodeStringWithLength(reader, length);
112
+ }
113
+ export function readUnicodeStringWithLength(reader, length) {
114
+ let text = '';
115
+ while (length--) {
116
+ const value = readUint16(reader);
117
+ if (value || length > 0) {
118
+ // remove trailing \0
119
+ text += String.fromCharCode(value);
120
+ }
121
+ }
122
+ return text;
123
+ }
124
+ export function readUnicodeStringWithLengthLE(reader, length) {
125
+ let text = '';
126
+ while (length--) {
127
+ const value = readUint16LE(reader);
128
+ if (value || length > 0) {
129
+ // remove trailing \0
130
+ text += String.fromCharCode(value);
131
+ }
132
+ }
133
+ return text;
134
+ }
135
+ export function readAsciiString(reader, length) {
136
+ let text = '';
137
+ while (length--) {
138
+ text += String.fromCharCode(readUint8(reader));
139
+ }
140
+ return text;
141
+ }
142
+ export function skipBytes(reader, count) {
143
+ reader.offset += count;
144
+ }
145
+ export function checkSignature(reader, a, b) {
146
+ const offset = reader.offset;
147
+ const signature = readSignature(reader);
148
+ if (signature !== a && signature !== b) {
149
+ throw new Error(`Invalid signature: '${signature}' at 0x${offset.toString(16)}`);
150
+ }
151
+ }
152
+ function readShortString(reader, length) {
153
+ const buffer = readBytes(reader, length);
154
+ let result = '';
155
+ for (let i = 0; i < buffer.length; i++) {
156
+ result += String.fromCharCode(buffer[i]);
157
+ }
158
+ return result;
159
+ }
160
+ function isValidSignature(sig) {
161
+ return sig === '8BIM' || sig === 'MeSa' || sig === 'AgHg' || sig === 'PHUT' || sig === 'DCSR';
162
+ }
163
+ export function readPsd(reader, readOptions = {}) {
164
+ // header
165
+ checkSignature(reader, '8BPS');
166
+ const version = readUint16(reader);
167
+ if (version !== 1 && version !== 2)
168
+ throw new Error(`Invalid PSD file version: ${version}`);
169
+ skipBytes(reader, 6);
170
+ const channels = readUint16(reader);
171
+ const height = readUint32(reader);
172
+ const width = readUint32(reader);
173
+ const bitsPerChannel = readUint16(reader);
174
+ const colorMode = readUint16(reader);
175
+ const maxSize = version === 1 ? 30000 : 300000;
176
+ if (width > maxSize || height > maxSize)
177
+ throw new Error(`Invalid size: ${width}x${height}`);
178
+ if (channels > 16)
179
+ throw new Error(`Invalid channel count: ${channels}`);
180
+ if (![1, 8, 16, 32].includes(bitsPerChannel))
181
+ throw new Error(`Invalid bitsPerChannel: ${bitsPerChannel}`);
182
+ if (supportedColorModes.indexOf(colorMode) === -1)
183
+ throw new Error(`Color mode not supported: ${colorModes[colorMode] ?? colorMode}`);
184
+ const psd = { width, height, channels, bitsPerChannel, colorMode };
185
+ Object.assign(reader, readOptions);
186
+ reader.large = version === 2;
187
+ reader.globalAlpha = false;
188
+ // color mode data
189
+ readSection(reader, 1, (left) => {
190
+ if (!left())
191
+ return;
192
+ if (colorMode === 2 /* ColorMode.Indexed */) {
193
+ // should have 256 colors here saved as 8bit channels RGB
194
+ if (left() != 768)
195
+ throw new Error('Invalid color palette size');
196
+ psd.palette = [];
197
+ for (let i = 0; i < 256; i++)
198
+ psd.palette.push({ r: readUint8(reader), g: 0, b: 0 });
199
+ for (let i = 0; i < 256; i++)
200
+ psd.palette[i].g = readUint8(reader);
201
+ for (let i = 0; i < 256; i++)
202
+ psd.palette[i].b = readUint8(reader);
203
+ }
204
+ else {
205
+ // TODO: unknown format for duotone, also seems to have some data here for 32bit colors
206
+ // if (options.throwForMissingFeatures) throw new Error('Color mode data not supported');
207
+ }
208
+ skipBytes(reader, left());
209
+ });
210
+ // image resources
211
+ const imageResources = {};
212
+ readSection(reader, 1, (left) => {
213
+ while (left() > 0) {
214
+ realignWithSignature(reader, isValidSignature);
215
+ const id = readUint16(reader);
216
+ readPascalString(reader, 2); // name
217
+ readSection(reader, 2, (left) => {
218
+ const handler = resourceHandlersMap[id];
219
+ const skip = id === 1036 && !!reader.skipThumbnail;
220
+ if (handler && !skip) {
221
+ try {
222
+ handler.read(reader, imageResources, left);
223
+ }
224
+ catch (e) {
225
+ if (reader.throwForMissingFeatures)
226
+ throw e;
227
+ skipBytes(reader, left());
228
+ }
229
+ }
230
+ else {
231
+ // options.logMissingFeatures && console.log(`Unhandled image resource: ${id} (${left()})`);
232
+ skipBytes(reader, left());
233
+ }
234
+ });
235
+ }
236
+ });
237
+ const { layersGroup, layerGroupsEnabledId, ...rest } = imageResources;
238
+ if (Object.keys(rest)) {
239
+ psd.imageResources = rest;
240
+ }
241
+ // layer and mask info
242
+ readSection(reader, 1, (left) => {
243
+ readSection(reader, 2, (left) => {
244
+ readLayerInfo(reader, psd, imageResources);
245
+ skipBytes(reader, left());
246
+ }, undefined, reader.large);
247
+ // SAI does not include this section
248
+ if (left() > 0) {
249
+ const globalLayerMaskInfo = readGlobalLayerMaskInfo(reader);
250
+ if (globalLayerMaskInfo)
251
+ psd.globalLayerMaskInfo = globalLayerMaskInfo;
252
+ }
253
+ else {
254
+ // revert back to end of section if exceeded section limits
255
+ // opt.logMissingFeatures && console.log('reverting to end of section');
256
+ skipBytes(reader, left());
257
+ }
258
+ while (left() > 0) {
259
+ // sometimes there are empty bytes here
260
+ while (left() && peekUint8(reader) === 0) {
261
+ // opt.logMissingFeatures && console.log('skipping 0 byte');
262
+ skipBytes(reader, 1);
263
+ }
264
+ if (left() >= 12) {
265
+ readAdditionalLayerInfo(reader, psd, psd, imageResources);
266
+ }
267
+ else {
268
+ // opt.logMissingFeatures && console.log('skipping leftover bytes', left());
269
+ skipBytes(reader, left());
270
+ }
271
+ }
272
+ }, undefined, reader.large);
273
+ const hasChildren = psd.children && psd.children.length;
274
+ const skipComposite = reader.skipCompositeImageData && (reader.skipLayerImageData || hasChildren);
275
+ if (!skipComposite) {
276
+ readImageData(reader, psd);
277
+ }
278
+ // TODO: show converted color mode instead of original PSD file color mode
279
+ // but add option to preserve file color mode (need to return image data instead of canvas in that case)
280
+ // psd.colorMode = ColorMode.RGB; // we convert all color modes to RGB
281
+ return psd;
282
+ }
283
+ export function readLayerInfo(reader, psd, imageResources) {
284
+ const { layersGroup = [], layerGroupsEnabledId = [] } = imageResources;
285
+ let layerCount = readInt16(reader);
286
+ if (layerCount < 0) {
287
+ reader.globalAlpha = true;
288
+ layerCount = -layerCount;
289
+ }
290
+ const layers = [];
291
+ const layerChannels = [];
292
+ for (let i = 0; i < layerCount; i++) {
293
+ const { layer, channels } = readLayerRecord(reader, psd, imageResources);
294
+ if (layersGroup[i] !== undefined)
295
+ layer.linkGroup = layersGroup[i];
296
+ if (layerGroupsEnabledId[i] !== undefined)
297
+ layer.linkGroupEnabled = !!layerGroupsEnabledId[i];
298
+ layers.push(layer);
299
+ layerChannels.push(channels);
300
+ }
301
+ if (!reader.skipLayerImageData) {
302
+ for (let i = 0; i < layerCount; i++) {
303
+ readLayerChannelImageData(reader, psd, layers[i], layerChannels[i]);
304
+ }
305
+ }
306
+ if (!psd.children)
307
+ psd.children = [];
308
+ const stack = [psd];
309
+ for (let i = layers.length - 1; i >= 0; i--) {
310
+ const l = layers[i];
311
+ const type = l.sectionDivider ? l.sectionDivider.type : 0 /* SectionDividerType.Other */;
312
+ if (type === 1 /* SectionDividerType.OpenFolder */ || type === 2 /* SectionDividerType.ClosedFolder */) {
313
+ l.opened = type === 1 /* SectionDividerType.OpenFolder */;
314
+ l.children = [];
315
+ if (l.sectionDivider?.key) {
316
+ l.blendMode = toBlendMode[l.sectionDivider.key] ?? l.blendMode;
317
+ }
318
+ stack[stack.length - 1].children.unshift(l);
319
+ stack.push(l);
320
+ }
321
+ else if (type === 3 /* SectionDividerType.BoundingSectionDivider */) {
322
+ stack.pop();
323
+ // this was workaround because I didn't know what `lsdk` section was, now it's probably not needed anymore
324
+ // } else if (l.name === '</Layer group>' && !l.sectionDivider && !l.top && !l.left && !l.bottom && !l.right) {
325
+ // // sometimes layer group terminator doesn't have sectionDivider, so we just guess here (PS bug ?)
326
+ // stack.pop();
327
+ }
328
+ else {
329
+ stack[stack.length - 1].children.unshift(l);
330
+ }
331
+ }
332
+ }
333
+ function readLayerRecord(reader, psd, imageResources) {
334
+ const layer = {};
335
+ layer.top = readInt32(reader);
336
+ layer.left = readInt32(reader);
337
+ layer.bottom = readInt32(reader);
338
+ layer.right = readInt32(reader);
339
+ const channelCount = readUint16(reader);
340
+ const channels = [];
341
+ for (let i = 0; i < channelCount; i++) {
342
+ let id = readInt16(reader);
343
+ let length = readUint32(reader);
344
+ if (reader.large) {
345
+ if (length !== 0)
346
+ throw new Error('Sizes larger than 4GB are not supported');
347
+ length = readUint32(reader);
348
+ }
349
+ channels.push({ id, length });
350
+ }
351
+ checkSignature(reader, '8BIM');
352
+ const blendMode = readSignature(reader);
353
+ if (!toBlendMode[blendMode])
354
+ throw new Error(`Invalid blend mode: '${blendMode}'`);
355
+ layer.blendMode = toBlendMode[blendMode];
356
+ layer.opacity = readUint8(reader) / 0xff;
357
+ layer.clipping = readUint8(reader) === 1;
358
+ const flags = readUint8(reader);
359
+ layer.transparencyProtected = (flags & 0x01) !== 0;
360
+ layer.hidden = (flags & 0x02) !== 0;
361
+ if (flags & 0x20)
362
+ layer.effectsOpen = true;
363
+ // 0x04 - obsolete
364
+ // 0x08 - 1 for Photoshop 5.0 and later, tells if bit 4 has useful information
365
+ // 0x10 - pixel data irrelevant to appearance of document
366
+ // 0x20 - effects/filters panel is expanded
367
+ skipBytes(reader, 1);
368
+ readSection(reader, 1, (left) => {
369
+ readLayerMaskData(reader, layer);
370
+ const blendingRanges = readLayerBlendingRanges(reader);
371
+ if (blendingRanges)
372
+ layer.blendingRanges = blendingRanges;
373
+ layer.name = readPascalString(reader, 1); // should be padded to 4, but is not sometimes
374
+ // HACK: fix for sometimes layer.name string not being padded correctly, just skip until we get valid signature
375
+ while (left() > 4 && !validSignatureAt(reader, reader.offset))
376
+ reader.offset++;
377
+ while (left() >= 12)
378
+ readAdditionalLayerInfo(reader, layer, psd, imageResources);
379
+ skipBytes(reader, left());
380
+ });
381
+ return { layer, channels };
382
+ }
383
+ function readLayerMaskData(reader, layer) {
384
+ return readSection(reader, 1, (left) => {
385
+ if (!left())
386
+ return undefined;
387
+ const mask = {};
388
+ layer.mask = mask;
389
+ mask.top = readInt32(reader);
390
+ mask.left = readInt32(reader);
391
+ mask.bottom = readInt32(reader);
392
+ mask.right = readInt32(reader);
393
+ mask.defaultColor = readUint8(reader);
394
+ const flags = readUint8(reader);
395
+ mask.positionRelativeToLayer = (flags & 1 /* LayerMaskFlags.PositionRelativeToLayer */) !== 0;
396
+ mask.disabled = (flags & 2 /* LayerMaskFlags.LayerMaskDisabled */) !== 0;
397
+ mask.fromVectorData = (flags & 8 /* LayerMaskFlags.LayerMaskFromRenderingOtherData */) !== 0;
398
+ if (left() >= 18) {
399
+ const realMask = {};
400
+ layer.realMask = realMask;
401
+ const realFlags = readUint8(reader);
402
+ realMask.positionRelativeToLayer = (realFlags & 1 /* LayerMaskFlags.PositionRelativeToLayer */) !== 0;
403
+ realMask.disabled = (realFlags & 2 /* LayerMaskFlags.LayerMaskDisabled */) !== 0;
404
+ realMask.fromVectorData = (realFlags & 8 /* LayerMaskFlags.LayerMaskFromRenderingOtherData */) !== 0;
405
+ realMask.defaultColor = readUint8(reader); // Real user mask background. 0 or 255.
406
+ realMask.top = readInt32(reader);
407
+ realMask.left = readInt32(reader);
408
+ realMask.bottom = readInt32(reader);
409
+ realMask.right = readInt32(reader);
410
+ }
411
+ if (flags & 16 /* LayerMaskFlags.MaskHasParametersAppliedToIt */) {
412
+ const params = readUint8(reader);
413
+ if (params & 1 /* MaskParams.UserMaskDensity */)
414
+ mask.userMaskDensity = readUint8(reader) / 0xff;
415
+ if (params & 2 /* MaskParams.UserMaskFeather */)
416
+ mask.userMaskFeather = readFloat64(reader);
417
+ if (params & 4 /* MaskParams.VectorMaskDensity */)
418
+ mask.vectorMaskDensity = readUint8(reader) / 0xff;
419
+ if (params & 8 /* MaskParams.VectorMaskFeather */)
420
+ mask.vectorMaskFeather = readFloat64(reader);
421
+ }
422
+ skipBytes(reader, left());
423
+ });
424
+ }
425
+ function readBlendingRange(reader) {
426
+ return [readUint8(reader), readUint8(reader), readUint8(reader), readUint8(reader)];
427
+ }
428
+ function readLayerBlendingRanges(reader) {
429
+ return readSection(reader, 1, (left) => {
430
+ const compositeGrayBlendSource = readBlendingRange(reader);
431
+ const compositeGraphBlendDestinationRange = readBlendingRange(reader);
432
+ const ranges = [];
433
+ while (left() > 0) {
434
+ const sourceRange = readBlendingRange(reader);
435
+ const destRange = readBlendingRange(reader);
436
+ ranges.push({ sourceRange, destRange });
437
+ }
438
+ return { compositeGrayBlendSource, compositeGraphBlendDestinationRange, ranges };
439
+ });
440
+ }
441
+ function readLayerChannelImageData(reader, psd, layer, channels) {
442
+ const layerWidth = (layer.right || 0) - (layer.left || 0);
443
+ const layerHeight = (layer.bottom || 0) - (layer.top || 0);
444
+ const cmyk = psd.colorMode === 4 /* ColorMode.CMYK */;
445
+ let imageData;
446
+ if (layerWidth && layerHeight) {
447
+ if (cmyk) {
448
+ if (psd.bitsPerChannel !== 8)
449
+ throw new Error('bitsPerChannel Not supproted');
450
+ imageData = { width: layerWidth, height: layerHeight, data: new Uint8ClampedArray(layerWidth * layerHeight * 5) };
451
+ for (let p = 4; p < imageData.data.byteLength; p += 5)
452
+ imageData.data[p] = 255;
453
+ }
454
+ else {
455
+ imageData = createImageDataBitDepth(layerWidth, layerHeight, psd.bitsPerChannel ?? 8);
456
+ resetImageData(imageData);
457
+ }
458
+ }
459
+ if (RAW_IMAGE_DATA) {
460
+ ;
461
+ layer.imageDataRaw = [];
462
+ layer.imageDataRawCompression = [];
463
+ }
464
+ for (const channel of channels) {
465
+ if (channel.length === 0)
466
+ continue;
467
+ if (channel.length < 2)
468
+ throw new Error('Invalid channel length');
469
+ const start = reader.offset;
470
+ let compression = readUint16(reader);
471
+ // try to fix broken files where there's 1 byte shift of channel
472
+ if (compression > 3) {
473
+ reader.offset -= 1;
474
+ compression = readUint16(reader);
475
+ }
476
+ // try to fix broken files where there's 1 byte shift of channel
477
+ if (compression > 3) {
478
+ reader.offset -= 3;
479
+ compression = readUint16(reader);
480
+ }
481
+ if (compression > 3)
482
+ throw new Error(`Invalid compression: ${compression}`);
483
+ if (channel.id === -2 /* ChannelID.UserMask */ || channel.id === -3 /* ChannelID.RealUserMask */) {
484
+ const mask = channel.id === -2 /* ChannelID.UserMask */ ? layer.mask : layer.realMask;
485
+ if (!mask)
486
+ throw new Error(`Missing layer ${channel.id === -2 /* ChannelID.UserMask */ ? 'mask' : 'real mask'} data`);
487
+ const maskWidth = (mask.right || 0) - (mask.left || 0);
488
+ const maskHeight = (mask.bottom || 0) - (mask.top || 0);
489
+ if (maskWidth < 0 || maskHeight < 0 || maskWidth > 30000 || maskHeight > 30000)
490
+ throw new Error('Invalid mask size');
491
+ if (maskWidth && maskHeight) {
492
+ const maskData = createImageDataBitDepth(maskWidth, maskHeight, psd.bitsPerChannel ?? 8);
493
+ resetImageData(maskData);
494
+ const start = reader.offset;
495
+ readData(reader, channel.length, maskData, compression, maskWidth, maskHeight, psd.bitsPerChannel ?? 8, 0, reader.large, 4);
496
+ if (RAW_IMAGE_DATA) {
497
+ if (channel.id === -2 /* ChannelID.UserMask */) {
498
+ ;
499
+ layer.maskDataRawCompression = compression;
500
+ layer.maskDataRaw = new Uint8Array(reader.view.buffer, reader.view.byteOffset + start, reader.offset - start);
501
+ }
502
+ else {
503
+ ;
504
+ layer.realMaskDataRawCompression = compression;
505
+ layer.realMaskDataRaw = new Uint8Array(reader.view.buffer, reader.view.byteOffset + start, reader.offset - start);
506
+ }
507
+ }
508
+ setupGrayscale(maskData);
509
+ if (reader.useImageData) {
510
+ mask.imageData = maskData;
511
+ }
512
+ else {
513
+ mask.canvas = imageDataToCanvas(maskData);
514
+ }
515
+ }
516
+ }
517
+ else {
518
+ const offset = offsetForChannel(channel.id, cmyk);
519
+ let targetData = imageData;
520
+ if (offset < 0) {
521
+ targetData = undefined;
522
+ if (reader.throwForMissingFeatures) {
523
+ throw new Error(`Channel not supported: ${channel.id}`);
524
+ }
525
+ }
526
+ readData(reader, channel.length, targetData, compression, layerWidth, layerHeight, psd.bitsPerChannel ?? 8, offset, reader.large, cmyk ? 5 : 4);
527
+ if (RAW_IMAGE_DATA) {
528
+ ;
529
+ layer.imageDataRawCompression[channel.id] = compression;
530
+ layer.imageDataRaw[channel.id] = new Uint8Array(reader.view.buffer, reader.view.byteOffset + start + 2, channel.length - 2);
531
+ }
532
+ reader.offset = start + channel.length;
533
+ if (targetData && psd.colorMode === 1 /* ColorMode.Grayscale */) {
534
+ setupGrayscale(targetData);
535
+ }
536
+ }
537
+ }
538
+ if (imageData) {
539
+ if (cmyk) {
540
+ const cmykData = imageData;
541
+ imageData = createImageData(cmykData.width, cmykData.height);
542
+ cmykToRgb(cmykData, imageData, false);
543
+ }
544
+ if (reader.useImageData) {
545
+ layer.imageData = imageData;
546
+ }
547
+ else {
548
+ layer.canvas = imageDataToCanvas(imageData);
549
+ }
550
+ }
551
+ }
552
+ export function readData(reader, length, data, compression, width, height, bitDepth, offset, large, step) {
553
+ if (compression === 0 /* Compression.RawData */) {
554
+ readDataRaw(reader, data, width, height, bitDepth, step, offset);
555
+ }
556
+ else if (compression === 1 /* Compression.RleCompressed */) {
557
+ readDataRLE(reader, data, width, height, bitDepth, step, [offset], large);
558
+ }
559
+ else if (compression === 2 /* Compression.ZipWithoutPrediction */) {
560
+ readDataZip(reader, length, data, width, height, bitDepth, step, offset, false);
561
+ }
562
+ else if (compression === 3 /* Compression.ZipWithPrediction */) {
563
+ readDataZip(reader, length, data, width, height, bitDepth, step, offset, true);
564
+ }
565
+ else {
566
+ throw new Error(`Invalid Compression type: ${compression}`);
567
+ }
568
+ }
569
+ export function readGlobalLayerMaskInfo(reader) {
570
+ return readSection(reader, 1, (left) => {
571
+ if (!left())
572
+ return undefined;
573
+ const overlayColorSpace = readUint16(reader);
574
+ const colorSpace1 = readUint16(reader);
575
+ const colorSpace2 = readUint16(reader);
576
+ const colorSpace3 = readUint16(reader);
577
+ const colorSpace4 = readUint16(reader);
578
+ const opacity = readUint16(reader) / 0xff;
579
+ const kind = readUint8(reader);
580
+ skipBytes(reader, left()); // 3 bytes of padding ?
581
+ return { overlayColorSpace, colorSpace1, colorSpace2, colorSpace3, colorSpace4, opacity, kind };
582
+ });
583
+ }
584
+ const fixOffsets = [0, 1, -1, 2, -2, 3, -3, 4, -4];
585
+ function realignWithSignature(reader, isValid) {
586
+ const sigOffset = reader.offset;
587
+ let sig = '';
588
+ // attempt to fix broken document by realigning with the signature
589
+ for (const offset of fixOffsets) {
590
+ try {
591
+ reader.offset = sigOffset + offset;
592
+ sig = readSignature(reader);
593
+ }
594
+ catch { }
595
+ if (isValid(sig))
596
+ break;
597
+ }
598
+ if (!isValid(sig)) {
599
+ throw new Error(`Invalid signature: '${sig}' at 0x${sigOffset.toString(16)}`);
600
+ }
601
+ return sig;
602
+ }
603
+ function isValidAdditionalInfoSignature(sig) {
604
+ return sig === '8BIM' || sig === '8B64';
605
+ }
606
+ export function readAdditionalLayerInfo(reader, target, psd, imageResources) {
607
+ const sig = realignWithSignature(reader, isValidAdditionalInfoSignature);
608
+ const key = readSignature(reader);
609
+ // `largeAdditionalInfoKeys` fallback, because some keys don't have 8B64 signature even when they are 64bit
610
+ const u64 = sig === '8B64' || (reader.large && largeAdditionalInfoKeys.indexOf(key) !== -1);
611
+ readSection(reader, 2, (left) => {
612
+ const handler = infoHandlersMap[key];
613
+ if (handler) {
614
+ try {
615
+ handler.read(reader, target, left, psd, imageResources);
616
+ }
617
+ catch (e) {
618
+ if (reader.throwForMissingFeatures)
619
+ throw e;
620
+ }
621
+ }
622
+ else {
623
+ reader.logMissingFeatures && reader.log(`Unhandled additional info: ${key}`);
624
+ skipBytes(reader, left());
625
+ }
626
+ if (left()) {
627
+ reader.logMissingFeatures && reader.log(`Unread ${left()} bytes left for additional info: ${key}`);
628
+ skipBytes(reader, left());
629
+ }
630
+ }, false, u64);
631
+ }
632
+ export function createImageDataBitDepth(width, height, bitDepth, channels = 4) {
633
+ if (bitDepth === 1 || bitDepth === 8) {
634
+ if (channels === 4) {
635
+ return createImageData(width, height);
636
+ }
637
+ else {
638
+ return { width, height, data: new Uint8ClampedArray(width * height * channels) };
639
+ }
640
+ }
641
+ else if (bitDepth === 16) {
642
+ return { width, height, data: new Uint16Array(width * height * channels) };
643
+ }
644
+ else if (bitDepth === 32) {
645
+ return { width, height, data: new Float32Array(width * height * channels) };
646
+ }
647
+ else {
648
+ throw new Error(`Invalid bitDepth (${bitDepth})`);
649
+ }
650
+ }
651
+ function readImageData(reader, psd) {
652
+ const compression = readUint16(reader);
653
+ const bitsPerChannel = psd.bitsPerChannel ?? 8;
654
+ if (supportedColorModes.indexOf(psd.colorMode) === -1)
655
+ throw new Error(`Color mode not supported: ${psd.colorMode}`);
656
+ if (compression !== 0 /* Compression.RawData */ && compression !== 1 /* Compression.RleCompressed */)
657
+ throw new Error(`Compression type not supported: ${compression}`);
658
+ const imageData = createImageDataBitDepth(psd.width, psd.height, bitsPerChannel);
659
+ resetImageData(imageData);
660
+ switch (psd.colorMode) {
661
+ case 0 /* ColorMode.Bitmap */: {
662
+ if (bitsPerChannel !== 1)
663
+ throw new Error('Invalid bitsPerChannel for bitmap color mode');
664
+ let bytes;
665
+ if (compression === 0 /* Compression.RawData */) {
666
+ bytes = readBytes(reader, Math.ceil(psd.width / 8) * psd.height);
667
+ }
668
+ else if (compression === 1 /* Compression.RleCompressed */) {
669
+ bytes = new Uint8Array(psd.width * psd.height);
670
+ readDataRLE(reader, { data: bytes, width: psd.width, height: psd.height }, psd.width, psd.height, 8, 1, [0], reader.large);
671
+ }
672
+ else {
673
+ throw new Error(`Bitmap compression not supported: ${compression}`);
674
+ }
675
+ decodeBitmap(bytes, imageData.data, psd.width, psd.height);
676
+ break;
677
+ }
678
+ case 3 /* ColorMode.RGB */:
679
+ case 1 /* ColorMode.Grayscale */: {
680
+ const channels = psd.colorMode === 1 /* ColorMode.Grayscale */ ? [0] : [0, 1, 2];
681
+ if (psd.channels && psd.channels > 3) {
682
+ for (let i = 3; i < psd.channels; i++) {
683
+ // TODO: store these channels in additional image data
684
+ channels.push(i);
685
+ }
686
+ }
687
+ else if (reader.globalAlpha) {
688
+ channels.push(3);
689
+ }
690
+ if (compression === 0 /* Compression.RawData */) {
691
+ for (let i = 0; i < channels.length; i++) {
692
+ readDataRaw(reader, imageData, psd.width, psd.height, bitsPerChannel, 4, channels[i]);
693
+ }
694
+ }
695
+ else if (compression === 1 /* Compression.RleCompressed */) {
696
+ const start = reader.offset;
697
+ readDataRLE(reader, imageData, psd.width, psd.height, bitsPerChannel, 4, channels, reader.large);
698
+ if (RAW_IMAGE_DATA)
699
+ psd.imageDataRaw = new Uint8Array(reader.view.buffer, reader.view.byteOffset + start, reader.offset - start);
700
+ }
701
+ if (psd.colorMode === 1 /* ColorMode.Grayscale */) {
702
+ setupGrayscale(imageData);
703
+ }
704
+ break;
705
+ }
706
+ case 2 /* ColorMode.Indexed */: {
707
+ if (bitsPerChannel !== 8)
708
+ throw new Error('bitsPerChannel Not supproted');
709
+ if (psd.channels !== 1)
710
+ throw new Error('Invalid channel count');
711
+ if (!psd.palette)
712
+ throw new Error('Missing color palette');
713
+ if (compression === 0 /* Compression.RawData */) {
714
+ throw new Error(`Not implemented`);
715
+ }
716
+ else if (compression === 1 /* Compression.RleCompressed */) {
717
+ const indexedImageData = {
718
+ width: imageData.width,
719
+ height: imageData.height,
720
+ data: new Uint8Array(imageData.width * imageData.height)
721
+ };
722
+ readDataRLE(reader, indexedImageData, psd.width, psd.height, bitsPerChannel, 1, [0], reader.large);
723
+ indexedToRgb(indexedImageData, imageData, psd.palette);
724
+ }
725
+ else {
726
+ throw new Error(`Not implemented`);
727
+ }
728
+ break;
729
+ }
730
+ case 4 /* ColorMode.CMYK */: {
731
+ if (bitsPerChannel !== 8)
732
+ throw new Error('bitsPerChannel Not supproted');
733
+ if (psd.channels !== 4 && psd.channels !== 5)
734
+ throw new Error(`Invalid channel count`);
735
+ const channels = [0, 1, 2, 3];
736
+ if (reader.globalAlpha)
737
+ channels.push(4);
738
+ if (compression === 0 /* Compression.RawData */) {
739
+ throw new Error(`Not implemented`);
740
+ // TODO: ...
741
+ // for (let i = 0; i < channels.length; i++) {
742
+ // readDataRaw(reader, imageData, channels[i], psd.width, psd.height);
743
+ // }
744
+ }
745
+ else if (compression === 1 /* Compression.RleCompressed */) {
746
+ const cmykImageData = {
747
+ width: imageData.width,
748
+ height: imageData.height,
749
+ data: new Uint8Array(imageData.width * imageData.height * 5)
750
+ };
751
+ const start = reader.offset;
752
+ readDataRLE(reader, cmykImageData, psd.width, psd.height, bitsPerChannel, 5, channels, reader.large);
753
+ cmykToRgb(cmykImageData, imageData, true);
754
+ if (RAW_IMAGE_DATA)
755
+ psd.imageDataRaw = new Uint8Array(reader.view.buffer, reader.view.byteOffset + start, reader.offset - start);
756
+ }
757
+ else {
758
+ throw new Error(`Not implemented`);
759
+ }
760
+ break;
761
+ }
762
+ default:
763
+ throw new Error(`Color mode not supported: ${psd.colorMode}`);
764
+ }
765
+ // remove weird white matte
766
+ if (reader.globalAlpha) {
767
+ if (psd.bitsPerChannel !== 8)
768
+ throw new Error('bitsPerChannel Not supproted');
769
+ const p = imageData.data;
770
+ const size = imageData.width * imageData.height * 4;
771
+ for (let i = 0; i < size; i += 4) {
772
+ const pa = p[i + 3];
773
+ if (pa != 0 && pa != 255) {
774
+ const a = pa / 255;
775
+ const ra = 1 / a;
776
+ const invA = 255 * (1 - ra);
777
+ p[i + 0] = p[i + 0] * ra + invA;
778
+ p[i + 1] = p[i + 1] * ra + invA;
779
+ p[i + 2] = p[i + 2] * ra + invA;
780
+ }
781
+ }
782
+ }
783
+ if (reader.useImageData) {
784
+ psd.imageData = imageData;
785
+ }
786
+ else {
787
+ psd.canvas = imageDataToCanvas(imageData);
788
+ }
789
+ }
790
+ function cmykToRgb(cmyk, rgb, reverseAlpha) {
791
+ const size = rgb.width * rgb.height * 4;
792
+ const srcData = cmyk.data;
793
+ const dstData = rgb.data;
794
+ for (let src = 0, dst = 0; dst < size; src += 5, dst += 4) {
795
+ const c = srcData[src];
796
+ const m = srcData[src + 1];
797
+ const y = srcData[src + 2];
798
+ const k = srcData[src + 3];
799
+ dstData[dst] = (((c * k) | 0) / 255) | 0;
800
+ dstData[dst + 1] = (((m * k) | 0) / 255) | 0;
801
+ dstData[dst + 2] = (((y * k) | 0) / 255) | 0;
802
+ dstData[dst + 3] = reverseAlpha ? 255 - srcData[src + 4] : srcData[src + 4];
803
+ }
804
+ // for (let src = 0, dst = 0; dst < size; src += 5, dst += 4) {
805
+ // const c = 1 - (srcData[src + 0] / 255);
806
+ // const m = 1 - (srcData[src + 1] / 255);
807
+ // const y = 1 - (srcData[src + 2] / 255);
808
+ // // const k = srcData[src + 3] / 255;
809
+ // dstData[dst + 0] = ((1 - c * 0.8) * 255) | 0;
810
+ // dstData[dst + 1] = ((1 - m * 0.8) * 255) | 0;
811
+ // dstData[dst + 2] = ((1 - y * 0.8) * 255) | 0;
812
+ // dstData[dst + 3] = reverseAlpha ? 255 - srcData[src + 4] : srcData[src + 4];
813
+ // }
814
+ }
815
+ function indexedToRgb(indexed, rgb, palette) {
816
+ const size = indexed.width * indexed.height;
817
+ const srcData = indexed.data;
818
+ const dstData = rgb.data;
819
+ for (let src = 0, dst = 0; src < size; src++, dst += 4) {
820
+ const c = palette[srcData[src]];
821
+ dstData[dst + 0] = c.r;
822
+ dstData[dst + 1] = c.g;
823
+ dstData[dst + 2] = c.b;
824
+ dstData[dst + 3] = 255;
825
+ }
826
+ }
827
+ function verifyCompatible(a, b) {
828
+ if (a.byteLength / a.length !== b.byteLength / b.length) {
829
+ throw new Error('Invalid array types');
830
+ }
831
+ }
832
+ function bytesToArray(bytes, bitDepth) {
833
+ if (bitDepth === 8) {
834
+ return bytes;
835
+ }
836
+ else if (bitDepth === 16) {
837
+ if (bytes.byteOffset % 2) {
838
+ const result = new Uint16Array(bytes.byteLength / 2);
839
+ new Uint8Array(result.buffer, result.byteOffset, result.byteLength).set(bytes);
840
+ return result;
841
+ }
842
+ else {
843
+ return new Uint16Array(bytes.buffer, bytes.byteOffset, bytes.byteLength / 2);
844
+ }
845
+ }
846
+ else if (bitDepth === 32) {
847
+ if (bytes.byteOffset % 4) {
848
+ const result = new Float32Array(bytes.byteLength / 4);
849
+ new Uint8Array(result.buffer, result.byteOffset, result.byteLength).set(bytes);
850
+ return result;
851
+ }
852
+ else {
853
+ return new Float32Array(bytes.buffer, bytes.byteOffset, bytes.byteLength / 4);
854
+ }
855
+ }
856
+ else {
857
+ throw new Error(`Invalid bitDepth (${bitDepth})`);
858
+ }
859
+ }
860
+ function copyChannelToPixelData(pixelData, channel, offset, step) {
861
+ verifyCompatible(pixelData.data, channel);
862
+ const size = pixelData.width * pixelData.height;
863
+ const data = pixelData.data;
864
+ for (let i = 0, p = offset | 0; i < size; i++, p = (p + step) | 0) {
865
+ data[p] = channel[i];
866
+ }
867
+ }
868
+ function readDataRaw(reader, pixelData, width, height, bitDepth, step, offset) {
869
+ const buffer = readBytes(reader, width * height * Math.floor(bitDepth / 8));
870
+ if (bitDepth == 32) {
871
+ for (let i = 0; i < buffer.byteLength; i += 4) {
872
+ const a = buffer[i + 0];
873
+ const b = buffer[i + 1];
874
+ const c = buffer[i + 2];
875
+ const d = buffer[i + 3];
876
+ buffer[i + 0] = d;
877
+ buffer[i + 1] = c;
878
+ buffer[i + 2] = b;
879
+ buffer[i + 3] = a;
880
+ }
881
+ }
882
+ const array = bytesToArray(buffer, bitDepth);
883
+ if (pixelData && offset < step) {
884
+ copyChannelToPixelData(pixelData, array, offset, step);
885
+ }
886
+ }
887
+ function decodePredicted(data, width, height, mod) {
888
+ for (let y = 0; y < height; y++) {
889
+ const offset = y * width;
890
+ for (let x = 1, o = offset + 1; x < width; x++, o++) {
891
+ data[o] = (data[o - 1] + data[o]) % mod;
892
+ }
893
+ }
894
+ }
895
+ export function readDataZip(reader, length, pixelData, width, height, bitDepth, step, offset, prediction) {
896
+ const compressed = readBytes(reader, length);
897
+ const decompressed = inflateSync(compressed);
898
+ if (pixelData && offset < step) {
899
+ const array = bytesToArray(decompressed, bitDepth);
900
+ if (bitDepth === 8) {
901
+ if (prediction)
902
+ decodePredicted(decompressed, width, height, 0x100);
903
+ copyChannelToPixelData(pixelData, decompressed, offset, step);
904
+ }
905
+ else if (bitDepth === 16) {
906
+ if (prediction)
907
+ decodePredicted(array, width, height, 0x10000);
908
+ copyChannelToPixelData(pixelData, array, offset, step);
909
+ }
910
+ else if (bitDepth === 32) {
911
+ if (prediction)
912
+ decodePredicted(decompressed, width * 4, height, 0x100);
913
+ let di = offset;
914
+ const dst = new Uint32Array(pixelData.data.buffer, pixelData.data.byteOffset, pixelData.data.length);
915
+ for (let y = 0; y < height; y++) {
916
+ let a = width * 4 * y;
917
+ for (let x = 0; x < width; x++, a++, di += step) {
918
+ const b = a + width;
919
+ const c = b + width;
920
+ const d = c + width;
921
+ dst[di] = ((decompressed[a] << 24) | (decompressed[b] << 16) | (decompressed[c] << 8) | decompressed[d]) >>> 0;
922
+ }
923
+ }
924
+ }
925
+ else {
926
+ throw new Error('Invalid bitDepth');
927
+ }
928
+ }
929
+ }
930
+ export function readDataRLE(reader, pixelData, width, height, bitDepth, step, offsets, large) {
931
+ const data = pixelData && pixelData.data;
932
+ let lengths;
933
+ if (large) {
934
+ lengths = new Uint32Array(offsets.length * height);
935
+ for (let o = 0, li = 0; o < offsets.length; o++) {
936
+ for (let y = 0; y < height; y++, li++) {
937
+ lengths[li] = readUint32(reader);
938
+ }
939
+ }
940
+ }
941
+ else {
942
+ lengths = new Uint16Array(offsets.length * height);
943
+ for (let o = 0, li = 0; o < offsets.length; o++) {
944
+ for (let y = 0; y < height; y++, li++) {
945
+ lengths[li] = readUint16(reader);
946
+ }
947
+ }
948
+ }
949
+ if (bitDepth !== 1 && bitDepth !== 8)
950
+ throw new Error(`Invalid bit depth (${bitDepth})`);
951
+ const extraLimit = (step - 1) | 0; // 3 for rgb, 4 for cmyk
952
+ for (let c = 0, li = 0; c < offsets.length; c++) {
953
+ const offset = offsets[c] | 0;
954
+ const extra = c > extraLimit || offset > extraLimit;
955
+ if (!data || extra) {
956
+ for (let y = 0; y < height; y++, li++) {
957
+ skipBytes(reader, lengths[li]);
958
+ }
959
+ }
960
+ else {
961
+ for (let y = 0, p = offset | 0; y < height; y++, li++) {
962
+ const length = lengths[li];
963
+ const buffer = readBytes(reader, length);
964
+ for (let i = 0, x = 0; i < length; i++) {
965
+ let header = buffer[i];
966
+ if (header > 128) {
967
+ const value = buffer[++i];
968
+ header = (256 - header) | 0;
969
+ for (let j = 0; j <= header && x < width; j = (j + 1) | 0, x = (x + 1) | 0) {
970
+ data[p] = value;
971
+ p = (p + step) | 0;
972
+ }
973
+ }
974
+ else if (header < 128) {
975
+ for (let j = 0; j <= header && x < width; j = (j + 1) | 0, x = (x + 1) | 0) {
976
+ data[p] = buffer[++i];
977
+ p = (p + step) | 0;
978
+ }
979
+ }
980
+ else {
981
+ // ignore 128
982
+ }
983
+ // This showed up on some images from non-photoshop programs, ignoring it seems to work just fine.
984
+ // if (i >= length) throw new Error(`Invalid RLE data: exceeded buffer size ${i}/${length}`);
985
+ }
986
+ }
987
+ }
988
+ }
989
+ }
990
+ export function readSection(reader, round, func, skipEmpty = true, eightBytes = false) {
991
+ let length = readUint32(reader);
992
+ if (eightBytes) {
993
+ if (length !== 0)
994
+ throw new Error('Sizes larger than 4GB are not supported');
995
+ length = readUint32(reader);
996
+ }
997
+ if (length <= 0 && skipEmpty)
998
+ return undefined;
999
+ let end = reader.offset + length;
1000
+ if (end > reader.view.byteLength)
1001
+ throw new Error('Section exceeds file size');
1002
+ const result = func(() => end - reader.offset);
1003
+ if (reader.offset !== end) {
1004
+ if (reader.offset > end) {
1005
+ warnOrThrow(reader, 'Exceeded section limits');
1006
+ }
1007
+ else {
1008
+ warnOrThrow(reader, `Unread section data`); // : ${end - reader.offset} bytes at 0x${reader.offset.toString(16)}`);
1009
+ }
1010
+ }
1011
+ while (length % round) {
1012
+ length++;
1013
+ end++;
1014
+ }
1015
+ // while (end % round) end++;
1016
+ reader.offset = end;
1017
+ return result;
1018
+ }
1019
+ export function readColor(reader) {
1020
+ const colorSpace = readUint16(reader);
1021
+ switch (colorSpace) {
1022
+ case 0 /* ColorSpace.RGB */: {
1023
+ const r = readUint16(reader) / 257;
1024
+ const g = readUint16(reader) / 257;
1025
+ const b = readUint16(reader) / 257;
1026
+ skipBytes(reader, 2);
1027
+ return { r, g, b };
1028
+ }
1029
+ case 1 /* ColorSpace.HSB */: {
1030
+ const h = readUint16(reader) / 0xffff;
1031
+ const s = readUint16(reader) / 0xffff;
1032
+ const b = readUint16(reader) / 0xffff;
1033
+ skipBytes(reader, 2);
1034
+ return { h, s, b };
1035
+ }
1036
+ case 2 /* ColorSpace.CMYK */: {
1037
+ const c = readUint16(reader) / 257;
1038
+ const m = readUint16(reader) / 257;
1039
+ const y = readUint16(reader) / 257;
1040
+ const k = readUint16(reader) / 257;
1041
+ return { c, m, y, k };
1042
+ }
1043
+ case 7 /* ColorSpace.Lab */: {
1044
+ const l = readInt16(reader) / 10000;
1045
+ const ta = readInt16(reader);
1046
+ const tb = readInt16(reader);
1047
+ const a = ta < 0 ? ta / 12800 : ta / 12700;
1048
+ const b = tb < 0 ? tb / 12800 : tb / 12700;
1049
+ skipBytes(reader, 2);
1050
+ return { l, a, b };
1051
+ }
1052
+ case 8 /* ColorSpace.Grayscale */: {
1053
+ const k = (readUint16(reader) * 255) / 10000;
1054
+ skipBytes(reader, 6);
1055
+ return { k };
1056
+ }
1057
+ default:
1058
+ throw new Error('Invalid color space');
1059
+ }
1060
+ }
1061
+ export function readPattern(reader) {
1062
+ readUint32(reader); // length
1063
+ const version = readUint32(reader);
1064
+ if (version !== 1)
1065
+ throw new Error(`Invalid pattern version: ${version}`);
1066
+ const colorMode = readUint32(reader);
1067
+ const x = readInt16(reader);
1068
+ const y = readInt16(reader);
1069
+ // we only support RGB and grayscale for now
1070
+ if (colorMode !== 3 /* ColorMode.RGB */ && colorMode !== 1 /* ColorMode.Grayscale */ && colorMode !== 2 /* ColorMode.Indexed */) {
1071
+ throw new Error(`Unsupported pattern color mode: ${colorMode}`);
1072
+ }
1073
+ let name = readUnicodeString(reader);
1074
+ const id = readPascalString(reader, 1);
1075
+ const palette = [];
1076
+ if (colorMode === 2 /* ColorMode.Indexed */) {
1077
+ for (let i = 0; i < 256; i++) {
1078
+ palette.push({
1079
+ r: readUint8(reader),
1080
+ g: readUint8(reader),
1081
+ b: readUint8(reader)
1082
+ });
1083
+ }
1084
+ skipBytes(reader, 4); // no idea what this is
1085
+ }
1086
+ // virtual memory array list
1087
+ const version2 = readUint32(reader);
1088
+ if (version2 !== 3)
1089
+ throw new Error(`Invalid pattern VMAL version: ${version2}`);
1090
+ readUint32(reader); // length
1091
+ const top = readUint32(reader);
1092
+ const left = readUint32(reader);
1093
+ const bottom = readUint32(reader);
1094
+ const right = readUint32(reader);
1095
+ const channelsCount = readUint32(reader);
1096
+ const width = right - left;
1097
+ const height = bottom - top;
1098
+ const data = new Uint8Array(width * height * 4);
1099
+ for (let i = 3; i < data.byteLength; i += 4) {
1100
+ data[i] = 255;
1101
+ }
1102
+ for (let i = 0, ch = 0; i < channelsCount + 2; i++) {
1103
+ const has = readUint32(reader);
1104
+ if (!has)
1105
+ continue;
1106
+ const length = readUint32(reader);
1107
+ const pixelDepth = readUint32(reader);
1108
+ const ctop = readUint32(reader);
1109
+ const cleft = readUint32(reader);
1110
+ const cbottom = readUint32(reader);
1111
+ const cright = readUint32(reader);
1112
+ const pixelDepth2 = readUint16(reader);
1113
+ const compressionMode = readUint8(reader); // 0 - raw, 1 - rle
1114
+ const dataLength = length - (4 + 16 + 2 + 1);
1115
+ const cdata = readBytes(reader, dataLength);
1116
+ if (pixelDepth !== 8 || pixelDepth2 !== 8) {
1117
+ throw new Error('16bit pixel depth not supported for patterns');
1118
+ }
1119
+ const w = cright - cleft;
1120
+ const h = cbottom - ctop;
1121
+ const ox = cleft - left;
1122
+ const oy = ctop - top;
1123
+ if (compressionMode === 0) {
1124
+ if (colorMode === 3 /* ColorMode.RGB */ && ch < 3) {
1125
+ for (let y = 0; y < h; y++) {
1126
+ for (let x = 0; x < w; x++) {
1127
+ const src = x + y * w;
1128
+ const dst = (ox + x + (y + oy) * width) * 4;
1129
+ data[dst + ch] = cdata[src];
1130
+ }
1131
+ }
1132
+ }
1133
+ if (colorMode === 1 /* ColorMode.Grayscale */ && ch < 1) {
1134
+ for (let y = 0; y < h; y++) {
1135
+ for (let x = 0; x < w; x++) {
1136
+ const src = x + y * w;
1137
+ const dst = (ox + x + (y + oy) * width) * 4;
1138
+ const value = cdata[src];
1139
+ data[dst + 0] = value;
1140
+ data[dst + 1] = value;
1141
+ data[dst + 2] = value;
1142
+ }
1143
+ }
1144
+ }
1145
+ if (colorMode === 2 /* ColorMode.Indexed */) {
1146
+ // TODO:
1147
+ throw new Error('Indexed pattern color mode not implemented');
1148
+ }
1149
+ }
1150
+ else if (compressionMode === 1) {
1151
+ const pixelData = { data, width, height };
1152
+ const tempData = { data: new Uint8Array(w * h), width: w, height: h };
1153
+ const cdataReader = createReader(cdata.buffer, cdata.byteOffset, cdata.byteLength);
1154
+ if (colorMode === 3 /* ColorMode.RGB */ && ch < 3) {
1155
+ readDataRLE(cdataReader, tempData, w, h, 8, 1, [0], false);
1156
+ copyChannelToRGBA(tempData, pixelData, ox, oy, ch);
1157
+ }
1158
+ if (colorMode === 1 /* ColorMode.Grayscale */ && ch < 1) {
1159
+ readDataRLE(cdataReader, tempData, w, h, 8, 1, [0], false);
1160
+ copyChannelToRGBA(tempData, pixelData, ox, oy, 0);
1161
+ setupGrayscale(pixelData);
1162
+ }
1163
+ if (colorMode === 2 /* ColorMode.Indexed */) {
1164
+ // TODO:
1165
+ throw new Error('Indexed pattern color mode not implemented');
1166
+ }
1167
+ }
1168
+ else {
1169
+ throw new Error('Invalid pattern compression mode');
1170
+ }
1171
+ ch++;
1172
+ }
1173
+ // TODO: use canvas instead of data ?
1174
+ return { id, name, x, y, bounds: { x: left, y: top, w: width, h: height }, data };
1175
+ }
1176
+ function copyChannelToRGBA(srcData, dstData, ox, oy, offset) {
1177
+ const w = srcData.width;
1178
+ const h = srcData.height;
1179
+ const width = dstData.width;
1180
+ for (let y = 0; y < h; y++) {
1181
+ for (let x = 0; x < w; x++) {
1182
+ const src = x + y * w;
1183
+ const dst = (ox + x + (y + oy) * width) * 4;
1184
+ const value = srcData.data[src];
1185
+ dstData.data[dst + offset] = value;
1186
+ }
1187
+ }
1188
+ }