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/additionalInfo.ts
CHANGED
|
@@ -1,16 +1,18 @@
|
|
|
1
1
|
import { fromByteArray, toByteArray } from 'base64-js';
|
|
2
2
|
import { readEffects, writeEffects } from './effectsHelpers';
|
|
3
3
|
import { clamp, createEnum, layerColors, MOCK_HANDLERS, normalizeLabAB } from './helpers';
|
|
4
|
-
import { LayerAdditionalInfo, BezierPath, Psd, BrightnessAdjustment, ExposureAdjustment, VibranceAdjustment, ColorBalanceAdjustment, BlackAndWhiteAdjustment, PhotoFilterAdjustment, ChannelMixerChannel, ChannelMixerAdjustment, PosterizeAdjustment, ThresholdAdjustment, GradientMapAdjustment, CMYK, SelectiveColorAdjustment, ColorLookupAdjustment, LevelsAdjustmentChannel, LevelsAdjustment, CurvesAdjustment, CurvesAdjustmentChannel, HueSaturationAdjustment, HueSaturationAdjustmentChannel, PresetInfo, Color, ColorBalanceValues, WriteOptions, LinkedFile, PlacedLayerType, Warp, KeyDescriptorItem, BooleanOperation, LayerEffectsInfo, Annotation,
|
|
5
|
-
import { PsdReader, readSignature, readUnicodeString, skipBytes, readUint32, readUint8, readFloat64, readUint16, readBytes, readInt16, checkSignature, readFloat32, readFixedPointPath32, readSection, readColor, readInt32, readPascalString, readUnicodeStringWithLength, readAsciiString,
|
|
4
|
+
import { LayerAdditionalInfo, LevelsFilterAdjustment, BezierPath, Psd, BrightnessAdjustment, ExposureAdjustment, VibranceAdjustment, ColorBalanceAdjustment, BlackAndWhiteAdjustment, PhotoFilterAdjustment, ChannelMixerChannel, ChannelMixerAdjustment, PosterizeAdjustment, ThresholdAdjustment, GradientMapAdjustment, CMYK, SelectiveColorAdjustment, ColorLookupAdjustment, LevelsAdjustmentChannel, LevelsAdjustment, CurvesAdjustment, CurvesAdjustmentChannel, HueSaturationAdjustment, HueSaturationAdjustmentChannel, PresetInfo, Color, ColorBalanceValues, WriteOptions, LinkedFile, PlacedLayerType, Warp, KeyDescriptorItem, BooleanOperation, LayerEffectsInfo, Annotation, VectorPathData, AnimationFrame, Timeline, PlacedLayerFilter, UnitsValue, Filter, PlacedLayer, Layer, CameraRawColors, CameraRawCurvePoint, CameraRawFilterSettings } from './psd';
|
|
5
|
+
import { PsdReader, readSignature, readUnicodeString, skipBytes, readUint32, readUint8, readFloat64, readUint16, readBytes, readInt16, checkSignature, readFloat32, readFixedPointPath32, readSection, readColor, readInt32, readPascalString, readUnicodeStringWithLength, readAsciiString, readPatternSafe, readLayerInfo } from './psdReader';
|
|
6
6
|
import { PsdWriter, writeZeros, writeSignature, writeBytes, writeUint32, writeUint16, writeFloat64, writeUint8, writeInt16, writeFloat32, writeFixedPointPath32, writeUnicodeString, writeSection, writeUnicodeStringWithPadding, writeColor, writePascalString, writeInt32, writePattern, writeUint64 } from './psdWriter';
|
|
7
|
-
import { Annt, BlnM, DescriptorColor, DescriptorUnitsValue, parsePercent, parseUnits, parseUnitsOrNumber, QuiltWarpDescriptor, strokeStyleLineAlignment, strokeStyleLineCapType, strokeStyleLineJoinType, TextDescriptor, textGridding, unitsPercent, unitsValue, WarpDescriptor, warpStyle, writeVersionAndDescriptor, readVersionAndDescriptor, StrokeDescriptor, Ornt, horzVrtcToXY, LmfxDescriptor, Lfx2Descriptor, FrameListDescriptor, TimelineDescriptor, FrameDescriptor, xyToHorzVrtc, serializeEffects, parseEffects, parseColor, serializeColor, serializeVectorContent, parseVectorContent, parseTrackList, serializeTrackList, FractionDescriptor, BlrM, BlrQ, SmBQ, SmBM, DspM, UndA, Cnvr, RplS, SphM, Wvtp, ZZTy, Dstr, Chnl, MztT, Lns, blurType, DfsM, ExtT, ExtR, FlCl, CntE, WndM, Drct, IntE, IntC, FlMd, unitsPercentF, frac, ClrS, descBoundsToBounds, boundsToDescBounds, presetKindType, gradientInterpolationMethodType, PxScDescriptor } from './descriptor';
|
|
7
|
+
import { Annt, BlnM, DescriptorColor, DescriptorUnitsValue, parsePercent, parseUnits, parseUnitsOrNumber, QuiltWarpDescriptor, strokeStyleLineAlignment, strokeStyleLineCapType, strokeStyleLineJoinType, TextDescriptor, textGridding, unitsPercent, unitsValue, WarpDescriptor, warpStyle, writeVersionAndDescriptor, readVersionAndDescriptor, StrokeDescriptor, Ornt, horzVrtcToXY, LmfxDescriptor, Lfx2Descriptor, FrameListDescriptor, TimelineDescriptor, FrameDescriptor, xyToHorzVrtc, serializeEffects, parseEffects, parseColor, serializeColor, serializeVectorContent, parseVectorContent, parseTrackList, serializeTrackList, FractionDescriptor, BlrM, BlrQ, SmBQ, SmBM, DspM, UndA, Cnvr, RplS, SphM, Wvtp, ZZTy, Dstr, Chnl, MztT, Lns, blurType, DfsM, ExtT, ExtR, FlCl, CntE, WndM, Drct, IntE, IntC, FlMd, unitsPercentF, frac, ClrS, descBoundsToBounds, boundsToDescBounds, presetKindType, gradientInterpolationMethodType, PxScDescriptor, cameraRawFilterClassID, patternColorHandling } from './descriptor';
|
|
8
8
|
import { serializeEngineData, parseEngineData } from './engineData';
|
|
9
|
-
import { encodeEngineData, decodeEngineData } from './text';
|
|
9
|
+
import { encodeEngineData, decodeEngineData, decodeLegacyTextData, LegacyTextData } from './text';
|
|
10
10
|
import { decodeEngineData2 } from './engineData2';
|
|
11
11
|
import type { InternalImageResources } from './imageResources';
|
|
12
12
|
|
|
13
13
|
export interface ExtendedWriteOptions extends WriteOptions {
|
|
14
|
+
colorMode?: number; // color mode of written document
|
|
15
|
+
bitsPerChannel?: number; // bit depth of written document
|
|
14
16
|
layerIds: Set<number>;
|
|
15
17
|
layerToId: Map<any, number>;
|
|
16
18
|
explicitLayerIds?: Set<number>; // ids set on layers in the document (generated ids can't use them)
|
|
@@ -103,7 +105,8 @@ addHandler(
|
|
|
103
105
|
// require('fs').writeFileSync('after.txt', require('util').inspect(after, false, 99, false), 'utf8');
|
|
104
106
|
|
|
105
107
|
// console.log(require('util').inspect(parseEngineData(text.EngineData), false, 99, true));
|
|
106
|
-
|
|
108
|
+
// descriptor anti-alias value is more reliable than the one from engine data
|
|
109
|
+
target.text = { ...target.text, ...textData, antiAlias: target.text!.antiAlias ?? textData.antiAlias };
|
|
107
110
|
// console.log(require('util').inspect(target.text, false, 99, true));
|
|
108
111
|
}
|
|
109
112
|
|
|
@@ -146,15 +149,95 @@ addHandler(
|
|
|
146
149
|
},
|
|
147
150
|
);
|
|
148
151
|
|
|
152
|
+
// type tool info from Photoshop 5.0 and 5.5, converted to regular text data (written as 'TySh')
|
|
153
|
+
addHandler(
|
|
154
|
+
'tySh',
|
|
155
|
+
() => false,
|
|
156
|
+
(reader, target, left) => {
|
|
157
|
+
if (readInt16(reader) !== 1) throw new Error(`Invalid tySh version`);
|
|
158
|
+
|
|
159
|
+
const transform: number[] = [];
|
|
160
|
+
for (let i = 0; i < 6; i++) transform.push(readFloat64(reader));
|
|
161
|
+
|
|
162
|
+
if (readInt16(reader) !== 6) throw new Error(`Invalid tySh font version`);
|
|
163
|
+
|
|
164
|
+
const data: LegacyTextData = { transform, faces: [], styles: [], lines: [], color: { r: 0, g: 0, b: 0 }, antiAlias: false };
|
|
165
|
+
const facesCount = readInt16(reader);
|
|
166
|
+
|
|
167
|
+
for (let i = 0; i < facesCount; i++) {
|
|
168
|
+
const mark = readInt16(reader);
|
|
169
|
+
readInt32(reader); // font type
|
|
170
|
+
const name = readPascalString(reader, 1);
|
|
171
|
+
const familyName = readPascalString(reader, 1);
|
|
172
|
+
const styleName = readPascalString(reader, 1);
|
|
173
|
+
const script = readInt16(reader);
|
|
174
|
+
const axesCount = readInt32(reader);
|
|
175
|
+
if (axesCount < 0 || axesCount * 4 > left()) throw new Error(`Invalid tySh design axes count`);
|
|
176
|
+
skipBytes(reader, axesCount * 4);
|
|
177
|
+
data.faces.push({ mark, name, familyName, styleName, script });
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
const stylesCount = readInt16(reader);
|
|
181
|
+
|
|
182
|
+
for (let i = 0; i < stylesCount; i++) {
|
|
183
|
+
const mark = readInt16(reader);
|
|
184
|
+
const faceMark = readInt16(reader);
|
|
185
|
+
const size = readInt32(reader);
|
|
186
|
+
const tracking = readInt32(reader);
|
|
187
|
+
const kerning = readInt32(reader);
|
|
188
|
+
const leading = readInt32(reader);
|
|
189
|
+
const baselineShift = readInt32(reader);
|
|
190
|
+
const autoKerning = !!readUint8(reader);
|
|
191
|
+
readUint8(reader); // rotate
|
|
192
|
+
data.styles.push({ mark, faceMark, size, tracking, kerning, leading, baselineShift, autoKerning });
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
readInt16(reader); // type (0 = point, 1 = paragraph)
|
|
196
|
+
readInt32(reader); // scaling factor
|
|
197
|
+
readInt32(reader); // character count
|
|
198
|
+
readInt32(reader); // horizontal placement
|
|
199
|
+
readInt32(reader); // vertical placement
|
|
200
|
+
readInt32(reader); // select start
|
|
201
|
+
readInt32(reader); // select end
|
|
202
|
+
|
|
203
|
+
const linesCount = readInt16(reader);
|
|
204
|
+
|
|
205
|
+
for (let i = 0; i < linesCount; i++) {
|
|
206
|
+
const charCount = readInt32(reader);
|
|
207
|
+
const orientation = readInt16(reader);
|
|
208
|
+
const alignment = readInt16(reader);
|
|
209
|
+
if (charCount < 0 || charCount * 4 > left()) throw new Error(`Invalid tySh character count`);
|
|
210
|
+
const chars: { code: number; style: number; }[] = [];
|
|
211
|
+
|
|
212
|
+
for (let j = 0; j < charCount; j++) {
|
|
213
|
+
const code = readUint16(reader);
|
|
214
|
+
const style = readInt16(reader);
|
|
215
|
+
chars.push({ code, style });
|
|
216
|
+
}
|
|
217
|
+
|
|
218
|
+
data.lines.push({ orientation, alignment, chars });
|
|
219
|
+
}
|
|
220
|
+
|
|
221
|
+
data.color = readColor(reader);
|
|
222
|
+
data.antiAlias = !!readUint8(reader);
|
|
223
|
+
|
|
224
|
+
if (!target.text) target.text = decodeLegacyTextData(data);
|
|
225
|
+
|
|
226
|
+
skipBytes(reader, left());
|
|
227
|
+
},
|
|
228
|
+
() => { },
|
|
229
|
+
);
|
|
230
|
+
|
|
149
231
|
// vector fills
|
|
150
232
|
|
|
151
233
|
addHandler(
|
|
152
234
|
'SoCo',
|
|
153
235
|
target => target.vectorFill !== undefined && target.vectorStroke === undefined &&
|
|
154
236
|
target.vectorFill.type === 'color',
|
|
155
|
-
(reader, target) => {
|
|
237
|
+
(reader, target, left) => {
|
|
156
238
|
const descriptor = readVersionAndDescriptor(reader);
|
|
157
239
|
target.vectorFill = parseVectorContent(descriptor);
|
|
240
|
+
skipBytes(reader, left());
|
|
158
241
|
},
|
|
159
242
|
(writer, target) => {
|
|
160
243
|
const { descriptor } = serializeVectorContent(target.vectorFill!);
|
|
@@ -228,7 +311,7 @@ function writeBezierKnot(writer: PsdWriter, points: number[], width: number, hei
|
|
|
228
311
|
|
|
229
312
|
export const booleanOperations: BooleanOperation[] = ['exclude', 'combine', 'subtract', 'intersect'];
|
|
230
313
|
|
|
231
|
-
export function readVectorMask(reader: PsdReader, vectorMask:
|
|
314
|
+
export function readVectorMask(reader: PsdReader, vectorMask: VectorPathData, width: number, height: number, size: number) {
|
|
232
315
|
const end = reader.offset + size;
|
|
233
316
|
const paths = vectorMask.paths;
|
|
234
317
|
let path: BezierPath | undefined = undefined;
|
|
@@ -283,6 +366,51 @@ export function readVectorMask(reader: PsdReader, vectorMask: LayerVectorMask, w
|
|
|
283
366
|
return paths;
|
|
284
367
|
}
|
|
285
368
|
|
|
369
|
+
// componentIndices - write index of path component for each subpath (Photoshop writes them for layer vector masks)
|
|
370
|
+
export function writeVectorMask(writer: PsdWriter, vectorMask: VectorPathData, width: number, height: number, componentIndices = false) {
|
|
371
|
+
// initial entry
|
|
372
|
+
writeUint16(writer, 6);
|
|
373
|
+
writeZeros(writer, 24);
|
|
374
|
+
|
|
375
|
+
const clipboard = vectorMask.clipboard;
|
|
376
|
+
if (clipboard) {
|
|
377
|
+
writeUint16(writer, 7);
|
|
378
|
+
writeFixedPointPath32(writer, clipboard.top);
|
|
379
|
+
writeFixedPointPath32(writer, clipboard.left);
|
|
380
|
+
writeFixedPointPath32(writer, clipboard.bottom);
|
|
381
|
+
writeFixedPointPath32(writer, clipboard.right);
|
|
382
|
+
writeFixedPointPath32(writer, clipboard.resolution);
|
|
383
|
+
writeZeros(writer, 4);
|
|
384
|
+
}
|
|
385
|
+
|
|
386
|
+
writeUint16(writer, 8);
|
|
387
|
+
writeUint16(writer, vectorMask.fillStartsWithAllPixels ? 1 : 0);
|
|
388
|
+
writeZeros(writer, 22);
|
|
389
|
+
|
|
390
|
+
let componentIndex = -1;
|
|
391
|
+
|
|
392
|
+
for (const path of vectorMask.paths) {
|
|
393
|
+
// subpaths without operation are part of the same path component as previous subpath
|
|
394
|
+
if (path.operation || componentIndex < 0) componentIndex++;
|
|
395
|
+
|
|
396
|
+
writeUint16(writer, path.open ? 3 : 0);
|
|
397
|
+
writeUint16(writer, path.knots.length);
|
|
398
|
+
writeUint16(writer, path.operation ? booleanOperations.indexOf(path.operation) : -1); // -1 for undefined
|
|
399
|
+
writeUint16(writer, path.fillRule === 'non-zero' ? 2 : 1);
|
|
400
|
+
writeZeros(writer, 4);
|
|
401
|
+
writeUint32(writer, componentIndices ? componentIndex : 0);
|
|
402
|
+
writeZeros(writer, 10);
|
|
403
|
+
|
|
404
|
+
const linkedKnot = path.open ? 4 : 1;
|
|
405
|
+
const unlinkedKnot = path.open ? 5 : 2;
|
|
406
|
+
|
|
407
|
+
for (const { linked, points } of path.knots) {
|
|
408
|
+
writeUint16(writer, linked ? linkedKnot : unlinkedKnot);
|
|
409
|
+
writeBezierKnot(writer, points, width, height);
|
|
410
|
+
}
|
|
411
|
+
}
|
|
412
|
+
}
|
|
413
|
+
|
|
286
414
|
addHandler(
|
|
287
415
|
'vmsk',
|
|
288
416
|
hasKey('vectorMask'),
|
|
@@ -312,45 +440,11 @@ addHandler(
|
|
|
312
440
|
|
|
313
441
|
writeUint32(writer, 3); // version
|
|
314
442
|
writeUint32(writer, flags);
|
|
315
|
-
|
|
316
|
-
// initial entry
|
|
317
|
-
writeUint16(writer, 6);
|
|
318
|
-
writeZeros(writer, 24);
|
|
319
|
-
|
|
320
|
-
const clipboard = vectorMask.clipboard;
|
|
321
|
-
if (clipboard) {
|
|
322
|
-
writeUint16(writer, 7);
|
|
323
|
-
writeFixedPointPath32(writer, clipboard.top);
|
|
324
|
-
writeFixedPointPath32(writer, clipboard.left);
|
|
325
|
-
writeFixedPointPath32(writer, clipboard.bottom);
|
|
326
|
-
writeFixedPointPath32(writer, clipboard.right);
|
|
327
|
-
writeFixedPointPath32(writer, clipboard.resolution);
|
|
328
|
-
writeZeros(writer, 4);
|
|
329
|
-
}
|
|
330
|
-
|
|
331
|
-
writeUint16(writer, 8);
|
|
332
|
-
writeUint16(writer, vectorMask.fillStartsWithAllPixels ? 1 : 0);
|
|
333
|
-
writeZeros(writer, 22);
|
|
334
|
-
|
|
335
|
-
for (const path of vectorMask.paths) {
|
|
336
|
-
writeUint16(writer, path.open ? 3 : 0);
|
|
337
|
-
writeUint16(writer, path.knots.length);
|
|
338
|
-
writeUint16(writer, path.operation ? booleanOperations.indexOf(path.operation) : -1); // -1 for undefined
|
|
339
|
-
writeUint16(writer, path.fillRule === 'non-zero' ? 2 : 1);
|
|
340
|
-
writeZeros(writer, 18); // TODO: these are sometimes non-zero
|
|
341
|
-
|
|
342
|
-
const linkedKnot = path.open ? 4 : 1;
|
|
343
|
-
const unlinkedKnot = path.open ? 5 : 2;
|
|
344
|
-
|
|
345
|
-
for (const { linked, points } of path.knots) {
|
|
346
|
-
writeUint16(writer, linked ? linkedKnot : unlinkedKnot);
|
|
347
|
-
writeBezierKnot(writer, points, width, height);
|
|
348
|
-
}
|
|
349
|
-
}
|
|
443
|
+
writeVectorMask(writer, vectorMask, width, height, true);
|
|
350
444
|
},
|
|
351
445
|
);
|
|
352
446
|
|
|
353
|
-
//
|
|
447
|
+
// written instead of 'vmsk' for layers with vector stroke ('vscg' section)
|
|
354
448
|
addHandlerAlias('vsms', 'vmsk');
|
|
355
449
|
// addHandlerAlias('vmsk', 'vsms');
|
|
356
450
|
|
|
@@ -365,7 +459,7 @@ addHandler(
|
|
|
365
459
|
},
|
|
366
460
|
);
|
|
367
461
|
|
|
368
|
-
interface
|
|
462
|
+
export interface KeyDescriptorListDescriptor {
|
|
369
463
|
keyDescriptorList: {
|
|
370
464
|
keyShapeInvalidated?: boolean;
|
|
371
465
|
keyOriginType?: number;
|
|
@@ -395,121 +489,128 @@ interface VogkDescriptor {
|
|
|
395
489
|
}[];
|
|
396
490
|
}
|
|
397
491
|
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
(
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
|
|
492
|
+
export function parseKeyDescriptorList(desc: KeyDescriptorListDescriptor) {
|
|
493
|
+
const keyDescriptorList: KeyDescriptorItem[] = [];
|
|
494
|
+
|
|
495
|
+
for (const i of desc.keyDescriptorList) {
|
|
496
|
+
const item: KeyDescriptorItem = {};
|
|
497
|
+
|
|
498
|
+
if (i.keyShapeInvalidated != null) item.keyShapeInvalidated = i.keyShapeInvalidated;
|
|
499
|
+
if (i.keyOriginType != null) item.keyOriginType = i.keyOriginType;
|
|
500
|
+
if (i.keyOriginResolution != null) item.keyOriginResolution = i.keyOriginResolution;
|
|
501
|
+
if (i.keyOriginShapeBBox) {
|
|
502
|
+
item.keyOriginShapeBoundingBox = {
|
|
503
|
+
top: parseUnitsOrNumber(i.keyOriginShapeBBox['Top ']),
|
|
504
|
+
left: parseUnitsOrNumber(i.keyOriginShapeBBox.Left),
|
|
505
|
+
bottom: parseUnitsOrNumber(i.keyOriginShapeBBox.Btom),
|
|
506
|
+
right: parseUnitsOrNumber(i.keyOriginShapeBBox.Rght),
|
|
507
|
+
};
|
|
508
|
+
}
|
|
509
|
+
const rectRadii = i.keyOriginRRectRadii;
|
|
510
|
+
if (rectRadii) {
|
|
511
|
+
item.keyOriginRRectRadii = {
|
|
512
|
+
topRight: parseUnits(rectRadii.topRight),
|
|
513
|
+
topLeft: parseUnits(rectRadii.topLeft),
|
|
514
|
+
bottomLeft: parseUnits(rectRadii.bottomLeft),
|
|
515
|
+
bottomRight: parseUnits(rectRadii.bottomRight),
|
|
516
|
+
};
|
|
517
|
+
}
|
|
518
|
+
const corners = i.keyOriginBoxCorners;
|
|
519
|
+
if (corners) {
|
|
520
|
+
item.keyOriginBoxCorners = [
|
|
521
|
+
{ x: corners.rectangleCornerA.Hrzn, y: corners.rectangleCornerA.Vrtc },
|
|
522
|
+
{ x: corners.rectangleCornerB.Hrzn, y: corners.rectangleCornerB.Vrtc },
|
|
523
|
+
{ x: corners.rectangleCornerC.Hrzn, y: corners.rectangleCornerC.Vrtc },
|
|
524
|
+
{ x: corners.rectangleCornerD.Hrzn, y: corners.rectangleCornerD.Vrtc },
|
|
525
|
+
];
|
|
526
|
+
}
|
|
527
|
+
const trnf = i.Trnf;
|
|
528
|
+
if (trnf) {
|
|
529
|
+
item.transform = [trnf.xx, trnf.xy, trnf.yx, trnf.yy, trnf.tx, trnf.ty];
|
|
530
|
+
}
|
|
407
531
|
|
|
408
|
-
|
|
409
|
-
|
|
532
|
+
keyDescriptorList.push(item);
|
|
533
|
+
}
|
|
410
534
|
|
|
411
|
-
|
|
412
|
-
|
|
413
|
-
if (i.keyOriginResolution != null) item.keyOriginResolution = i.keyOriginResolution;
|
|
414
|
-
if (i.keyOriginShapeBBox) {
|
|
415
|
-
item.keyOriginShapeBoundingBox = {
|
|
416
|
-
top: parseUnitsOrNumber(i.keyOriginShapeBBox['Top ']),
|
|
417
|
-
left: parseUnitsOrNumber(i.keyOriginShapeBBox.Left),
|
|
418
|
-
bottom: parseUnitsOrNumber(i.keyOriginShapeBBox.Btom),
|
|
419
|
-
right: parseUnitsOrNumber(i.keyOriginShapeBBox.Rght),
|
|
420
|
-
};
|
|
421
|
-
}
|
|
422
|
-
const rectRadii = i.keyOriginRRectRadii;
|
|
423
|
-
if (rectRadii) {
|
|
424
|
-
item.keyOriginRRectRadii = {
|
|
425
|
-
topRight: parseUnits(rectRadii.topRight),
|
|
426
|
-
topLeft: parseUnits(rectRadii.topLeft),
|
|
427
|
-
bottomLeft: parseUnits(rectRadii.bottomLeft),
|
|
428
|
-
bottomRight: parseUnits(rectRadii.bottomRight),
|
|
429
|
-
};
|
|
430
|
-
}
|
|
431
|
-
const corners = i.keyOriginBoxCorners;
|
|
432
|
-
if (corners) {
|
|
433
|
-
item.keyOriginBoxCorners = [
|
|
434
|
-
{ x: corners.rectangleCornerA.Hrzn, y: corners.rectangleCornerA.Vrtc },
|
|
435
|
-
{ x: corners.rectangleCornerB.Hrzn, y: corners.rectangleCornerB.Vrtc },
|
|
436
|
-
{ x: corners.rectangleCornerC.Hrzn, y: corners.rectangleCornerC.Vrtc },
|
|
437
|
-
{ x: corners.rectangleCornerD.Hrzn, y: corners.rectangleCornerD.Vrtc },
|
|
438
|
-
];
|
|
439
|
-
}
|
|
440
|
-
const trnf = i.Trnf;
|
|
441
|
-
if (trnf) {
|
|
442
|
-
item.transform = [trnf.xx, trnf.xy, trnf.yx, trnf.yy, trnf.tx, trnf.ty];
|
|
443
|
-
}
|
|
535
|
+
return keyDescriptorList;
|
|
536
|
+
}
|
|
444
537
|
|
|
445
|
-
|
|
446
|
-
|
|
538
|
+
export function serializeKeyDescriptorList(keyDescriptorList: KeyDescriptorItem[]) {
|
|
539
|
+
const desc: KeyDescriptorListDescriptor = { keyDescriptorList: [] };
|
|
447
540
|
|
|
448
|
-
|
|
449
|
-
|
|
450
|
-
(writer, target) => {
|
|
451
|
-
target;
|
|
452
|
-
const orig = target.vectorOrigination!;
|
|
453
|
-
const desc: VogkDescriptor = { keyDescriptorList: [] };
|
|
541
|
+
for (let i = 0; i < keyDescriptorList.length; i++) {
|
|
542
|
+
const item = keyDescriptorList[i];
|
|
454
543
|
|
|
455
|
-
|
|
456
|
-
const item = orig.keyDescriptorList[i];
|
|
544
|
+
desc.keyDescriptorList.push({} as any); // we're adding keyOriginIndex at the end
|
|
457
545
|
|
|
458
|
-
|
|
546
|
+
const out = desc.keyDescriptorList[desc.keyDescriptorList.length - 1];
|
|
459
547
|
|
|
460
|
-
|
|
548
|
+
if (item.keyOriginType != null) out.keyOriginType = item.keyOriginType;
|
|
549
|
+
if (item.keyOriginResolution != null) out.keyOriginResolution = item.keyOriginResolution;
|
|
461
550
|
|
|
462
|
-
|
|
463
|
-
|
|
551
|
+
const radii = item.keyOriginRRectRadii;
|
|
552
|
+
if (radii) {
|
|
553
|
+
out.keyOriginRRectRadii = {
|
|
554
|
+
unitValueQuadVersion: 1,
|
|
555
|
+
topRight: unitsValue(radii.topRight, 'topRight'),
|
|
556
|
+
topLeft: unitsValue(radii.topLeft, 'topLeft'),
|
|
557
|
+
bottomLeft: unitsValue(radii.bottomLeft, 'bottomLeft'),
|
|
558
|
+
bottomRight: unitsValue(radii.bottomRight, 'bottomRight'),
|
|
559
|
+
};
|
|
560
|
+
}
|
|
464
561
|
|
|
465
|
-
|
|
466
|
-
|
|
467
|
-
|
|
468
|
-
|
|
469
|
-
|
|
470
|
-
|
|
471
|
-
|
|
472
|
-
|
|
473
|
-
|
|
474
|
-
|
|
562
|
+
const box = item.keyOriginShapeBoundingBox;
|
|
563
|
+
if (box) {
|
|
564
|
+
out.keyOriginShapeBBox = {
|
|
565
|
+
unitValueQuadVersion: 1,
|
|
566
|
+
'Top ': unitsValue(box.top, 'top'),
|
|
567
|
+
Left: unitsValue(box.left, 'left'),
|
|
568
|
+
Btom: unitsValue(box.bottom, 'bottom'),
|
|
569
|
+
Rght: unitsValue(box.right, 'right'),
|
|
570
|
+
};
|
|
571
|
+
}
|
|
475
572
|
|
|
476
|
-
|
|
477
|
-
|
|
478
|
-
|
|
479
|
-
|
|
480
|
-
|
|
481
|
-
|
|
482
|
-
|
|
483
|
-
|
|
484
|
-
|
|
485
|
-
}
|
|
573
|
+
const corners = item.keyOriginBoxCorners;
|
|
574
|
+
if (corners && corners.length === 4) {
|
|
575
|
+
out.keyOriginBoxCorners = {
|
|
576
|
+
rectangleCornerA: { Hrzn: corners[0].x, Vrtc: corners[0].y },
|
|
577
|
+
rectangleCornerB: { Hrzn: corners[1].x, Vrtc: corners[1].y },
|
|
578
|
+
rectangleCornerC: { Hrzn: corners[2].x, Vrtc: corners[2].y },
|
|
579
|
+
rectangleCornerD: { Hrzn: corners[3].x, Vrtc: corners[3].y },
|
|
580
|
+
};
|
|
581
|
+
}
|
|
486
582
|
|
|
487
|
-
|
|
488
|
-
|
|
489
|
-
|
|
490
|
-
|
|
491
|
-
|
|
492
|
-
|
|
493
|
-
|
|
494
|
-
|
|
495
|
-
|
|
583
|
+
const transform = item.transform;
|
|
584
|
+
if (transform && transform.length === 6) {
|
|
585
|
+
out.Trnf = {
|
|
586
|
+
xx: transform[0],
|
|
587
|
+
xy: transform[1],
|
|
588
|
+
yx: transform[2],
|
|
589
|
+
yy: transform[3],
|
|
590
|
+
tx: transform[4],
|
|
591
|
+
ty: transform[5],
|
|
592
|
+
};
|
|
593
|
+
}
|
|
496
594
|
|
|
497
|
-
|
|
498
|
-
|
|
499
|
-
|
|
500
|
-
xx: transform[0],
|
|
501
|
-
xy: transform[1],
|
|
502
|
-
yx: transform[2],
|
|
503
|
-
yy: transform[3],
|
|
504
|
-
tx: transform[4],
|
|
505
|
-
ty: transform[5],
|
|
506
|
-
};
|
|
507
|
-
}
|
|
595
|
+
if (item.keyShapeInvalidated != null) out.keyShapeInvalidated = item.keyShapeInvalidated;
|
|
596
|
+
out.keyOriginIndex = i;
|
|
597
|
+
}
|
|
508
598
|
|
|
509
|
-
|
|
510
|
-
|
|
511
|
-
}
|
|
599
|
+
return desc;
|
|
600
|
+
}
|
|
512
601
|
|
|
602
|
+
addHandler(
|
|
603
|
+
'vogk',
|
|
604
|
+
hasKey('vectorOrigination'),
|
|
605
|
+
(reader, target, left) => {
|
|
606
|
+
if (readInt32(reader) !== 1) throw new Error(`Invalid vogk version`);
|
|
607
|
+
const desc = readVersionAndDescriptor(reader) as KeyDescriptorListDescriptor;
|
|
608
|
+
// console.log(require('util').inspect(desc, false, 99, true));
|
|
609
|
+
target.vectorOrigination = { keyDescriptorList: parseKeyDescriptorList(desc) };
|
|
610
|
+
skipBytes(reader, left());
|
|
611
|
+
},
|
|
612
|
+
(writer, target) => {
|
|
613
|
+
const desc = serializeKeyDescriptorList(target.vectorOrigination!.keyDescriptorList);
|
|
513
614
|
writeInt32(writer, 1); // version
|
|
514
615
|
writeVersionAndDescriptor(writer, '', 'null', desc);
|
|
515
616
|
}
|
|
@@ -517,7 +618,7 @@ addHandler(
|
|
|
517
618
|
|
|
518
619
|
addHandler(
|
|
519
620
|
'lmfx',
|
|
520
|
-
target => target.effects !== undefined && hasMultiEffects(target.effects),
|
|
621
|
+
target => target.effects !== undefined && !isGroupLayer(target) && hasMultiEffects(target.effects),
|
|
521
622
|
(reader, target, left) => {
|
|
522
623
|
const version = readUint32(reader);
|
|
523
624
|
if (version !== 0) throw new Error('Invalid lmfx version');
|
|
@@ -526,12 +627,12 @@ addHandler(
|
|
|
526
627
|
// console.log('READ', require('util').inspect(desc, false, 99, true));
|
|
527
628
|
|
|
528
629
|
// discard if read in 'lrFX' or 'lfx2' section
|
|
529
|
-
target.effects = parseEffects(desc,
|
|
630
|
+
target.effects = parseEffects(desc, reader);
|
|
530
631
|
|
|
531
632
|
skipBytes(reader, left());
|
|
532
633
|
},
|
|
533
|
-
(writer, target
|
|
534
|
-
const desc = serializeEffects(target.effects!,
|
|
634
|
+
(writer, target) => {
|
|
635
|
+
const desc = serializeEffects(target.effects!, writer, true);
|
|
535
636
|
// console.log('WRITE', require('util').inspect(desc, false, 99, true));
|
|
536
637
|
|
|
537
638
|
writeUint32(writer, 0); // version
|
|
@@ -621,7 +722,12 @@ addHandler(
|
|
|
621
722
|
|
|
622
723
|
if (target.id !== undefined) {
|
|
623
724
|
id = target.id;
|
|
624
|
-
|
|
725
|
+
|
|
726
|
+
if (options.layerIds.has(id)) {
|
|
727
|
+
// make sure we don't have duplicate layer ids (and don't take id explicitly set on another layer)
|
|
728
|
+
const explicitIds = getExplicitLayerIds(psd, options);
|
|
729
|
+
while (options.layerIds.has(id) || explicitIds.has(id)) id += 100;
|
|
730
|
+
}
|
|
625
731
|
} else {
|
|
626
732
|
// generate id for layers that don't have it, if it's referenced by other sections
|
|
627
733
|
const explicitIds = getExplicitLayerIds(psd, options);
|
|
@@ -721,6 +827,82 @@ addHandler(
|
|
|
721
827
|
},
|
|
722
828
|
);
|
|
723
829
|
|
|
830
|
+
addHandler(
|
|
831
|
+
'vmgm',
|
|
832
|
+
hasKey('vectorMaskAsGlobalMask'),
|
|
833
|
+
(reader, target) => {
|
|
834
|
+
target.vectorMaskAsGlobalMask = !!readUint8(reader);
|
|
835
|
+
skipBytes(reader, 3);
|
|
836
|
+
},
|
|
837
|
+
(writer, target) => {
|
|
838
|
+
writeUint8(writer, target.vectorMaskAsGlobalMask ? 1 : 0);
|
|
839
|
+
writeZeros(writer, 3);
|
|
840
|
+
},
|
|
841
|
+
);
|
|
842
|
+
|
|
843
|
+
addHandler(
|
|
844
|
+
'ffxi',
|
|
845
|
+
hasKey('foreignEffectId'),
|
|
846
|
+
(reader, target) => {
|
|
847
|
+
target.foreignEffectId = readInt32(reader);
|
|
848
|
+
},
|
|
849
|
+
(writer, target) => {
|
|
850
|
+
writeInt32(writer, target.foreignEffectId!);
|
|
851
|
+
},
|
|
852
|
+
);
|
|
853
|
+
|
|
854
|
+
addHandler(
|
|
855
|
+
'shpa',
|
|
856
|
+
hasKey('patternData'),
|
|
857
|
+
(reader, target, left) => {
|
|
858
|
+
if (readInt32(reader) !== 0) throw new Error(`Invalid shpa version`);
|
|
859
|
+
|
|
860
|
+
const count = readInt32(reader);
|
|
861
|
+
target.patternData = [];
|
|
862
|
+
|
|
863
|
+
for (let i = 0; i < count && left() > 0; i++) {
|
|
864
|
+
const signature = readSignature(reader);
|
|
865
|
+
const key = readSignature(reader);
|
|
866
|
+
const patternsCount = readInt32(reader);
|
|
867
|
+
const copyOnSheetDuplication = !!readUint8(reader);
|
|
868
|
+
skipBytes(reader, 3);
|
|
869
|
+
const patterns: NonNullable<LayerAdditionalInfo['patternData']>[number]['patterns'] = [];
|
|
870
|
+
|
|
871
|
+
for (let j = 0; j < patternsCount && left() > 0; j++) {
|
|
872
|
+
const colorHandling = patternColorHandling.decode('.' + readSignature(reader));
|
|
873
|
+
readPascalString(reader, 1); // name
|
|
874
|
+
const name = readUnicodeString(reader);
|
|
875
|
+
const id = readPascalString(reader, 1);
|
|
876
|
+
patterns.push({ colorHandling, name, id });
|
|
877
|
+
}
|
|
878
|
+
|
|
879
|
+
target.patternData.push({ signature, key, copyOnSheetDuplication, patterns });
|
|
880
|
+
}
|
|
881
|
+
|
|
882
|
+
skipBytes(reader, left());
|
|
883
|
+
},
|
|
884
|
+
(writer, target) => {
|
|
885
|
+
const sets = target.patternData!;
|
|
886
|
+
writeInt32(writer, 0); // version
|
|
887
|
+
writeInt32(writer, sets.length);
|
|
888
|
+
|
|
889
|
+
for (const set of sets) {
|
|
890
|
+
writeSignature(writer, set.signature);
|
|
891
|
+
writeSignature(writer, set.key);
|
|
892
|
+
writeInt32(writer, set.patterns.length);
|
|
893
|
+
writeUint8(writer, set.copyOnSheetDuplication ? 1 : 0);
|
|
894
|
+
writeZeros(writer, 3);
|
|
895
|
+
|
|
896
|
+
for (const pattern of set.patterns) {
|
|
897
|
+
writeSignature(writer, patternColorHandling.encode(pattern.colorHandling).split('.')[1]);
|
|
898
|
+
writePascalString(writer, pattern.name.replace(/[^\x20-\x7e]/g, '?'), 1);
|
|
899
|
+
writeUnicodeString(writer, pattern.name);
|
|
900
|
+
writePascalString(writer, pattern.id, 1);
|
|
901
|
+
}
|
|
902
|
+
}
|
|
903
|
+
},
|
|
904
|
+
);
|
|
905
|
+
|
|
724
906
|
addHandler(
|
|
725
907
|
'lspf',
|
|
726
908
|
hasKey('protected'),
|
|
@@ -733,15 +915,17 @@ addHandler(
|
|
|
733
915
|
};
|
|
734
916
|
|
|
735
917
|
if (flags & 0x08) target.protected.artboards = true;
|
|
918
|
+
if (flags & 0x80000000) target.protected.all = true;
|
|
736
919
|
},
|
|
737
920
|
(writer, target) => {
|
|
738
921
|
const flags =
|
|
739
922
|
(target.protected!.transparency ? 0x01 : 0) |
|
|
740
923
|
(target.protected!.composite ? 0x02 : 0) |
|
|
741
924
|
(target.protected!.position ? 0x04 : 0) |
|
|
742
|
-
(target.protected!.artboards ? 0x08 : 0)
|
|
925
|
+
(target.protected!.artboards ? 0x08 : 0) |
|
|
926
|
+
(target.protected!.all ? 0x80000000 : 0);
|
|
743
927
|
|
|
744
|
-
writeUint32(writer, flags);
|
|
928
|
+
writeUint32(writer, flags >>> 0);
|
|
745
929
|
},
|
|
746
930
|
);
|
|
747
931
|
|
|
@@ -764,6 +948,62 @@ interface CustomDescriptor {
|
|
|
764
948
|
layerTime?: number;
|
|
765
949
|
}
|
|
766
950
|
|
|
951
|
+
interface GeneratorSettingsDescriptor {
|
|
952
|
+
generatorSettings?: {
|
|
953
|
+
_name: '';
|
|
954
|
+
_classID: string; // 'null' or 'generatorSettings'
|
|
955
|
+
exportAs?: {
|
|
956
|
+
_name: '';
|
|
957
|
+
_classID: 'null';
|
|
958
|
+
exportOption?: string; // JSON
|
|
959
|
+
scaleSettings?: string; // JSON
|
|
960
|
+
};
|
|
961
|
+
generator_45_assets?: { _name: ''; _classID: string; json: string; };
|
|
962
|
+
layerTime?: number;
|
|
963
|
+
crema?: { _name: ''; _classID: string; json: string; };
|
|
964
|
+
};
|
|
965
|
+
}
|
|
966
|
+
|
|
967
|
+
function parseGeneratorSettings(settings: NonNullable<GeneratorSettingsDescriptor['generatorSettings']>) {
|
|
968
|
+
const result: NonNullable<LayerAdditionalInfo['generatorSettings']> = {};
|
|
969
|
+
|
|
970
|
+
if (settings.exportAs) {
|
|
971
|
+
result.exportAs = {};
|
|
972
|
+
if (settings.exportAs.exportOption !== undefined) result.exportAs.exportOption = settings.exportAs.exportOption;
|
|
973
|
+
if (settings.exportAs.scaleSettings !== undefined) result.exportAs.scaleSettings = settings.exportAs.scaleSettings;
|
|
974
|
+
}
|
|
975
|
+
|
|
976
|
+
if (settings.generator_45_assets) result.assets = settings.generator_45_assets.json;
|
|
977
|
+
if (settings.layerTime !== undefined) result.layerTime = settings.layerTime;
|
|
978
|
+
if (settings.crema) result.crema = settings.crema.json;
|
|
979
|
+
|
|
980
|
+
return result;
|
|
981
|
+
}
|
|
982
|
+
|
|
983
|
+
function serializeGeneratorSettings(generatorSettings: NonNullable<LayerAdditionalInfo['generatorSettings']>): GeneratorSettingsDescriptor {
|
|
984
|
+
const settings: GeneratorSettingsDescriptor['generatorSettings'] = {
|
|
985
|
+
_name: '',
|
|
986
|
+
_classID: generatorSettings.assets !== undefined ? 'generatorSettings' : 'null',
|
|
987
|
+
};
|
|
988
|
+
|
|
989
|
+
if (generatorSettings.exportAs) {
|
|
990
|
+
settings.exportAs = { _name: '', _classID: 'null' };
|
|
991
|
+
if (generatorSettings.exportAs.exportOption !== undefined) settings.exportAs.exportOption = generatorSettings.exportAs.exportOption;
|
|
992
|
+
if (generatorSettings.exportAs.scaleSettings !== undefined) settings.exportAs.scaleSettings = generatorSettings.exportAs.scaleSettings;
|
|
993
|
+
}
|
|
994
|
+
|
|
995
|
+
if (generatorSettings.assets !== undefined) settings.generator_45_assets = { _name: '', _classID: 'generatorSettings', json: generatorSettings.assets };
|
|
996
|
+
if (generatorSettings.layerTime !== undefined) settings.layerTime = generatorSettings.layerTime;
|
|
997
|
+
if (generatorSettings.crema !== undefined) settings.crema = { _name: '', _classID: 'null', json: generatorSettings.crema };
|
|
998
|
+
|
|
999
|
+
return { generatorSettings: settings };
|
|
1000
|
+
}
|
|
1001
|
+
|
|
1002
|
+
// document (Psd object) stores generator settings in 'extn' section instead of 'shmd' section
|
|
1003
|
+
function isDocument(target: LayerAdditionalInfo) {
|
|
1004
|
+
return (target as Psd).width !== undefined;
|
|
1005
|
+
}
|
|
1006
|
+
|
|
767
1007
|
interface CmlsDescriptor {
|
|
768
1008
|
origFXRefPoint?: { Hrzn: number; Vrtc: number; };
|
|
769
1009
|
LyrI: number;
|
|
@@ -777,112 +1017,113 @@ interface CmlsDescriptor {
|
|
|
777
1017
|
|
|
778
1018
|
addHandler(
|
|
779
1019
|
'shmd', // Metadata setting
|
|
780
|
-
target => target.timestamp !== undefined || target.animationFrames !== undefined || target.animationFrameFlags !== undefined || target.timeline !== undefined || target.comps !== undefined
|
|
1020
|
+
target => target.timestamp !== undefined || target.animationFrames !== undefined || target.animationFrameFlags !== undefined || target.timeline !== undefined || target.comps !== undefined ||
|
|
1021
|
+
(target.generatorSettings !== undefined && !isDocument(target)),
|
|
781
1022
|
(reader, target, left) => {
|
|
782
1023
|
const count = readUint32(reader);
|
|
783
1024
|
|
|
784
1025
|
for (let i = 0; i < count; i++) {
|
|
785
|
-
|
|
1026
|
+
const signature = readSignature(reader);
|
|
1027
|
+
if (signature !== '8BIM' && signature !== '8ELE') throw new Error(`Invalid shmd signature: '${signature}'`); // 8ELE in Photoshop Elements
|
|
786
1028
|
const key = readSignature(reader);
|
|
787
1029
|
readUint8(reader); // copy
|
|
788
1030
|
skipBytes(reader, 3);
|
|
789
1031
|
|
|
790
|
-
|
|
791
|
-
|
|
792
|
-
|
|
793
|
-
|
|
794
|
-
if (
|
|
795
|
-
|
|
796
|
-
|
|
797
|
-
|
|
798
|
-
|
|
799
|
-
|
|
800
|
-
|
|
801
|
-
|
|
802
|
-
|
|
803
|
-
|
|
804
|
-
|
|
805
|
-
|
|
806
|
-
|
|
807
|
-
|
|
808
|
-
|
|
809
|
-
|
|
810
|
-
|
|
811
|
-
|
|
812
|
-
|
|
813
|
-
|
|
814
|
-
|
|
815
|
-
|
|
816
|
-
|
|
817
|
-
|
|
818
|
-
|
|
819
|
-
|
|
820
|
-
|
|
821
|
-
|
|
822
|
-
|
|
823
|
-
|
|
824
|
-
|
|
825
|
-
|
|
826
|
-
|
|
827
|
-
|
|
828
|
-
|
|
829
|
-
|
|
830
|
-
|
|
831
|
-
|
|
832
|
-
|
|
833
|
-
|
|
834
|
-
|
|
835
|
-
|
|
1032
|
+
const end = reader.offset + 4 + reader.view.getUint32(reader.offset);
|
|
1033
|
+
|
|
1034
|
+
try {
|
|
1035
|
+
readSection(reader, 1, left => {
|
|
1036
|
+
if (key === 'cust') {
|
|
1037
|
+
const desc = readVersionAndDescriptor(reader) as CustomDescriptor;
|
|
1038
|
+
// console.log('cust', target.name, require('util').inspect(desc, false, 99, true));
|
|
1039
|
+
if (desc.layerTime !== undefined) target.timestamp = desc.layerTime;
|
|
1040
|
+
} else if (key === 'mlst') {
|
|
1041
|
+
const desc = readVersionAndDescriptor(reader) as FrameListDescriptor;
|
|
1042
|
+
// console.log('mlst', target.name, require('util').inspect(desc, false, 99, true));
|
|
1043
|
+
|
|
1044
|
+
target.animationFrames = [];
|
|
1045
|
+
|
|
1046
|
+
for (let i = 0; i < desc.LaSt.length; i++) {
|
|
1047
|
+
const f = desc.LaSt[i];
|
|
1048
|
+
const frame: AnimationFrame = { frames: f.FrLs };
|
|
1049
|
+
if (f.enab !== undefined) frame.enable = f.enab;
|
|
1050
|
+
if (f.Ofst) frame.offset = horzVrtcToXY(f.Ofst);
|
|
1051
|
+
if (f.FXRf) frame.referencePoint = horzVrtcToXY(f.FXRf);
|
|
1052
|
+
if (f.Lefx) frame.effects = parseEffects(f.Lefx, reader);
|
|
1053
|
+
if (f.blendOptions && f.blendOptions.Opct) frame.opacity = parsePercent(f.blendOptions.Opct);
|
|
1054
|
+
target.animationFrames.push(frame);
|
|
1055
|
+
}
|
|
1056
|
+
} else if (key === 'mdyn') {
|
|
1057
|
+
// frame flags
|
|
1058
|
+
readUint16(reader); // unknown
|
|
1059
|
+
const propagate = readUint8(reader);
|
|
1060
|
+
const flags = readUint8(reader);
|
|
1061
|
+
|
|
1062
|
+
target.animationFrameFlags = {
|
|
1063
|
+
propagateFrameOne: !propagate,
|
|
1064
|
+
unifyLayerPosition: (flags & 1) !== 0,
|
|
1065
|
+
unifyLayerStyle: (flags & 2) !== 0,
|
|
1066
|
+
unifyLayerVisibility: (flags & 4) !== 0,
|
|
1067
|
+
};
|
|
1068
|
+
} else if (key === 'tmln') {
|
|
1069
|
+
const desc = readVersionAndDescriptor(reader) as TimelineDescriptor;
|
|
1070
|
+
const timeScope = desc.timeScope;
|
|
1071
|
+
// console.log('tmln', target.name, target.id, require('util').inspect(desc, false, 99, true));
|
|
1072
|
+
|
|
1073
|
+
const timeline: Timeline = {
|
|
1074
|
+
start: frac(timeScope.Strt),
|
|
1075
|
+
duration: frac(timeScope.duration),
|
|
1076
|
+
inTime: frac(timeScope.inTime),
|
|
1077
|
+
outTime: frac(timeScope.outTime),
|
|
1078
|
+
autoScope: desc.autoScope,
|
|
1079
|
+
audioLevel: desc.audioLevel,
|
|
1080
|
+
};
|
|
836
1081
|
|
|
837
|
-
|
|
838
|
-
|
|
839
|
-
|
|
1082
|
+
if (desc.trackList) {
|
|
1083
|
+
timeline.tracks = parseTrackList(desc.trackList, reader);
|
|
1084
|
+
}
|
|
840
1085
|
|
|
841
|
-
|
|
842
|
-
|
|
843
|
-
|
|
844
|
-
|
|
845
|
-
|
|
1086
|
+
target.timeline = timeline;
|
|
1087
|
+
// console.log('tmln:result', target.name, target.id, require('util').inspect(timeline, false, 99, true));
|
|
1088
|
+
} else if (key === 'cmls') {
|
|
1089
|
+
const desc = readVersionAndDescriptor(reader) as CmlsDescriptor;
|
|
1090
|
+
// console.log('cmls', require('util').inspect(desc, false, 99, true));
|
|
846
1091
|
|
|
847
|
-
|
|
848
|
-
|
|
849
|
-
|
|
1092
|
+
target.comps = {
|
|
1093
|
+
settings: [],
|
|
1094
|
+
};
|
|
850
1095
|
|
|
851
|
-
|
|
1096
|
+
if (desc.origFXRefPoint) target.comps.originalEffectsReferencePoint = { x: desc.origFXRefPoint.Hrzn, y: desc.origFXRefPoint.Vrtc };
|
|
852
1097
|
|
|
853
|
-
|
|
854
|
-
|
|
855
|
-
|
|
856
|
-
|
|
857
|
-
|
|
858
|
-
|
|
859
|
-
|
|
860
|
-
|
|
861
|
-
|
|
862
|
-
generatorSettings
|
|
863
|
-
|
|
864
|
-
|
|
865
|
-
};
|
|
866
|
-
layerTime: number;
|
|
867
|
-
};
|
|
1098
|
+
for (const item of desc.layerSettings) {
|
|
1099
|
+
target.comps.settings.push({ compList: item.compList });
|
|
1100
|
+
const t = target.comps.settings[target.comps.settings.length - 1];
|
|
1101
|
+
if ('enab' in item) t.enabled = item.enab;
|
|
1102
|
+
if (item.Ofst) t.offset = { x: item.Ofst.Hrzn, y: item.Ofst.Vrtc };
|
|
1103
|
+
if (item.FXRefPoint) t.effectsReferencePoint = { x: item.FXRefPoint.Hrzn, y: item.FXRefPoint.Vrtc };
|
|
1104
|
+
}
|
|
1105
|
+
} else if (key === 'extn') {
|
|
1106
|
+
const desc = readVersionAndDescriptor(reader) as GeneratorSettingsDescriptor;
|
|
1107
|
+
if (desc.generatorSettings) target.generatorSettings = parseGeneratorSettings(desc.generatorSettings);
|
|
1108
|
+
} else {
|
|
1109
|
+
reader.logMissingFeatures && reader.log('Unhandled "shmd" section key', key);
|
|
868
1110
|
}
|
|
869
1111
|
|
|
870
|
-
|
|
871
|
-
|
|
872
|
-
|
|
873
|
-
|
|
874
|
-
|
|
875
|
-
|
|
876
|
-
|
|
877
|
-
|
|
878
|
-
skipBytes(reader, left());
|
|
879
|
-
});
|
|
1112
|
+
skipBytes(reader, left());
|
|
1113
|
+
});
|
|
1114
|
+
} catch (e) {
|
|
1115
|
+
// failure to read one item shouldn't drop all other items
|
|
1116
|
+
if (reader.throwForMissingFeatures) throw e;
|
|
1117
|
+
reader.logMissingFeatures && reader.log(`Failed to read "shmd" section key ${key}: ${(e as Error).message}`);
|
|
1118
|
+
reader.offset = end;
|
|
1119
|
+
}
|
|
880
1120
|
}
|
|
881
1121
|
|
|
882
1122
|
skipBytes(reader, left());
|
|
883
1123
|
},
|
|
884
1124
|
(writer, target, _, options) => {
|
|
885
1125
|
const { animationFrames, animationFrameFlags, timestamp, timeline, comps } = target;
|
|
1126
|
+
const generatorSettings = isDocument(target) ? undefined : target.generatorSettings;
|
|
886
1127
|
|
|
887
1128
|
let count = 0;
|
|
888
1129
|
if (animationFrames) count++;
|
|
@@ -890,6 +1131,7 @@ addHandler(
|
|
|
890
1131
|
if (timeline) count++;
|
|
891
1132
|
if (timestamp !== undefined) count++;
|
|
892
1133
|
if (comps) count++;
|
|
1134
|
+
if (generatorSettings) count++;
|
|
893
1135
|
writeUint32(writer, count);
|
|
894
1136
|
|
|
895
1137
|
if (animationFrames) {
|
|
@@ -910,8 +1152,8 @@ addHandler(
|
|
|
910
1152
|
frame.FrLs = f.frames;
|
|
911
1153
|
if (f.offset) frame.Ofst = xyToHorzVrtc(f.offset);
|
|
912
1154
|
if (f.referencePoint) frame.FXRf = xyToHorzVrtc(f.referencePoint);
|
|
913
|
-
if (f.effects) frame.Lefx = serializeEffects(f.effects,
|
|
914
|
-
if (f.opacity !== undefined) frame.blendOptions = { Opct:
|
|
1155
|
+
if (f.effects) frame.Lefx = serializeEffects(f.effects, writer, hasMultiEffects(f.effects));
|
|
1156
|
+
if (f.opacity !== undefined) frame.blendOptions = { Opct: unitsPercentF(f.opacity) };
|
|
915
1157
|
desc.LaSt.push(frame);
|
|
916
1158
|
}
|
|
917
1159
|
|
|
@@ -954,7 +1196,7 @@ addHandler(
|
|
|
954
1196
|
} as any;
|
|
955
1197
|
|
|
956
1198
|
if (timeline.tracks) {
|
|
957
|
-
desc.trackList = serializeTrackList(timeline.tracks);
|
|
1199
|
+
desc.trackList = serializeTrackList(timeline.tracks, writer);
|
|
958
1200
|
}
|
|
959
1201
|
|
|
960
1202
|
desc.LyrI = getLayerId(target, options);
|
|
@@ -1005,6 +1247,16 @@ addHandler(
|
|
|
1005
1247
|
writeVersionAndDescriptor(writer, '', 'null', desc);
|
|
1006
1248
|
}, true);
|
|
1007
1249
|
}
|
|
1250
|
+
|
|
1251
|
+
if (generatorSettings) {
|
|
1252
|
+
writeSignature(writer, '8BIM');
|
|
1253
|
+
writeSignature(writer, 'extn');
|
|
1254
|
+
writeUint8(writer, 0); // copy (always false)
|
|
1255
|
+
writeZeros(writer, 3);
|
|
1256
|
+
writeSection(writer, 2, () => {
|
|
1257
|
+
writeVersionAndDescriptor(writer, '', 'null', serializeGeneratorSettings(generatorSettings));
|
|
1258
|
+
}, true);
|
|
1259
|
+
}
|
|
1008
1260
|
},
|
|
1009
1261
|
);
|
|
1010
1262
|
|
|
@@ -1096,7 +1348,7 @@ addHandler(
|
|
|
1096
1348
|
lineAlignment: strokeStyleLineAlignment.decode(desc.strokeStyleLineAlignment),
|
|
1097
1349
|
scaleLock: desc.strokeStyleScaleLock,
|
|
1098
1350
|
strokeAdjust: desc.strokeStyleStrokeAdjust,
|
|
1099
|
-
lineDashSet: desc.strokeStyleLineDashSet.map(parseUnits),
|
|
1351
|
+
lineDashSet: (desc.strokeStyleLineDashSet || []).map(parseUnits),
|
|
1100
1352
|
blendMode: BlnM.decode(desc.strokeStyleBlendMode),
|
|
1101
1353
|
opacity: parsePercent(desc.strokeStyleOpacity),
|
|
1102
1354
|
content: parseVectorContent(desc.strokeStyleContent),
|
|
@@ -1135,8 +1387,8 @@ interface ArtbDescriptor {
|
|
|
1135
1387
|
artboardRect: { 'Top ': number; Left: number; Btom: number; Rght: number; };
|
|
1136
1388
|
guideIndeces: any[];
|
|
1137
1389
|
artboardPresetName: string;
|
|
1138
|
-
'Clr '
|
|
1139
|
-
artboardBackgroundType
|
|
1390
|
+
'Clr '?: DescriptorColor;
|
|
1391
|
+
artboardBackgroundType?: number;
|
|
1140
1392
|
}
|
|
1141
1393
|
|
|
1142
1394
|
addHandler(
|
|
@@ -1149,10 +1401,11 @@ addHandler(
|
|
|
1149
1401
|
rect: { top: rect['Top '], left: rect.Left, bottom: rect.Btom, right: rect.Rght },
|
|
1150
1402
|
guideIndices: desc.guideIndeces,
|
|
1151
1403
|
presetName: desc.artboardPresetName,
|
|
1152
|
-
color: parseColor(desc['Clr ']),
|
|
1153
|
-
backgroundType: desc.artboardBackgroundType,
|
|
1154
1404
|
};
|
|
1155
1405
|
|
|
1406
|
+
if (desc['Clr ']) target.artboard.color = parseColor(desc['Clr ']);
|
|
1407
|
+
if (desc.artboardBackgroundType !== undefined) target.artboard.backgroundType = desc.artboardBackgroundType;
|
|
1408
|
+
|
|
1156
1409
|
skipBytes(reader, left());
|
|
1157
1410
|
},
|
|
1158
1411
|
(writer, target) => {
|
|
@@ -1299,13 +1552,13 @@ function encodeWarp(warp: Warp): WarpDescriptor {
|
|
|
1299
1552
|
|
|
1300
1553
|
addHandler(
|
|
1301
1554
|
'PlLd',
|
|
1302
|
-
|
|
1555
|
+
target => !!target.placedLayer && !target.placedLayer.external, // linked smart objects are stored in SoLE
|
|
1303
1556
|
(reader, target, left) => {
|
|
1304
1557
|
if (readSignature(reader) !== 'plcL') throw new Error(`Invalid PlLd signature`);
|
|
1305
1558
|
if (readInt32(reader) !== 3) throw new Error(`Invalid PlLd version`);
|
|
1306
1559
|
const id = readPascalString(reader, 1);
|
|
1307
1560
|
const pageNumber = readInt32(reader);
|
|
1308
|
-
const totalPages = readInt32(reader); //
|
|
1561
|
+
const totalPages = readInt32(reader); // for multi-page documents (PDF)
|
|
1309
1562
|
readInt32(reader); // anitAliasPolicy 16
|
|
1310
1563
|
const placedLayerType = readInt32(reader); // 0 = unknown, 1 = vector, 2 = raster, 3 = image stack
|
|
1311
1564
|
if (!placedLayerTypes[placedLayerType]) throw new Error('Invalid PlLd type');
|
|
@@ -1857,7 +2110,7 @@ type SoLdDescriptorFilterItem = {
|
|
|
1857
2110
|
'Fl ': string;
|
|
1858
2111
|
};
|
|
1859
2112
|
} | {
|
|
1860
|
-
filterID: 991 | 943; //
|
|
2113
|
+
filterID: 991 | 943; // 943 in older Photoshop versions (2021), 991 in newer ones, settings are the same
|
|
1861
2114
|
Fltr: {
|
|
1862
2115
|
_name: 'Rigid Transform';
|
|
1863
2116
|
_classID: 'rigidTransform';
|
|
@@ -2036,7 +2289,7 @@ type SoLdDescriptorFilterItem = {
|
|
|
2036
2289
|
Fltr: {
|
|
2037
2290
|
_name: 'Curves';
|
|
2038
2291
|
_classID: 'Crvs';
|
|
2039
|
-
presetKind
|
|
2292
|
+
presetKind?: string; // 'presetKindType.presetKindCustom';
|
|
2040
2293
|
Adjs?: {
|
|
2041
2294
|
_name: '';
|
|
2042
2295
|
_classID: 'CrvA';
|
|
@@ -2045,6 +2298,14 @@ type SoLdDescriptorFilterItem = {
|
|
|
2045
2298
|
Mpng?: number[];
|
|
2046
2299
|
}[];
|
|
2047
2300
|
};
|
|
2301
|
+
} | {
|
|
2302
|
+
filterID: 1282829427;
|
|
2303
|
+
Fltr: {
|
|
2304
|
+
_name: 'Levels';
|
|
2305
|
+
_classID: 'Lvls';
|
|
2306
|
+
presetKind?: string; // 'presetKindType.presetKindCustom';
|
|
2307
|
+
Adjs?: FilterLevelsAdjustment[];
|
|
2308
|
+
};
|
|
2048
2309
|
} | {
|
|
2049
2310
|
filterID: 1231976050;
|
|
2050
2311
|
} | {
|
|
@@ -2066,6 +2327,18 @@ interface FilterCurvesCurvePoint {
|
|
|
2066
2327
|
Cnty?: boolean;
|
|
2067
2328
|
}
|
|
2068
2329
|
|
|
2330
|
+
interface FilterLevelsAdjustment {
|
|
2331
|
+
_name: '';
|
|
2332
|
+
_classID: 'LvlA';
|
|
2333
|
+
Chnl: string[]; // 'Chnl.Cmps' | 'Chnl.Rd ' | 'Chnl.Grn ' | 'Chnl.Bl '
|
|
2334
|
+
Inpt?: number[]; // shadow, highlight
|
|
2335
|
+
Otpt?: number[]; // shadow, highlight
|
|
2336
|
+
'Gmm '?: number;
|
|
2337
|
+
}
|
|
2338
|
+
|
|
2339
|
+
const levelsFilterKeys = ['_name', '_classID', 'presetKind', 'Adjs'];
|
|
2340
|
+
const levelsFilterAdjustmentKeys = ['_name', '_classID', 'Chnl', 'Inpt', 'Otpt', 'Gmm '];
|
|
2341
|
+
|
|
2069
2342
|
interface SoLdDescriptorFilter {
|
|
2070
2343
|
_name: '',
|
|
2071
2344
|
_classID: 'filterFXStyle',
|
|
@@ -2129,7 +2402,92 @@ function pointToHrznVrtc(point: { x: UnitsValue; y: UnitsValue; }): HrznVrtcDesc
|
|
|
2129
2402
|
};
|
|
2130
2403
|
}
|
|
2131
2404
|
|
|
2132
|
-
|
|
2405
|
+
// Camera Raw Filter setting paths (in CameraRawFilterSettings) and their descriptor keys
|
|
2406
|
+
const cameraRawColorKeys: [keyof CameraRawColors, string][] = [
|
|
2407
|
+
['red', 'R'], ['orange', 'O'], ['yellow', 'Y'], ['green', 'G'], ['aqua', 'A'], ['blue', 'B'], ['purple', 'P'], ['magenta', 'M'],
|
|
2408
|
+
];
|
|
2409
|
+
|
|
2410
|
+
const cameraRawSettings: [string, string][] = [
|
|
2411
|
+
['version', 'CrVe'], ['processVersion', 'PrVN'], ['processVersionCode', 'PrVe'], ['whiteBalance', 'WBal'],
|
|
2412
|
+
['temperature', 'Temp'], ['tint', 'Tint'], ['exposure', 'Ex12'], ['contrast', 'Cr12'], ['highlights', 'Hi12'],
|
|
2413
|
+
['shadows', 'Sh12'], ['whites', 'Wh12'], ['blacks', 'Bk12'], ['texture', 'CrTx'], ['clarity', 'Cl12'],
|
|
2414
|
+
['dehaze', 'Dhze'], ['vibrance', 'Vibr'], ['saturation', 'Strt'],
|
|
2415
|
+
['parametricCurve.shadows', 'PC_S'], ['parametricCurve.darks', 'PC_D'], ['parametricCurve.lights', 'PC_L'],
|
|
2416
|
+
['parametricCurve.highlights', 'PC_H'], ['parametricCurve.shadowSplit', 'PC_1'], ['parametricCurve.midtoneSplit', 'PC_2'],
|
|
2417
|
+
['parametricCurve.highlightSplit', 'PC_3'],
|
|
2418
|
+
['pointCurve.composite', 'Crv '], ['pointCurve.red', 'CrvR'], ['pointCurve.green', 'CrvG'], ['pointCurve.blue', 'CrvB'],
|
|
2419
|
+
...cameraRawColorKeys.map(([name, key]) => [`hsl.hue.${name}`, `HA_${key}`] as [string, string]),
|
|
2420
|
+
...cameraRawColorKeys.map(([name, key]) => [`hsl.saturation.${name}`, `SA_${key}`] as [string, string]),
|
|
2421
|
+
...cameraRawColorKeys.map(([name, key]) => [`hsl.luminance.${name}`, `LA_${key}`] as [string, string]),
|
|
2422
|
+
['colorGrading.shadows.hue', 'STSH'], ['colorGrading.shadows.saturation', 'STSS'], ['colorGrading.shadows.luminance', 'CgSL'],
|
|
2423
|
+
['colorGrading.midtones.hue', 'CgMH'], ['colorGrading.midtones.saturation', 'CgMS'], ['colorGrading.midtones.luminance', 'CgML'],
|
|
2424
|
+
['colorGrading.highlights.hue', 'STHH'], ['colorGrading.highlights.saturation', 'STHS'], ['colorGrading.highlights.luminance', 'CgHL'],
|
|
2425
|
+
['colorGrading.global.hue', 'CgGH'], ['colorGrading.global.saturation', 'CgGS'], ['colorGrading.global.luminance', 'CgGL'],
|
|
2426
|
+
['colorGrading.blending', 'CgBl'], ['colorGrading.balance', 'STB '],
|
|
2427
|
+
['sharpening.amount', 'Shrp'], ['sharpening.radius', 'ShpR'], ['sharpening.detail', 'ShpD'], ['sharpening.masking', 'ShpM'],
|
|
2428
|
+
['noiseReduction.luminance', 'LNR '], ['noiseReduction.luminanceDetail', 'LNRD'], ['noiseReduction.luminanceContrast', 'LNRC'],
|
|
2429
|
+
['noiseReduction.color', 'CNR '], ['noiseReduction.colorDetail', 'CNRD'], ['noiseReduction.colorSmoothness', 'CNRS'],
|
|
2430
|
+
['grain.amount', 'GRNA'], ['grain.size', 'GRNS'], ['grain.roughness', 'GRNF'],
|
|
2431
|
+
['postCropVignette.amount', 'PCVA'], ['postCropVignette.midpoint', 'PCVM'], ['postCropVignette.feather', 'PCVF'],
|
|
2432
|
+
['postCropVignette.roundness', 'PCVR'],
|
|
2433
|
+
];
|
|
2434
|
+
|
|
2435
|
+
const cameraRawCurveKeys = ['Crv ', 'CrvR', 'CrvG', 'CrvB'];
|
|
2436
|
+
const cameraRawFilterID = 2783;
|
|
2437
|
+
|
|
2438
|
+
function parseCameraRawSettings(desc: any, reader: PsdReader) {
|
|
2439
|
+
const settings: any = {};
|
|
2440
|
+
|
|
2441
|
+
for (const [path, key] of cameraRawSettings) {
|
|
2442
|
+
let value = desc[key];
|
|
2443
|
+
if (value === undefined) continue;
|
|
2444
|
+
|
|
2445
|
+
if (key === 'WBal') {
|
|
2446
|
+
value = (value as string).split('.')[1];
|
|
2447
|
+
} else if (cameraRawCurveKeys.includes(key)) {
|
|
2448
|
+
const values = value as number[];
|
|
2449
|
+
value = [];
|
|
2450
|
+
for (let i = 0; i + 1 < values.length; i += 2) value.push({ input: values[i], output: values[i + 1] });
|
|
2451
|
+
}
|
|
2452
|
+
|
|
2453
|
+
const parts = path.split('.');
|
|
2454
|
+
let target = settings;
|
|
2455
|
+
for (let i = 0; i < parts.length - 1; i++) target = target[parts[i]] = target[parts[i]] ?? {};
|
|
2456
|
+
target[parts[parts.length - 1]] = value;
|
|
2457
|
+
}
|
|
2458
|
+
|
|
2459
|
+
for (const key of Object.keys(desc)) {
|
|
2460
|
+
if (key !== '_name' && key !== '_classID' && !cameraRawSettings.some(s => s[1] === key)) {
|
|
2461
|
+
reader.logMissingFeatures && reader.log(`Unhandled Camera Raw Filter setting: ${key}`);
|
|
2462
|
+
}
|
|
2463
|
+
}
|
|
2464
|
+
|
|
2465
|
+
return settings as CameraRawFilterSettings;
|
|
2466
|
+
}
|
|
2467
|
+
|
|
2468
|
+
function serializeCameraRawSettings(settings: CameraRawFilterSettings) {
|
|
2469
|
+
const desc: any = { _name: '', _classID: cameraRawFilterClassID };
|
|
2470
|
+
|
|
2471
|
+
for (const [path, key] of cameraRawSettings) {
|
|
2472
|
+
let value: any = settings;
|
|
2473
|
+
for (const part of path.split('.')) value = value?.[part];
|
|
2474
|
+
if (value === undefined) continue;
|
|
2475
|
+
|
|
2476
|
+
if (key === 'WBal') {
|
|
2477
|
+
value = `WBal.${value}`;
|
|
2478
|
+
} else if (cameraRawCurveKeys.includes(key)) {
|
|
2479
|
+
value = (value as CameraRawCurvePoint[]).flatMap(p => [Math.round(p.input), Math.round(p.output)]);
|
|
2480
|
+
} else if (typeof value === 'number' && key !== 'Ex12' && key !== 'ShpR') {
|
|
2481
|
+
value = Math.round(value);
|
|
2482
|
+
}
|
|
2483
|
+
|
|
2484
|
+
desc[key] = value;
|
|
2485
|
+
}
|
|
2486
|
+
|
|
2487
|
+
return desc;
|
|
2488
|
+
}
|
|
2489
|
+
|
|
2490
|
+
function parseFilterFXItem(f: SoLdDescriptorFilterItem, reader: PsdReader): Filter | undefined {
|
|
2133
2491
|
const base: Omit<Filter, 'type' | 'filter'> = {
|
|
2134
2492
|
name: f['Nm '],
|
|
2135
2493
|
opacity: parsePercent(f.blendOptions.Opct),
|
|
@@ -2140,6 +2498,10 @@ function parseFilterFXItem(f: SoLdDescriptorFilterItem, options: ReadOptions): F
|
|
|
2140
2498
|
backgroundColor: parseColor(f.BckC),
|
|
2141
2499
|
};
|
|
2142
2500
|
|
|
2501
|
+
if ('Fltr' in f && (f.Fltr._classID as string) === cameraRawFilterClassID) {
|
|
2502
|
+
return { ...base, type: 'camera raw', filter: parseCameraRawSettings(f.Fltr, reader) };
|
|
2503
|
+
}
|
|
2504
|
+
|
|
2143
2505
|
if ('Fltr' in f) {
|
|
2144
2506
|
switch (f.Fltr._classID) {
|
|
2145
2507
|
case 'boxblur': return {
|
|
@@ -2209,7 +2571,7 @@ function parseFilterFXItem(f: SoLdDescriptorFilterItem, options: ReadOptions): F
|
|
|
2209
2571
|
undefinedAreas: UndA.decode(f.Fltr.UndA),
|
|
2210
2572
|
displacementFile: {
|
|
2211
2573
|
signature: f.Fltr.DspF.sig,
|
|
2212
|
-
path: f.Fltr.DspF.path,
|
|
2574
|
+
path: f.Fltr.DspF.path,
|
|
2213
2575
|
},
|
|
2214
2576
|
},
|
|
2215
2577
|
};
|
|
@@ -2544,8 +2906,7 @@ function parseFilterFXItem(f: SoLdDescriptorFilterItem, options: ReadOptions): F
|
|
|
2544
2906
|
],
|
|
2545
2907
|
puppetShapeList: (f.Fltr.puppetShapeList || []).map(p => ({
|
|
2546
2908
|
rigidType: p.rigidType,
|
|
2547
|
-
//
|
|
2548
|
-
// TODO: VrsN
|
|
2909
|
+
// VrsM / VrsN - version (always 1.0)
|
|
2549
2910
|
originalVertexArray: uint8ToPoints(p.originalVertexArray),
|
|
2550
2911
|
deformedVertexArray: uint8ToPoints(p.deformedVertexArray),
|
|
2551
2912
|
indexArray: Array.from(uint8ToUint32(p.indexArray)),
|
|
@@ -2664,7 +3025,8 @@ function parseFilterFXItem(f: SoLdDescriptorFilterItem, options: ReadOptions): F
|
|
|
2664
3025
|
...base,
|
|
2665
3026
|
type: 'curves',
|
|
2666
3027
|
filter: {
|
|
2667
|
-
|
|
3028
|
+
// Photoshop doesn't write preset kind for filters with explicit values
|
|
3029
|
+
...(f.Fltr.presetKind !== undefined ? { presetKind: presetKindType.decode(f.Fltr.presetKind) } : {}),
|
|
2668
3030
|
...(f.Fltr.Adjs ? {
|
|
2669
3031
|
adjustments: f.Fltr.Adjs.map(a => {
|
|
2670
3032
|
const channels = a.Chnl.map(Chnl.decode);
|
|
@@ -2687,6 +3049,40 @@ function parseFilterFXItem(f: SoLdDescriptorFilterItem, options: ReadOptions): F
|
|
|
2687
3049
|
},
|
|
2688
3050
|
};
|
|
2689
3051
|
};
|
|
3052
|
+
case 'Lvls': {
|
|
3053
|
+
const adjustments = f.Fltr.Adjs;
|
|
3054
|
+
const isKnown = (obj: object, keys: string[]) => Object.keys(obj).every(k => keys.indexOf(k) !== -1);
|
|
3055
|
+
|
|
3056
|
+
// skip filter with settings we don't know about
|
|
3057
|
+
if (!isKnown(f.Fltr, levelsFilterKeys) || (adjustments && !adjustments.every(a => isKnown(a, levelsFilterAdjustmentKeys)))) {
|
|
3058
|
+
if (reader.throwForMissingFeatures) throw new Error('Unknown levels filter settings');
|
|
3059
|
+
reader.logMissingFeatures && reader.log(`Skipped levels smart filter with unknown settings`);
|
|
3060
|
+
return undefined;
|
|
3061
|
+
}
|
|
3062
|
+
|
|
3063
|
+
return {
|
|
3064
|
+
...base,
|
|
3065
|
+
type: 'levels',
|
|
3066
|
+
filter: {
|
|
3067
|
+
...(f.Fltr.presetKind !== undefined ? { presetKind: presetKindType.decode(f.Fltr.presetKind) } : {}),
|
|
3068
|
+
...(adjustments ? {
|
|
3069
|
+
adjustments: adjustments.map(a => {
|
|
3070
|
+
const result: LevelsFilterAdjustment = { channels: a.Chnl.map(Chnl.decode) };
|
|
3071
|
+
if (a.Inpt) {
|
|
3072
|
+
result.shadowInput = a.Inpt[0];
|
|
3073
|
+
result.highlightInput = a.Inpt[1];
|
|
3074
|
+
}
|
|
3075
|
+
if (a.Otpt) {
|
|
3076
|
+
result.shadowOutput = a.Otpt[0];
|
|
3077
|
+
result.highlightOutput = a.Otpt[1];
|
|
3078
|
+
}
|
|
3079
|
+
if (a['Gmm '] !== undefined) result.midtoneInput = a['Gmm '];
|
|
3080
|
+
return result;
|
|
3081
|
+
}),
|
|
3082
|
+
} : {}),
|
|
3083
|
+
},
|
|
3084
|
+
};
|
|
3085
|
+
};
|
|
2690
3086
|
case 'BrgC': {
|
|
2691
3087
|
return {
|
|
2692
3088
|
...base,
|
|
@@ -2699,10 +3095,11 @@ function parseFilterFXItem(f: SoLdDescriptorFilterItem, options: ReadOptions): F
|
|
|
2699
3095
|
};
|
|
2700
3096
|
};
|
|
2701
3097
|
default:
|
|
2702
|
-
if (
|
|
3098
|
+
if (reader.throwForMissingFeatures) {
|
|
2703
3099
|
// console.log('FILTER', require('util').inspect(f, false, 99, true));
|
|
2704
3100
|
throw new Error(`Unknown filter classId: ${(f as any).Fltr._classID}`);
|
|
2705
3101
|
}
|
|
3102
|
+
reader.logMissingFeatures && reader.log(`Unhandled smart filter: ${(f as any).Fltr._classID} (${f['Nm ']})`);
|
|
2706
3103
|
return undefined;
|
|
2707
3104
|
}
|
|
2708
3105
|
} else {
|
|
@@ -2721,22 +3118,24 @@ function parseFilterFXItem(f: SoLdDescriptorFilterItem, options: ReadOptions): F
|
|
|
2721
3118
|
case 1314149187: return { ...base, type: 'ntsc colors' };
|
|
2722
3119
|
case 1231976050: return { ...base, type: 'invert' }
|
|
2723
3120
|
default:
|
|
2724
|
-
if (
|
|
3121
|
+
if (reader.throwForMissingFeatures) {
|
|
2725
3122
|
// console.log('FILTER', require('util').inspect(f, false, 99, true));
|
|
2726
3123
|
throw new Error(`Unknown filterID: ${(f as any).filterID}`);
|
|
2727
3124
|
}
|
|
3125
|
+
reader.logMissingFeatures && reader.log(`Unhandled smart filter: ${(f as any).filterID} (${f['Nm ']})`);
|
|
3126
|
+
return undefined;
|
|
2728
3127
|
}
|
|
2729
3128
|
}
|
|
2730
3129
|
}
|
|
2731
3130
|
|
|
2732
|
-
function parseFilterFX(desc: SoLdDescriptorFilter,
|
|
3131
|
+
function parseFilterFX(desc: SoLdDescriptorFilter, reader: PsdReader): PlacedLayerFilter {
|
|
2733
3132
|
return {
|
|
2734
3133
|
enabled: desc.enab,
|
|
2735
3134
|
validAtPosition: desc.validAtPosition,
|
|
2736
3135
|
maskEnabled: desc.filterMaskEnable,
|
|
2737
3136
|
maskLinked: desc.filterMaskLinked,
|
|
2738
3137
|
maskExtendWithWhite: desc.filterMaskExtendWithWhite,
|
|
2739
|
-
list: desc.filterFXList.map(x => parseFilterFXItem(x,
|
|
3138
|
+
list: desc.filterFXList.map(x => parseFilterFXItem(x, reader)).filter((x): x is Filter => !!x),
|
|
2740
3139
|
};
|
|
2741
3140
|
}
|
|
2742
3141
|
|
|
@@ -3275,14 +3674,14 @@ function serializeFilterFXItem(f: Filter): SoLdDescriptorFilterItem {
|
|
|
3275
3674
|
Fltr: {
|
|
3276
3675
|
_name: 'Rigid Transform',
|
|
3277
3676
|
_classID: 'rigidTransform',
|
|
3278
|
-
'null': ['Ordn.Trgt'], //
|
|
3677
|
+
'null': ['Ordn.Trgt'], // target layer reference
|
|
3279
3678
|
rigidType: f.filter.rigidType,
|
|
3280
3679
|
puppetShapeList: (f.filter.puppetShapeList || []).map(p => ({
|
|
3281
3680
|
_name: '',
|
|
3282
3681
|
_classID: 'puppetShape',
|
|
3283
3682
|
rigidType: p.rigidType,
|
|
3284
|
-
VrsM: 1, //
|
|
3285
|
-
VrsN: 0, //
|
|
3683
|
+
VrsM: 1, // version major
|
|
3684
|
+
VrsN: 0, // version minor
|
|
3286
3685
|
originalVertexArray: toUint8(new Float32Array(pointsToArray(p.originalVertexArray))),
|
|
3287
3686
|
deformedVertexArray: toUint8(new Float32Array(pointsToArray(p.deformedVertexArray))),
|
|
3288
3687
|
indexArray: toUint8(new Uint32Array(p.indexArray)),
|
|
@@ -3397,7 +3796,7 @@ function serializeFilterFXItem(f: Filter): SoLdDescriptorFilterItem {
|
|
|
3397
3796
|
Fltr: {
|
|
3398
3797
|
_name: 'Curves',
|
|
3399
3798
|
_classID: 'Crvs',
|
|
3400
|
-
presetKind: presetKindType.encode(f.filter.presetKind),
|
|
3799
|
+
...(f.filter.presetKind !== undefined ? { presetKind: presetKindType.encode(f.filter.presetKind) } : {}),
|
|
3401
3800
|
...(f.filter.adjustments ? {
|
|
3402
3801
|
Adjs: f.filter.adjustments.map(a => 'curve' in a ? {
|
|
3403
3802
|
_name: '',
|
|
@@ -3420,6 +3819,33 @@ function serializeFilterFXItem(f: Filter): SoLdDescriptorFilterItem {
|
|
|
3420
3819
|
},
|
|
3421
3820
|
filterID: 1131574899,
|
|
3422
3821
|
};
|
|
3822
|
+
case 'levels': return {
|
|
3823
|
+
...base,
|
|
3824
|
+
Fltr: {
|
|
3825
|
+
_name: 'Levels',
|
|
3826
|
+
_classID: 'Lvls',
|
|
3827
|
+
...(f.filter.presetKind !== undefined ? { presetKind: presetKindType.encode(f.filter.presetKind) } : {}),
|
|
3828
|
+
...(f.filter.adjustments ? {
|
|
3829
|
+
Adjs: f.filter.adjustments.map(a => {
|
|
3830
|
+
const adjustment: FilterLevelsAdjustment = { _name: '', _classID: 'LvlA', Chnl: a.channels.map(Chnl.encode) };
|
|
3831
|
+
if (a.shadowInput !== undefined || a.highlightInput !== undefined) {
|
|
3832
|
+
adjustment.Inpt = [Math.round(a.shadowInput ?? 0), Math.round(a.highlightInput ?? 255)];
|
|
3833
|
+
}
|
|
3834
|
+
if (a.shadowOutput !== undefined || a.highlightOutput !== undefined) {
|
|
3835
|
+
adjustment.Otpt = [Math.round(a.shadowOutput ?? 0), Math.round(a.highlightOutput ?? 255)];
|
|
3836
|
+
}
|
|
3837
|
+
if (a.midtoneInput !== undefined) adjustment['Gmm '] = a.midtoneInput;
|
|
3838
|
+
return adjustment;
|
|
3839
|
+
}),
|
|
3840
|
+
} : {}),
|
|
3841
|
+
},
|
|
3842
|
+
filterID: 1282829427,
|
|
3843
|
+
};
|
|
3844
|
+
case 'camera raw': return {
|
|
3845
|
+
...base,
|
|
3846
|
+
Fltr: serializeCameraRawSettings(f.filter),
|
|
3847
|
+
filterID: cameraRawFilterID,
|
|
3848
|
+
} as any;
|
|
3423
3849
|
case 'brightness/contrast': return {
|
|
3424
3850
|
...base,
|
|
3425
3851
|
Fltr: {
|
|
@@ -3463,8 +3889,8 @@ interface SoLdDescriptor {
|
|
|
3463
3889
|
'Sz ': { _name: '', _classID: 'Pnt ', Wdth: number; Hght: number; };
|
|
3464
3890
|
Rslt: DescriptorUnitsValue;
|
|
3465
3891
|
filterFX?: SoLdDescriptorFilter;
|
|
3466
|
-
comp?: number;
|
|
3467
|
-
compInfo?: { compID: number; originalCompID: number; };
|
|
3892
|
+
comp?: number;
|
|
3893
|
+
compInfo?: { compID: number; originalCompID: number; };
|
|
3468
3894
|
Impr?: {}; // ???
|
|
3469
3895
|
ClMg?: { // TODO: support this ?
|
|
3470
3896
|
_name: '';
|
|
@@ -3512,7 +3938,7 @@ function getWarpFromPlacedLayer(placed: PlacedLayer): Warp {
|
|
|
3512
3938
|
|
|
3513
3939
|
addHandler(
|
|
3514
3940
|
'SoLd',
|
|
3515
|
-
|
|
3941
|
+
target => !!target.placedLayer && !target.placedLayer.external, // linked smart objects are stored in SoLE
|
|
3516
3942
|
(reader, target, left) => {
|
|
3517
3943
|
if (readSignature(reader) !== 'soLD') throw new Error(`Invalid SoLd type`);
|
|
3518
3944
|
const version = readInt32(reader);
|
|
@@ -3573,6 +3999,12 @@ addHandler(
|
|
|
3573
3999
|
const placedType = placedLayerTypes.indexOf(placed.type);
|
|
3574
4000
|
if (placedType === -1) throw new Error('Invalid placedLayer type');
|
|
3575
4001
|
|
|
4002
|
+
const warp = getWarpFromPlacedLayer(placed);
|
|
4003
|
+
// size of linked image matches warp bounds if it's not provided
|
|
4004
|
+
const bounds = warp.bounds;
|
|
4005
|
+
const width = placed.width || (bounds ? bounds.right.value - bounds.left.value : 0);
|
|
4006
|
+
const height = placed.height || (bounds ? bounds.bottom.value - bounds.top.value : 0);
|
|
4007
|
+
|
|
3576
4008
|
const desc: SoLdDescriptor = {
|
|
3577
4009
|
Idnt: placed.id,
|
|
3578
4010
|
placed: placed.placed ?? placed.id,
|
|
@@ -3587,12 +4019,12 @@ addHandler(
|
|
|
3587
4019
|
Trnf: placed.transform,
|
|
3588
4020
|
nonAffineTransform: placed.nonAffineTransform ?? placed.transform,
|
|
3589
4021
|
// quiltWarp: {} as any,
|
|
3590
|
-
warp: encodeWarp(
|
|
4022
|
+
warp: encodeWarp(warp),
|
|
3591
4023
|
'Sz ': {
|
|
3592
4024
|
_name: '',
|
|
3593
4025
|
_classID: 'Pnt ',
|
|
3594
|
-
Wdth:
|
|
3595
|
-
Hght:
|
|
4026
|
+
Wdth: width,
|
|
4027
|
+
Hght: height,
|
|
3596
4028
|
},
|
|
3597
4029
|
Rslt: placed.resolution ? unitsValue(placed.resolution, 'resolution') : { units: 'Density', value: 72 },
|
|
3598
4030
|
};
|
|
@@ -3610,15 +4042,6 @@ addHandler(
|
|
|
3610
4042
|
};
|
|
3611
4043
|
}
|
|
3612
4044
|
|
|
3613
|
-
// TODO:
|
|
3614
|
-
// desc.comp = -1;
|
|
3615
|
-
// desc.compInfo = { _name: '', _classID: 'null', compID: -1, originalCompID: -1 } as any;
|
|
3616
|
-
// desc.ClMg = {
|
|
3617
|
-
// _name: '',
|
|
3618
|
-
// _classID: 'ClMg',
|
|
3619
|
-
// placedLayerOCIOConversion: 'placedLayerOCIOConversion.placedLayerOCIOConvertEmbedded'
|
|
3620
|
-
// } as any;
|
|
3621
|
-
|
|
3622
4045
|
// if (JSON.stringify(t) !== JSON.stringify(desc)) {
|
|
3623
4046
|
// console.log('read', require('util').inspect(t, false, 99, true));
|
|
3624
4047
|
// console.log('write', require('util').inspect(desc, false, 99, true));
|
|
@@ -3650,7 +4073,17 @@ addHandler(
|
|
|
3650
4073
|
},
|
|
3651
4074
|
);
|
|
3652
4075
|
|
|
3653
|
-
|
|
4076
|
+
const soLdHandler = infoHandlersMap['SoLd'];
|
|
4077
|
+
|
|
4078
|
+
addHandler(
|
|
4079
|
+
'SoLE', // linked smart object (Photoshop doesn't write PlLd for these)
|
|
4080
|
+
target => !!target.placedLayer?.external,
|
|
4081
|
+
(reader, target, left, psd, imageResources) => {
|
|
4082
|
+
soLdHandler.read(reader, target, left, psd, imageResources);
|
|
4083
|
+
target.placedLayer!.external = true;
|
|
4084
|
+
},
|
|
4085
|
+
soLdHandler.write,
|
|
4086
|
+
);
|
|
3654
4087
|
|
|
3655
4088
|
addHandler(
|
|
3656
4089
|
'fxrp',
|
|
@@ -3667,25 +4100,50 @@ addHandler(
|
|
|
3667
4100
|
},
|
|
3668
4101
|
);
|
|
3669
4102
|
|
|
3670
|
-
|
|
3671
|
-
|
|
3672
|
-
|
|
3673
|
-
|
|
3674
|
-
|
|
3675
|
-
|
|
3676
|
-
|
|
3677
|
-
|
|
3678
|
-
)
|
|
4103
|
+
function createGlobalLayerInfoHandler(key: string, bitsPerChannel: number | undefined) {
|
|
4104
|
+
addHandler(
|
|
4105
|
+
key,
|
|
4106
|
+
() => false,
|
|
4107
|
+
(reader, _target, left, psd, imageResources) => {
|
|
4108
|
+
// Photoshop writes empty (or 8bit compatibility) layer info section alongside Lr16/Lr32 in 16/32bit documents,
|
|
4109
|
+
// layers from global info with matching bit depth replace layers from that section, other ones are only used
|
|
4110
|
+
// if the section is empty
|
|
4111
|
+
if (!reader.layersInGlobalInfo && (psd.bitsPerChannel === bitsPerChannel || !psd.children?.length)) {
|
|
4112
|
+
const sectionLayers = psd.children;
|
|
4113
|
+
psd.children = [];
|
|
4114
|
+
|
|
4115
|
+
try {
|
|
4116
|
+
readLayerInfo(reader, psd, imageResources);
|
|
4117
|
+
} catch (e) {
|
|
4118
|
+
psd.children = sectionLayers; // keep layers from layer info section if global info is broken
|
|
4119
|
+
throw e;
|
|
4120
|
+
}
|
|
3679
4121
|
|
|
3680
|
-
|
|
3681
|
-
|
|
3682
|
-
|
|
3683
|
-
|
|
3684
|
-
|
|
3685
|
-
|
|
3686
|
-
|
|
3687
|
-
|
|
3688
|
-
|
|
4122
|
+
reader.layersInGlobalInfo = true;
|
|
4123
|
+
}
|
|
4124
|
+
|
|
4125
|
+
skipBytes(reader, left());
|
|
4126
|
+
},
|
|
4127
|
+
() => { },
|
|
4128
|
+
);
|
|
4129
|
+
}
|
|
4130
|
+
|
|
4131
|
+
createGlobalLayerInfoHandler('Lr16', 16);
|
|
4132
|
+
createGlobalLayerInfoHandler('Lr32', 32);
|
|
4133
|
+
createGlobalLayerInfoHandler('Layr', undefined);
|
|
4134
|
+
|
|
4135
|
+
// merged transparency markers, first extra channel of composite image is transparency
|
|
4136
|
+
for (const key of ['Mtrn', 'Mt16', 'Mt32']) {
|
|
4137
|
+
addHandler(
|
|
4138
|
+
key,
|
|
4139
|
+
() => false,
|
|
4140
|
+
(reader, _target, left) => {
|
|
4141
|
+
reader.globalAlpha = true;
|
|
4142
|
+
skipBytes(reader, left());
|
|
4143
|
+
},
|
|
4144
|
+
() => { },
|
|
4145
|
+
);
|
|
4146
|
+
}
|
|
3689
4147
|
|
|
3690
4148
|
addHandler(
|
|
3691
4149
|
'LMsk',
|
|
@@ -3708,35 +4166,24 @@ addHandler(
|
|
|
3708
4166
|
},
|
|
3709
4167
|
);
|
|
3710
4168
|
|
|
3711
|
-
|
|
3712
|
-
|
|
3713
|
-
|
|
3714
|
-
|
|
3715
|
-
(
|
|
3716
|
-
|
|
3717
|
-
(
|
|
3718
|
-
|
|
3719
|
-
|
|
3720
|
-
|
|
3721
|
-
}
|
|
3722
|
-
|
|
3723
|
-
|
|
3724
|
-
|
|
3725
|
-
|
|
3726
|
-
|
|
3727
|
-
|
|
3728
|
-
|
|
3729
|
-
target.patterns.push(pattern);
|
|
3730
|
-
}
|
|
3731
|
-
},
|
|
3732
|
-
(writer, target, _, _options) => {
|
|
3733
|
-
const patterns = target.patterns || [];
|
|
3734
|
-
for (const pattern of patterns) {
|
|
3735
|
-
writePattern(writer, pattern);
|
|
3736
|
-
}
|
|
3737
|
-
},
|
|
3738
|
-
);
|
|
3739
|
-
}
|
|
4169
|
+
addHandler(
|
|
4170
|
+
'Patt', // 'Pat2' and 'Pat3' (16 and 32 bit documents) are aliases, patterns are always written as 8 bit
|
|
4171
|
+
target => !!(target.patterns && target.patterns.length > 0),
|
|
4172
|
+
(reader, target, left) => {
|
|
4173
|
+
while (left() > 0) {
|
|
4174
|
+
const pattern = readPatternSafe(reader);
|
|
4175
|
+
if (!pattern) continue;
|
|
4176
|
+
if (target.patterns === undefined) target.patterns = [];
|
|
4177
|
+
target.patterns.push(pattern);
|
|
4178
|
+
}
|
|
4179
|
+
},
|
|
4180
|
+
(writer, target, _, _options) => {
|
|
4181
|
+
const patterns = target.patterns || [];
|
|
4182
|
+
for (const pattern of patterns) {
|
|
4183
|
+
writePattern(writer, pattern);
|
|
4184
|
+
}
|
|
4185
|
+
},
|
|
4186
|
+
);
|
|
3740
4187
|
|
|
3741
4188
|
addHandlerAlias('Pat2', 'Patt');
|
|
3742
4189
|
addHandlerAlias('Pat3', 'Patt');
|
|
@@ -3954,6 +4401,11 @@ interface LinkedFileDescriptor {
|
|
|
3954
4401
|
relPath: string;
|
|
3955
4402
|
}
|
|
3956
4403
|
|
|
4404
|
+
function readFileTypeSignature(reader: PsdReader) {
|
|
4405
|
+
const value = readSignature(reader);
|
|
4406
|
+
return /^[\0 ]*$/.test(value) ? '' : value.trim(); // empty value is stored as spaces or zeros
|
|
4407
|
+
}
|
|
4408
|
+
|
|
3957
4409
|
function createLnkHandler(tag: string) {
|
|
3958
4410
|
addHandler(
|
|
3959
4411
|
tag,
|
|
@@ -3978,8 +4430,8 @@ function createLnkHandler(tag: string) {
|
|
|
3978
4430
|
const id = readPascalString(reader, 1);
|
|
3979
4431
|
const name = readUnicodeString(reader);
|
|
3980
4432
|
|
|
3981
|
-
const fileType =
|
|
3982
|
-
const fileCreator =
|
|
4433
|
+
const fileType = readFileTypeSignature(reader); // ' ' if empty
|
|
4434
|
+
const fileCreator = readFileTypeSignature(reader); // ' ' or '\0\0\0\0' if empty
|
|
3983
4435
|
const dataSize = readLength64(reader);
|
|
3984
4436
|
const hasFileOpenDescriptor = readUint8(reader);
|
|
3985
4437
|
const fileOpenDescriptor = hasFileOpenDescriptor ? readVersionAndDescriptor(reader) as FileOpenDescriptor : undefined;
|
|
@@ -4137,28 +4589,55 @@ addHandlerAlias('lnk3', 'lnk2');
|
|
|
4137
4589
|
|
|
4138
4590
|
interface PthsDescriptor {
|
|
4139
4591
|
pathList: {
|
|
4592
|
+
_name: '';
|
|
4140
4593
|
_classID: 'pathInfoClass';
|
|
4141
4594
|
pathUnicodeName: string;
|
|
4142
|
-
pathSymmetryClass
|
|
4595
|
+
pathSymmetryClass?: {
|
|
4596
|
+
_name: '';
|
|
4143
4597
|
_classID: 'pathSymmetryClass';
|
|
4144
4598
|
pathSymmetryMode: string; // 'pathSymmetryModeEnum.pathSymmetryModeBasicPath'
|
|
4145
4599
|
};
|
|
4146
4600
|
}[];
|
|
4147
4601
|
}
|
|
4148
4602
|
|
|
4603
|
+
// unicode names of saved paths (path data is stored in image resources 2000-2998)
|
|
4149
4604
|
addHandler(
|
|
4150
4605
|
'pths',
|
|
4151
|
-
|
|
4152
|
-
(reader,
|
|
4606
|
+
target => (target as Psd).imageResources?.savedPaths !== undefined,
|
|
4607
|
+
(reader, _target, left, psd) => {
|
|
4153
4608
|
const desc = readVersionAndDescriptor(reader, true) as PthsDescriptor;
|
|
4154
|
-
|
|
4155
|
-
|
|
4156
|
-
|
|
4157
|
-
|
|
4609
|
+
const imageResources = psd.imageResources ??= {};
|
|
4610
|
+
const savedPaths = imageResources.savedPaths ??= [];
|
|
4611
|
+
|
|
4612
|
+
for (let i = 0; i < desc.pathList.length && i < savedPaths.length; i++) {
|
|
4613
|
+
const { pathUnicodeName, pathSymmetryClass } = desc.pathList[i];
|
|
4614
|
+
const path = savedPaths[i];
|
|
4615
|
+
|
|
4616
|
+
// clipping path is referenced by (non-unicode) name
|
|
4617
|
+
if (imageResources.clippingPath && imageResources.clippingPath.name === path.name) {
|
|
4618
|
+
imageResources.clippingPath.name = pathUnicodeName;
|
|
4619
|
+
}
|
|
4620
|
+
|
|
4621
|
+
path.name = pathUnicodeName;
|
|
4622
|
+
if (pathSymmetryClass) path.symmetryMode = pathSymmetryClass.pathSymmetryMode.split('.')[1];
|
|
4623
|
+
}
|
|
4624
|
+
|
|
4625
|
+
skipBytes(reader, left());
|
|
4158
4626
|
},
|
|
4159
|
-
(writer, _target) => {
|
|
4627
|
+
(writer, _target, psd) => {
|
|
4160
4628
|
const desc: PthsDescriptor = {
|
|
4161
|
-
pathList:
|
|
4629
|
+
pathList: psd.imageResources!.savedPaths!.map(path => ({
|
|
4630
|
+
_name: '',
|
|
4631
|
+
_classID: 'pathInfoClass',
|
|
4632
|
+
pathUnicodeName: path.name,
|
|
4633
|
+
...(path.symmetryMode ? {
|
|
4634
|
+
pathSymmetryClass: {
|
|
4635
|
+
_name: '',
|
|
4636
|
+
_classID: 'pathSymmetryClass',
|
|
4637
|
+
pathSymmetryMode: `pathSymmetryModeEnum.${path.symmetryMode}`,
|
|
4638
|
+
},
|
|
4639
|
+
} : {}),
|
|
4640
|
+
})),
|
|
4162
4641
|
};
|
|
4163
4642
|
|
|
4164
4643
|
writeVersionAndDescriptor(writer, '', 'pathsDataClass', desc);
|
|
@@ -4173,26 +4652,19 @@ addHandler(
|
|
|
4173
4652
|
);
|
|
4174
4653
|
|
|
4175
4654
|
addHandler(
|
|
4176
|
-
'lfxs',
|
|
4177
|
-
|
|
4178
|
-
// NOTE: this might be insufficient
|
|
4179
|
-
// target => target.effects !== undefined && (
|
|
4180
|
-
// !!target.effects.dropShadow?.some(e => e.choke) ||
|
|
4181
|
-
// !!target.effects.innerShadow?.some(e => e.choke) ||
|
|
4182
|
-
// !!target.effects.outerGlow?.choke ||
|
|
4183
|
-
// !!target.effects.innerGlow?.choke
|
|
4184
|
-
// ),
|
|
4655
|
+
'lfxs', // effects of group layers (instead of 'lfx2' / 'lmfx')
|
|
4656
|
+
target => target.effects !== undefined && isGroupLayer(target),
|
|
4185
4657
|
(reader, target, left) => {
|
|
4186
4658
|
const version = readUint32(reader);
|
|
4187
4659
|
if (version !== 0) throw new Error(`Invalid lfxs version`);
|
|
4188
4660
|
|
|
4189
4661
|
const desc: Lfx2Descriptor & LmfxDescriptor = readVersionAndDescriptor(reader);
|
|
4190
|
-
target.effects = parseEffects(desc,
|
|
4662
|
+
target.effects = parseEffects(desc, reader);
|
|
4191
4663
|
|
|
4192
4664
|
skipBytes(reader, left());
|
|
4193
4665
|
},
|
|
4194
|
-
(writer, target
|
|
4195
|
-
const desc = serializeEffects(target.effects!,
|
|
4666
|
+
(writer, target) => {
|
|
4667
|
+
const desc = serializeEffects(target.effects!, writer, true);
|
|
4196
4668
|
writeUint32(writer, 0); // version
|
|
4197
4669
|
writeVersionAndDescriptor(writer, '', 'null', desc);
|
|
4198
4670
|
},
|
|
@@ -4238,6 +4710,12 @@ function readLevelsChannel(reader: PsdReader): LevelsAdjustmentChannel {
|
|
|
4238
4710
|
return { shadowInput, highlightInput, shadowOutput, highlightOutput, midtoneInput };
|
|
4239
4711
|
}
|
|
4240
4712
|
|
|
4713
|
+
const defaultLevelsChannel: LevelsAdjustmentChannel = { shadowInput: 0, highlightInput: 255, shadowOutput: 0, highlightOutput: 255, midtoneInput: 1 };
|
|
4714
|
+
|
|
4715
|
+
function isDefaultLevelsChannel(c: LevelsAdjustmentChannel) {
|
|
4716
|
+
return c.shadowInput === 0 && c.highlightInput === 255 && c.shadowOutput === 0 && c.highlightOutput === 255 && c.midtoneInput === 1;
|
|
4717
|
+
}
|
|
4718
|
+
|
|
4241
4719
|
function writeLevelsChannel(writer: PsdWriter, channel: LevelsAdjustmentChannel) {
|
|
4242
4720
|
writeInt16(writer, channel.shadowInput);
|
|
4243
4721
|
writeInt16(writer, channel.highlightInput);
|
|
@@ -4252,62 +4730,56 @@ addHandler(
|
|
|
4252
4730
|
(reader, target, left) => {
|
|
4253
4731
|
if (readUint16(reader) !== 2) throw new Error('Invalid levl version');
|
|
4254
4732
|
|
|
4255
|
-
const
|
|
4733
|
+
const channels: LevelsAdjustmentChannel[] = [];
|
|
4734
|
+
|
|
4735
|
+
for (let i = 0; i < 29 && left() >= 10; i++) {
|
|
4736
|
+
channels.push(readLevelsChannel(reader));
|
|
4737
|
+
}
|
|
4738
|
+
|
|
4739
|
+
if (left() >= 8) {
|
|
4740
|
+
const signature = readSignature(reader);
|
|
4741
|
+
let totalChannelRecords: number;
|
|
4256
4742
|
|
|
4257
|
-
|
|
4258
|
-
|
|
4743
|
+
if (signature === 'ls\x00\x03') {
|
|
4744
|
+
// Clip Studio Paint writes 'ls' instead of 'Lvls' signature (version is part of the read signature)
|
|
4745
|
+
totalChannelRecords = readUint16(reader);
|
|
4746
|
+
} else {
|
|
4747
|
+
if (signature !== 'Lvls') throw new Error(`Invalid levl signature`);
|
|
4748
|
+
if (readUint16(reader) !== 3) throw new Error(`Invalid levl version`);
|
|
4749
|
+
totalChannelRecords = readUint16(reader);
|
|
4750
|
+
}
|
|
4751
|
+
|
|
4752
|
+
while (channels.length < totalChannelRecords && left() >= 10) {
|
|
4753
|
+
channels.push(readLevelsChannel(reader));
|
|
4754
|
+
}
|
|
4259
4755
|
}
|
|
4260
4756
|
|
|
4261
|
-
|
|
4757
|
+
// records for all possible channels are stored, skip unused ones
|
|
4758
|
+
while (channels.length && isDefaultLevelsChannel(channels[channels.length - 1])) channels.pop();
|
|
4262
4759
|
|
|
4263
4760
|
target.adjustment = {
|
|
4264
4761
|
...target.adjustment as PresetInfo,
|
|
4265
4762
|
type: 'levels',
|
|
4266
|
-
|
|
4267
|
-
red,
|
|
4268
|
-
green,
|
|
4269
|
-
blue,
|
|
4763
|
+
channels,
|
|
4270
4764
|
};
|
|
4271
4765
|
|
|
4272
|
-
if (left()) {
|
|
4273
|
-
if (readSignature(reader) !== 'Lvls') throw new Error(`Invalid levl signature`);
|
|
4274
|
-
if (readUint16(reader) !== 3) throw new Error(`Invalid levl version`);
|
|
4275
|
-
|
|
4276
|
-
const totalChannelRecords = readUint16(reader);
|
|
4277
|
-
|
|
4278
|
-
for (let i = 0; i < (totalChannelRecords - levels.length) && left(); i++) {
|
|
4279
|
-
levels.push(readLevelsChannel(reader));
|
|
4280
|
-
}
|
|
4281
|
-
}
|
|
4282
|
-
|
|
4283
4766
|
skipBytes(reader, left());
|
|
4284
4767
|
},
|
|
4285
4768
|
(writer, target) => {
|
|
4286
|
-
const
|
|
4287
|
-
const defaultChannel = {
|
|
4288
|
-
shadowInput: 0,
|
|
4289
|
-
highlightInput: 255,
|
|
4290
|
-
shadowOutput: 0,
|
|
4291
|
-
highlightOutput: 255,
|
|
4292
|
-
midtoneInput: 1,
|
|
4293
|
-
};
|
|
4769
|
+
const channels = (target.adjustment as LevelsAdjustment).channels || [];
|
|
4294
4770
|
|
|
4295
4771
|
writeUint16(writer, 2); // version
|
|
4296
|
-
writeLevelsChannel(writer, info.rgb || defaultChannel);
|
|
4297
|
-
writeLevelsChannel(writer, info.red || defaultChannel);
|
|
4298
|
-
writeLevelsChannel(writer, info.green || defaultChannel);
|
|
4299
|
-
writeLevelsChannel(writer, info.blue || defaultChannel);
|
|
4300
4772
|
|
|
4301
|
-
for (let i = 0; i <
|
|
4302
|
-
writeLevelsChannel(writer,
|
|
4773
|
+
for (let i = 0; i < 29; i++) {
|
|
4774
|
+
writeLevelsChannel(writer, channels[i] || defaultLevelsChannel);
|
|
4303
4775
|
}
|
|
4304
4776
|
|
|
4305
4777
|
writeSignature(writer, 'Lvls');
|
|
4306
4778
|
writeUint16(writer, 3); // version
|
|
4307
4779
|
writeUint16(writer, 62); // total count
|
|
4308
4780
|
|
|
4309
|
-
for (let i =
|
|
4310
|
-
writeLevelsChannel(writer,
|
|
4781
|
+
for (let i = 29; i < 62; i++) {
|
|
4782
|
+
writeLevelsChannel(writer, channels[i] || defaultLevelsChannel);
|
|
4311
4783
|
}
|
|
4312
4784
|
},
|
|
4313
4785
|
);
|
|
@@ -4338,71 +4810,82 @@ addHandler(
|
|
|
4338
4810
|
'curv',
|
|
4339
4811
|
adjustmentType('curves'),
|
|
4340
4812
|
(reader, target, left) => {
|
|
4341
|
-
readUint8(reader);
|
|
4342
|
-
|
|
4343
|
-
|
|
4344
|
-
const
|
|
4345
|
-
const
|
|
4813
|
+
const isMap = !!readUint8(reader); // curves drawn with pencil tool (lookup tables instead of points)
|
|
4814
|
+
const version = readUint16(reader);
|
|
4815
|
+
if (version !== 1 && version !== 4) throw new Error('Invalid curv version');
|
|
4816
|
+
const countOrMask = readUint32(reader);
|
|
4817
|
+
const channels: (CurvesAdjustmentChannel | number[])[] = [];
|
|
4818
|
+
const readChannel = () => isMap ? Array.from(readBytes(reader, 256)) : readCurveChannel(reader);
|
|
4819
|
+
|
|
4820
|
+
if (version === 1) {
|
|
4821
|
+
for (let i = 0; i < 32; i++) {
|
|
4822
|
+
if (countOrMask & (1 << i)) {
|
|
4823
|
+
while (channels.length < i) channels.push([]);
|
|
4824
|
+
channels.push(readChannel());
|
|
4825
|
+
}
|
|
4826
|
+
}
|
|
4827
|
+
} else {
|
|
4828
|
+
if (countOrMask > 64) throw new Error('Invalid curv channel count');
|
|
4829
|
+
for (let i = 0; i < countOrMask; i++) channels.push(readChannel());
|
|
4830
|
+
}
|
|
4346
4831
|
|
|
4347
|
-
|
|
4348
|
-
if (
|
|
4349
|
-
|
|
4350
|
-
|
|
4832
|
+
// extra section stores curves for all channels (including ones that don't fit in the mask)
|
|
4833
|
+
if (version === 1 && left() >= 10 && readSignature(reader) === 'Crv ') {
|
|
4834
|
+
readUint16(reader); // version
|
|
4835
|
+
const count = readUint32(reader);
|
|
4836
|
+
|
|
4837
|
+
for (let i = 0; i < count && left() >= 2; i++) {
|
|
4838
|
+
const index = readUint16(reader);
|
|
4839
|
+
const channel = readChannel();
|
|
4840
|
+
while (channels.length < index) channels.push([]);
|
|
4841
|
+
channels[index] = channel;
|
|
4842
|
+
}
|
|
4843
|
+
}
|
|
4351
4844
|
|
|
4352
4845
|
target.adjustment = {
|
|
4353
4846
|
...target.adjustment as PresetInfo,
|
|
4354
|
-
|
|
4847
|
+
type: 'curves',
|
|
4848
|
+
...(isMap ? { values: channels as number[][] } : { channels: channels as CurvesAdjustmentChannel[] }),
|
|
4355
4849
|
};
|
|
4356
4850
|
|
|
4357
|
-
// ignoring, duplicate information
|
|
4358
|
-
// checkSignature(reader, 'Crv ');
|
|
4359
|
-
|
|
4360
|
-
// const cVersion = readUint16(reader);
|
|
4361
|
-
// readUint16(reader);
|
|
4362
|
-
// const channelCount = readUint16(reader);
|
|
4363
|
-
|
|
4364
|
-
// for (let i = 0; i < channelCount; i++) {
|
|
4365
|
-
// const index = readUint16(reader);
|
|
4366
|
-
// const nodes = readUint16(reader);
|
|
4367
|
-
|
|
4368
|
-
// for (let j = 0; j < nodes; j++) {
|
|
4369
|
-
// const output = readInt16(reader);
|
|
4370
|
-
// const input = readInt16(reader);
|
|
4371
|
-
// }
|
|
4372
|
-
// }
|
|
4373
|
-
|
|
4374
4851
|
skipBytes(reader, left());
|
|
4375
4852
|
},
|
|
4376
4853
|
(writer, target) => {
|
|
4377
|
-
const
|
|
4378
|
-
const
|
|
4379
|
-
|
|
4380
|
-
|
|
4854
|
+
const adjustment = target.adjustment as CurvesAdjustment;
|
|
4855
|
+
const isMap = !!adjustment.values;
|
|
4856
|
+
const channels: (CurvesAdjustmentChannel | number[])[] = (isMap ? adjustment.values : adjustment.channels) || [];
|
|
4857
|
+
const used: number[] = [];
|
|
4858
|
+
let channelsMask = 0;
|
|
4859
|
+
|
|
4860
|
+
for (let i = 0; i < channels.length; i++) {
|
|
4861
|
+
if (channels[i]?.length) {
|
|
4862
|
+
if (i < 32) channelsMask |= 1 << i;
|
|
4863
|
+
used.push(i);
|
|
4864
|
+
}
|
|
4865
|
+
}
|
|
4381
4866
|
|
|
4382
|
-
|
|
4383
|
-
|
|
4384
|
-
|
|
4385
|
-
|
|
4867
|
+
const writeChannel = (channel: CurvesAdjustmentChannel | number[]) => {
|
|
4868
|
+
if (isMap) {
|
|
4869
|
+
for (let i = 0; i < 256; i++) writeUint8(writer, clamp(Math.round((channel as number[])[i] ?? i), 0, 255));
|
|
4870
|
+
} else {
|
|
4871
|
+
writeCurveChannel(writer, channel as CurvesAdjustmentChannel);
|
|
4872
|
+
}
|
|
4873
|
+
};
|
|
4386
4874
|
|
|
4387
|
-
writeUint8(writer, 0);
|
|
4875
|
+
writeUint8(writer, isMap ? 1 : 0);
|
|
4388
4876
|
writeUint16(writer, 1); // version
|
|
4389
|
-
|
|
4390
|
-
writeUint16(writer, channels);
|
|
4877
|
+
writeUint32(writer, channelsMask >>> 0);
|
|
4391
4878
|
|
|
4392
|
-
|
|
4393
|
-
if (red && red.length) writeCurveChannel(writer, red);
|
|
4394
|
-
if (green && green.length) writeCurveChannel(writer, green);
|
|
4395
|
-
if (blue && blue.length) writeCurveChannel(writer, blue);
|
|
4879
|
+
for (const i of used) if (i < 32) writeChannel(channels[i]);
|
|
4396
4880
|
|
|
4397
4881
|
writeSignature(writer, 'Crv ');
|
|
4398
4882
|
writeUint16(writer, 4); // version
|
|
4399
|
-
|
|
4400
|
-
writeUint16(writer, channelCount);
|
|
4883
|
+
writeUint32(writer, used.length);
|
|
4401
4884
|
|
|
4402
|
-
|
|
4403
|
-
|
|
4404
|
-
|
|
4405
|
-
|
|
4885
|
+
for (const i of used) {
|
|
4886
|
+
writeUint16(writer, i);
|
|
4887
|
+
writeChannel(channels[i]);
|
|
4888
|
+
}
|
|
4406
4889
|
},
|
|
4407
4890
|
);
|
|
4408
4891
|
|
|
@@ -4487,10 +4970,26 @@ addHandler(
|
|
|
4487
4970
|
(reader, target, left) => {
|
|
4488
4971
|
if (readUint16(reader) !== 2) throw new Error('Invalid hue2 version');
|
|
4489
4972
|
|
|
4973
|
+
const colorize = !!readUint8(reader);
|
|
4974
|
+
skipBytes(reader, 1);
|
|
4975
|
+
const colorizeHue = readInt16(reader); // stored as -180..180
|
|
4976
|
+
const colorizeSaturation = readInt16(reader);
|
|
4977
|
+
const colorizeLightness = readInt16(reader);
|
|
4978
|
+
|
|
4490
4979
|
target.adjustment = {
|
|
4491
4980
|
...target.adjustment as PresetInfo,
|
|
4492
4981
|
type: 'hue/saturation',
|
|
4493
|
-
|
|
4982
|
+
colorize,
|
|
4983
|
+
colorization: {
|
|
4984
|
+
hue: colorizeHue < 0 ? colorizeHue + 360 : colorizeHue,
|
|
4985
|
+
saturation: colorizeSaturation,
|
|
4986
|
+
lightness: colorizeLightness,
|
|
4987
|
+
},
|
|
4988
|
+
master: {
|
|
4989
|
+
hue: readInt16(reader),
|
|
4990
|
+
saturation: readInt16(reader),
|
|
4991
|
+
lightness: readInt16(reader),
|
|
4992
|
+
},
|
|
4494
4993
|
reds: readHueChannel(reader),
|
|
4495
4994
|
yellows: readHueChannel(reader),
|
|
4496
4995
|
greens: readHueChannel(reader),
|
|
@@ -4505,7 +5004,15 @@ addHandler(
|
|
|
4505
5004
|
const info = target.adjustment as HueSaturationAdjustment;
|
|
4506
5005
|
|
|
4507
5006
|
writeUint16(writer, 2); // version
|
|
4508
|
-
|
|
5007
|
+
writeUint8(writer, info.colorize ? 1 : 0);
|
|
5008
|
+
writeUint8(writer, 0);
|
|
5009
|
+
const colorizeHue = Math.round(info.colorization?.hue ?? 0);
|
|
5010
|
+
writeInt16(writer, colorizeHue > 180 ? colorizeHue - 360 : colorizeHue);
|
|
5011
|
+
writeInt16(writer, Math.round(info.colorization?.saturation ?? 0));
|
|
5012
|
+
writeInt16(writer, Math.round(info.colorization?.lightness ?? 0));
|
|
5013
|
+
writeInt16(writer, Math.round(info.master?.hue ?? 0));
|
|
5014
|
+
writeInt16(writer, Math.round(info.master?.saturation ?? 0));
|
|
5015
|
+
writeInt16(writer, Math.round(info.master?.lightness ?? 0));
|
|
4509
5016
|
writeHueChannel(writer, info.reds);
|
|
4510
5017
|
writeHueChannel(writer, info.yellows);
|
|
4511
5018
|
writeHueChannel(writer, info.greens);
|
|
@@ -4619,16 +5126,17 @@ addHandler(
|
|
|
4619
5126
|
if (version === 2) {
|
|
4620
5127
|
color = readColor(reader);
|
|
4621
5128
|
} else { // version 3
|
|
4622
|
-
//
|
|
4623
|
-
//
|
|
4624
|
-
|
|
4625
|
-
const
|
|
4626
|
-
const
|
|
4627
|
-
|
|
5129
|
+
// XYZ color relative to D50 white point, 32768 = 1.0 (not documented, matches colors of Photoshop presets),
|
|
5130
|
+
// converted to Lab (same scale as readColor()) so it can be written back as version 2
|
|
5131
|
+
const x = readInt32(reader) / 32768;
|
|
5132
|
+
const y = readInt32(reader) / 32768;
|
|
5133
|
+
const z = readInt32(reader) / 32768;
|
|
5134
|
+
if (!(x >= 0 && y >= 0 && z >= 0 && x <= 2 && y <= 2 && z <= 2)) throw new Error('Invalid phfl XYZ color');
|
|
5135
|
+
const f = (t: number) => t > 216 / 24389 ? Math.cbrt(t) : (24389 / 27 * t + 16) / 116;
|
|
4628
5136
|
color = {
|
|
4629
|
-
l:
|
|
4630
|
-
a: normalizeLabAB(
|
|
4631
|
-
b: normalizeLabAB(
|
|
5137
|
+
l: Math.max(0, 116 * f(y) - 16) / 100,
|
|
5138
|
+
a: normalizeLabAB(500 * (f(x) - f(y))),
|
|
5139
|
+
b: normalizeLabAB(200 * (f(y) - f(z))),
|
|
4632
5140
|
};
|
|
4633
5141
|
}
|
|
4634
5142
|
|
|
@@ -4844,7 +5352,7 @@ addHandler(
|
|
|
4844
5352
|
const hasMethod = !!readUint8(reader); reader.offset--;
|
|
4845
5353
|
if (hasMethod) {
|
|
4846
5354
|
const method = readSignature(reader);
|
|
4847
|
-
info.method = gradientInterpolationMethodType.decode(method);
|
|
5355
|
+
info.method = gradientInterpolationMethodType.decode(`gradientInterpolationMethodType.${method}`);
|
|
4848
5356
|
}
|
|
4849
5357
|
|
|
4850
5358
|
info.name = readUnicodeString(reader);
|
|
@@ -4917,7 +5425,7 @@ addHandler(
|
|
|
4917
5425
|
writeUint8(writer, info.dither ? 1 : 0);
|
|
4918
5426
|
|
|
4919
5427
|
if (info.method !== undefined) {
|
|
4920
|
-
writeSignature(writer, gradientInterpolationMethodType.encode(info.method));
|
|
5428
|
+
writeSignature(writer, gradientInterpolationMethodType.encode(info.method).split('.')[1]);
|
|
4921
5429
|
}
|
|
4922
5430
|
|
|
4923
5431
|
writeUnicodeStringWithPadding(writer, info.name || '');
|
|
@@ -5316,6 +5824,10 @@ addHandler(
|
|
|
5316
5824
|
);
|
|
5317
5825
|
|
|
5318
5826
|
addHandlerAlias('FXid', 'FEid');
|
|
5827
|
+
addHandlerAlias('FELS', 'FEid'); // filter effects 3
|
|
5828
|
+
addHandlerAlias('hue ', 'hue2'); // old hue/saturation (Photoshop 4.0), same structure as 'hue2'
|
|
5829
|
+
addHandlerAlias('plLd', 'PlLd'); // old placed layer key
|
|
5830
|
+
addHandlerAlias('abdd', 'artb'); // artboard data 3
|
|
5319
5831
|
|
|
5320
5832
|
addHandler(
|
|
5321
5833
|
'FMsk',
|
|
@@ -5334,14 +5846,14 @@ addHandler(
|
|
|
5334
5846
|
|
|
5335
5847
|
interface ArtdDescriptor {
|
|
5336
5848
|
'Cnt ': number;
|
|
5337
|
-
autoExpandOffset
|
|
5338
|
-
origin
|
|
5339
|
-
autoExpandEnabled
|
|
5340
|
-
autoNestEnabled
|
|
5341
|
-
autoPositionEnabled
|
|
5849
|
+
autoExpandOffset?: { Hrzn: number; Vrtc: number; };
|
|
5850
|
+
origin?: { Hrzn: number; Vrtc: number; };
|
|
5851
|
+
autoExpandEnabled?: boolean;
|
|
5852
|
+
autoNestEnabled?: boolean;
|
|
5853
|
+
autoPositionEnabled?: boolean;
|
|
5342
5854
|
shrinkwrapOnSaveEnabled?: boolean;
|
|
5343
|
-
docDefaultNewArtboardBackgroundColor
|
|
5344
|
-
docDefaultNewArtboardBackgroundType
|
|
5855
|
+
docDefaultNewArtboardBackgroundColor?: DescriptorColor;
|
|
5856
|
+
docDefaultNewArtboardBackgroundType?: number;
|
|
5345
5857
|
}
|
|
5346
5858
|
|
|
5347
5859
|
addHandler(
|
|
@@ -5349,17 +5861,15 @@ addHandler(
|
|
|
5349
5861
|
target => (target as Psd).artboards !== undefined,
|
|
5350
5862
|
(reader, target, left) => {
|
|
5351
5863
|
const desc = readVersionAndDescriptor(reader) as ArtdDescriptor;
|
|
5352
|
-
(target as Psd).artboards = {
|
|
5353
|
-
|
|
5354
|
-
|
|
5355
|
-
|
|
5356
|
-
|
|
5357
|
-
|
|
5358
|
-
|
|
5359
|
-
|
|
5360
|
-
|
|
5361
|
-
docDefaultNewArtboardBackgroundType: desc.docDefaultNewArtboardBackgroundType,
|
|
5362
|
-
};
|
|
5864
|
+
const artboards: Psd['artboards'] = (target as Psd).artboards = { count: desc['Cnt '] };
|
|
5865
|
+
if (desc.autoExpandOffset) artboards.autoExpandOffset = { horizontal: desc.autoExpandOffset.Hrzn, vertical: desc.autoExpandOffset.Vrtc };
|
|
5866
|
+
if (desc.origin) artboards.origin = { horizontal: desc.origin.Hrzn, vertical: desc.origin.Vrtc };
|
|
5867
|
+
if (desc.autoExpandEnabled !== undefined) artboards.autoExpandEnabled = desc.autoExpandEnabled;
|
|
5868
|
+
if (desc.autoNestEnabled !== undefined) artboards.autoNestEnabled = desc.autoNestEnabled;
|
|
5869
|
+
if (desc.autoPositionEnabled !== undefined) artboards.autoPositionEnabled = desc.autoPositionEnabled;
|
|
5870
|
+
artboards.shrinkwrapOnSaveEnabled = !!desc.shrinkwrapOnSaveEnabled;
|
|
5871
|
+
if (desc.docDefaultNewArtboardBackgroundColor) artboards.docDefaultNewArtboardBackgroundColor = parseColor(desc.docDefaultNewArtboardBackgroundColor);
|
|
5872
|
+
if (desc.docDefaultNewArtboardBackgroundType !== undefined) artboards.docDefaultNewArtboardBackgroundType = desc.docDefaultNewArtboardBackgroundType;
|
|
5363
5873
|
|
|
5364
5874
|
skipBytes(reader, left());
|
|
5365
5875
|
},
|
|
@@ -5380,13 +5890,17 @@ addHandler(
|
|
|
5380
5890
|
},
|
|
5381
5891
|
);
|
|
5382
5892
|
|
|
5893
|
+
function isGroupLayer(target: LayerAdditionalInfo) {
|
|
5894
|
+
return !!(target as Layer).children;
|
|
5895
|
+
}
|
|
5896
|
+
|
|
5383
5897
|
export function hasMultiEffects(effects: LayerEffectsInfo) {
|
|
5384
5898
|
return Object.keys(effects).map(key => (effects as any)[key]).some(v => Array.isArray(v) && v.length > 1);
|
|
5385
5899
|
}
|
|
5386
5900
|
|
|
5387
5901
|
addHandler(
|
|
5388
5902
|
'lfx2',
|
|
5389
|
-
target => target.effects !== undefined && !hasMultiEffects(target.effects),
|
|
5903
|
+
target => target.effects !== undefined && !isGroupLayer(target) && !hasMultiEffects(target.effects),
|
|
5390
5904
|
(reader, target, left) => {
|
|
5391
5905
|
const version = readUint32(reader);
|
|
5392
5906
|
if (version !== 0) throw new Error(`Invalid lfx2 version`);
|
|
@@ -5394,14 +5908,13 @@ addHandler(
|
|
|
5394
5908
|
const desc: Lfx2Descriptor & LmfxDescriptor = readVersionAndDescriptor(reader);
|
|
5395
5909
|
// console.log('READ', require('util').inspect(desc, false, 99, true));
|
|
5396
5910
|
|
|
5397
|
-
//
|
|
5398
|
-
|
|
5399
|
-
target.effects = parseEffects(desc, !!reader.logMissingFeatures);
|
|
5911
|
+
// discard if read in 'lrFX' section ('lmfx' and 'lfx2' are never stored together)
|
|
5912
|
+
target.effects = parseEffects(desc, reader);
|
|
5400
5913
|
|
|
5401
5914
|
skipBytes(reader, left());
|
|
5402
5915
|
},
|
|
5403
|
-
(writer, target
|
|
5404
|
-
const desc = serializeEffects(target.effects!,
|
|
5916
|
+
(writer, target) => {
|
|
5917
|
+
const desc = serializeEffects(target.effects!, writer, true);
|
|
5405
5918
|
// console.log('WRITE', require('util').inspect(desc, false, 99, true));
|
|
5406
5919
|
|
|
5407
5920
|
writeUint32(writer, 0); // version
|
|
@@ -5469,26 +5982,102 @@ addHandler(
|
|
|
5469
5982
|
},
|
|
5470
5983
|
);
|
|
5471
5984
|
|
|
5472
|
-
|
|
5473
|
-
|
|
5474
|
-
|
|
5475
|
-
|
|
5476
|
-
|
|
5985
|
+
// generator settings of the document (layers store them in 'shmd' section)
|
|
5986
|
+
addHandler(
|
|
5987
|
+
'extn',
|
|
5988
|
+
target => target.generatorSettings !== undefined && isDocument(target),
|
|
5989
|
+
(reader, target, left) => {
|
|
5990
|
+
const desc = readVersionAndDescriptor(reader) as GeneratorSettingsDescriptor;
|
|
5991
|
+
if (desc.generatorSettings) target.generatorSettings = parseGeneratorSettings(desc.generatorSettings);
|
|
5992
|
+
skipBytes(reader, left());
|
|
5993
|
+
},
|
|
5994
|
+
(writer, target) => {
|
|
5995
|
+
writeVersionAndDescriptor(writer, '', 'null', serializeGeneratorSettings(target.generatorSettings!));
|
|
5996
|
+
},
|
|
5997
|
+
);
|
|
5998
|
+
|
|
5999
|
+
// generator settings stored by older versions of Photoshop (written back in 'shmd' or 'extn' section)
|
|
6000
|
+
addHandler(
|
|
6001
|
+
'extd',
|
|
6002
|
+
() => false,
|
|
6003
|
+
(reader, target, left) => {
|
|
6004
|
+
const desc = readVersionAndDescriptor(reader) as GeneratorSettingsDescriptor;
|
|
6005
|
+
if (desc.generatorSettings && target.generatorSettings === undefined) {
|
|
6006
|
+
target.generatorSettings = parseGeneratorSettings(desc.generatorSettings);
|
|
6007
|
+
}
|
|
6008
|
+
skipBytes(reader, left());
|
|
6009
|
+
},
|
|
6010
|
+
() => { },
|
|
6011
|
+
);
|
|
6012
|
+
|
|
6013
|
+
interface FrgbDescriptor {
|
|
6014
|
+
framedGroupRect: { _name: ''; _classID: 'classFloatRect'; 'Top ': number; Left: number; Btom: number; Rght: number; };
|
|
6015
|
+
framedGroupType: number;
|
|
6016
|
+
LockPPI?: boolean;
|
|
6017
|
+
PreferredResolution?: number;
|
|
6018
|
+
SavedIteratedName?: string;
|
|
5477
6019
|
}
|
|
5478
6020
|
|
|
5479
|
-
// extension settings ?, ignore it
|
|
5480
6021
|
addHandler(
|
|
5481
|
-
'
|
|
5482
|
-
|
|
5483
|
-
(reader, target) => {
|
|
5484
|
-
const desc
|
|
5485
|
-
|
|
6022
|
+
'frgb', // frame tool group
|
|
6023
|
+
hasKey('framedGroup'),
|
|
6024
|
+
(reader, target, left) => {
|
|
6025
|
+
const desc = readVersionAndDescriptor(reader) as FrgbDescriptor;
|
|
6026
|
+
const rect = desc.framedGroupRect;
|
|
6027
|
+
target.framedGroup = {
|
|
6028
|
+
rect: { top: rect['Top '], left: rect.Left, bottom: rect.Btom, right: rect.Rght },
|
|
6029
|
+
type: desc.framedGroupType,
|
|
6030
|
+
};
|
|
6031
|
+
if (desc.LockPPI !== undefined) target.framedGroup.lockPPI = desc.LockPPI;
|
|
6032
|
+
if (desc.PreferredResolution !== undefined) target.framedGroup.preferredResolution = desc.PreferredResolution;
|
|
6033
|
+
if (desc.SavedIteratedName !== undefined) target.framedGroup.savedIteratedName = desc.SavedIteratedName;
|
|
6034
|
+
skipBytes(reader, left());
|
|
6035
|
+
},
|
|
6036
|
+
(writer, target) => {
|
|
6037
|
+
const group = target.framedGroup!;
|
|
6038
|
+
const rect = group.rect;
|
|
6039
|
+
const desc: FrgbDescriptor = {
|
|
6040
|
+
framedGroupRect: { _name: '', _classID: 'classFloatRect', 'Top ': rect.top, Left: rect.left, Btom: rect.bottom, Rght: rect.right },
|
|
6041
|
+
framedGroupType: group.type,
|
|
6042
|
+
};
|
|
6043
|
+
if (group.lockPPI !== undefined) desc.LockPPI = group.lockPPI;
|
|
6044
|
+
if (group.preferredResolution !== undefined) desc.PreferredResolution = group.preferredResolution;
|
|
6045
|
+
if (group.savedIteratedName !== undefined) desc.SavedIteratedName = group.savedIteratedName;
|
|
6046
|
+
writeVersionAndDescriptor(writer, '', 'framedGroup', desc);
|
|
6047
|
+
},
|
|
6048
|
+
);
|
|
5486
6049
|
|
|
5487
|
-
|
|
6050
|
+
addHandler(
|
|
6051
|
+
'anFX',
|
|
6052
|
+
hasKey('animationEffects'),
|
|
6053
|
+
(reader, target, left) => {
|
|
6054
|
+
target.animationEffects = readBytes(reader, left());
|
|
5488
6055
|
},
|
|
5489
6056
|
(writer, target) => {
|
|
5490
|
-
|
|
5491
|
-
|
|
6057
|
+
writeBytes(writer, target.animationEffects);
|
|
6058
|
+
},
|
|
6059
|
+
);
|
|
6060
|
+
|
|
6061
|
+
addHandler(
|
|
6062
|
+
'PxSD', // pixel source data 2 (video layer frames)
|
|
6063
|
+
hasKey('pixelSourceData'),
|
|
6064
|
+
(reader, target, left) => {
|
|
6065
|
+
target.pixelSourceData = [];
|
|
6066
|
+
|
|
6067
|
+
while (left() >= 8) {
|
|
6068
|
+
const length = readLength64(reader);
|
|
6069
|
+
if (length > left()) throw new Error('Invalid PxSD data length');
|
|
6070
|
+
target.pixelSourceData.push(readBytes(reader, length));
|
|
6071
|
+
}
|
|
6072
|
+
|
|
6073
|
+
skipBytes(reader, left());
|
|
6074
|
+
},
|
|
6075
|
+
(writer, target) => {
|
|
6076
|
+
for (const data of target.pixelSourceData!) {
|
|
6077
|
+
writeUint32(writer, 0);
|
|
6078
|
+
writeUint32(writer, data.byteLength);
|
|
6079
|
+
writeBytes(writer, data);
|
|
6080
|
+
}
|
|
5492
6081
|
},
|
|
5493
6082
|
);
|
|
5494
6083
|
|