ag-psd 32.0.0 → 34.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.v8-cache/v24.7.0-x64-ef5a0af0/03e19709 +0 -0
- package/.v8-cache/v24.7.0-x64-ef5a0af0/05135617 +0 -0
- package/.v8-cache/v24.7.0-x64-ef5a0af0/1130885f +0 -0
- package/.v8-cache/v24.7.0-x64-ef5a0af0/226e2801 +0 -0
- package/.v8-cache/v24.7.0-x64-ef5a0af0/2a19133e +0 -0
- package/.v8-cache/v24.7.0-x64-ef5a0af0/4511bacf +0 -0
- package/.v8-cache/v24.7.0-x64-ef5a0af0/68416cb2 +0 -0
- package/.v8-cache/v24.7.0-x64-ef5a0af0/71df9cb3 +0 -0
- package/.v8-cache/v24.7.0-x64-ef5a0af0/7ea06cea +0 -0
- package/.v8-cache/v24.7.0-x64-ef5a0af0/87cf2209 +0 -0
- package/.v8-cache/v24.7.0-x64-ef5a0af0/932f0770 +0 -0
- package/.v8-cache/v24.7.0-x64-ef5a0af0/acc36e66 +0 -0
- package/.v8-cache/v24.7.0-x64-ef5a0af0/b3c2fab7 +0 -0
- package/.v8-cache/v24.7.0-x64-ef5a0af0/c0b789d2 +0 -0
- package/.v8-cache/v24.7.0-x64-ef5a0af0/c314aece +0 -0
- package/.v8-cache/v24.7.0-x64-ef5a0af0/cfc49f4f +0 -0
- package/.v8-cache/v24.7.0-x64-ef5a0af0/cff9434a +0 -0
- package/.v8-cache/v24.7.0-x64-ef5a0af0/d13d7610 +0 -0
- package/.v8-cache/v24.7.0-x64-ef5a0af0/dc86f000 +0 -0
- package/.v8-cache/v24.7.0-x64-ef5a0af0/e03e2acd +0 -0
- package/.v8-cache/v24.7.0-x64-ef5a0af0/e6745c2b +0 -0
- package/.v8-cache/v24.7.0-x64-ef5a0af0/e6b99945 +0 -0
- package/.v8-cache/v24.7.0-x64-ef5a0af0/eeeebdfe +0 -0
- package/.v8-cache/v24.7.0-x64-ef5a0af0/f6fd8125 +0 -0
- package/CHANGELOG.md +100 -0
- package/LICENSE +5 -0
- package/README.md +4 -7
- package/README_PSD.md +261 -84
- package/TODO.md +13 -84
- package/dist/abr.d.ts +11 -4
- package/dist/abr.js +362 -230
- package/dist/abr.js.map +1 -1
- package/dist/additionalInfo.d.ts +56 -2
- package/dist/additionalInfo.js +828 -361
- package/dist/additionalInfo.js.map +1 -1
- package/dist/ase.d.ts +6 -1
- package/dist/ase.js +61 -61
- package/dist/ase.js.map +1 -1
- package/dist/bundle.js +3321 -1364
- package/dist/descriptor.d.ts +28 -13
- package/dist/descriptor.js +295 -241
- package/dist/descriptor.js.map +1 -1
- package/dist/helpers.d.ts +7 -1
- package/dist/helpers.js +246 -4
- package/dist/helpers.js.map +1 -1
- package/dist/imageResources.d.ts +15 -3
- package/dist/imageResources.js +586 -144
- package/dist/imageResources.js.map +1 -1
- package/dist/index.d.ts +1 -0
- package/dist/index.js +1 -0
- package/dist/index.js.map +1 -1
- package/dist/jpeg.js +8 -8
- package/dist/jpeg.js.map +1 -1
- package/dist/psd.d.ts +292 -19
- package/dist/psd.js.map +1 -1
- package/dist/psdReader.d.ts +10 -0
- package/dist/psdReader.js +424 -216
- package/dist/psdReader.js.map +1 -1
- package/dist/psdWriter.d.ts +2 -0
- package/dist/psdWriter.js +373 -113
- package/dist/psdWriter.js.map +1 -1
- package/dist/text.d.ts +32 -1
- package/dist/text.js +75 -6
- package/dist/text.js.map +1 -1
- package/dist/utf8.js +0 -3
- package/dist/utf8.js.map +1 -1
- package/dist-es/abr.d.ts +11 -4
- package/dist-es/abr.js +362 -230
- package/dist-es/abr.js.map +1 -1
- package/dist-es/additionalInfo.d.ts +56 -2
- package/dist-es/additionalInfo.js +828 -361
- package/dist-es/additionalInfo.js.map +1 -1
- package/dist-es/ase.d.ts +6 -1
- package/dist-es/ase.js +61 -61
- package/dist-es/ase.js.map +1 -1
- package/dist-es/descriptor.d.ts +28 -13
- package/dist-es/descriptor.js +295 -241
- package/dist-es/descriptor.js.map +1 -1
- package/dist-es/helpers.d.ts +7 -1
- package/dist-es/helpers.js +246 -4
- package/dist-es/helpers.js.map +1 -1
- package/dist-es/imageResources.d.ts +15 -3
- package/dist-es/imageResources.js +586 -144
- package/dist-es/imageResources.js.map +1 -1
- package/dist-es/index.d.ts +1 -0
- package/dist-es/index.js +1 -0
- package/dist-es/index.js.map +1 -1
- package/dist-es/jpeg.js +8 -8
- package/dist-es/jpeg.js.map +1 -1
- package/dist-es/psd.d.ts +292 -19
- package/dist-es/psd.js.map +1 -1
- package/dist-es/psdReader.d.ts +10 -0
- package/dist-es/psdReader.js +424 -216
- package/dist-es/psdReader.js.map +1 -1
- package/dist-es/psdWriter.d.ts +2 -0
- package/dist-es/psdWriter.js +373 -113
- package/dist-es/psdWriter.js.map +1 -1
- package/dist-es/text.d.ts +32 -1
- package/dist-es/text.js +75 -6
- package/dist-es/text.js.map +1 -1
- package/dist-es/utf8.js +0 -3
- package/dist-es/utf8.js.map +1 -1
- package/package.json +2 -2
- package/perf.js +312 -9
- package/src/abr.ts +382 -235
- package/src/additionalInfo.ts +1071 -482
- package/src/ase.ts +66 -61
- package/src/descriptor.ts +291 -242
- package/src/helpers.ts +266 -3
- package/src/imageResources.ts +736 -169
- package/src/index.ts +1 -0
- package/src/jpeg.ts +8 -8
- package/src/psd.ts +271 -32
- package/src/psdReader.ts +440 -221
- package/src/psdWriter.ts +402 -110
- package/src/text.ts +93 -7
- package/src/utf8.ts +0 -4
package/src/psdWriter.ts
CHANGED
|
@@ -1,20 +1,24 @@
|
|
|
1
1
|
import { Psd, Layer, LayerAdditionalInfo, ColorMode, SectionDividerType, WriteOptions, Color, GlobalLayerMaskInfo, PixelData, LayerMaskData, Compression, ChannelID, PatternInfo } from './psd';
|
|
2
|
-
import { hasAlpha, createCanvas, writeDataRLE, LayerChannelData, ChannelData, offsetForChannel, createImageData, fromBlendMode, clamp, LayerMaskFlags, MaskParams, ColorSpace, Bounds, largeAdditionalInfoKeys,
|
|
3
|
-
import {
|
|
2
|
+
import { hasAlpha, createCanvas, writeDataRLE, LayerChannelData, ChannelData, offsetForChannel, createImageData, fromBlendMode, clamp, LayerMaskFlags, MaskParams, ColorSpace, Bounds, largeAdditionalInfoKeys, writeDataZipWithoutPrediction, imageDataToCanvas, denormalizeLabAB, PlanarData, planarToBytes, writeDataRLEBytes, writeDataZipWithPrediction, rgbToCmykValues } from './helpers';
|
|
3
|
+
import { deflate as deflateSync } from 'pako';
|
|
4
|
+
import { ExtendedWriteOptions, infoHandlers, infoHandlersMap } from './additionalInfo';
|
|
4
5
|
import { InternalImageResources, resourceHandlers } from './imageResources';
|
|
6
|
+
import { getCompositeImageData, getLayerImageData, getLayerMaskImageData, getLayerRealMaskImageData } from './psdReader';
|
|
5
7
|
|
|
6
8
|
export interface PsdWriter {
|
|
7
9
|
offset: number;
|
|
8
10
|
buffer: ArrayBuffer;
|
|
9
11
|
view: DataView;
|
|
10
12
|
tempBuffer: Uint8Array | undefined;
|
|
13
|
+
logMissingFeatures?: boolean;
|
|
14
|
+
log(...args: any[]): void;
|
|
11
15
|
}
|
|
12
16
|
|
|
13
17
|
export function createWriter(size = 4096): PsdWriter {
|
|
14
18
|
const buffer = new ArrayBuffer(size);
|
|
15
19
|
const view = new DataView(buffer);
|
|
16
20
|
const offset = 0;
|
|
17
|
-
return { buffer, view, offset, tempBuffer: undefined };
|
|
21
|
+
return { buffer, view, offset, tempBuffer: undefined, log: console.log };
|
|
18
22
|
}
|
|
19
23
|
|
|
20
24
|
export function getWriterBuffer(writer: PsdWriter) {
|
|
@@ -215,11 +219,38 @@ export function writeSection(writer: PsdWriter, round: number, func: () => void,
|
|
|
215
219
|
function verifyPixelData(data: PixelData | undefined, name: string) {
|
|
216
220
|
// check element size instead of instanceof, so arrays from other realms (iframes, workers, vm) are accepted
|
|
217
221
|
const bytesPerElement = data && (data.data as any).BYTES_PER_ELEMENT;
|
|
218
|
-
if (bytesPerElement !== undefined && bytesPerElement !== 1) {
|
|
219
|
-
throw new Error(`${name} has incorrect bitDepth (only
|
|
222
|
+
if (bytesPerElement !== undefined && bytesPerElement !== 1 && bytesPerElement !== 2 && !(data!.data instanceof Float32Array)) {
|
|
223
|
+
throw new Error(`${name} has incorrect bitDepth (only Uint8Array, Uint8ClampedArray, Uint16Array and Float32Array data is supported for writing)`);
|
|
220
224
|
}
|
|
221
225
|
}
|
|
222
226
|
|
|
227
|
+
// supported bit depths for each color mode
|
|
228
|
+
const writeBitDepths: { [colorMode: number]: number[] | undefined; } = {
|
|
229
|
+
[ColorMode.Bitmap]: [1],
|
|
230
|
+
[ColorMode.Grayscale]: [8, 16, 32],
|
|
231
|
+
[ColorMode.Indexed]: [8],
|
|
232
|
+
[ColorMode.RGB]: [8, 16, 32],
|
|
233
|
+
[ColorMode.CMYK]: [8, 16],
|
|
234
|
+
};
|
|
235
|
+
|
|
236
|
+
const colorModeNames: { [colorMode: number]: string; } = {
|
|
237
|
+
[ColorMode.Bitmap]: 'Bitmap', [ColorMode.Grayscale]: 'Grayscale', [ColorMode.Indexed]: 'Indexed', [ColorMode.RGB]: 'RGB',
|
|
238
|
+
[ColorMode.CMYK]: 'CMYK', [ColorMode.Multichannel]: 'Multichannel', [ColorMode.Duotone]: 'Duotone', [ColorMode.Lab]: 'Lab',
|
|
239
|
+
};
|
|
240
|
+
|
|
241
|
+
function colorChannelsCount(colorMode: ColorMode) {
|
|
242
|
+
return colorMode === ColorMode.RGB ? 3 : (colorMode === ColorMode.CMYK ? 4 : 1);
|
|
243
|
+
}
|
|
244
|
+
|
|
245
|
+
// HDR toning settings that Photoshop stores in color mode data of 32bit documents (default values of new document),
|
|
246
|
+
// Photoshop refuses to open 32bit documents without them
|
|
247
|
+
const hdrToningData = [
|
|
248
|
+
104, 100, 114, 116, 0, 0, 0, 3, 62, 107, 133, 31, 0, 0, 0, 2, 0, 0, 0, 7, 0, 76, 0, 105, 0, 110, 0, 101, 0, 97, 0, 114, 0, 0,
|
|
249
|
+
0, 2, 0, 2, 0, 0, 0, 0, 0, 255, 0, 255, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 65, 128, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 63, 128, 0, 0,
|
|
250
|
+
104, 100, 114, 97, 0, 0, 0, 6, 0, 0, 0, 0, 65, 160, 0, 0, 65, 240, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 63, 128, 0, 0, 0, 0, 0, 0,
|
|
251
|
+
0, 0,
|
|
252
|
+
];
|
|
253
|
+
|
|
223
254
|
function verifyBitCount(target: Psd | Layer) {
|
|
224
255
|
target.children?.forEach(verifyBitCount);
|
|
225
256
|
|
|
@@ -236,19 +267,38 @@ export function writePsd(writer: PsdWriter, psd: Psd, options: WriteOptions = {}
|
|
|
236
267
|
if ((psd.width > 30000 || psd.height > 30000) && !options.psb)
|
|
237
268
|
throw new Error('Document size is too large (max is 30000x30000, use PSB format instead)');
|
|
238
269
|
|
|
239
|
-
const
|
|
270
|
+
const colorMode = psd.colorMode ?? ColorMode.RGB;
|
|
271
|
+
const bitsPerChannel = psd.bitsPerChannel ?? (colorMode === ColorMode.Bitmap ? 1 : 8);
|
|
272
|
+
const bitDepths = writeBitDepths[colorMode];
|
|
273
|
+
|
|
274
|
+
if (!bitDepths)
|
|
275
|
+
throw new Error(`${colorModeNames[colorMode] ?? colorMode} color mode is not supported for writing`);
|
|
276
|
+
|
|
277
|
+
if (bitDepths.indexOf(bitsPerChannel) === -1)
|
|
278
|
+
throw new Error(`bitsPerChannel ${bitsPerChannel} is not supported for writing in ${colorModeNames[colorMode]} color mode`);
|
|
279
|
+
|
|
280
|
+
// Photoshop doesn't support layers in bitmap and indexed color modes
|
|
281
|
+
const layersAllowed = colorMode !== ColorMode.Bitmap && colorMode !== ColorMode.Indexed;
|
|
282
|
+
|
|
283
|
+
if (!layersAllowed && psd.children?.length)
|
|
284
|
+
throw new Error(`Layers are not supported in ${colorModeNames[colorMode]} color mode`);
|
|
240
285
|
|
|
241
|
-
if (
|
|
242
|
-
throw new Error('
|
|
286
|
+
if (colorMode === ColorMode.Indexed && !psd.palette)
|
|
287
|
+
throw new Error('Missing color palette for Indexed color mode');
|
|
288
|
+
|
|
289
|
+
if (options.log) writer.log = options.log;
|
|
290
|
+
if (options.logMissingFeatures) writer.logMissingFeatures = true;
|
|
243
291
|
|
|
244
292
|
const layers: Layer[] = [];
|
|
245
293
|
addChildren(layers, psd.children);
|
|
246
|
-
if (!layers.length) layers.push({});
|
|
294
|
+
if (!layers.length && layersAllowed) layers.push({});
|
|
247
295
|
|
|
248
296
|
verifyBitCount(psd);
|
|
249
297
|
|
|
250
298
|
const imageResources: InternalImageResources = { ...psd.imageResources };
|
|
251
|
-
const opt: ExtendedWriteOptions = { ...options, layerIds: new Set(), layerToId: new Map() };
|
|
299
|
+
const opt: ExtendedWriteOptions = { ...options, layerIds: new Set(), layerToId: new Map(), colorMode, bitsPerChannel };
|
|
300
|
+
// 16 and 32 bit documents store layers in Lr16 / Lr32 section of global additional info
|
|
301
|
+
const layersInGlobalInfo = bitsPerChannel === 16 || bitsPerChannel === 32;
|
|
252
302
|
|
|
253
303
|
if (opt.generateThumbnail) {
|
|
254
304
|
imageResources.thumbnail = createThumbnail(psd);
|
|
@@ -260,10 +310,14 @@ export function writePsd(writer: PsdWriter, psd: Psd, options: WriteOptions = {}
|
|
|
260
310
|
imageData = psd.canvas.getContext('2d')!.getImageData(0, 0, psd.canvas.width, psd.canvas.height);
|
|
261
311
|
}
|
|
262
312
|
|
|
313
|
+
if (!imageData && psd.rawCompositeData) { // read with `useRawData` option
|
|
314
|
+
imageData = getCompositeImageData(psd);
|
|
315
|
+
}
|
|
316
|
+
|
|
263
317
|
if (imageData && (psd.width !== imageData.width || psd.height !== imageData.height))
|
|
264
318
|
throw new Error('Document canvas must have the same size as document');
|
|
265
319
|
|
|
266
|
-
const globalAlpha = !!imageData && hasAlpha(imageData);
|
|
320
|
+
const globalAlpha = !!imageData && layersAllowed && hasAlpha(imageData);
|
|
267
321
|
// only used for encoding a single layer channel at a time, composite image is encoded directly into the writer buffer
|
|
268
322
|
writer.tempBuffer = new Uint8Array(getLargestLayerSize(psd.children, !!options.psb));
|
|
269
323
|
|
|
@@ -271,21 +325,22 @@ export function writePsd(writer: PsdWriter, psd: Psd, options: WriteOptions = {}
|
|
|
271
325
|
writeSignature(writer, '8BPS');
|
|
272
326
|
writeUint16(writer, options.psb ? 2 : 1); // version
|
|
273
327
|
writeZeros(writer, 6);
|
|
274
|
-
writeUint16(writer, globalAlpha ?
|
|
328
|
+
writeUint16(writer, colorChannelsCount(colorMode) + (globalAlpha ? 1 : 0)); // channels
|
|
275
329
|
writeUint32(writer, psd.height);
|
|
276
330
|
writeUint32(writer, psd.width);
|
|
277
331
|
writeUint16(writer, bitsPerChannel); // bits per channel
|
|
278
|
-
writeUint16(writer,
|
|
332
|
+
writeUint16(writer, colorMode);
|
|
279
333
|
|
|
280
334
|
// color mode data
|
|
281
335
|
writeSection(writer, 1, () => {
|
|
282
|
-
if (
|
|
283
|
-
|
|
284
|
-
for (let i = 0; i < 256; i++) writeUint8(writer,
|
|
285
|
-
for (let i = 0; i < 256; i++) writeUint8(writer,
|
|
336
|
+
if (colorMode === ColorMode.Indexed) {
|
|
337
|
+
const palette = psd.palette!;
|
|
338
|
+
for (let i = 0; i < 256; i++) writeUint8(writer, palette[i]?.r ?? 0);
|
|
339
|
+
for (let i = 0; i < 256; i++) writeUint8(writer, palette[i]?.g ?? 0);
|
|
340
|
+
for (let i = 0; i < 256; i++) writeUint8(writer, palette[i]?.b ?? 0);
|
|
341
|
+
} else if (bitsPerChannel === 32) {
|
|
342
|
+
writeBytes(writer, new Uint8Array(hdrToningData));
|
|
286
343
|
}
|
|
287
|
-
|
|
288
|
-
// TODO: other data?
|
|
289
344
|
});
|
|
290
345
|
|
|
291
346
|
// image resources
|
|
@@ -298,30 +353,42 @@ export function writePsd(writer: PsdWriter, psd: Psd, options: WriteOptions = {}
|
|
|
298
353
|
const has = handler.has(imageResources);
|
|
299
354
|
const count = has === false ? 0 : (has === true ? 1 : has);
|
|
300
355
|
for (let i = 0; i < count; i++) {
|
|
356
|
+
const id = handler.keyCount > 1 ? handler.key + i : handler.key;
|
|
357
|
+
const name = handler.name ? handler.name(imageResources, i) : '';
|
|
301
358
|
writeSignature(writer, '8BIM');
|
|
302
|
-
writeUint16(writer,
|
|
303
|
-
writePascalString(writer,
|
|
304
|
-
writeSection(writer, 2, () => handler.write(writer, imageResources, i));
|
|
359
|
+
writeUint16(writer, id);
|
|
360
|
+
writePascalString(writer, name, 2);
|
|
361
|
+
writeSection(writer, 2, () => handler.write(writer, imageResources, i, psd));
|
|
305
362
|
}
|
|
306
363
|
}
|
|
307
364
|
});
|
|
308
365
|
|
|
309
366
|
// layer and mask info
|
|
310
367
|
writeSection(writer, 2, () => {
|
|
311
|
-
|
|
368
|
+
if (layersInGlobalInfo || !layersAllowed) {
|
|
369
|
+
writeSection(writer, 4, () => { }, true, opt.psb); // empty layer info
|
|
370
|
+
} else {
|
|
371
|
+
writeLayerInfo(writer, layers, psd, globalAlpha, opt);
|
|
372
|
+
}
|
|
373
|
+
|
|
312
374
|
writeGlobalLayerMaskInfo(writer, psd.globalLayerMaskInfo);
|
|
375
|
+
|
|
376
|
+
if (layersInGlobalInfo) {
|
|
377
|
+
if (globalAlpha) writeGlobalInfoSection(writer, bitsPerChannel === 16 ? 'Mt16' : 'Mt32', !!opt.psb, () => { });
|
|
378
|
+
writeGlobalInfoSection(writer, bitsPerChannel === 16 ? 'Lr16' : 'Lr32', !!opt.psb, () => {
|
|
379
|
+
writeLayerInfoData(writer, layers, psd, globalAlpha, opt);
|
|
380
|
+
});
|
|
381
|
+
}
|
|
382
|
+
|
|
313
383
|
writeAdditionalLayerInfo(writer, psd, psd, opt);
|
|
314
384
|
}, undefined, !!opt.psb);
|
|
315
385
|
|
|
316
386
|
writer.tempBuffer = undefined;
|
|
317
387
|
|
|
318
388
|
// image data
|
|
319
|
-
|
|
389
|
+
if (colorMode === ColorMode.RGB && bitsPerChannel === 8 && (!imageData || is8bit(imageData))) {
|
|
390
|
+
writeUint16(writer, Compression.RleCompressed); // Photoshop doesn't support zip compression of composite image data
|
|
320
391
|
|
|
321
|
-
if (RAW_IMAGE_DATA && (psd as any).imageDataRaw) {
|
|
322
|
-
console.log('writing raw image data');
|
|
323
|
-
writeBytes(writer, (psd as any).imageDataRaw);
|
|
324
|
-
} else {
|
|
325
392
|
const channels = globalAlpha ? [0, 1, 2, 3] : [0, 1, 2];
|
|
326
393
|
const data = getCompositeData(imageData, psd.width, psd.height, globalAlpha);
|
|
327
394
|
|
|
@@ -330,9 +397,187 @@ export function writePsd(writer: PsdWriter, psd: Psd, options: WriteOptions = {}
|
|
|
330
397
|
ensureSize(writer, writer.offset + maxSize);
|
|
331
398
|
const buffer = new Uint8Array(writer.buffer, writer.offset, maxSize);
|
|
332
399
|
writer.offset += writeDataRLE(buffer, data, channels, !!options.psb).length;
|
|
400
|
+
} else {
|
|
401
|
+
writeCompositeImageData(writer, psd, imageData, colorMode, bitsPerChannel, globalAlpha, !!options.psb);
|
|
333
402
|
}
|
|
334
403
|
}
|
|
335
404
|
|
|
405
|
+
function writeGlobalInfoSection(writer: PsdWriter, key: string, psb: boolean, func: () => void) {
|
|
406
|
+
writeSignature(writer, psb ? '8B64' : '8BIM');
|
|
407
|
+
writeSignature(writer, key);
|
|
408
|
+
writeSection(writer, 4, func, false, psb);
|
|
409
|
+
}
|
|
410
|
+
|
|
411
|
+
function is8bit(data: PixelData) {
|
|
412
|
+
return (data.data as any).BYTES_PER_ELEMENT === 1;
|
|
413
|
+
}
|
|
414
|
+
|
|
415
|
+
// converts image data (8, 16 or 32 bit RGBA data) to channels of the document color mode and bit depth
|
|
416
|
+
function getPlanarChannels(imageData: PixelData, colorMode: ColorMode, bitsPerChannel: number, matte: boolean) {
|
|
417
|
+
const { data, width, height } = imageData;
|
|
418
|
+
const size = width * height;
|
|
419
|
+
const srcFloat = data instanceof Float32Array;
|
|
420
|
+
const srcMax = srcFloat ? 1 : (data instanceof Uint16Array ? 0xffff : 0xff);
|
|
421
|
+
const dstFloat = bitsPerChannel === 32;
|
|
422
|
+
const dstMax = dstFloat ? 1 : (bitsPerChannel === 16 ? 0xffff : 0xff);
|
|
423
|
+
const colorCount = colorChannelsCount(colorMode);
|
|
424
|
+
const create = () => dstFloat ? new Float32Array(size) : (bitsPerChannel === 16 ? new Uint16Array(size) : new Uint8Array(size));
|
|
425
|
+
const colors: PlanarData[] = [];
|
|
426
|
+
for (let i = 0; i < colorCount; i++) colors.push(create());
|
|
427
|
+
const alpha = create();
|
|
428
|
+
|
|
429
|
+
// color values are gamma corrected when converting between float (linear) and integer values (same as imageDataToCanvas)
|
|
430
|
+
const toNormalized = (v: number) => srcFloat ? Math.pow(clamp(v, 0, 1), 1 / 2.2) : v / srcMax;
|
|
431
|
+
const fromNormalized = (v: number) => dstFloat ? Math.pow(v, 2.2) : Math.round(clamp(v, 0, 1) * dstMax);
|
|
432
|
+
const sameType = srcFloat === dstFloat && srcMax === dstMax;
|
|
433
|
+
const convertColor = (v: number) => sameType ? (dstFloat ? v : Math.round(v)) : fromNormalized(toNormalized(v));
|
|
434
|
+
const convertAlpha = (v: number) => sameType ? v : (dstFloat ? v / srcMax : Math.round(clamp(v / srcMax, 0, 1) * dstMax));
|
|
435
|
+
const cmyk = [0, 0, 0, 0];
|
|
436
|
+
|
|
437
|
+
for (let i = 0, p = 0; i < size; i++, p += 4) {
|
|
438
|
+
const a = data[p + 3];
|
|
439
|
+
const alphaValue = convertAlpha(a);
|
|
440
|
+
alpha[i] = alphaValue;
|
|
441
|
+
// composite image colors are blended with white (or no ink) for documents with transparency
|
|
442
|
+
const blend = matte && a !== srcMax && a > 0 ? clamp(a / srcMax, 0, 1) : 1;
|
|
443
|
+
|
|
444
|
+
if (colorMode === ColorMode.CMYK) {
|
|
445
|
+
rgbToCmykValues(toNormalized(data[p]), toNormalized(data[p + 1]), toNormalized(data[p + 2]), cmyk);
|
|
446
|
+
|
|
447
|
+
for (let j = 0; j < 4; j++) {
|
|
448
|
+
colors[j][i] = Math.round((1 - clamp(cmyk[j] * blend, 0, 1)) * dstMax); // stored inverted (0 = 100% ink)
|
|
449
|
+
}
|
|
450
|
+
} else {
|
|
451
|
+
for (let j = 0; j < colorCount; j++) {
|
|
452
|
+
let value: number;
|
|
453
|
+
|
|
454
|
+
if (colorMode === ColorMode.RGB) {
|
|
455
|
+
value = convertColor(data[p + j]);
|
|
456
|
+
} else {
|
|
457
|
+
const r = data[p], g = data[p + 1], b = data[p + 2];
|
|
458
|
+
value = convertColor((r === g && g === b) ? r : (0.299 * r + 0.587 * g + 0.114 * b));
|
|
459
|
+
}
|
|
460
|
+
|
|
461
|
+
if (blend !== 1) value = dstFloat ? value * blend + 1 - blend : Math.round(value * blend + dstMax * (1 - blend));
|
|
462
|
+
colors[j][i] = value;
|
|
463
|
+
}
|
|
464
|
+
}
|
|
465
|
+
}
|
|
466
|
+
|
|
467
|
+
return { colors, alpha };
|
|
468
|
+
}
|
|
469
|
+
|
|
470
|
+
function encodeChannel(values: PlanarData, width: number, height: number, options: WriteOptions) {
|
|
471
|
+
if (!(values instanceof Uint8Array)) {
|
|
472
|
+
return { data: writeDataZipWithPrediction(values, width, height), compression: Compression.ZipWithPrediction };
|
|
473
|
+
} else if (options.compress) {
|
|
474
|
+
return { data: deflateSync(values), compression: Compression.ZipWithoutPrediction };
|
|
475
|
+
} else {
|
|
476
|
+
return { data: writeDataRLEBytes(values, width, height, !!options.psb), compression: Compression.RleCompressed };
|
|
477
|
+
}
|
|
478
|
+
}
|
|
479
|
+
|
|
480
|
+
function writeCompositeImageData(
|
|
481
|
+
writer: PsdWriter, psd: Psd, imageData: PixelData | undefined, colorMode: ColorMode, bitsPerChannel: number, globalAlpha: boolean, psb: boolean
|
|
482
|
+
) {
|
|
483
|
+
const { width, height } = psd;
|
|
484
|
+
const data = imageData ?? { data: new Uint8Array(width * height * 4), width, height };
|
|
485
|
+
const parts: Uint8Array[] = [];
|
|
486
|
+
|
|
487
|
+
if (colorMode === ColorMode.Bitmap || colorMode === ColorMode.Indexed) {
|
|
488
|
+
const values = colorMode === ColorMode.Bitmap ?
|
|
489
|
+
getBitmapData(data) :
|
|
490
|
+
getIndexedData(data, psd.palette!, psd.imageResources?.transparencyIndex);
|
|
491
|
+
const rowBytes = colorMode === ColorMode.Bitmap ? Math.ceil(width / 8) : width;
|
|
492
|
+
|
|
493
|
+
if (colorMode === ColorMode.Bitmap) {
|
|
494
|
+
writeUint16(writer, Compression.RawData);
|
|
495
|
+
parts.push(values);
|
|
496
|
+
} else {
|
|
497
|
+
writeUint16(writer, Compression.RleCompressed);
|
|
498
|
+
parts.push(writeDataRLEBytes(values, rowBytes, height, psb));
|
|
499
|
+
}
|
|
500
|
+
} else {
|
|
501
|
+
const { colors, alpha } = getPlanarChannels(data, colorMode, bitsPerChannel, globalAlpha);
|
|
502
|
+
const channels = globalAlpha ? [...colors, alpha] : colors;
|
|
503
|
+
|
|
504
|
+
if (bitsPerChannel === 8) {
|
|
505
|
+
// row byte counts for all channels are stored before data of all channels
|
|
506
|
+
const encoded = channels.map(c => writeDataRLEBytes(c as Uint8Array, width, height, psb));
|
|
507
|
+
const countsSize = height * (psb ? 4 : 2);
|
|
508
|
+
writeUint16(writer, Compression.RleCompressed);
|
|
509
|
+
for (const e of encoded) parts.push(e.subarray(0, countsSize));
|
|
510
|
+
for (const e of encoded) parts.push(e.subarray(countsSize));
|
|
511
|
+
} else {
|
|
512
|
+
writeUint16(writer, Compression.RawData); // Photoshop writes 16 and 32 bit composite image without compression
|
|
513
|
+
for (const c of channels) parts.push(planarToBytes(c));
|
|
514
|
+
}
|
|
515
|
+
}
|
|
516
|
+
|
|
517
|
+
for (const part of parts) writeBytes(writer, part);
|
|
518
|
+
}
|
|
519
|
+
|
|
520
|
+
function getBitmapData({ data, width, height }: PixelData) {
|
|
521
|
+
const rowBytes = Math.ceil(width / 8);
|
|
522
|
+
const result = new Uint8Array(rowBytes * height);
|
|
523
|
+
const max = data instanceof Float32Array ? 1 : (data instanceof Uint16Array ? 0xffff : 0xff);
|
|
524
|
+
|
|
525
|
+
for (let y = 0, p = 0; y < height; y++) {
|
|
526
|
+
for (let x = 0; x < width; x++, p += 4) {
|
|
527
|
+
const a = data[p + 3] / max;
|
|
528
|
+
const value = ((0.299 * data[p] + 0.587 * data[p + 1] + 0.114 * data[p + 2]) / max) * a + (1 - a); // blend with white
|
|
529
|
+
if (value < 0.5) result[y * rowBytes + (x >> 3)] |= 0x80 >> (x & 7); // 1 = black
|
|
530
|
+
}
|
|
531
|
+
}
|
|
532
|
+
|
|
533
|
+
return result;
|
|
534
|
+
}
|
|
535
|
+
|
|
536
|
+
function getIndexedData({ data, width, height }: PixelData, palette: { r: number; g: number; b: number; }[], transparencyIndex: number | undefined) {
|
|
537
|
+
const size = width * height;
|
|
538
|
+
const result = new Uint8Array(size);
|
|
539
|
+
const shift = data instanceof Uint16Array ? 8 : 0;
|
|
540
|
+
const cache = new Map<number, number>();
|
|
541
|
+
|
|
542
|
+
for (let i = 0; i < palette.length && i < 256; i++) {
|
|
543
|
+
const key = (palette[i].r << 16) | (palette[i].g << 8) | palette[i].b;
|
|
544
|
+
if (i !== transparencyIndex && !cache.has(key)) cache.set(key, i);
|
|
545
|
+
}
|
|
546
|
+
|
|
547
|
+
for (let i = 0, p = 0; i < size; i++, p += 4) {
|
|
548
|
+
const read = (v: number) => data instanceof Float32Array ? Math.round(Math.pow(clamp(v, 0, 1), 1 / 2.2) * 255) : (v >>> shift);
|
|
549
|
+
const a = data instanceof Float32Array ? data[p + 3] * 255 : (data[p + 3] >>> shift);
|
|
550
|
+
|
|
551
|
+
if (transparencyIndex !== undefined && transparencyIndex >= 0 && a < 128) {
|
|
552
|
+
result[i] = transparencyIndex;
|
|
553
|
+
continue;
|
|
554
|
+
}
|
|
555
|
+
|
|
556
|
+
const r = read(data[p]), g = read(data[p + 1]), b = read(data[p + 2]);
|
|
557
|
+
const key = (r << 16) | (g << 8) | b;
|
|
558
|
+
let index = cache.get(key);
|
|
559
|
+
|
|
560
|
+
if (index === undefined) {
|
|
561
|
+
// use closest color from the palette
|
|
562
|
+
let best = Infinity;
|
|
563
|
+
index = 0;
|
|
564
|
+
|
|
565
|
+
for (let j = 0; j < palette.length && j < 256; j++) {
|
|
566
|
+
if (j === transparencyIndex) continue;
|
|
567
|
+
const dr = palette[j].r - r, dg = palette[j].g - g, db = palette[j].b - b;
|
|
568
|
+
const distance = dr * dr + dg * dg + db * db;
|
|
569
|
+
if (distance < best) { best = distance; index = j; }
|
|
570
|
+
}
|
|
571
|
+
|
|
572
|
+
cache.set(key, index);
|
|
573
|
+
}
|
|
574
|
+
|
|
575
|
+
result[i] = index;
|
|
576
|
+
}
|
|
577
|
+
|
|
578
|
+
return result;
|
|
579
|
+
}
|
|
580
|
+
|
|
336
581
|
function getCompositeData(imageData: PixelData | undefined, width: number, height: number, globalAlpha: boolean): PixelData {
|
|
337
582
|
if (!imageData) return { data: new Uint8Array(width * height * 4), width, height };
|
|
338
583
|
|
|
@@ -359,67 +604,76 @@ function getCompositeData(imageData: PixelData | undefined, width: number, heigh
|
|
|
359
604
|
}
|
|
360
605
|
|
|
361
606
|
function writeLayerInfo(writer: PsdWriter, layers: Layer[], psd: Psd, globalAlpha: boolean, options: ExtendedWriteOptions) {
|
|
362
|
-
writeSection(writer, 4, () =>
|
|
363
|
-
|
|
607
|
+
writeSection(writer, 4, () => writeLayerInfoData(writer, layers, psd, globalAlpha, options), true, options.psb);
|
|
608
|
+
}
|
|
364
609
|
|
|
365
|
-
|
|
610
|
+
function writeLayerInfoData(writer: PsdWriter, layers: Layer[], psd: Psd, globalAlpha: boolean, options: ExtendedWriteOptions) {
|
|
611
|
+
writeInt16(writer, globalAlpha ? -layers.length : layers.length);
|
|
366
612
|
|
|
367
|
-
|
|
368
|
-
for (const layerData of layersData) {
|
|
369
|
-
const { layer, top, left, bottom, right, channels } = layerData;
|
|
613
|
+
const layersData = layers.map((l, i) => getChannels(writer, l, i === 0, options));
|
|
370
614
|
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
writeInt32(writer, right);
|
|
375
|
-
writeUint16(writer, channels.length);
|
|
615
|
+
// layer records
|
|
616
|
+
for (const layerData of layersData) {
|
|
617
|
+
const { layer, top, left, bottom, right, channels } = layerData;
|
|
376
618
|
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
|
|
619
|
+
writeInt32(writer, top);
|
|
620
|
+
writeInt32(writer, left);
|
|
621
|
+
writeInt32(writer, bottom);
|
|
622
|
+
writeInt32(writer, right);
|
|
623
|
+
writeUint16(writer, channels.length);
|
|
624
|
+
|
|
625
|
+
for (const c of channels) {
|
|
626
|
+
writeInt16(writer, c.id);
|
|
627
|
+
if (options.psb) {
|
|
628
|
+
writeUint64(writer, c.length);
|
|
629
|
+
} else {
|
|
630
|
+
writeUint32(writer, c.length);
|
|
384
631
|
}
|
|
632
|
+
}
|
|
385
633
|
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
|
|
634
|
+
writeSignature(writer, '8BIM');
|
|
635
|
+
writeSignature(writer, fromBlendMode[layer.blendMode!] || 'norm');
|
|
636
|
+
writeUint8(writer, Math.round(clamp(layer.opacity ?? 1, 0, 1) * 255));
|
|
637
|
+
writeUint8(writer, layer.clipping ? 1 : 0);
|
|
390
638
|
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
}
|
|
397
|
-
if (layer.effectsOpen) flags |= 0x20;
|
|
398
|
-
|
|
399
|
-
writeUint8(writer, flags);
|
|
400
|
-
writeUint8(writer, 0); // filler
|
|
401
|
-
writeSection(writer, 1, () => {
|
|
402
|
-
writeLayerMaskData(writer, layer, layerData);
|
|
403
|
-
writeLayerBlendingRanges(writer, layer);
|
|
404
|
-
writePascalString(writer, (layer.name || '').substring(0, 255), 4);
|
|
405
|
-
writeAdditionalLayerInfo(writer, layer, psd, options);
|
|
406
|
-
});
|
|
639
|
+
let flags = 0x08; // 1 for Photoshop 5.0 and later, tells if bit 4 has useful information
|
|
640
|
+
if (layer.transparencyProtected) flags |= 0x01;
|
|
641
|
+
if (layer.hidden) flags |= 0x02;
|
|
642
|
+
if (layer.vectorMask || (layer.sectionDivider && layer.sectionDivider.type !== SectionDividerType.Other) || layer.adjustment) {
|
|
643
|
+
flags |= 0x10; // pixel data irrelevant to appearance of document
|
|
407
644
|
}
|
|
645
|
+
if (layer.effectsOpen) flags |= 0x20;
|
|
408
646
|
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
|
|
412
|
-
|
|
647
|
+
writeUint8(writer, flags);
|
|
648
|
+
writeUint8(writer, 0); // filler
|
|
649
|
+
writeSection(writer, 1, () => {
|
|
650
|
+
writeLayerMaskData(writer, layer, layerData);
|
|
651
|
+
writeLayerBlendingRanges(writer, layer);
|
|
652
|
+
writePascalString(writer, (layer.name || '').substring(0, 255), 4);
|
|
653
|
+
writeAdditionalLayerInfo(writer, layer, psd, options);
|
|
654
|
+
});
|
|
655
|
+
}
|
|
413
656
|
|
|
414
|
-
|
|
415
|
-
|
|
416
|
-
|
|
657
|
+
// layer channel image data
|
|
658
|
+
for (const layerData of layersData) {
|
|
659
|
+
for (const channel of layerData.channels) {
|
|
660
|
+
writeUint16(writer, channel.compression);
|
|
661
|
+
|
|
662
|
+
if (channel.data) {
|
|
663
|
+
writeBytes(writer, channel.data);
|
|
417
664
|
}
|
|
418
665
|
}
|
|
419
|
-
}
|
|
666
|
+
}
|
|
420
667
|
}
|
|
421
668
|
|
|
422
|
-
|
|
669
|
+
// real mask is only stored separately if layer has both vector and pixel masks, otherwise it's stored as the main mask
|
|
670
|
+
function getMasks(layer: Layer) {
|
|
671
|
+
return layer.mask ? { mask: layer.mask, realMask: layer.realMask } : { mask: layer.realMask, realMask: undefined };
|
|
672
|
+
}
|
|
673
|
+
|
|
674
|
+
function writeLayerMaskData(writer: PsdWriter, layer: Layer, layerData: LayerChannelData) {
|
|
675
|
+
const { mask, realMask } = getMasks(layer);
|
|
676
|
+
|
|
423
677
|
writeSection(writer, 4, () => {
|
|
424
678
|
if (!mask && !realMask) return;
|
|
425
679
|
|
|
@@ -512,13 +766,13 @@ function writeAdditionalLayerInfo(writer: PsdWriter, target: LayerAdditionalInfo
|
|
|
512
766
|
let key = handler.key;
|
|
513
767
|
|
|
514
768
|
if (key === 'Txt2' && options.invalidateTextLayers) continue;
|
|
515
|
-
if (key === 'vmsk' &&
|
|
769
|
+
if (key === 'vmsk' && infoHandlersMap['vscg'].has(target)) key = 'vsms'; // Photoshop uses 'vsms' for layers with vector stroke
|
|
516
770
|
|
|
517
771
|
if (handler.has(target)) {
|
|
518
772
|
const large = options.psb && largeAdditionalInfoKeys.indexOf(key) !== -1;
|
|
519
773
|
const writeTotalLength = key !== 'Txt2' && key !== 'cinf' && key !== 'extn' && key !== 'CAI ' && key !== 'OCIO';
|
|
520
774
|
const fourBytes = key === 'levl' || key === 'Txt2' || key === 'luni' || key === 'vmsk' || key === 'artb' || key === 'artd' ||
|
|
521
|
-
key === 'vogk' || key === 'SoLd' || key === 'lnk2' || key === 'vscg' || key === 'vsms' || key === 'GdFl' ||
|
|
775
|
+
key === 'vogk' || key === 'SoLd' || key === 'SoLE' || key === 'lnk2' || key === 'vscg' || key === 'vsms' || key === 'GdFl' ||
|
|
522
776
|
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';
|
|
523
777
|
|
|
524
778
|
writeSignature(writer, large ? '8B64' : '8BIM');
|
|
@@ -624,7 +878,7 @@ function createThumbnail(psd: Psd) {
|
|
|
624
878
|
return canvas;
|
|
625
879
|
}
|
|
626
880
|
|
|
627
|
-
function getMaskChannels(tempBuffer: Uint8Array, layerData: LayerChannelData,
|
|
881
|
+
function getMaskChannels(tempBuffer: Uint8Array, layerData: LayerChannelData, mask: LayerMaskData, options: WriteOptions, realMask: boolean) {
|
|
628
882
|
let top = (mask.top as any) | 0;
|
|
629
883
|
let left = (mask.left as any) | 0;
|
|
630
884
|
let right = (mask.right as any) | 0;
|
|
@@ -645,13 +899,14 @@ function getMaskChannels(tempBuffer: Uint8Array, layerData: LayerChannelData, la
|
|
|
645
899
|
|
|
646
900
|
let buffer: Uint8Array;
|
|
647
901
|
let compression: Compression;
|
|
902
|
+
const bitsPerChannel = (options as ExtendedWriteOptions).bitsPerChannel ?? 8;
|
|
648
903
|
|
|
649
|
-
if (
|
|
650
|
-
buffer = (layer as any)[realMask ? 'realMaskDataRaw' : 'maskDataRaw'];
|
|
651
|
-
compression = (layer as any)[realMask ? 'realMaskDataRawCompression' : 'maskDataRawCompression'];
|
|
652
|
-
} else if (!imageData) {
|
|
904
|
+
if (!imageData) {
|
|
653
905
|
buffer = new Uint8Array(0);
|
|
654
906
|
compression = Compression.RleCompressed;
|
|
907
|
+
} else if (bitsPerChannel !== 8 || !is8bit(imageData)) {
|
|
908
|
+
const { colors } = getPlanarChannels(imageData, ColorMode.RGB, bitsPerChannel, false);
|
|
909
|
+
({ data: buffer, compression } = encodeChannel(colors[0], width, height, options));
|
|
655
910
|
} else if (options.compress) {
|
|
656
911
|
buffer = writeDataZipWithoutPrediction(imageData, [0]);
|
|
657
912
|
compression = Compression.ZipWithoutPrediction;
|
|
@@ -674,20 +929,46 @@ function bounds(obj: { top?: number; left?: number; bottom?: number; right?: num
|
|
|
674
929
|
} : undefined;
|
|
675
930
|
}
|
|
676
931
|
|
|
677
|
-
function getChannels(
|
|
932
|
+
function getChannels(writer: PsdWriter, layer: Layer, background: boolean, options: WriteOptions): LayerChannelData {
|
|
933
|
+
let source = layer;
|
|
934
|
+
|
|
678
935
|
if (layer.rawData) {
|
|
679
|
-
|
|
680
|
-
|
|
681
|
-
|
|
682
|
-
|
|
683
|
-
mask
|
|
684
|
-
realMask
|
|
936
|
+
const { colorMode, bitsPerChannel, large } = layer.rawData;
|
|
937
|
+
const opt = options as ExtendedWriteOptions;
|
|
938
|
+
|
|
939
|
+
if (large === !!options.psb && colorMode === (opt.colorMode ?? ColorMode.RGB) && bitsPerChannel === (opt.bitsPerChannel ?? 8)) {
|
|
940
|
+
// same mask mapping as in writeLayerMaskData
|
|
941
|
+
const { mask, realMask } = getMasks(layer);
|
|
942
|
+
return {
|
|
943
|
+
layer,
|
|
944
|
+
channels: layer.rawData.channels.map(c => ({ ...c, length: 2 + (c.data?.byteLength ?? 0) })),
|
|
945
|
+
...bounds(layer)!,
|
|
946
|
+
mask: bounds(mask),
|
|
947
|
+
realMask: bounds(realMask),
|
|
948
|
+
};
|
|
949
|
+
}
|
|
950
|
+
|
|
951
|
+
// raw data is in different format than the one we're writing, decode it so it can be re-encoded
|
|
952
|
+
source = {
|
|
953
|
+
...layer,
|
|
954
|
+
rawData: undefined,
|
|
955
|
+
canvas: undefined,
|
|
956
|
+
imageData: getLayerImageData(layer),
|
|
957
|
+
mask: layer.mask && { ...layer.mask, canvas: undefined, imageData: getLayerMaskImageData(layer) },
|
|
958
|
+
realMask: layer.realMask && { ...layer.realMask, canvas: undefined, imageData: getLayerRealMaskImageData(layer) },
|
|
685
959
|
};
|
|
960
|
+
|
|
961
|
+
// temp buffer is sized only for layers with image data, grow it and keep it for following layers
|
|
962
|
+
const size = getLargestLayerSize([{ ...source, children: undefined }], !!options.psb);
|
|
963
|
+
if (writer.tempBuffer!.byteLength < size) writer.tempBuffer = new Uint8Array(size);
|
|
686
964
|
}
|
|
687
965
|
|
|
688
|
-
const
|
|
689
|
-
|
|
690
|
-
|
|
966
|
+
const tempBuffer = writer.tempBuffer!;
|
|
967
|
+
const { mask, realMask } = getMasks(source);
|
|
968
|
+
const layerData = getLayerChannels(tempBuffer, source, background, options);
|
|
969
|
+
if (mask) getMaskChannels(tempBuffer, layerData, mask, options, false);
|
|
970
|
+
if (realMask) getMaskChannels(tempBuffer, layerData, realMask, options, true);
|
|
971
|
+
layerData.layer = layer;
|
|
691
972
|
return layerData;
|
|
692
973
|
}
|
|
693
974
|
|
|
@@ -699,11 +980,8 @@ function getLayerDimentions({ canvas, imageData }: Layer): { width: number; heig
|
|
|
699
980
|
}
|
|
700
981
|
|
|
701
982
|
function cropImageData(data: PixelData, left: number, top: number, width: number, height: number) {
|
|
702
|
-
|
|
703
|
-
|
|
704
|
-
}
|
|
705
|
-
|
|
706
|
-
const croppedData = createImageData(width, height);
|
|
983
|
+
const croppedData: PixelData = is8bit(data) ? createImageData(width, height) :
|
|
984
|
+
{ width, height, data: new (data.data.constructor as any)(width * height * 4) };
|
|
707
985
|
const srcData = data.data;
|
|
708
986
|
const dstData = croppedData.data;
|
|
709
987
|
|
|
@@ -735,8 +1013,9 @@ function getLayerChannels(tempBuffer: Uint8Array, layer: Layer, background: bool
|
|
|
735
1013
|
let { width, height } = getLayerDimentions(layer);
|
|
736
1014
|
|
|
737
1015
|
if (!(layer.canvas || layer.imageData) || !width || !height) {
|
|
738
|
-
|
|
739
|
-
|
|
1016
|
+
// layer without image data is empty, keep inverted (negative size) bounds from some files as they are
|
|
1017
|
+
if (layer.right === undefined || right > left) right = left;
|
|
1018
|
+
if (layer.bottom === undefined || bottom > top) bottom = top;
|
|
740
1019
|
return { layer, top, left, right, bottom, channels };
|
|
741
1020
|
}
|
|
742
1021
|
|
|
@@ -768,20 +1047,32 @@ function getLayerChannels(tempBuffer: Uint8Array, layer: Layer, background: bool
|
|
|
768
1047
|
ChannelID.Color2,
|
|
769
1048
|
];
|
|
770
1049
|
|
|
771
|
-
if (!background || options.noBackground || layer.mask || hasAlpha(imageData)
|
|
1050
|
+
if (!background || options.noBackground || layer.mask || hasAlpha(imageData)) {
|
|
772
1051
|
channelIds.unshift(ChannelID.Transparency);
|
|
773
1052
|
}
|
|
774
1053
|
|
|
1054
|
+
const colorMode = (options as ExtendedWriteOptions).colorMode ?? ColorMode.RGB;
|
|
1055
|
+
const bitsPerChannel = (options as ExtendedWriteOptions).bitsPerChannel ?? 8;
|
|
1056
|
+
|
|
1057
|
+
if (colorMode !== ColorMode.RGB || bitsPerChannel !== 8 || !is8bit(imageData)) {
|
|
1058
|
+
const { colors, alpha } = getPlanarChannels(imageData, colorMode, bitsPerChannel, false);
|
|
1059
|
+
const ids = channelIds[0] === ChannelID.Transparency ? [ChannelID.Transparency] : [];
|
|
1060
|
+
for (let i = 0; i < colors.length; i++) ids.push(i as ChannelID);
|
|
1061
|
+
|
|
1062
|
+
channels = ids.map(id => {
|
|
1063
|
+
const { data, compression } = encodeChannel(id === ChannelID.Transparency ? alpha : colors[id], width, height, options);
|
|
1064
|
+
return { id, compression, data, length: 2 + data.length };
|
|
1065
|
+
});
|
|
1066
|
+
|
|
1067
|
+
return { layer, top, left, right, bottom, channels };
|
|
1068
|
+
}
|
|
1069
|
+
|
|
775
1070
|
channels = channelIds.map(id => {
|
|
776
|
-
const offset = offsetForChannel(id, false); //
|
|
1071
|
+
const offset = offsetForChannel(id, false); // image data is always RGBA
|
|
777
1072
|
let data: Uint8Array;
|
|
778
1073
|
let compression: Compression;
|
|
779
1074
|
|
|
780
|
-
if (
|
|
781
|
-
// console.log('written raw layer image data');
|
|
782
|
-
data = (layer as any).imageDataRaw[id];
|
|
783
|
-
compression = (layer as any).imageDataRawCompression[id];
|
|
784
|
-
} else if (options.compress) {
|
|
1075
|
+
if (options.compress) {
|
|
785
1076
|
data = writeDataZipWithoutPrediction(imageData, [offset]);
|
|
786
1077
|
compression = Compression.ZipWithoutPrediction;
|
|
787
1078
|
} else {
|
|
@@ -868,11 +1159,12 @@ export function writeColor(writer: PsdWriter, color: Color | undefined) {
|
|
|
868
1159
|
writeUint16(writer, Math.round(color.b * 0xffff));
|
|
869
1160
|
writeUint16(writer, 0);
|
|
870
1161
|
} else if ('c' in color) {
|
|
1162
|
+
// 0 = 100% ink
|
|
871
1163
|
writeUint16(writer, ColorSpace.CMYK);
|
|
872
|
-
writeUint16(writer, Math.round(color.c * 257));
|
|
873
|
-
writeUint16(writer, Math.round(color.m * 257));
|
|
874
|
-
writeUint16(writer, Math.round(color.y * 257));
|
|
875
|
-
writeUint16(writer, Math.round(color.k * 257));
|
|
1164
|
+
writeUint16(writer, 0xffff - Math.round(clamp(color.c, 0, 255) * 257));
|
|
1165
|
+
writeUint16(writer, 0xffff - Math.round(clamp(color.m, 0, 255) * 257));
|
|
1166
|
+
writeUint16(writer, 0xffff - Math.round(clamp(color.y, 0, 255) * 257));
|
|
1167
|
+
writeUint16(writer, 0xffff - Math.round(clamp(color.k, 0, 255) * 257));
|
|
876
1168
|
} else {
|
|
877
1169
|
writeUint16(writer, ColorSpace.Grayscale);
|
|
878
1170
|
writeUint16(writer, Math.round(color.k * 10000 / 255));
|