@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.
- package/README.md +45 -0
- package/dist/abr.d.ts +143 -0
- package/dist/abr.js +300 -0
- package/dist/additionalInfo.d.ts +26 -0
- package/dist/additionalInfo.js +3891 -0
- package/dist/csh.d.ts +10 -0
- package/dist/csh.js +32 -0
- package/dist/descriptor.d.ts +566 -0
- package/dist/descriptor.js +1958 -0
- package/dist/effectsHelpers.d.ts +5 -0
- package/dist/effectsHelpers.js +280 -0
- package/dist/engineData.d.ts +2 -0
- package/dist/engineData.js +330 -0
- package/dist/engineData2.d.ts +2 -0
- package/dist/engineData2.js +405 -0
- package/dist/helpers.d.ts +89 -0
- package/dist/helpers.js +300 -0
- package/dist/imageResources.d.ts +17 -0
- package/dist/imageResources.js +1070 -0
- package/dist/index.d.ts +19 -0
- package/dist/index.js +30 -0
- package/dist/initializeCanvas.d.ts +1 -0
- package/dist/initializeCanvas.js +21 -0
- package/dist/jpeg.d.ts +1 -0
- package/dist/jpeg.js +1017 -0
- package/dist/psd.d.ts +1822 -0
- package/dist/psd.js +7 -0
- package/dist/psdReader.d.ts +46 -0
- package/dist/psdReader.js +1188 -0
- package/dist/psdWriter.d.ts +33 -0
- package/dist/psdWriter.js +733 -0
- package/dist/text.d.ts +179 -0
- package/dist/text.js +556 -0
- package/dist/utf8.d.ts +4 -0
- package/dist/utf8.js +150 -0
- package/package.json +46 -0
|
@@ -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
|
+
}
|