UNPKG

ag-psd

Version:

Library for reading and writing PSD files

1,638 lines (1,479 loc) 151 kB
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