ag-psd 15.0.3 → 15.0.5

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