@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,733 @@
|
|
|
1
|
+
import { hasAlpha, createCanvas, writeDataRLE, offsetForChannel, createImageData, fromBlendMode, clamp, largeAdditionalInfoKeys, RAW_IMAGE_DATA, writeDataZipWithoutPrediction, imageDataToCanvas } from './helpers';
|
|
2
|
+
import { infoHandlers } from './additionalInfo';
|
|
3
|
+
import { resourceHandlers } from './imageResources';
|
|
4
|
+
export function createWriter(size = 4096) {
|
|
5
|
+
const buffer = new ArrayBuffer(size);
|
|
6
|
+
const view = new DataView(buffer);
|
|
7
|
+
const offset = 0;
|
|
8
|
+
return { buffer, view, offset, tempBuffer: undefined };
|
|
9
|
+
}
|
|
10
|
+
export function getWriterBuffer(writer) {
|
|
11
|
+
return writer.buffer.slice(0, writer.offset);
|
|
12
|
+
}
|
|
13
|
+
export function getWriterBufferNoCopy(writer) {
|
|
14
|
+
return new Uint8Array(writer.buffer, 0, writer.offset);
|
|
15
|
+
}
|
|
16
|
+
export function writeUint8(writer, value) {
|
|
17
|
+
const offset = addSize(writer, 1);
|
|
18
|
+
writer.view.setUint8(offset, value);
|
|
19
|
+
}
|
|
20
|
+
export function writeInt16(writer, value) {
|
|
21
|
+
const offset = addSize(writer, 2);
|
|
22
|
+
writer.view.setInt16(offset, value, false);
|
|
23
|
+
}
|
|
24
|
+
export function writeUint16(writer, value) {
|
|
25
|
+
const offset = addSize(writer, 2);
|
|
26
|
+
writer.view.setUint16(offset, value, false);
|
|
27
|
+
}
|
|
28
|
+
export function writeUint16LE(writer, value) {
|
|
29
|
+
const offset = addSize(writer, 2);
|
|
30
|
+
writer.view.setUint16(offset, value, true);
|
|
31
|
+
}
|
|
32
|
+
export function writeInt32(writer, value) {
|
|
33
|
+
const offset = addSize(writer, 4);
|
|
34
|
+
writer.view.setInt32(offset, value, false);
|
|
35
|
+
}
|
|
36
|
+
export function writeInt32LE(writer, value) {
|
|
37
|
+
const offset = addSize(writer, 4);
|
|
38
|
+
writer.view.setInt32(offset, value, true);
|
|
39
|
+
}
|
|
40
|
+
export function writeUint32(writer, value) {
|
|
41
|
+
const offset = addSize(writer, 4);
|
|
42
|
+
writer.view.setUint32(offset, value, false);
|
|
43
|
+
}
|
|
44
|
+
export function writeFloat32(writer, value) {
|
|
45
|
+
const offset = addSize(writer, 4);
|
|
46
|
+
writer.view.setFloat32(offset, value, false);
|
|
47
|
+
}
|
|
48
|
+
export function writeFloat64(writer, value) {
|
|
49
|
+
const offset = addSize(writer, 8);
|
|
50
|
+
writer.view.setFloat64(offset, value, false);
|
|
51
|
+
}
|
|
52
|
+
// 32-bit fixed-point number 16.16
|
|
53
|
+
export function writeFixedPoint32(writer, value) {
|
|
54
|
+
writeInt32(writer, value * (1 << 16));
|
|
55
|
+
}
|
|
56
|
+
// 32-bit fixed-point number 8.24
|
|
57
|
+
export function writeFixedPointPath32(writer, value) {
|
|
58
|
+
writeInt32(writer, value * (1 << 24));
|
|
59
|
+
}
|
|
60
|
+
export function writeBytes(writer, buffer) {
|
|
61
|
+
if (buffer) {
|
|
62
|
+
ensureSize(writer, writer.offset + buffer.length);
|
|
63
|
+
const bytes = new Uint8Array(writer.buffer);
|
|
64
|
+
bytes.set(buffer, writer.offset);
|
|
65
|
+
writer.offset += buffer.length;
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
export function writeZeros(writer, count) {
|
|
69
|
+
for (let i = 0; i < count; i++) {
|
|
70
|
+
writeUint8(writer, 0);
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
export function writeSignature(writer, signature) {
|
|
74
|
+
if (signature.length !== 4)
|
|
75
|
+
throw new Error(`Invalid signature: '${signature}'`);
|
|
76
|
+
for (let i = 0; i < 4; i++) {
|
|
77
|
+
writeUint8(writer, signature.charCodeAt(i));
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
export function writeAsciiString(writer, text) {
|
|
81
|
+
for (let i = 0; i < text.length; i++) {
|
|
82
|
+
writeUint8(writer, text.charCodeAt(i));
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
export function writePascalString(writer, text, padTo) {
|
|
86
|
+
let length = text.length;
|
|
87
|
+
if (length > 255)
|
|
88
|
+
throw new Error(`String too long`);
|
|
89
|
+
writeUint8(writer, length);
|
|
90
|
+
for (let i = 0; i < length; i++) {
|
|
91
|
+
const code = text.charCodeAt(i);
|
|
92
|
+
// writeUint8(writer, code); // for testing
|
|
93
|
+
writeUint8(writer, code < 128 ? code : '?'.charCodeAt(0));
|
|
94
|
+
}
|
|
95
|
+
while (++length % padTo) {
|
|
96
|
+
writeUint8(writer, 0);
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
export function writeUnicodeStringWithoutLength(writer, text) {
|
|
100
|
+
for (let i = 0; i < text.length; i++) {
|
|
101
|
+
writeUint16(writer, text.charCodeAt(i));
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
export function writeUnicodeStringWithoutLengthLE(writer, text) {
|
|
105
|
+
for (let i = 0; i < text.length; i++) {
|
|
106
|
+
writeUint16LE(writer, text.charCodeAt(i));
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
export function writeUnicodeString(writer, text) {
|
|
110
|
+
writeUint32(writer, text.length);
|
|
111
|
+
writeUnicodeStringWithoutLength(writer, text);
|
|
112
|
+
}
|
|
113
|
+
export function writeUnicodeStringWithPadding(writer, text) {
|
|
114
|
+
writeUint32(writer, text.length + 1);
|
|
115
|
+
for (let i = 0; i < text.length; i++) {
|
|
116
|
+
writeUint16(writer, text.charCodeAt(i));
|
|
117
|
+
}
|
|
118
|
+
writeUint16(writer, 0);
|
|
119
|
+
}
|
|
120
|
+
function getLargestLayerSize(layers = []) {
|
|
121
|
+
let max = 0;
|
|
122
|
+
for (const layer of layers) {
|
|
123
|
+
if (layer.canvas || layer.imageData) {
|
|
124
|
+
const { width, height } = getLayerDimentions(layer);
|
|
125
|
+
max = Math.max(max, 2 * height + 2 * width * height);
|
|
126
|
+
}
|
|
127
|
+
if (layer.children) {
|
|
128
|
+
max = Math.max(max, getLargestLayerSize(layer.children));
|
|
129
|
+
}
|
|
130
|
+
}
|
|
131
|
+
return max;
|
|
132
|
+
}
|
|
133
|
+
export function writeSection(writer, round, func, writeTotalLength = false, large = false) {
|
|
134
|
+
if (large)
|
|
135
|
+
writeUint32(writer, 0);
|
|
136
|
+
const offset = writer.offset;
|
|
137
|
+
writeUint32(writer, 0);
|
|
138
|
+
func();
|
|
139
|
+
let length = writer.offset - offset - 4;
|
|
140
|
+
let len = length;
|
|
141
|
+
while (len % round) {
|
|
142
|
+
writeUint8(writer, 0);
|
|
143
|
+
len++;
|
|
144
|
+
}
|
|
145
|
+
// while (writer.offset % round) {
|
|
146
|
+
// writeUint8(writer, 0);
|
|
147
|
+
// len++;
|
|
148
|
+
// }
|
|
149
|
+
if (writeTotalLength) {
|
|
150
|
+
length = len;
|
|
151
|
+
}
|
|
152
|
+
writer.view.setUint32(offset, length, false);
|
|
153
|
+
}
|
|
154
|
+
function verifyBitCount(target) {
|
|
155
|
+
target.children?.forEach(verifyBitCount);
|
|
156
|
+
const data = target.imageData;
|
|
157
|
+
if (data && (data.data instanceof Uint32Array || data.data instanceof Uint16Array)) {
|
|
158
|
+
throw new Error('imageData has incorrect bitDepth');
|
|
159
|
+
}
|
|
160
|
+
if ('mask' in target && target.mask) {
|
|
161
|
+
const data = target.mask.imageData;
|
|
162
|
+
if (data && (data.data instanceof Uint32Array || data.data instanceof Uint16Array)) {
|
|
163
|
+
throw new Error('mask imageData has incorrect bitDepth');
|
|
164
|
+
}
|
|
165
|
+
}
|
|
166
|
+
}
|
|
167
|
+
export function writePsd(writer, psd, options = {}) {
|
|
168
|
+
if (!(+psd.width > 0 && +psd.height > 0))
|
|
169
|
+
throw new Error('Invalid document size');
|
|
170
|
+
if ((psd.width > 30000 || psd.height > 30000) && !options.psb)
|
|
171
|
+
throw new Error('Document size is too large (max is 30000x30000, use PSB format instead)');
|
|
172
|
+
const bitsPerChannel = psd.bitsPerChannel ?? 8;
|
|
173
|
+
if (bitsPerChannel !== 8)
|
|
174
|
+
throw new Error('bitsPerChannel other than 8 are not supported for writing');
|
|
175
|
+
verifyBitCount(psd);
|
|
176
|
+
const imageResources = { ...psd.imageResources };
|
|
177
|
+
const opt = { ...options, layerIds: new Set(), layerToId: new Map() };
|
|
178
|
+
if (opt.generateThumbnail) {
|
|
179
|
+
imageResources.thumbnail = createThumbnail(psd);
|
|
180
|
+
}
|
|
181
|
+
let imageData = psd.imageData;
|
|
182
|
+
if (!imageData && psd.canvas) {
|
|
183
|
+
imageData = psd.canvas.getContext('2d').getImageData(0, 0, psd.canvas.width, psd.canvas.height);
|
|
184
|
+
}
|
|
185
|
+
if (imageData && (psd.width !== imageData.width || psd.height !== imageData.height))
|
|
186
|
+
throw new Error('Document canvas must have the same size as document');
|
|
187
|
+
const globalAlpha = !!imageData && hasAlpha(imageData);
|
|
188
|
+
const maxBufferSize = Math.max(getLargestLayerSize(psd.children), 4 * 2 * psd.width * psd.height + 2 * psd.height);
|
|
189
|
+
writer.tempBuffer = new Uint8Array(maxBufferSize);
|
|
190
|
+
// header
|
|
191
|
+
writeSignature(writer, '8BPS');
|
|
192
|
+
writeUint16(writer, options.psb ? 2 : 1); // version
|
|
193
|
+
writeZeros(writer, 6);
|
|
194
|
+
writeUint16(writer, globalAlpha ? 4 : 3); // channels
|
|
195
|
+
writeUint32(writer, psd.height);
|
|
196
|
+
writeUint32(writer, psd.width);
|
|
197
|
+
writeUint16(writer, bitsPerChannel); // bits per channel
|
|
198
|
+
writeUint16(writer, 3 /* ColorMode.RGB */); // we only support saving RGB right now
|
|
199
|
+
// color mode data
|
|
200
|
+
writeSection(writer, 1, () => {
|
|
201
|
+
if (psd.palette) {
|
|
202
|
+
for (let i = 0; i < 256; i++)
|
|
203
|
+
writeUint8(writer, psd.palette[i]?.r || 0);
|
|
204
|
+
for (let i = 0; i < 256; i++)
|
|
205
|
+
writeUint8(writer, psd.palette[i]?.g || 0);
|
|
206
|
+
for (let i = 0; i < 256; i++)
|
|
207
|
+
writeUint8(writer, psd.palette[i]?.b || 0);
|
|
208
|
+
}
|
|
209
|
+
// TODO: other data?
|
|
210
|
+
});
|
|
211
|
+
const layers = [];
|
|
212
|
+
addChildren(layers, psd.children);
|
|
213
|
+
if (!layers.length)
|
|
214
|
+
layers.push({});
|
|
215
|
+
// image resources
|
|
216
|
+
imageResources.layersGroup = layers.map(l => l.linkGroup || 0);
|
|
217
|
+
imageResources.layerGroupsEnabledId = layers.map(l => l.linkGroupEnabled == false ? 0 : 1);
|
|
218
|
+
writeSection(writer, 1, () => {
|
|
219
|
+
for (const handler of resourceHandlers) {
|
|
220
|
+
const has = handler.has(imageResources);
|
|
221
|
+
const count = has === false ? 0 : (has === true ? 1 : has);
|
|
222
|
+
for (let i = 0; i < count; i++) {
|
|
223
|
+
writeSignature(writer, '8BIM');
|
|
224
|
+
writeUint16(writer, handler.key);
|
|
225
|
+
writePascalString(writer, '', 2);
|
|
226
|
+
writeSection(writer, 2, () => handler.write(writer, imageResources, i));
|
|
227
|
+
}
|
|
228
|
+
}
|
|
229
|
+
});
|
|
230
|
+
// layer and mask info
|
|
231
|
+
writeSection(writer, 2, () => {
|
|
232
|
+
writeLayerInfo(writer, layers, psd, globalAlpha, opt);
|
|
233
|
+
writeGlobalLayerMaskInfo(writer, psd.globalLayerMaskInfo);
|
|
234
|
+
writeAdditionalLayerInfo(writer, psd, psd, opt);
|
|
235
|
+
}, undefined, !!opt.psb);
|
|
236
|
+
// image data
|
|
237
|
+
const channels = globalAlpha ? [0, 1, 2, 3] : [0, 1, 2];
|
|
238
|
+
const width = imageData ? imageData.width : psd.width;
|
|
239
|
+
const height = imageData ? imageData.height : psd.height;
|
|
240
|
+
const data = { data: new Uint8Array(width * height * 4), width, height };
|
|
241
|
+
writeUint16(writer, 1 /* Compression.RleCompressed */); // Photoshop doesn't support zip compression of composite image data
|
|
242
|
+
if (RAW_IMAGE_DATA && psd.imageDataRaw) {
|
|
243
|
+
console.log('writing raw image data');
|
|
244
|
+
writeBytes(writer, psd.imageDataRaw);
|
|
245
|
+
}
|
|
246
|
+
else {
|
|
247
|
+
if (imageData)
|
|
248
|
+
data.data.set(new Uint8Array(imageData.data.buffer, imageData.data.byteOffset, imageData.data.byteLength));
|
|
249
|
+
// add weird white matte
|
|
250
|
+
if (globalAlpha) {
|
|
251
|
+
const size = data.width * data.height * 4;
|
|
252
|
+
const p = data.data;
|
|
253
|
+
for (let i = 0; i < size; i += 4) {
|
|
254
|
+
const pa = p[i + 3];
|
|
255
|
+
if (pa != 0 && pa != 255) {
|
|
256
|
+
const a = pa / 255;
|
|
257
|
+
const ra = 255 * (1 - a);
|
|
258
|
+
p[i + 0] = p[i + 0] * a + ra;
|
|
259
|
+
p[i + 1] = p[i + 1] * a + ra;
|
|
260
|
+
p[i + 2] = p[i + 2] * a + ra;
|
|
261
|
+
}
|
|
262
|
+
}
|
|
263
|
+
}
|
|
264
|
+
writeBytes(writer, writeDataRLE(writer.tempBuffer, data, channels, !!options.psb));
|
|
265
|
+
}
|
|
266
|
+
}
|
|
267
|
+
function writeLayerInfo(writer, layers, psd, globalAlpha, options) {
|
|
268
|
+
writeSection(writer, 4, () => {
|
|
269
|
+
writeInt16(writer, globalAlpha ? -layers.length : layers.length);
|
|
270
|
+
const layersData = layers.map((l, i) => getChannels(writer.tempBuffer, l, i === 0, options));
|
|
271
|
+
// layer records
|
|
272
|
+
for (const layerData of layersData) {
|
|
273
|
+
const { layer, top, left, bottom, right, channels } = layerData;
|
|
274
|
+
writeInt32(writer, top);
|
|
275
|
+
writeInt32(writer, left);
|
|
276
|
+
writeInt32(writer, bottom);
|
|
277
|
+
writeInt32(writer, right);
|
|
278
|
+
writeUint16(writer, channels.length);
|
|
279
|
+
for (const c of channels) {
|
|
280
|
+
writeInt16(writer, c.channelId);
|
|
281
|
+
if (options.psb)
|
|
282
|
+
writeUint32(writer, 0);
|
|
283
|
+
writeUint32(writer, c.length);
|
|
284
|
+
}
|
|
285
|
+
writeSignature(writer, '8BIM');
|
|
286
|
+
writeSignature(writer, fromBlendMode[layer.blendMode] || 'norm');
|
|
287
|
+
writeUint8(writer, Math.round(clamp(layer.opacity ?? 1, 0, 1) * 255));
|
|
288
|
+
writeUint8(writer, layer.clipping ? 1 : 0);
|
|
289
|
+
let flags = 0x08; // 1 for Photoshop 5.0 and later, tells if bit 4 has useful information
|
|
290
|
+
if (layer.transparencyProtected)
|
|
291
|
+
flags |= 0x01;
|
|
292
|
+
if (layer.hidden)
|
|
293
|
+
flags |= 0x02;
|
|
294
|
+
if (layer.vectorMask || (layer.sectionDivider && layer.sectionDivider.type !== 0 /* SectionDividerType.Other */) || layer.adjustment) {
|
|
295
|
+
flags |= 0x10; // pixel data irrelevant to appearance of document
|
|
296
|
+
}
|
|
297
|
+
if (layer.effectsOpen)
|
|
298
|
+
flags |= 0x20;
|
|
299
|
+
writeUint8(writer, flags);
|
|
300
|
+
writeUint8(writer, 0); // filler
|
|
301
|
+
writeSection(writer, 1, () => {
|
|
302
|
+
writeLayerMaskData(writer, layer, layerData);
|
|
303
|
+
writeLayerBlendingRanges(writer, layer);
|
|
304
|
+
writePascalString(writer, (layer.name || '').substring(0, 255), 4);
|
|
305
|
+
writeAdditionalLayerInfo(writer, layer, psd, options);
|
|
306
|
+
});
|
|
307
|
+
}
|
|
308
|
+
// layer channel image data
|
|
309
|
+
for (const layerData of layersData) {
|
|
310
|
+
for (const channel of layerData.channels) {
|
|
311
|
+
writeUint16(writer, channel.compression);
|
|
312
|
+
if (channel.buffer) {
|
|
313
|
+
writeBytes(writer, channel.buffer);
|
|
314
|
+
}
|
|
315
|
+
}
|
|
316
|
+
}
|
|
317
|
+
}, true, options.psb);
|
|
318
|
+
}
|
|
319
|
+
function writeLayerMaskData(writer, { mask, realMask }, layerData) {
|
|
320
|
+
writeSection(writer, 1, () => {
|
|
321
|
+
if (!mask && !realMask)
|
|
322
|
+
return;
|
|
323
|
+
let params = 0, flags = 0, realFlags = 0;
|
|
324
|
+
if (mask) {
|
|
325
|
+
if (mask.userMaskDensity !== undefined)
|
|
326
|
+
params |= 1 /* MaskParams.UserMaskDensity */;
|
|
327
|
+
if (mask.userMaskFeather !== undefined)
|
|
328
|
+
params |= 2 /* MaskParams.UserMaskFeather */;
|
|
329
|
+
if (mask.vectorMaskDensity !== undefined)
|
|
330
|
+
params |= 4 /* MaskParams.VectorMaskDensity */;
|
|
331
|
+
if (mask.vectorMaskFeather !== undefined)
|
|
332
|
+
params |= 8 /* MaskParams.VectorMaskFeather */;
|
|
333
|
+
if (mask.disabled)
|
|
334
|
+
flags |= 2 /* LayerMaskFlags.LayerMaskDisabled */;
|
|
335
|
+
if (mask.positionRelativeToLayer)
|
|
336
|
+
flags |= 1 /* LayerMaskFlags.PositionRelativeToLayer */;
|
|
337
|
+
if (mask.fromVectorData)
|
|
338
|
+
flags |= 8 /* LayerMaskFlags.LayerMaskFromRenderingOtherData */;
|
|
339
|
+
if (params)
|
|
340
|
+
flags |= 16 /* LayerMaskFlags.MaskHasParametersAppliedToIt */;
|
|
341
|
+
}
|
|
342
|
+
const m = layerData.mask || {};
|
|
343
|
+
writeInt32(writer, m.top || 0);
|
|
344
|
+
writeInt32(writer, m.left || 0);
|
|
345
|
+
writeInt32(writer, m.bottom || 0);
|
|
346
|
+
writeInt32(writer, m.right || 0);
|
|
347
|
+
writeUint8(writer, mask && mask.defaultColor || 0);
|
|
348
|
+
writeUint8(writer, flags);
|
|
349
|
+
if (realMask) {
|
|
350
|
+
if (realMask.disabled)
|
|
351
|
+
realFlags |= 2 /* LayerMaskFlags.LayerMaskDisabled */;
|
|
352
|
+
if (realMask.positionRelativeToLayer)
|
|
353
|
+
realFlags |= 1 /* LayerMaskFlags.PositionRelativeToLayer */;
|
|
354
|
+
if (realMask.fromVectorData)
|
|
355
|
+
realFlags |= 8 /* LayerMaskFlags.LayerMaskFromRenderingOtherData */;
|
|
356
|
+
const r = layerData.realMask || {};
|
|
357
|
+
writeUint8(writer, realFlags);
|
|
358
|
+
writeUint8(writer, realMask.defaultColor || 0);
|
|
359
|
+
writeInt32(writer, r.top || 0);
|
|
360
|
+
writeInt32(writer, r.left || 0);
|
|
361
|
+
writeInt32(writer, r.bottom || 0);
|
|
362
|
+
writeInt32(writer, r.right || 0);
|
|
363
|
+
}
|
|
364
|
+
if (params && mask) {
|
|
365
|
+
writeUint8(writer, params);
|
|
366
|
+
if (mask.userMaskDensity !== undefined)
|
|
367
|
+
writeUint8(writer, Math.round(mask.userMaskDensity * 0xff));
|
|
368
|
+
if (mask.userMaskFeather !== undefined)
|
|
369
|
+
writeFloat64(writer, mask.userMaskFeather);
|
|
370
|
+
if (mask.vectorMaskDensity !== undefined)
|
|
371
|
+
writeUint8(writer, Math.round(mask.vectorMaskDensity * 0xff));
|
|
372
|
+
if (mask.vectorMaskFeather !== undefined)
|
|
373
|
+
writeFloat64(writer, mask.vectorMaskFeather);
|
|
374
|
+
}
|
|
375
|
+
writeZeros(writer, 2);
|
|
376
|
+
});
|
|
377
|
+
}
|
|
378
|
+
function writerBlendingRange(writer, range) {
|
|
379
|
+
writeUint8(writer, range[0]);
|
|
380
|
+
writeUint8(writer, range[1]);
|
|
381
|
+
writeUint8(writer, range[2]);
|
|
382
|
+
writeUint8(writer, range[3]);
|
|
383
|
+
}
|
|
384
|
+
function writeLayerBlendingRanges(writer, layer) {
|
|
385
|
+
writeSection(writer, 1, () => {
|
|
386
|
+
const ranges = layer.blendingRanges;
|
|
387
|
+
if (ranges) {
|
|
388
|
+
writerBlendingRange(writer, ranges.compositeGrayBlendSource);
|
|
389
|
+
writerBlendingRange(writer, ranges.compositeGraphBlendDestinationRange);
|
|
390
|
+
for (const r of ranges.ranges) {
|
|
391
|
+
writerBlendingRange(writer, r.sourceRange);
|
|
392
|
+
writerBlendingRange(writer, r.destRange);
|
|
393
|
+
}
|
|
394
|
+
}
|
|
395
|
+
});
|
|
396
|
+
}
|
|
397
|
+
function writeGlobalLayerMaskInfo(writer, info) {
|
|
398
|
+
writeSection(writer, 1, () => {
|
|
399
|
+
if (info) {
|
|
400
|
+
writeUint16(writer, info.overlayColorSpace);
|
|
401
|
+
writeUint16(writer, info.colorSpace1);
|
|
402
|
+
writeUint16(writer, info.colorSpace2);
|
|
403
|
+
writeUint16(writer, info.colorSpace3);
|
|
404
|
+
writeUint16(writer, info.colorSpace4);
|
|
405
|
+
writeUint16(writer, info.opacity * 0xff);
|
|
406
|
+
writeUint8(writer, info.kind);
|
|
407
|
+
writeZeros(writer, 3);
|
|
408
|
+
}
|
|
409
|
+
});
|
|
410
|
+
}
|
|
411
|
+
function writeAdditionalLayerInfo(writer, target, psd, options) {
|
|
412
|
+
for (const handler of infoHandlers) {
|
|
413
|
+
let key = handler.key;
|
|
414
|
+
if (key === 'Txt2' && options.invalidateTextLayers)
|
|
415
|
+
continue;
|
|
416
|
+
if (key === 'vmsk' && options.psb)
|
|
417
|
+
key = 'vsms';
|
|
418
|
+
if (handler.has(target)) {
|
|
419
|
+
const large = options.psb && largeAdditionalInfoKeys.indexOf(key) !== -1;
|
|
420
|
+
const writeTotalLength = key !== 'Txt2' && key !== 'cinf' && key !== 'extn' && key !== 'CAI ' && key !== 'OCIO';
|
|
421
|
+
const fourBytes = key === 'Txt2' || key === 'luni' || key === 'vmsk' || key === 'artb' || key === 'artd' ||
|
|
422
|
+
key === 'vogk' || key === 'SoLd' || key === 'lnk2' || key === 'vscg' || key === 'vsms' || key === 'GdFl' ||
|
|
423
|
+
key === 'lmfx' || key === 'lrFX' || key === 'cinf' || key === 'PlLd' || key === 'Anno' || key === 'CAI ' || key === 'OCIO' || key === 'GenI' || key === 'FEid' || key === 'curv' || key === 'CgEd' || key === 'vibA' || key === 'blwh' || key === 'grdm';
|
|
424
|
+
writeSignature(writer, large ? '8B64' : '8BIM');
|
|
425
|
+
writeSignature(writer, key);
|
|
426
|
+
writeSection(writer, fourBytes ? 4 : 2, () => {
|
|
427
|
+
handler.write(writer, target, psd, options);
|
|
428
|
+
}, writeTotalLength, large);
|
|
429
|
+
}
|
|
430
|
+
}
|
|
431
|
+
}
|
|
432
|
+
function addChildren(layers, children) {
|
|
433
|
+
if (!children)
|
|
434
|
+
return;
|
|
435
|
+
// const layerIds: number[] = [2];
|
|
436
|
+
// const timestamps: number[] = [1740120767.0230637];
|
|
437
|
+
for (const c of children) {
|
|
438
|
+
if (c.children && c.canvas)
|
|
439
|
+
throw new Error(`Invalid layer, cannot have both 'canvas' and 'children' properties`);
|
|
440
|
+
if (c.children && c.imageData)
|
|
441
|
+
throw new Error(`Invalid layer, cannot have both 'imageData' and 'children' properties`);
|
|
442
|
+
if (c.children) {
|
|
443
|
+
layers.push({
|
|
444
|
+
name: '</Layer group>',
|
|
445
|
+
sectionDivider: {
|
|
446
|
+
type: 3 /* SectionDividerType.BoundingSectionDivider */,
|
|
447
|
+
},
|
|
448
|
+
// blendingRanges: children[0].blendingRanges,
|
|
449
|
+
// nameSource: 'lset',
|
|
450
|
+
// id: layerIds.shift(),
|
|
451
|
+
// protected: {
|
|
452
|
+
// transparency: false,
|
|
453
|
+
// composite: false,
|
|
454
|
+
// position: false,
|
|
455
|
+
// },
|
|
456
|
+
// layerColor: 'red',
|
|
457
|
+
// timestamp: timestamps.shift(),
|
|
458
|
+
// referencePoint: { x: 0, y: 0 },
|
|
459
|
+
});
|
|
460
|
+
addChildren(layers, c.children);
|
|
461
|
+
layers.push({
|
|
462
|
+
...c,
|
|
463
|
+
blendMode: c.blendMode === 'pass through' ? 'normal' : c.blendMode,
|
|
464
|
+
sectionDivider: {
|
|
465
|
+
type: c.opened === false ? 2 /* SectionDividerType.ClosedFolder */ : 1 /* SectionDividerType.OpenFolder */,
|
|
466
|
+
key: fromBlendMode[c.blendMode] || 'pass',
|
|
467
|
+
subType: 0,
|
|
468
|
+
},
|
|
469
|
+
});
|
|
470
|
+
}
|
|
471
|
+
else {
|
|
472
|
+
layers.push({ ...c });
|
|
473
|
+
}
|
|
474
|
+
}
|
|
475
|
+
}
|
|
476
|
+
function resizeBuffer(writer, size) {
|
|
477
|
+
let newLength = writer.buffer.byteLength;
|
|
478
|
+
do {
|
|
479
|
+
newLength *= 2;
|
|
480
|
+
} while (size > newLength);
|
|
481
|
+
const newBuffer = new ArrayBuffer(newLength);
|
|
482
|
+
const newBytes = new Uint8Array(newBuffer);
|
|
483
|
+
const oldBytes = new Uint8Array(writer.buffer);
|
|
484
|
+
newBytes.set(oldBytes);
|
|
485
|
+
writer.buffer = newBuffer;
|
|
486
|
+
writer.view = new DataView(writer.buffer);
|
|
487
|
+
}
|
|
488
|
+
function ensureSize(writer, size) {
|
|
489
|
+
if (size > writer.buffer.byteLength) {
|
|
490
|
+
resizeBuffer(writer, size);
|
|
491
|
+
}
|
|
492
|
+
}
|
|
493
|
+
function addSize(writer, size) {
|
|
494
|
+
const offset = writer.offset;
|
|
495
|
+
ensureSize(writer, writer.offset += size);
|
|
496
|
+
return offset;
|
|
497
|
+
}
|
|
498
|
+
function createThumbnail(psd) {
|
|
499
|
+
const canvas = createCanvas(10, 10);
|
|
500
|
+
let scale = 1;
|
|
501
|
+
if (psd.width > psd.height) {
|
|
502
|
+
canvas.width = 160;
|
|
503
|
+
canvas.height = Math.floor(psd.height * (canvas.width / psd.width));
|
|
504
|
+
scale = canvas.width / psd.width;
|
|
505
|
+
}
|
|
506
|
+
else {
|
|
507
|
+
canvas.height = 160;
|
|
508
|
+
canvas.width = Math.floor(psd.width * (canvas.height / psd.height));
|
|
509
|
+
scale = canvas.height / psd.height;
|
|
510
|
+
}
|
|
511
|
+
const context = canvas.getContext('2d');
|
|
512
|
+
context.scale(scale, scale);
|
|
513
|
+
if (psd.imageData) {
|
|
514
|
+
context.drawImage(imageDataToCanvas(psd.imageData), 0, 0);
|
|
515
|
+
}
|
|
516
|
+
else if (psd.canvas) {
|
|
517
|
+
context.drawImage(psd.canvas, 0, 0);
|
|
518
|
+
}
|
|
519
|
+
return canvas;
|
|
520
|
+
}
|
|
521
|
+
function getMaskChannels(tempBuffer, layerData, layer, mask, options, realMask) {
|
|
522
|
+
let top = mask.top | 0;
|
|
523
|
+
let left = mask.left | 0;
|
|
524
|
+
let right = mask.right | 0;
|
|
525
|
+
let bottom = mask.bottom | 0;
|
|
526
|
+
let { width, height } = getLayerDimentions(mask);
|
|
527
|
+
let imageData = mask.imageData;
|
|
528
|
+
if (!imageData && mask.canvas && width && height) {
|
|
529
|
+
imageData = mask.canvas.getContext('2d').getImageData(0, 0, width, height);
|
|
530
|
+
}
|
|
531
|
+
if (imageData && (imageData.width !== width || imageData.height !== height)) {
|
|
532
|
+
throw new Error('Invalid imageData dimentions');
|
|
533
|
+
}
|
|
534
|
+
right = left + width;
|
|
535
|
+
bottom = top + height;
|
|
536
|
+
let buffer;
|
|
537
|
+
let compression;
|
|
538
|
+
if (RAW_IMAGE_DATA && layer[realMask ? 'realMaskDataRaw' : 'maskDataRaw']) {
|
|
539
|
+
buffer = layer[realMask ? 'realMaskDataRaw' : 'maskDataRaw'];
|
|
540
|
+
compression = layer[realMask ? 'realMaskDataRawCompression' : 'maskDataRawCompression'];
|
|
541
|
+
}
|
|
542
|
+
else if (!imageData) {
|
|
543
|
+
buffer = new Uint8Array(0);
|
|
544
|
+
compression = 1 /* Compression.RleCompressed */;
|
|
545
|
+
}
|
|
546
|
+
else if (options.compress) {
|
|
547
|
+
buffer = writeDataZipWithoutPrediction(imageData, [0]);
|
|
548
|
+
compression = 2 /* Compression.ZipWithoutPrediction */;
|
|
549
|
+
}
|
|
550
|
+
else {
|
|
551
|
+
buffer = writeDataRLE(tempBuffer, imageData, [0], !!options.psb);
|
|
552
|
+
compression = 1 /* Compression.RleCompressed */;
|
|
553
|
+
}
|
|
554
|
+
layerData.channels.push({ channelId: realMask ? -3 /* ChannelID.RealUserMask */ : -2 /* ChannelID.UserMask */, compression, buffer, length: 2 + buffer.length });
|
|
555
|
+
layerData[realMask ? 'realMask' : 'mask'] = { top, left, right, bottom };
|
|
556
|
+
}
|
|
557
|
+
function getChannels(tempBuffer, layer, background, options) {
|
|
558
|
+
const layerData = getLayerChannels(tempBuffer, layer, background, options);
|
|
559
|
+
if (layer.mask)
|
|
560
|
+
getMaskChannels(tempBuffer, layerData, layer, layer.mask, options, false);
|
|
561
|
+
if (layer.realMask)
|
|
562
|
+
getMaskChannels(tempBuffer, layerData, layer, layer.realMask, options, true);
|
|
563
|
+
return layerData;
|
|
564
|
+
}
|
|
565
|
+
function getLayerDimentions({ canvas, imageData }) {
|
|
566
|
+
// this way in case canvas/imageData are incorrect objects without width/height properties
|
|
567
|
+
const width = imageData?.width ?? canvas?.width ?? 0;
|
|
568
|
+
const height = imageData?.height ?? canvas?.height ?? 0;
|
|
569
|
+
return { width, height };
|
|
570
|
+
}
|
|
571
|
+
function cropImageData(data, left, top, width, height) {
|
|
572
|
+
if (data.data instanceof Uint32Array || data.data instanceof Uint16Array) {
|
|
573
|
+
throw new Error('imageData has incorrect bit depth');
|
|
574
|
+
}
|
|
575
|
+
const croppedData = createImageData(width, height);
|
|
576
|
+
const srcData = data.data;
|
|
577
|
+
const dstData = croppedData.data;
|
|
578
|
+
for (let y = 0; y < height; y++) {
|
|
579
|
+
for (let x = 0; x < width; x++) {
|
|
580
|
+
let src = ((x + left) + (y + top) * data.width) * 4;
|
|
581
|
+
let dst = (x + y * width) * 4;
|
|
582
|
+
dstData[dst] = srcData[src];
|
|
583
|
+
dstData[dst + 1] = srcData[src + 1];
|
|
584
|
+
dstData[dst + 2] = srcData[src + 2];
|
|
585
|
+
dstData[dst + 3] = srcData[src + 3];
|
|
586
|
+
}
|
|
587
|
+
}
|
|
588
|
+
return croppedData;
|
|
589
|
+
}
|
|
590
|
+
function getLayerChannels(tempBuffer, layer, background, options) {
|
|
591
|
+
let top = layer.top | 0;
|
|
592
|
+
let left = layer.left | 0;
|
|
593
|
+
let right = layer.right | 0;
|
|
594
|
+
let bottom = layer.bottom | 0;
|
|
595
|
+
let channels = [
|
|
596
|
+
{ channelId: -1 /* ChannelID.Transparency */, compression: 0 /* Compression.RawData */, buffer: undefined, length: 2 },
|
|
597
|
+
{ channelId: 0 /* ChannelID.Color0 */, compression: 0 /* Compression.RawData */, buffer: undefined, length: 2 },
|
|
598
|
+
{ channelId: 1 /* ChannelID.Color1 */, compression: 0 /* Compression.RawData */, buffer: undefined, length: 2 },
|
|
599
|
+
{ channelId: 2 /* ChannelID.Color2 */, compression: 0 /* Compression.RawData */, buffer: undefined, length: 2 },
|
|
600
|
+
];
|
|
601
|
+
let { width, height } = getLayerDimentions(layer);
|
|
602
|
+
if (!(layer.canvas || layer.imageData) || !width || !height) {
|
|
603
|
+
right = left;
|
|
604
|
+
bottom = top;
|
|
605
|
+
return { layer, top, left, right, bottom, channels };
|
|
606
|
+
}
|
|
607
|
+
right = left + width;
|
|
608
|
+
bottom = top + height;
|
|
609
|
+
let data = layer.imageData || layer.canvas.getContext('2d').getImageData(0, 0, width, height);
|
|
610
|
+
if (options.trimImageData) {
|
|
611
|
+
const trimmed = trimData(data);
|
|
612
|
+
if (trimmed.left !== 0 || trimmed.top !== 0 || trimmed.right !== data.width || trimmed.bottom !== data.height) {
|
|
613
|
+
left += trimmed.left;
|
|
614
|
+
top += trimmed.top;
|
|
615
|
+
right -= (data.width - trimmed.right);
|
|
616
|
+
bottom -= (data.height - trimmed.bottom);
|
|
617
|
+
width = right - left;
|
|
618
|
+
height = bottom - top;
|
|
619
|
+
if (!width || !height)
|
|
620
|
+
return { layer, top, left, right, bottom, channels };
|
|
621
|
+
data = cropImageData(data, trimmed.left, trimmed.top, width, height);
|
|
622
|
+
}
|
|
623
|
+
}
|
|
624
|
+
const channelIds = [
|
|
625
|
+
0 /* ChannelID.Color0 */,
|
|
626
|
+
1 /* ChannelID.Color1 */,
|
|
627
|
+
2 /* ChannelID.Color2 */,
|
|
628
|
+
];
|
|
629
|
+
if (!background || options.noBackground || layer.mask || hasAlpha(data) || (RAW_IMAGE_DATA && layer.imageDataRaw?.['-1'])) {
|
|
630
|
+
channelIds.unshift(-1 /* ChannelID.Transparency */);
|
|
631
|
+
}
|
|
632
|
+
channels = channelIds.map(channelId => {
|
|
633
|
+
const offset = offsetForChannel(channelId, false); // TODO: psd.colorMode === ColorMode.CMYK);
|
|
634
|
+
let buffer;
|
|
635
|
+
let compression;
|
|
636
|
+
if (RAW_IMAGE_DATA && layer.imageDataRaw) {
|
|
637
|
+
// console.log('written raw layer image data');
|
|
638
|
+
buffer = layer.imageDataRaw[channelId];
|
|
639
|
+
compression = layer.imageDataRawCompression[channelId];
|
|
640
|
+
}
|
|
641
|
+
else if (options.compress) {
|
|
642
|
+
buffer = writeDataZipWithoutPrediction(data, [offset]);
|
|
643
|
+
compression = 2 /* Compression.ZipWithoutPrediction */;
|
|
644
|
+
}
|
|
645
|
+
else {
|
|
646
|
+
buffer = writeDataRLE(tempBuffer, data, [offset], !!options.psb);
|
|
647
|
+
compression = 1 /* Compression.RleCompressed */;
|
|
648
|
+
}
|
|
649
|
+
return { channelId, compression, buffer, length: 2 + buffer.length };
|
|
650
|
+
});
|
|
651
|
+
return { layer, top, left, right, bottom, channels };
|
|
652
|
+
}
|
|
653
|
+
function isRowEmpty({ data, width }, y, left, right) {
|
|
654
|
+
const start = ((y * width + left) * 4 + 3) | 0;
|
|
655
|
+
const end = (start + (right - left) * 4) | 0;
|
|
656
|
+
for (let i = start; i < end; i = (i + 4) | 0) {
|
|
657
|
+
if (data[i] !== 0) {
|
|
658
|
+
return false;
|
|
659
|
+
}
|
|
660
|
+
}
|
|
661
|
+
return true;
|
|
662
|
+
}
|
|
663
|
+
function isColEmpty({ data, width }, x, top, bottom) {
|
|
664
|
+
const stride = (width * 4) | 0;
|
|
665
|
+
const start = (top * stride + x * 4 + 3) | 0;
|
|
666
|
+
for (let y = top, i = start; y < bottom; y++, i = (i + stride) | 0) {
|
|
667
|
+
if (data[i] !== 0) {
|
|
668
|
+
return false;
|
|
669
|
+
}
|
|
670
|
+
}
|
|
671
|
+
return true;
|
|
672
|
+
}
|
|
673
|
+
function trimData(data) {
|
|
674
|
+
let top = 0;
|
|
675
|
+
let left = 0;
|
|
676
|
+
let right = data.width;
|
|
677
|
+
let bottom = data.height;
|
|
678
|
+
while (top < bottom && isRowEmpty(data, top, left, right))
|
|
679
|
+
top++;
|
|
680
|
+
while (bottom > top && isRowEmpty(data, bottom - 1, left, right))
|
|
681
|
+
bottom--;
|
|
682
|
+
while (left < right && isColEmpty(data, left, top, bottom))
|
|
683
|
+
left++;
|
|
684
|
+
while (right > left && isColEmpty(data, right - 1, top, bottom))
|
|
685
|
+
right--;
|
|
686
|
+
return { top, left, right, bottom };
|
|
687
|
+
}
|
|
688
|
+
export function writeColor(writer, color) {
|
|
689
|
+
if (!color) {
|
|
690
|
+
writeUint16(writer, 0 /* ColorSpace.RGB */);
|
|
691
|
+
writeZeros(writer, 8);
|
|
692
|
+
}
|
|
693
|
+
else if ('r' in color) {
|
|
694
|
+
writeUint16(writer, 0 /* ColorSpace.RGB */);
|
|
695
|
+
writeUint16(writer, Math.round(color.r * 257));
|
|
696
|
+
writeUint16(writer, Math.round(color.g * 257));
|
|
697
|
+
writeUint16(writer, Math.round(color.b * 257));
|
|
698
|
+
writeUint16(writer, 0);
|
|
699
|
+
}
|
|
700
|
+
else if ('fr' in color) {
|
|
701
|
+
writeUint16(writer, 0 /* ColorSpace.RGB */);
|
|
702
|
+
writeUint16(writer, Math.round(color.fr * 255 * 257));
|
|
703
|
+
writeUint16(writer, Math.round(color.fg * 255 * 257));
|
|
704
|
+
writeUint16(writer, Math.round(color.fb * 255 * 257));
|
|
705
|
+
writeUint16(writer, 0);
|
|
706
|
+
}
|
|
707
|
+
else if ('l' in color) {
|
|
708
|
+
writeUint16(writer, 7 /* ColorSpace.Lab */);
|
|
709
|
+
writeInt16(writer, Math.round(color.l * 10000));
|
|
710
|
+
writeInt16(writer, Math.round(color.a < 0 ? (color.a * 12800) : (color.a * 12700)));
|
|
711
|
+
writeInt16(writer, Math.round(color.b < 0 ? (color.b * 12800) : (color.b * 12700)));
|
|
712
|
+
writeUint16(writer, 0);
|
|
713
|
+
}
|
|
714
|
+
else if ('h' in color) {
|
|
715
|
+
writeUint16(writer, 1 /* ColorSpace.HSB */);
|
|
716
|
+
writeUint16(writer, Math.round(color.h * 0xffff));
|
|
717
|
+
writeUint16(writer, Math.round(color.s * 0xffff));
|
|
718
|
+
writeUint16(writer, Math.round(color.b * 0xffff));
|
|
719
|
+
writeUint16(writer, 0);
|
|
720
|
+
}
|
|
721
|
+
else if ('c' in color) {
|
|
722
|
+
writeUint16(writer, 2 /* ColorSpace.CMYK */);
|
|
723
|
+
writeUint16(writer, Math.round(color.c * 257));
|
|
724
|
+
writeUint16(writer, Math.round(color.m * 257));
|
|
725
|
+
writeUint16(writer, Math.round(color.y * 257));
|
|
726
|
+
writeUint16(writer, Math.round(color.k * 257));
|
|
727
|
+
}
|
|
728
|
+
else {
|
|
729
|
+
writeUint16(writer, 8 /* ColorSpace.Grayscale */);
|
|
730
|
+
writeUint16(writer, Math.round(color.k * 10000 / 255));
|
|
731
|
+
writeZeros(writer, 6);
|
|
732
|
+
}
|
|
733
|
+
}
|