ag-psd
Version:
Library for reading and writing PSD files
1,638 lines (1,479 loc) • 151 kB
text/typescript
import { fromByteArray, toByteArray } from 'base64-js';
import { readEffects, writeEffects } from './effectsHelpers';
import { clamp, createEnum, layerColors, MOCK_HANDLERS } from './helpers';
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, LayerVectorMask, AnimationFrame, Timeline, PlacedLayerFilter, UnitsValue, Filter, PlacedLayer, ReadOptions, Layer } from './psd';
import { PsdReader, readSignature, readUnicodeString, skipBytes, readUint32, readUint8, readFloat64, readUint16, readBytes, readInt16, checkSignature, readFloat32, readFixedPointPath32, readSection, readColor, readInt32, readPascalString, readUnicodeStringWithLength, readAsciiString, readPattern, readLayerInfo } from './psdReader';
import { PsdWriter, writeZeros, writeSignature, writeBytes, writeUint32, writeUint16, writeFloat64, writeUint8, writeInt16, writeFloat32, writeFixedPointPath32, writeUnicodeString, writeSection, writeUnicodeStringWithPadding, writeColor, writePascalString, writeInt32 } from './psdWriter';
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 } from './descriptor';
import { serializeEngineData, parseEngineData } from './engineData';
import { encodeEngineData, decodeEngineData } from './text';
import { decodeEngineData2 } from './engineData2';
import type { InternalImageResources } from './imageResources';
export interface ExtendedWriteOptions extends WriteOptions {
layerIds: Set<number>;
layerToId: Map<any, number>;
}
type HasMethod = (target: LayerAdditionalInfo) => boolean;
type ReadMethod = (reader: PsdReader, target: LayerAdditionalInfo, left: () => number, psd: Psd, imageResources: InternalImageResources) => void;
type WriteMethod = (writer: PsdWriter, target: LayerAdditionalInfo, psd: Psd, options: ExtendedWriteOptions) => void;
export interface InfoHandler {
key: string;
has: HasMethod;
read: ReadMethod;
write: WriteMethod;
}
const fromAtoZ = 'abcdefghijklmnopqrstuvwxyz';
export const infoHandlers: InfoHandler[] = [];
export const infoHandlersMap: { [key: string]: InfoHandler; } = {};
function addHandler(key: string, has: HasMethod, read: ReadMethod, write: WriteMethod) {
const handler: InfoHandler = { key, has, read, write };
infoHandlers.push(handler);
infoHandlersMap[handler.key] = handler;
}
function addHandlerAlias(key: string, target: string) {
infoHandlersMap[key] = infoHandlersMap[target];
}
function hasKey(key: keyof LayerAdditionalInfo) {
return (target: LayerAdditionalInfo) => target[key] !== undefined;
}
function readLength64(reader: PsdReader) {
if (readUint32(reader)) throw new Error(`Resource size above 4 GB limit at ${reader.offset.toString(16)}`);
return readUint32(reader);
}
function writeLength64(writer: PsdWriter, length: number) {
writeUint32(writer, 0);
writeUint32(writer, length);
}
addHandler(
'TySh',
hasKey('text'),
(reader, target, leftBytes) => {
if (readInt16(reader) !== 1) throw new Error(`Invalid TySh version`);
const transform: number[] = [];
for (let i = 0; i < 6; i++) transform.push(readFloat64(reader));
if (readInt16(reader) !== 50) throw new Error(`Invalid TySh text version`);
const text: TextDescriptor = readVersionAndDescriptor(reader);
// console.log(require('util').inspect(text, false, 99, false), 'utf8');
if (readInt16(reader) !== 1) throw new Error(`Invalid TySh warp version`);
const warp: WarpDescriptor = readVersionAndDescriptor(reader);
// console.log(require('util').inspect(warp, false, 99, false), 'utf8');
target.text = {
transform,
left: readFloat32(reader),
top: readFloat32(reader),
right: readFloat32(reader),
bottom: readFloat32(reader),
text: text['Txt '].replace(/\r/g, '\n'),
index: text.TextIndex || 0,
gridding: textGridding.decode(text.textGridding),
antiAlias: Annt.decode(text.AntA),
orientation: Ornt.decode(text.Ornt),
warp: {
style: warpStyle.decode(warp.warpStyle),
value: warp.warpValue || 0,
perspective: warp.warpPerspective || 0,
perspectiveOther: warp.warpPerspectiveOther || 0,
rotate: Ornt.decode(warp.warpRotate),
},
};
if (text.bounds) target.text.bounds = descBoundsToBounds(text.bounds);
if (text.boundingBox) target.text.boundingBox = descBoundsToBounds(text.boundingBox);
if (text.EngineData) {
const engineData = parseEngineData(text.EngineData);
const textData = decodeEngineData(engineData);
// console.log(require('util').inspect(engineData, false, 99, false), 'utf8');
// require('fs').writeFileSync(`layer-${target.name}.txt`, require('util').inspect(engineData, false, 99, false), 'utf8');
// const before = parseEngineData(text.EngineData);
// const after = encodeEngineData(engineData);
// require('fs').writeFileSync('before.txt', require('util').inspect(before, false, 99, false), 'utf8');
// require('fs').writeFileSync('after.txt', require('util').inspect(after, false, 99, false), 'utf8');
// console.log(require('util').inspect(parseEngineData(text.EngineData), false, 99, true));
target.text = { ...target.text, ...textData };
// console.log(require('util').inspect(target.text, false, 99, true));
}
skipBytes(reader, leftBytes());
},
(writer, target) => {
const text = target.text!;
const warp = text.warp || {};
const transform = text.transform || [1, 0, 0, 1, 0, 0];
const textDescriptor: TextDescriptor = {
'Txt ': (text.text || '').replace(/\r?\n/g, '\r'),
textGridding: textGridding.encode(text.gridding),
Ornt: Ornt.encode(text.orientation),
AntA: Annt.encode(text.antiAlias),
...(text.bounds ? { bounds: boundsToDescBounds(text.bounds) } : {}),
...(text.boundingBox ? { boundingBox: boundsToDescBounds(text.boundingBox) } : {}),
TextIndex: text.index || 0,
EngineData: serializeEngineData(encodeEngineData(text)),
};
writeInt16(writer, 1); // version
for (let i = 0; i < 6; i++) {
writeFloat64(writer, transform[i]);
}
writeInt16(writer, 50); // text version
writeVersionAndDescriptor(writer, '', 'TxLr', textDescriptor, 'text');
writeInt16(writer, 1); // warp version
writeVersionAndDescriptor(writer, '', 'warp', encodeWarp(warp));
writeFloat32(writer, text.left!);
writeFloat32(writer, text.top!);
writeFloat32(writer, text.right!);
writeFloat32(writer, text.bottom!);
// writeZeros(writer, 2);
},
);
// vector fills
addHandler(
'SoCo',
target => target.vectorFill !== undefined && target.vectorStroke === undefined &&
target.vectorFill.type === 'color',
(reader, target) => {
const descriptor = readVersionAndDescriptor(reader);
target.vectorFill = parseVectorContent(descriptor);
},
(writer, target) => {
const { descriptor } = serializeVectorContent(target.vectorFill!);
writeVersionAndDescriptor(writer, '', 'null', descriptor);
},
);
addHandler(
'GdFl',
target => target.vectorFill !== undefined && target.vectorStroke === undefined &&
(target.vectorFill.type === 'solid' || target.vectorFill.type === 'noise'),
(reader, target, left) => {
const descriptor = readVersionAndDescriptor(reader);
target.vectorFill = parseVectorContent(descriptor);
skipBytes(reader, left());
},
(writer, target) => {
const { descriptor } = serializeVectorContent(target.vectorFill!);
writeVersionAndDescriptor(writer, '', 'null', descriptor);
},
);
addHandler(
'PtFl',
target => target.vectorFill !== undefined && target.vectorStroke === undefined &&
target.vectorFill.type === 'pattern',
(reader, target) => {
const descriptor = readVersionAndDescriptor(reader);
target.vectorFill = parseVectorContent(descriptor);
},
(writer, target) => {
const { descriptor } = serializeVectorContent(target.vectorFill!);
writeVersionAndDescriptor(writer, '', 'null', descriptor);
},
);
addHandler(
'vscg',
target => target.vectorFill !== undefined && target.vectorStroke !== undefined,
(reader, target, left) => {
readSignature(reader); // key
const desc = readVersionAndDescriptor(reader);
target.vectorFill = parseVectorContent(desc);
skipBytes(reader, left());
},
(writer, target) => {
const { descriptor, key } = serializeVectorContent(target.vectorFill!);
writeSignature(writer, key);
writeVersionAndDescriptor(writer, '', 'null', descriptor);
},
);
export function readBezierKnot(reader: PsdReader, width: number, height: number) {
const y0 = readFixedPointPath32(reader) * height;
const x0 = readFixedPointPath32(reader) * width;
const y1 = readFixedPointPath32(reader) * height;
const x1 = readFixedPointPath32(reader) * width;
const y2 = readFixedPointPath32(reader) * height;
const x2 = readFixedPointPath32(reader) * width;
return [x0, y0, x1, y1, x2, y2];
}
function writeBezierKnot(writer: PsdWriter, points: number[], width: number, height: number) {
writeFixedPointPath32(writer, points[1] / height); // y0
writeFixedPointPath32(writer, points[0] / width); // x0
writeFixedPointPath32(writer, points[3] / height); // y1
writeFixedPointPath32(writer, points[2] / width); // x1
writeFixedPointPath32(writer, points[5] / height); // y2
writeFixedPointPath32(writer, points[4] / width); // x2
}
export const booleanOperations: BooleanOperation[] = ['exclude', 'combine', 'subtract', 'intersect'];
export function readVectorMask(reader: PsdReader, vectorMask: LayerVectorMask, width: number, height: number, size: number) {
const end = reader.offset + size;
const paths = vectorMask.paths;
let path: BezierPath | undefined = undefined;
while ((end - reader.offset) >= 26) {
const selector = readUint16(reader);
switch (selector) {
case 0: // Closed subpath length record
case 3: { // Open subpath length record
readUint16(reader); // count
const boolOp = readInt16(reader);
const flags = readUint16(reader); // bit 1 always 1 ?
skipBytes(reader, 18);
path = {
open: selector === 3,
knots: [],
fillRule: flags === 2 ? 'non-zero' : 'even-odd',
};
if (boolOp !== -1) path.operation = booleanOperations[boolOp];
paths.push(path);
break;
}
case 1: // Closed subpath Bezier knot, linked
case 2: // Closed subpath Bezier knot, unlinked
case 4: // Open subpath Bezier knot, linked
case 5: // Open subpath Bezier knot, unlinked
path!.knots.push({ linked: (selector === 1 || selector === 4), points: readBezierKnot(reader, width, height) });
break;
case 6: // Path fill rule record
skipBytes(reader, 24);
break;
case 7: { // Clipboard record
// TODO: check if these need to be multiplied by document size
const top = readFixedPointPath32(reader);
const left = readFixedPointPath32(reader);
const bottom = readFixedPointPath32(reader);
const right = readFixedPointPath32(reader);
const resolution = readFixedPointPath32(reader);
skipBytes(reader, 4);
vectorMask.clipboard = { top, left, bottom, right, resolution };
break;
}
case 8: // Initial fill rule record
vectorMask.fillStartsWithAllPixels = !!readUint16(reader);
skipBytes(reader, 22);
break;
default: throw new Error('Invalid vmsk section');
}
}
return paths;
}
addHandler(
'vmsk',
hasKey('vectorMask'),
(reader, target, left, { width, height }) => {
if (readUint32(reader) !== 3) throw new Error('Invalid vmsk version');
target.vectorMask = { paths: [] };
const vectorMask = target.vectorMask;
const flags = readUint32(reader);
vectorMask.invert = (flags & 1) !== 0;
vectorMask.notLink = (flags & 2) !== 0;
vectorMask.disable = (flags & 4) !== 0;
readVectorMask(reader, vectorMask, width, height, left());
// drawBezierPaths(vectorMask.paths, width, height, 'out.png');
skipBytes(reader, left());
},
(writer, target, { width, height }) => {
const vectorMask = target.vectorMask!;
const flags =
(vectorMask.invert ? 1 : 0) |
(vectorMask.notLink ? 2 : 0) |
(vectorMask.disable ? 4 : 0);
writeUint32(writer, 3); // version
writeUint32(writer, flags);
// initial entry
writeUint16(writer, 6);
writeZeros(writer, 24);
const clipboard = vectorMask.clipboard;
if (clipboard) {
writeUint16(writer, 7);
writeFixedPointPath32(writer, clipboard.top);
writeFixedPointPath32(writer, clipboard.left);
writeFixedPointPath32(writer, clipboard.bottom);
writeFixedPointPath32(writer, clipboard.right);
writeFixedPointPath32(writer, clipboard.resolution);
writeZeros(writer, 4);
}
if (vectorMask.fillStartsWithAllPixels !== undefined) {
writeUint16(writer, 8);
writeUint16(writer, vectorMask.fillStartsWithAllPixels ? 1 : 0);
writeZeros(writer, 22);
}
for (const path of vectorMask.paths) {
writeUint16(writer, path.open ? 3 : 0);
writeUint16(writer, path.knots.length);
writeUint16(writer, path.operation ? booleanOperations.indexOf(path.operation) : -1); // -1 for undefined
writeUint16(writer, path.fillRule === 'non-zero' ? 2 : 1);
writeZeros(writer, 18); // TODO: these are sometimes non-zero
const linkedKnot = path.open ? 4 : 1;
const unlinkedKnot = path.open ? 5 : 2;
for (const { linked, points } of path.knots) {
writeUint16(writer, linked ? linkedKnot : unlinkedKnot);
writeBezierKnot(writer, points, width, height);
}
}
},
);
// TODO: need to write vmsk if has outline ?
addHandlerAlias('vsms', 'vmsk');
// addHandlerAlias('vmsk', 'vsms');
addHandler(
'vowv', // something with vectors?
hasKey('vowv'),
(reader, target) => {
target.vowv = readUint32(reader); // always 2 ????
},
(writer, target) => {
writeUint32(writer, target.vowv!);
},
);
interface VogkDescriptor {
keyDescriptorList: {
keyShapeInvalidated?: boolean;
keyOriginType?: number;
keyOriginResolution?: number;
keyOriginRRectRadii?: {
unitValueQuadVersion: number;
topRight: DescriptorUnitsValue;
topLeft: DescriptorUnitsValue;
bottomLeft: DescriptorUnitsValue;
bottomRight: DescriptorUnitsValue;
};
keyOriginShapeBBox?: {
unitValueQuadVersion: number;
'Top ': DescriptorUnitsValue | number;
Left: DescriptorUnitsValue | number;
Btom: DescriptorUnitsValue | number;
Rght: DescriptorUnitsValue | number;
};
keyOriginBoxCorners?: {
rectangleCornerA: { Hrzn: number; Vrtc: number; };
rectangleCornerB: { Hrzn: number; Vrtc: number; };
rectangleCornerC: { Hrzn: number; Vrtc: number; };
rectangleCornerD: { Hrzn: number; Vrtc: number; };
};
Trnf?: { xx: number; xy: number; yx: number; yy: number; tx: number; ty: number; },
keyOriginIndex: number;
}[];
}
addHandler(
'vogk',
hasKey('vectorOrigination'),
(reader, target, left) => {
if (readInt32(reader) !== 1) throw new Error(`Invalid vogk version`);
const desc = readVersionAndDescriptor(reader) as VogkDescriptor;
// console.log(require('util').inspect(desc, false, 99, true));
target.vectorOrigination = { keyDescriptorList: [] };
for (const i of desc.keyDescriptorList) {
const item: KeyDescriptorItem = {};
if (i.keyShapeInvalidated != null) item.keyShapeInvalidated = i.keyShapeInvalidated;
if (i.keyOriginType != null) item.keyOriginType = i.keyOriginType;
if (i.keyOriginResolution != null) item.keyOriginResolution = i.keyOriginResolution;
if (i.keyOriginShapeBBox) {
item.keyOriginShapeBoundingBox = {
top: parseUnitsOrNumber(i.keyOriginShapeBBox['Top ']),
left: parseUnitsOrNumber(i.keyOriginShapeBBox.Left),
bottom: parseUnitsOrNumber(i.keyOriginShapeBBox.Btom),
right: parseUnitsOrNumber(i.keyOriginShapeBBox.Rght),
};
}
const rectRadii = i.keyOriginRRectRadii;
if (rectRadii) {
item.keyOriginRRectRadii = {
topRight: parseUnits(rectRadii.topRight),
topLeft: parseUnits(rectRadii.topLeft),
bottomLeft: parseUnits(rectRadii.bottomLeft),
bottomRight: parseUnits(rectRadii.bottomRight),
};
}
const corners = i.keyOriginBoxCorners;
if (corners) {
item.keyOriginBoxCorners = [
{ x: corners.rectangleCornerA.Hrzn, y: corners.rectangleCornerA.Vrtc },
{ x: corners.rectangleCornerB.Hrzn, y: corners.rectangleCornerB.Vrtc },
{ x: corners.rectangleCornerC.Hrzn, y: corners.rectangleCornerC.Vrtc },
{ x: corners.rectangleCornerD.Hrzn, y: corners.rectangleCornerD.Vrtc },
];
}
const trnf = i.Trnf;
if (trnf) {
item.transform = [trnf.xx, trnf.xy, trnf.xy, trnf.yy, trnf.tx, trnf.ty];
}
target.vectorOrigination.keyDescriptorList.push(item);
}
skipBytes(reader, left());
},
(writer, target) => {
target;
const orig = target.vectorOrigination!;
const desc: VogkDescriptor = { keyDescriptorList: [] };
for (let i = 0; i < orig.keyDescriptorList.length; i++) {
const item = orig.keyDescriptorList[i];
desc.keyDescriptorList.push({} as any); // we're adding keyOriginIndex at the end
const out = desc.keyDescriptorList[desc.keyDescriptorList.length - 1];
if (item.keyOriginType != null) out.keyOriginType = item.keyOriginType;
if (item.keyOriginResolution != null) out.keyOriginResolution = item.keyOriginResolution;
const radii = item.keyOriginRRectRadii;
if (radii) {
out.keyOriginRRectRadii = {
unitValueQuadVersion: 1,
topRight: unitsValue(radii.topRight, 'topRight'),
topLeft: unitsValue(radii.topLeft, 'topLeft'),
bottomLeft: unitsValue(radii.bottomLeft, 'bottomLeft'),
bottomRight: unitsValue(radii.bottomRight, 'bottomRight'),
};
}
const box = item.keyOriginShapeBoundingBox;
if (box) {
out.keyOriginShapeBBox = {
unitValueQuadVersion: 1,
'Top ': unitsValue(box.top, 'top'),
Left: unitsValue(box.left, 'left'),
Btom: unitsValue(box.bottom, 'bottom'),
Rght: unitsValue(box.right, 'right'),
};
}
const corners = item.keyOriginBoxCorners;
if (corners && corners.length === 4) {
out.keyOriginBoxCorners = {
rectangleCornerA: { Hrzn: corners[0].x, Vrtc: corners[0].y },
rectangleCornerB: { Hrzn: corners[1].x, Vrtc: corners[1].y },
rectangleCornerC: { Hrzn: corners[2].x, Vrtc: corners[2].y },
rectangleCornerD: { Hrzn: corners[3].x, Vrtc: corners[3].y },
};
}
const transform = item.transform;
if (transform && transform.length === 6) {
out.Trnf = {
xx: transform[0],
xy: transform[1],
yx: transform[2],
yy: transform[3],
tx: transform[4],
ty: transform[5],
};
}
if (item.keyShapeInvalidated != null) out.keyShapeInvalidated = item.keyShapeInvalidated;
out.keyOriginIndex = i;
}
writeInt32(writer, 1); // version
writeVersionAndDescriptor(writer, '', 'null', desc);
}
);
addHandler(
'lmfx',
target => target.effects !== undefined && hasMultiEffects(target.effects),
(reader, target, left) => {
const version = readUint32(reader);
if (version !== 0) throw new Error('Invalid lmfx version');
const desc: LmfxDescriptor = readVersionAndDescriptor(reader);
// console.log('READ', require('util').inspect(desc, false, 99, true));
// discard if read in 'lrFX' or 'lfx2' section
target.effects = parseEffects(desc, !!reader.logMissingFeatures);
skipBytes(reader, left());
},
(writer, target, _, options) => {
const desc = serializeEffects(target.effects!, !!options.logMissingFeatures, true);
// console.log('WRITE', require('util').inspect(desc, false, 99, true));
writeUint32(writer, 0); // version
writeVersionAndDescriptor(writer, '', 'null', desc);
},
);
addHandler(
'lrFX',
hasKey('effects'),
(reader, target, left) => {
if (!target.effects) target.effects = readEffects(reader);
skipBytes(reader, left());
},
(writer, target) => {
writeEffects(writer, target.effects!);
},
);
addHandler(
'luni',
hasKey('name'),
(reader, target, left) => {
if (left() > 4) {
const length = readUint32(reader);
if (left() >= (length * 2)) {
target.name = readUnicodeStringWithLength(reader, length);
} else {
if (reader.logDevFeatures) reader.log('name in luni section is too long');
}
} else {
if (reader.logDevFeatures) reader.log('empty luni section');
}
skipBytes(reader, left());
},
(writer, target) => {
writeUnicodeString(writer, target.name!);
// writeUint16(writer, 0); // padding (but not extending string length)
},
);
addHandler(
'lnsr',
hasKey('nameSource'),
(reader, target) => target.nameSource = readSignature(reader),
(writer, target) => writeSignature(writer, target.nameSource!),
);
addHandler(
'lyid',
hasKey('id'),
(reader, target) => {
target.id = readUint32(reader);
},
(writer, target, _psd, options) => {
let id = target.id!;
while (options.layerIds.has(id)) id += 100; // make sure we don't have duplicate layer ids
writeUint32(writer, id);
options.layerIds.add(id);
options.layerToId.set(target, id);
},
);
addHandler(
'lsct',
hasKey('sectionDivider'),
(reader, target, left) => {
target.sectionDivider = { type: readUint32(reader) };
if (left()) {
checkSignature(reader, '8BIM');
target.sectionDivider.key = readSignature(reader);
}
if (left()) {
target.sectionDivider.subType = readUint32(reader);
}
},
(writer, target) => {
writeUint32(writer, target.sectionDivider!.type);
if (target.sectionDivider!.key) {
writeSignature(writer, '8BIM');
writeSignature(writer, target.sectionDivider!.key);
if (target.sectionDivider!.subType !== undefined) {
writeUint32(writer, target.sectionDivider!.subType);
}
}
},
);
// it seems lsdk is used when there's a layer is nested more than 6 levels, but I don't know why?
// maybe some limitation of old version of PS?
addHandlerAlias('lsdk', 'lsct');
addHandler(
'clbl',
hasKey('blendClippendElements'),
(reader, target) => {
target.blendClippendElements = !!readUint8(reader);
skipBytes(reader, 3);
},
(writer, target) => {
writeUint8(writer, target.blendClippendElements ? 1 : 0);
writeZeros(writer, 3);
},
);
addHandler(
'infx',
hasKey('blendInteriorElements'),
(reader, target) => {
target.blendInteriorElements = !!readUint8(reader);
skipBytes(reader, 3);
},
(writer, target) => {
writeUint8(writer, target.blendInteriorElements ? 1 : 0);
writeZeros(writer, 3);
},
);
addHandler(
'knko',
hasKey('knockout'),
(reader, target) => {
target.knockout = !!readUint8(reader);
skipBytes(reader, 3);
},
(writer, target) => {
writeUint8(writer, target.knockout ? 1 : 0);
writeZeros(writer, 3);
},
);
addHandler(
'lmgm',
hasKey('layerMaskAsGlobalMask'),
(reader, target) => {
target.layerMaskAsGlobalMask = !!readUint8(reader);
skipBytes(reader, 3);
},
(writer, target) => {
writeUint8(writer, target.layerMaskAsGlobalMask ? 1 : 0);
writeZeros(writer, 3);
},
);
addHandler(
'lspf',
hasKey('protected'),
(reader, target) => {
const flags = readUint32(reader);
target.protected = {
transparency: (flags & 0x01) !== 0,
composite: (flags & 0x02) !== 0,
position: (flags & 0x04) !== 0,
};
if (flags & 0x08) target.protected.artboards = true;
},
(writer, target) => {
const flags =
(target.protected!.transparency ? 0x01 : 0) |
(target.protected!.composite ? 0x02 : 0) |
(target.protected!.position ? 0x04 : 0) |
(target.protected!.artboards ? 0x08 : 0);
writeUint32(writer, flags);
},
);
addHandler(
'lclr',
hasKey('layerColor'),
(reader, target) => {
const color = readUint16(reader);
skipBytes(reader, 6);
target.layerColor = layerColors[color];
},
(writer, target) => {
const index = layerColors.indexOf(target.layerColor!);
writeUint16(writer, index === -1 ? 0 : index);
writeZeros(writer, 6);
},
);
interface CustomDescriptor {
layerTime?: number;
}
interface CmlsDescriptor {
origFXRefPoint?: { Hrzn: number; Vrtc: number; };
LyrI: number;
layerSettings: {
enab?: boolean;
Ofst?: { Hrzn: number; Vrtc: number; };
FXRefPoint?: { Hrzn: number; Vrtc: number; };
compList: number[];
}[];
}
addHandler(
'shmd', // Metadata setting
target => target.timestamp !== undefined || target.animationFrames !== undefined || target.animationFrameFlags !== undefined || target.timeline !== undefined || target.comps !== undefined,
(reader, target, left) => {
const count = readUint32(reader);
for (let i = 0; i < count; i++) {
checkSignature(reader, '8BIM');
const key = readSignature(reader);
readUint8(reader); // copy
skipBytes(reader, 3);
readSection(reader, 1, left => {
if (key === 'cust') {
const desc = readVersionAndDescriptor(reader) as CustomDescriptor;
// console.log('cust', target.name, require('util').inspect(desc, false, 99, true));
if (desc.layerTime !== undefined) target.timestamp = desc.layerTime;
} else if (key === 'mlst') {
const desc = readVersionAndDescriptor(reader) as FrameListDescriptor;
// console.log('mlst', target.name, require('util').inspect(desc, false, 99, true));
target.animationFrames = [];
for (let i = 0; i < desc.LaSt.length; i++) {
const f = desc.LaSt[i];
const frame: AnimationFrame = { frames: f.FrLs };
if (f.enab !== undefined) frame.enable = f.enab;
if (f.Ofst) frame.offset = horzVrtcToXY(f.Ofst);
if (f.FXRf) frame.referencePoint = horzVrtcToXY(f.FXRf);
if (f.Lefx) frame.effects = parseEffects(f.Lefx, !!reader.logMissingFeatures);
if (f.blendOptions && f.blendOptions.Opct) frame.opacity = parsePercent(f.blendOptions.Opct);
target.animationFrames.push(frame);
}
} else if (key === 'mdyn') {
// frame flags
readUint16(reader); // unknown
const propagate = readUint8(reader);
const flags = readUint8(reader);
target.animationFrameFlags = {
propagateFrameOne: !propagate,
unifyLayerPosition: (flags & 1) !== 0,
unifyLayerStyle: (flags & 2) !== 0,
unifyLayerVisibility: (flags & 4) !== 0,
};
} else if (key === 'tmln') {
const desc = readVersionAndDescriptor(reader) as TimelineDescriptor;
const timeScope = desc.timeScope;
// console.log('tmln', target.name, target.id, require('util').inspect(desc, false, 99, true));
const timeline: Timeline = {
start: frac(timeScope.Strt),
duration: frac(timeScope.duration),
inTime: frac(timeScope.inTime),
outTime: frac(timeScope.outTime),
autoScope: desc.autoScope,
audioLevel: desc.audioLevel,
};
if (desc.trackList) {
timeline.tracks = parseTrackList(desc.trackList, !!reader.logMissingFeatures);
}
target.timeline = timeline;
// console.log('tmln:result', target.name, target.id, require('util').inspect(timeline, false, 99, true));
} else if (key === 'cmls') {
const desc = readVersionAndDescriptor(reader) as CmlsDescriptor;
// console.log('cmls', require('util').inspect(desc, false, 99, true));
target.comps = {
settings: [],
};
if (desc.origFXRefPoint) target.comps.originalEffectsReferencePoint = { x: desc.origFXRefPoint.Hrzn, y: desc.origFXRefPoint.Vrtc };
for (const item of desc.layerSettings) {
target.comps.settings.push({ compList: item.compList });
const t = target.comps.settings[target.comps.settings.length - 1];
if ('enab' in item) t.enabled = item.enab;
if (item.Ofst) t.offset = { x: item.Ofst.Hrzn, y: item.Ofst.Vrtc };
if (item.FXRefPoint) t.effectsReferencePoint = { x: item.FXRefPoint.Hrzn, y: item.FXRefPoint.Vrtc };
}
} else if (key === 'extn') {
interface ExtnDescriptor {
generatorSettings: {
exportAs: {
exportOption: string;
};
layerTime: number;
};
}
const desc = readVersionAndDescriptor(reader) as ExtnDescriptor;
// console.log(require('util').inspect(desc, false, 99, true));
desc; // TODO: save this
reader.logMissingFeatures && reader.log('Unhandled "shmd" section key', key);
} else {
reader.logMissingFeatures && reader.log('Unhandled "shmd" section key', key);
}
skipBytes(reader, left());
});
}
skipBytes(reader, left());
},
(writer, target, _, options) => {
const { animationFrames, animationFrameFlags, timestamp, timeline, comps } = target;
let count = 0;
if (animationFrames) count++;
if (animationFrameFlags) count++;
if (timeline) count++;
if (timestamp !== undefined) count++;
if (comps) count++;
writeUint32(writer, count);
if (animationFrames) {
writeSignature(writer, '8BIM');
writeSignature(writer, 'mlst');
writeUint8(writer, 0); // copy (always false)
writeZeros(writer, 3);
writeSection(writer, 2, () => {
const desc: FrameListDescriptor = {
LaID: target.id ?? 0,
LaSt: [],
};
for (let i = 0; i < animationFrames.length; i++) {
const f = animationFrames[i];
const frame: FrameDescriptor = {} as any;
if (f.enable !== undefined) frame.enab = f.enable;
frame.FrLs = f.frames;
if (f.offset) frame.Ofst = xyToHorzVrtc(f.offset);
if (f.referencePoint) frame.FXRf = xyToHorzVrtc(f.referencePoint);
if (f.effects) frame.Lefx = serializeEffects(f.effects, false, false);
if (f.opacity !== undefined) frame.blendOptions = { Opct: unitsPercent(f.opacity) };
desc.LaSt.push(frame);
}
writeVersionAndDescriptor(writer, '', 'null', desc);
}, true);
}
if (animationFrameFlags) {
writeSignature(writer, '8BIM');
writeSignature(writer, 'mdyn');
writeUint8(writer, 0); // copy (always false)
writeZeros(writer, 3);
writeSection(writer, 2, () => {
writeUint16(writer, 0); // unknown
writeUint8(writer, animationFrameFlags.propagateFrameOne ? 0x0 : 0xf);
writeUint8(writer,
(animationFrameFlags.unifyLayerPosition ? 1 : 0) |
(animationFrameFlags.unifyLayerStyle ? 2 : 0) |
(animationFrameFlags.unifyLayerVisibility ? 4 : 0));
});
}
if (timeline) {
writeSignature(writer, '8BIM');
writeSignature(writer, 'tmln');
writeUint8(writer, 0); // copy (always false)
writeZeros(writer, 3);
writeSection(writer, 2, () => {
const desc: TimelineDescriptor = {
Vrsn: 1,
timeScope: {
Vrsn: 1,
Strt: timeline.start,
duration: timeline.duration,
inTime: timeline.inTime,
outTime: timeline.outTime,
},
autoScope: timeline.autoScope,
audioLevel: timeline.audioLevel,
} as any;
if (timeline.tracks) {
desc.trackList = serializeTrackList(timeline.tracks);
}
const id = options.layerToId.get(target) || target.id;
if (!id) throw new Error('You need to provide layer.id value whan writing document with animations');
desc.LyrI = id;
// console.log('WRITE:tmln', target.name, target.id, require('util').inspect(desc, false, 99, true));
writeVersionAndDescriptor(writer, '', 'null', desc, 'anim');
}, true);
}
if (timestamp !== undefined) {
writeSignature(writer, '8BIM');
writeSignature(writer, 'cust');
writeUint8(writer, 0); // copy (always false)
writeZeros(writer, 3);
writeSection(writer, 2, () => {
const desc: CustomDescriptor = {
layerTime: timestamp,
};
writeVersionAndDescriptor(writer, '', 'metadata', desc);
}, true);
}
if (comps) {
writeSignature(writer, '8BIM');
writeSignature(writer, 'cmls');
writeUint8(writer, 0); // copy (always false)
writeZeros(writer, 3);
writeSection(writer, 2, () => {
const id = options.layerToId.get(target) || target.id;
if (!id) throw new Error('You need to provide layer.id value whan writing document with layer comps');
const desc: CmlsDescriptor = {} as any;
if (comps.originalEffectsReferencePoint) {
desc.origFXRefPoint = { Hrzn: comps.originalEffectsReferencePoint.x, Vrtc: comps.originalEffectsReferencePoint.y };
}
desc.LyrI = id;
desc.layerSettings = [];
for (const item of comps.settings) {
const t: CmlsDescriptor['layerSettings'][0] = {} as any;
if (item.enabled !== undefined) t.enab = item.enabled;
if (item.offset) t.Ofst = { Hrzn: item.offset.x, Vrtc: item.offset.y };
if (item.effectsReferencePoint) t.FXRefPoint = { Hrzn: item.effectsReferencePoint.x, Vrtc: item.effectsReferencePoint.y };
t.compList = item.compList;
desc.layerSettings.push(t);
}
// console.log('cmls', require('util').inspect(desc, false, 99, true));
writeVersionAndDescriptor(writer, '', 'null', desc);
}, true);
}
},
);
addHandler(
'vstk',
hasKey('vectorStroke'),
(reader, target, left) => {
const desc = readVersionAndDescriptor(reader) as StrokeDescriptor;
// console.log(require('util').inspect(desc, false, 99, true));
target.vectorStroke = {
strokeEnabled: desc.strokeEnabled,
fillEnabled: desc.fillEnabled,
lineWidth: parseUnits(desc.strokeStyleLineWidth),
lineDashOffset: parseUnits(desc.strokeStyleLineDashOffset),
miterLimit: desc.strokeStyleMiterLimit,
lineCapType: strokeStyleLineCapType.decode(desc.strokeStyleLineCapType),
lineJoinType: strokeStyleLineJoinType.decode(desc.strokeStyleLineJoinType),
lineAlignment: strokeStyleLineAlignment.decode(desc.strokeStyleLineAlignment),
scaleLock: desc.strokeStyleScaleLock,
strokeAdjust: desc.strokeStyleStrokeAdjust,
lineDashSet: desc.strokeStyleLineDashSet.map(parseUnits),
blendMode: BlnM.decode(desc.strokeStyleBlendMode),
opacity: parsePercent(desc.strokeStyleOpacity),
content: parseVectorContent(desc.strokeStyleContent),
resolution: desc.strokeStyleResolution,
};
skipBytes(reader, left());
},
(writer, target) => {
const stroke = target.vectorStroke!;
const desc: StrokeDescriptor = {
strokeStyleVersion: 2,
strokeEnabled: !!stroke.strokeEnabled,
fillEnabled: !!stroke.fillEnabled,
strokeStyleLineWidth: stroke.lineWidth || { value: 3, units: 'Points' },
strokeStyleLineDashOffset: stroke.lineDashOffset || { value: 0, units: 'Points' },
strokeStyleMiterLimit: stroke.miterLimit ?? 100,
strokeStyleLineCapType: strokeStyleLineCapType.encode(stroke.lineCapType),
strokeStyleLineJoinType: strokeStyleLineJoinType.encode(stroke.lineJoinType),
strokeStyleLineAlignment: strokeStyleLineAlignment.encode(stroke.lineAlignment),
strokeStyleScaleLock: !!stroke.scaleLock,
strokeStyleStrokeAdjust: !!stroke.strokeAdjust,
strokeStyleLineDashSet: stroke.lineDashSet || [],
strokeStyleBlendMode: BlnM.encode(stroke.blendMode),
strokeStyleOpacity: unitsPercent(stroke.opacity ?? 1),
strokeStyleContent: serializeVectorContent(
stroke.content || { type: 'color', color: { r: 0, g: 0, b: 0 } }).descriptor,
strokeStyleResolution: stroke.resolution ?? 72,
};
writeVersionAndDescriptor(writer, '', 'strokeStyle', desc);
},
);
interface ArtbDescriptor {
artboardRect: { 'Top ': number; Left: number; Btom: number; Rght: number; };
guideIndeces: any[];
artboardPresetName: string;
'Clr ': DescriptorColor;
artboardBackgroundType: number;
}
addHandler(
'artb', // per-layer arboard info
hasKey('artboard'),
(reader, target, left) => {
const desc = readVersionAndDescriptor(reader) as ArtbDescriptor;
const rect = desc.artboardRect;
target.artboard = {
rect: { top: rect['Top '], left: rect.Left, bottom: rect.Btom, right: rect.Rght },
guideIndices: desc.guideIndeces,
presetName: desc.artboardPresetName,
color: parseColor(desc['Clr ']),
backgroundType: desc.artboardBackgroundType,
};
skipBytes(reader, left());
},
(writer, target) => {
const artboard = target.artboard!;
const rect = artboard.rect;
const desc: ArtbDescriptor = {
artboardRect: { 'Top ': rect.top, Left: rect.left, Btom: rect.bottom, Rght: rect.right },
guideIndeces: artboard.guideIndices || [],
artboardPresetName: artboard.presetName || '',
'Clr ': serializeColor(artboard.color),
artboardBackgroundType: artboard.backgroundType ?? 1,
};
writeVersionAndDescriptor(writer, '', 'artboard', desc);
},
);
addHandler(
'sn2P',
hasKey('usingAlignedRendering'),
(reader, target) => target.usingAlignedRendering = !!readUint32(reader),
(writer, target) => writeUint32(writer, target.usingAlignedRendering ? 1 : 0),
);
const placedLayerTypes: PlacedLayerType[] = ['unknown', 'vector', 'raster', 'image stack'];
function parseWarp(warp: WarpDescriptor & QuiltWarpDescriptor): Warp {
const result: Warp = {
style: warpStyle.decode(warp.warpStyle),
...(warp.warpValues ? { values: warp.warpValues } : { value: warp.warpValue || 0 }),
perspective: warp.warpPerspective || 0,
perspectiveOther: warp.warpPerspectiveOther || 0,
rotate: Ornt.decode(warp.warpRotate),
bounds: warp.bounds && {
top: parseUnitsOrNumber(warp.bounds['Top ']),
left: parseUnitsOrNumber(warp.bounds.Left),
bottom: parseUnitsOrNumber(warp.bounds.Btom),
right: parseUnitsOrNumber(warp.bounds.Rght),
},
uOrder: warp.uOrder,
vOrder: warp.vOrder,
};
if (warp.deformNumRows != null || warp.deformNumCols != null) {
result.deformNumRows = warp.deformNumRows;
result.deformNumCols = warp.deformNumCols;
}
const envelopeWarp = warp.customEnvelopeWarp;
if (envelopeWarp) {
result.customEnvelopeWarp = {
meshPoints: [],
};
const xs = envelopeWarp.meshPoints.find(i => i.type === 'Hrzn')?.values || [];
const ys = envelopeWarp.meshPoints.find(i => i.type === 'Vrtc')?.values || [];
for (let i = 0; i < xs.length; i++) {
result.customEnvelopeWarp!.meshPoints.push({ x: xs[i], y: ys[i] });
}
if (envelopeWarp.quiltSliceX || envelopeWarp.quiltSliceY) {
result.customEnvelopeWarp.quiltSliceX = envelopeWarp.quiltSliceX?.[0]?.values || [];
result.customEnvelopeWarp.quiltSliceY = envelopeWarp.quiltSliceY?.[0]?.values || [];
}
}
return result;
}
function isQuiltWarp(warp: Warp) {
return warp.deformNumCols != null || warp.deformNumRows != null ||
warp.customEnvelopeWarp?.quiltSliceX || warp.customEnvelopeWarp?.quiltSliceY;
}
function encodeWarp(warp: Warp): WarpDescriptor {
const bounds = warp.bounds;
const desc: WarpDescriptor = {
warpStyle: warpStyle.encode(warp.style),
...(warp.values ? { warpValues: warp.values } : { warpValue: warp.value || 0 }),
warpPerspective: warp.perspective || 0,
warpPerspectiveOther: warp.perspectiveOther || 0,
warpRotate: Ornt.encode(warp.rotate),
bounds: /*1 ? { // testing
_classID: 'classFloatRect',
'Top ': bounds && bounds.top && bounds.top.value || 0,
Left: bounds && bounds.left && bounds.left.value || 0,
Btom: bounds && bounds.bottom && bounds.bottom.value || 0,
Rght: bounds && bounds.right && bounds.right.value || 0,
} :*/ {
'Top ': unitsValue(bounds && bounds.top || { units: 'Pixels', value: 0 }, 'bounds.top'),
Left: unitsValue(bounds && bounds.left || { units: 'Pixels', value: 0 }, 'bounds.left'),
Btom: unitsValue(bounds && bounds.bottom || { units: 'Pixels', value: 0 }, 'bounds.bottom'),
Rght: unitsValue(bounds && bounds.right || { units: 'Pixels', value: 0 }, 'bounds.right'),
},
uOrder: warp.uOrder || 0,
vOrder: warp.vOrder || 0,
};
const isQuilt = isQuiltWarp(warp);
if (isQuilt) {
const desc2 = desc as QuiltWarpDescriptor;
desc2.deformNumRows = warp.deformNumRows || 0;
desc2.deformNumCols = warp.deformNumCols || 0;
}
const customEnvelopeWarp = warp.customEnvelopeWarp;
if (customEnvelopeWarp) {
const meshPoints = customEnvelopeWarp.meshPoints || [];
if (isQuilt) {
const desc2 = desc as QuiltWarpDescriptor;
desc2.customEnvelopeWarp = {
_name: '',
_classID: 'customEnvelopeWarp',
quiltSliceX: [{
type: 'quiltSliceX',
values: customEnvelopeWarp.quiltSliceX || [],
}],
quiltSliceY: [{
type: 'quiltSliceY',
values: customEnvelopeWarp.quiltSliceY || [],
}],
meshPoints: [
{ type: 'Hrzn', values: meshPoints.map(p => p.x) },
{ type: 'Vrtc', values: meshPoints.map(p => p.y) },
],
};
} else {
desc.customEnvelopeWarp = {
_name: '',
_classID: 'customEnvelopeWarp',
meshPoints: [
{ type: 'Hrzn', values: meshPoints.map(p => p.x) },
{ type: 'Vrtc', values: meshPoints.map(p => p.y) },
],
};
}
}
return desc;
}
addHandler(
'PlLd',
hasKey('placedLayer'),
(reader, target, left) => {
if (readSignature(reader) !== 'plcL') throw new Error(`Invalid PlLd signature`);
if (readInt32(reader) !== 3) throw new Error(`Invalid PlLd version`);
const id = readPascalString(reader, 1);
const pageNumber = readInt32(reader);
const totalPages = readInt32(reader); // TODO: check how this works ?
readInt32(reader); // anitAliasPolicy 16
const placedLayerType = readInt32(reader); // 0 = unknown, 1 = vector, 2 = raster, 3 = image stack
if (!placedLayerTypes[placedLayerType]) throw new Error('Invalid PlLd type');
const transform: number[] = [];
for (let i = 0; i < 8; i++) transform.push(readFloat64(reader)); // x, y of 4 corners of the transform
const warpVersion = readInt32(reader);
if (warpVersion !== 0) throw new Error(`Invalid Warp version ${warpVersion}`);
const warp: WarpDescriptor & QuiltWarpDescriptor = readVersionAndDescriptor(reader);
target.placedLayer = target.placedLayer || { // skip if SoLd already set it
id,
type: placedLayerTypes[placedLayerType],
pageNumber,
totalPages,
transform,
warp: parseWarp(warp),
};
// console.log('PlLd warp', require('util').inspect(warp, false, 99, true));
// console.log('PlLd', require('util').inspect(target.placedLayer, false, 99, true));
skipBytes(reader, left());
},
(writer, target) => {
const placed = target.placedLayer!;
writeSignature(writer, 'plcL');
writeInt32(writer, 3); // version
if (!placed.id || typeof placed.id !== 'string' || !/^[0-9a-f]{8}-([0-9a-f]{4}-){3}[0-9a-f]{12}$/.test(placed.id)) {
throw new Error('Placed layer ID must be in a GUID format (example: 20953ddb-9391-11ec-b4f1-c15674f50bc4)');
}
writePascalString(writer, placed.id, 1);
writeInt32(writer, 1); // pageNumber
writeInt32(writer, 1); // totalPages
writeInt32(writer, 16); // anitAliasPolicy
if (placedLayerTypes.indexOf(placed.type) === -1) throw new Error('Invalid placedLayer type');
writeInt32(writer, placedLayerTypes.indexOf(placed.type));
for (let i = 0; i < 8; i++) writeFloat64(writer, placed.transform[i]);
writeInt32(writer, 0); // warp version
const warp = getWarpFromPlacedLayer(placed);
const isQuilt = isQuiltWarp(warp);
const type = isQuilt ? 'quiltWarp' : 'warp';
writeVersionAndDescriptor(writer, '', type, encodeWarp(warp), type);
},
);
interface HrznVrtcDescriptor {
_name: '';
_classID: 'Pnt ';
Hrzn: DescriptorUnitsValue;
Vrtc: DescriptorUnitsValue;
}
/*
interface K3DLight {
'Nm ': string;
'Rd ': number;
'Grn ': number;
'Bl ': number;
hots: number;
FlOf: number;
shdw: number;
attn: boolean;
attt: number;
atta: number;
attb: number;
attc: number;
orad: number;
irad: number;
mult: number;
Type: number;
ison: boolean;
ssml: number;
afon: boolean;
afpw: number;
key3DMatrix: {
key3DMatrixData: Uint8Array;
},
'X ': number;
'Y ': number;
'Z ': number;
tarx: number;
tary: number;
tarz: number;
key3DPosition: {
key3DXPos: number;
key3DYPos: number;
key3DZPos: number;
key3DXAngle: number;
key3DYAngle: number;
key3DZAngle: number;
};
}
*/
type SoLdDescriptorFilterItem = {
_name: '',
_classID: 'filterFX',
'Nm ': string;
blendOptions: {
_name: '';
_classID: 'blendOptions';
Opct: DescriptorUnitsValue;
'Md ': string; // blend mode
};
enab: boolean;
hasoptions: boolean;
FrgC: DescriptorColor;
BckC: DescriptorColor;
} & ({
filterID: 1098281575; // average
} | {
filterID: 1114403360; // blur
} | {
filterID: 1114403405; // blur more
} | {
filterID: 697;
Fltr: {
_name: 'Box Blur';
_classID: 'boxblur';
'Rds ': DescriptorUnitsValue;
};
} | {
filterID: 1198747202;
Fltr: {
_name: 'Gaussian Blur' | '高斯模糊';
_classID: 'GsnB';
'Rds ': DescriptorUnitsValue;
};
} | {
filterID: 1299476034;
Fltr: {
_name: 'Motion Blur';
_classID: 'MtnB';
Angl: number;
Dstn: DescriptorUnitsValue;
};
} | {
filterID: 1382313026;
Fltr: {
_name: 'Radial Blur';
_classID: 'RdlB';
Amnt: number;
BlrM: string;
BlrQ: string;
};
} | {
filterID: 702;
Fltr: {
_name: 'Shape Blur';
_classID: 'shapeBlur';
'Rds ': DescriptorUnitsValue;
customShape: {
_name: '';
_classID: 'customShape';
'Nm ': string;
Idnt: string;
};
};
} | {
filterID: 1399681602;
Fltr: {
_name: 'Smart Blur';
_classID: 'SmrB';
'Rds ': number;
Thsh: number;
SmBQ: string;
SmBM: string;
};
} | {
filterID: 701;
Fltr: {
_name: 'Surface Blur';
_classID: 'surfaceBlur';
'Rds ': DescriptorUnitsValue;
Thsh: number;
};
} | {
filterID: 1148416108;
Fltr: {
_name: 'Displace';
_classID: 'Dspl';
HrzS: number;
VrtS: number;
DspM: string;
UndA: string;
DspF: {
sig: string;
path: string;
};
};
} | {
filterID: 1349411688;
Fltr: {
_name: 'Pinch';
_classID: 'Pnch';
Amnt: number;
};
} | {
filterID: 1349284384;
Fltr: {
_name: 'Polar Coordinates';
_classID: 'Plr ';
Cnvr: string;
};
} | {
filterID: 1383099493;
Fltr: {
_name: 'Ripple';
_classID: 'Rple';
Amnt: number;
RplS: string;
};
} | {
filterID: 1399353888;
Fltr: {
_name: 'Shear';
_classID: 'Shr ';
ShrP: { _name: '', _classID: 'Pnt ', Hrzn: number; Vrtc: number; }[];
UndA: string;
ShrS: number;
ShrE: number;
};
} | {
filterID: 1399875698;
Fltr: {
_name: 'Spherize';
_classID: 'Sphr';
Amnt: number;
SphM: string;
};
} | {
filterID: 1417114220;
Fltr: {
_name: 'Twirl';
_classID: 'Twrl';
Angl: number;
};
} | {
filterID: 1466005093;
Fltr: {
_name: 'Wave';
_classID: 'Wave';
Wvtp: string;
NmbG: number;
WLMn: number;
WLMx: number;
AmMn: number;
AmMx: number;
SclH: number;
SclV: number;
UndA: string;
RndS: number;
};
} | {
filterID: 1516722791;
Fltr: {
_name: 'ZigZag';
_classID: 'ZgZg';
Amnt: number;
NmbR: number;
ZZTy: string;
};
} | {
filterID: 1097092723;
Fltr: {
_name: 'Add Noise';
_classID: 'AdNs';
Dstr: string;
Nose: DescriptorUnitsValue;
Mnch: boolean;
FlRs: number;
};
} | {
filterID: 1148416099;
} | {
filterID: 1148417107;
Fltr: {
_name: 'Dust & Scratches';
_classID: 'DstS';
'Rds ': number;
Thsh: number;
};
} | {
filterID: 1298427424;
Fltr: {
_name: 'Median';
_classID: 'Mdn ';
'Rds ': DescriptorUnitsValue;
};
} | {
filterID: 633;
Fltr: {
_name: 'Reduce Noise';
_classID: 'denoise';
ClNs: DescriptorUnitsValue; // percent
Shrp: DescriptorUnitsValue; // percent
removeJPEGArtifact: boolean;
channelDenoise: {
_name: '';
_classID: 'channelDenoiseParams';
Chnl: string[];
Amnt: number;
EdgF?: number;
}[];
preset: string;
};
} | {
filterID: 1131180616;
Fltr: {
_name: 'Color Halftone';
_classID: 'ClrH';
'Rds ': number;
Ang1: number;
Ang2: number;
Ang3: number;
Ang4: number;
};
} | {
filterID: 1131574132;
Fltr: {
_name: 'Crystallize';
_classID: 'Crst';
ClSz: number;
FlRs: number;
};
} | {
filterID: 1180922912;
} | {
filterID: 1181902701;
} | {
filterID: 1299870830;
Fltr: {
_name: 'Mezzotint';
_classID: 'Mztn';
MztT: string;
FlRs: number;
};
} | {
filterID: 1299407648;
Fltr: {
_name: 'Mosaic';
_classID: 'Msc ';
ClSz: DescriptorUnitsValue;
};
} | {
filterID: 1349416044;
Fltr: {
_name: 'Pointillize';
_classID: 'Pntl';
ClSz: number;
FlRs: number;
};
} | {
filterID: 1131177075;
Fltr: {
_name: 'Clouds';
_classID: 'Clds';
FlRs: number;
};
} | {
filterID: 1147564611;
Fltr: {
_name: 'Difference Clouds',
_classID: 'DfrC',
FlRs: number;
};
} | {
filterID: 1180856947;
Fltr: {
_name: 'Fibers';
_classID: 'Fbrs';
Vrnc: number;
Strg: number;
RndS: number;
};
} | {
filterID: 1282306886;
Fltr: {
_name: 'Lens Flare';
_classID: 'LnsF';
Brgh: number;
FlrC: { _name: ''; _classID: 'Pnt '; Hrzn: number; Vrtc: number; };
'Lns ': string;
};
} /*| {
filterID: 587;
Fltr: {
k3DLights: K3DLight[];
key3DCurrentCameraPosition: {
key3DXPos: number;
key3DYPos: number;
key3DZPos: number;
key3DXAngle: number;
key3DYAngle: number;
key3DZAngle: number;
},
Glos: number;
Mtrl: number;
Exps: number;
AmbB: number;
AmbC: DescriptorColor;
BmpA: number;
BmpC: string[];
Wdth: number;
Hght: number;
};
}*/ | {
filterID: 1399353968 | 1399353925 | 1399353933;
} | {
filterID: 698;
Fltr: {
_name: 'Smart Sharpen';
_classID: 'smartSharpen';
Amnt: DescriptorUnitsValue; // %
'Rds ': DescriptorUnitsValue;
Thsh: number;
Angl: number;
moreAccurate: boolean;
blur: string;
preset: string;
sdwM: {
_name: 'Parameters',
_classID: 'adaptCorrectTones',
Amnt: DescriptorUnitsValue; // %
Wdth: DescriptorUnitsValue; // %
'Rds ': number;
};
hglM: {
_n