ag-psd 33.0.0 → 34.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (86) hide show
  1. package/.v8-cache/v24.7.0-x64-ef5a0af0/05135617 +0 -0
  2. package/.v8-cache/v24.7.0-x64-ef5a0af0/226e2801 +0 -0
  3. package/.v8-cache/v24.7.0-x64-ef5a0af0/4511bacf +0 -0
  4. package/.v8-cache/v24.7.0-x64-ef5a0af0/68416cb2 +0 -0
  5. package/.v8-cache/v24.7.0-x64-ef5a0af0/71df9cb3 +0 -0
  6. package/.v8-cache/v24.7.0-x64-ef5a0af0/7ea06cea +0 -0
  7. package/.v8-cache/v24.7.0-x64-ef5a0af0/87cf2209 +0 -0
  8. package/.v8-cache/v24.7.0-x64-ef5a0af0/932f0770 +0 -0
  9. package/.v8-cache/v24.7.0-x64-ef5a0af0/acc36e66 +0 -0
  10. package/.v8-cache/v24.7.0-x64-ef5a0af0/b3c2fab7 +0 -0
  11. package/.v8-cache/v24.7.0-x64-ef5a0af0/c0b789d2 +0 -0
  12. package/.v8-cache/v24.7.0-x64-ef5a0af0/c314aece +0 -0
  13. package/.v8-cache/v24.7.0-x64-ef5a0af0/cfc49f4f +0 -0
  14. package/.v8-cache/v24.7.0-x64-ef5a0af0/d13d7610 +0 -0
  15. package/.v8-cache/v24.7.0-x64-ef5a0af0/dc86f000 +0 -0
  16. package/.v8-cache/v24.7.0-x64-ef5a0af0/e03e2acd +0 -0
  17. package/.v8-cache/v24.7.0-x64-ef5a0af0/e6b99945 +0 -0
  18. package/CHANGELOG.md +22 -0
  19. package/README.md +1 -1
  20. package/README_PSD.md +25 -9
  21. package/TODO.md +13 -84
  22. package/dist/abr.js +12 -7
  23. package/dist/abr.js.map +1 -1
  24. package/dist/additionalInfo.d.ts +2 -0
  25. package/dist/additionalInfo.js +361 -75
  26. package/dist/additionalInfo.js.map +1 -1
  27. package/dist/bundle.js +1524 -389
  28. package/dist/descriptor.d.ts +14 -5
  29. package/dist/descriptor.js +102 -64
  30. package/dist/descriptor.js.map +1 -1
  31. package/dist/helpers.d.ts +6 -0
  32. package/dist/helpers.js +242 -2
  33. package/dist/helpers.js.map +1 -1
  34. package/dist/imageResources.js +84 -3
  35. package/dist/imageResources.js.map +1 -1
  36. package/dist/index.d.ts +1 -0
  37. package/dist/index.js +1 -0
  38. package/dist/index.js.map +1 -1
  39. package/dist/psd.d.ts +144 -2
  40. package/dist/psd.js.map +1 -1
  41. package/dist/psdReader.js +206 -110
  42. package/dist/psdReader.js.map +1 -1
  43. package/dist/psdWriter.d.ts +1 -0
  44. package/dist/psdWriter.js +336 -91
  45. package/dist/psdWriter.js.map +1 -1
  46. package/dist/text.d.ts +32 -1
  47. package/dist/text.js +58 -0
  48. package/dist/text.js.map +1 -1
  49. package/dist-es/abr.js +12 -7
  50. package/dist-es/abr.js.map +1 -1
  51. package/dist-es/additionalInfo.d.ts +2 -0
  52. package/dist-es/additionalInfo.js +361 -75
  53. package/dist-es/additionalInfo.js.map +1 -1
  54. package/dist-es/descriptor.d.ts +14 -5
  55. package/dist-es/descriptor.js +102 -64
  56. package/dist-es/descriptor.js.map +1 -1
  57. package/dist-es/helpers.d.ts +6 -0
  58. package/dist-es/helpers.js +242 -2
  59. package/dist-es/helpers.js.map +1 -1
  60. package/dist-es/imageResources.js +84 -3
  61. package/dist-es/imageResources.js.map +1 -1
  62. package/dist-es/index.d.ts +1 -0
  63. package/dist-es/index.js +1 -0
  64. package/dist-es/index.js.map +1 -1
  65. package/dist-es/psd.d.ts +144 -2
  66. package/dist-es/psd.js.map +1 -1
  67. package/dist-es/psdReader.js +206 -110
  68. package/dist-es/psdReader.js.map +1 -1
  69. package/dist-es/psdWriter.d.ts +1 -0
  70. package/dist-es/psdWriter.js +336 -91
  71. package/dist-es/psdWriter.js.map +1 -1
  72. package/dist-es/text.d.ts +32 -1
  73. package/dist-es/text.js +58 -0
  74. package/dist-es/text.js.map +1 -1
  75. package/package.json +1 -1
  76. package/perf.js +312 -9
  77. package/src/abr.ts +14 -7
  78. package/src/additionalInfo.ts +433 -87
  79. package/src/descriptor.ts +103 -58
  80. package/src/helpers.ts +262 -2
  81. package/src/imageResources.ts +107 -4
  82. package/src/index.ts +1 -0
  83. package/src/psd.ts +115 -2
  84. package/src/psdReader.ts +226 -108
  85. package/src/psdWriter.ts +345 -90
  86. package/src/text.ts +76 -1
@@ -1,16 +1,18 @@
1
1
  import { fromByteArray, toByteArray } from 'base64-js';
2
2
  import { readEffects, writeEffects } from './effectsHelpers';
3
3
  import { clamp, createEnum, layerColors, MOCK_HANDLERS, normalizeLabAB } from './helpers';
4
- import { LayerAdditionalInfo, LevelsFilterAdjustment, BezierPath, Psd, BrightnessAdjustment, ExposureAdjustment, VibranceAdjustment, ColorBalanceAdjustment, BlackAndWhiteAdjustment, PhotoFilterAdjustment, ChannelMixerChannel, ChannelMixerAdjustment, PosterizeAdjustment, ThresholdAdjustment, GradientMapAdjustment, CMYK, SelectiveColorAdjustment, ColorLookupAdjustment, LevelsAdjustmentChannel, LevelsAdjustment, CurvesAdjustment, CurvesAdjustmentChannel, HueSaturationAdjustment, HueSaturationAdjustmentChannel, PresetInfo, Color, ColorBalanceValues, WriteOptions, LinkedFile, PlacedLayerType, Warp, KeyDescriptorItem, BooleanOperation, LayerEffectsInfo, Annotation, VectorPathData, AnimationFrame, Timeline, PlacedLayerFilter, UnitsValue, Filter, PlacedLayer, Layer } from './psd';
4
+ import { LayerAdditionalInfo, LevelsFilterAdjustment, BezierPath, Psd, BrightnessAdjustment, ExposureAdjustment, VibranceAdjustment, ColorBalanceAdjustment, BlackAndWhiteAdjustment, PhotoFilterAdjustment, ChannelMixerChannel, ChannelMixerAdjustment, PosterizeAdjustment, ThresholdAdjustment, GradientMapAdjustment, CMYK, SelectiveColorAdjustment, ColorLookupAdjustment, LevelsAdjustmentChannel, LevelsAdjustment, CurvesAdjustment, CurvesAdjustmentChannel, HueSaturationAdjustment, HueSaturationAdjustmentChannel, PresetInfo, Color, ColorBalanceValues, WriteOptions, LinkedFile, PlacedLayerType, Warp, KeyDescriptorItem, BooleanOperation, LayerEffectsInfo, Annotation, VectorPathData, AnimationFrame, Timeline, PlacedLayerFilter, UnitsValue, Filter, PlacedLayer, Layer, CameraRawColors, CameraRawCurvePoint, CameraRawFilterSettings } from './psd';
5
5
  import { PsdReader, readSignature, readUnicodeString, skipBytes, readUint32, readUint8, readFloat64, readUint16, readBytes, readInt16, checkSignature, readFloat32, readFixedPointPath32, readSection, readColor, readInt32, readPascalString, readUnicodeStringWithLength, readAsciiString, readPatternSafe, readLayerInfo } from './psdReader';
6
6
  import { PsdWriter, writeZeros, writeSignature, writeBytes, writeUint32, writeUint16, writeFloat64, writeUint8, writeInt16, writeFloat32, writeFixedPointPath32, writeUnicodeString, writeSection, writeUnicodeStringWithPadding, writeColor, writePascalString, writeInt32, writePattern, writeUint64 } from './psdWriter';
7
- import { Annt, BlnM, DescriptorColor, DescriptorUnitsValue, parsePercent, parseUnits, parseUnitsOrNumber, QuiltWarpDescriptor, strokeStyleLineAlignment, strokeStyleLineCapType, strokeStyleLineJoinType, TextDescriptor, textGridding, unitsPercent, unitsValue, WarpDescriptor, warpStyle, writeVersionAndDescriptor, readVersionAndDescriptor, StrokeDescriptor, Ornt, horzVrtcToXY, LmfxDescriptor, Lfx2Descriptor, FrameListDescriptor, TimelineDescriptor, FrameDescriptor, xyToHorzVrtc, serializeEffects, parseEffects, parseColor, serializeColor, serializeVectorContent, parseVectorContent, parseTrackList, serializeTrackList, FractionDescriptor, BlrM, BlrQ, SmBQ, SmBM, DspM, UndA, Cnvr, RplS, SphM, Wvtp, ZZTy, Dstr, Chnl, MztT, Lns, blurType, DfsM, ExtT, ExtR, FlCl, CntE, WndM, Drct, IntE, IntC, FlMd, unitsPercentF, frac, ClrS, descBoundsToBounds, boundsToDescBounds, presetKindType, gradientInterpolationMethodType, PxScDescriptor } from './descriptor';
7
+ import { Annt, BlnM, DescriptorColor, DescriptorUnitsValue, parsePercent, parseUnits, parseUnitsOrNumber, QuiltWarpDescriptor, strokeStyleLineAlignment, strokeStyleLineCapType, strokeStyleLineJoinType, TextDescriptor, textGridding, unitsPercent, unitsValue, WarpDescriptor, warpStyle, writeVersionAndDescriptor, readVersionAndDescriptor, StrokeDescriptor, Ornt, horzVrtcToXY, LmfxDescriptor, Lfx2Descriptor, FrameListDescriptor, TimelineDescriptor, FrameDescriptor, xyToHorzVrtc, serializeEffects, parseEffects, parseColor, serializeColor, serializeVectorContent, parseVectorContent, parseTrackList, serializeTrackList, FractionDescriptor, BlrM, BlrQ, SmBQ, SmBM, DspM, UndA, Cnvr, RplS, SphM, Wvtp, ZZTy, Dstr, Chnl, MztT, Lns, blurType, DfsM, ExtT, ExtR, FlCl, CntE, WndM, Drct, IntE, IntC, FlMd, unitsPercentF, frac, ClrS, descBoundsToBounds, boundsToDescBounds, presetKindType, gradientInterpolationMethodType, PxScDescriptor, cameraRawFilterClassID, patternColorHandling } from './descriptor';
8
8
  import { serializeEngineData, parseEngineData } from './engineData';
9
- import { encodeEngineData, decodeEngineData } from './text';
9
+ import { encodeEngineData, decodeEngineData, decodeLegacyTextData, LegacyTextData } from './text';
10
10
  import { decodeEngineData2 } from './engineData2';
11
11
  import type { InternalImageResources } from './imageResources';
12
12
 
13
13
  export interface ExtendedWriteOptions extends WriteOptions {
14
+ colorMode?: number; // color mode of written document
15
+ bitsPerChannel?: number; // bit depth of written document
14
16
  layerIds: Set<number>;
15
17
  layerToId: Map<any, number>;
16
18
  explicitLayerIds?: Set<number>; // ids set on layers in the document (generated ids can't use them)
@@ -147,15 +149,95 @@ addHandler(
147
149
  },
148
150
  );
149
151
 
152
+ // type tool info from Photoshop 5.0 and 5.5, converted to regular text data (written as 'TySh')
153
+ addHandler(
154
+ 'tySh',
155
+ () => false,
156
+ (reader, target, left) => {
157
+ if (readInt16(reader) !== 1) throw new Error(`Invalid tySh version`);
158
+
159
+ const transform: number[] = [];
160
+ for (let i = 0; i < 6; i++) transform.push(readFloat64(reader));
161
+
162
+ if (readInt16(reader) !== 6) throw new Error(`Invalid tySh font version`);
163
+
164
+ const data: LegacyTextData = { transform, faces: [], styles: [], lines: [], color: { r: 0, g: 0, b: 0 }, antiAlias: false };
165
+ const facesCount = readInt16(reader);
166
+
167
+ for (let i = 0; i < facesCount; i++) {
168
+ const mark = readInt16(reader);
169
+ readInt32(reader); // font type
170
+ const name = readPascalString(reader, 1);
171
+ const familyName = readPascalString(reader, 1);
172
+ const styleName = readPascalString(reader, 1);
173
+ const script = readInt16(reader);
174
+ const axesCount = readInt32(reader);
175
+ if (axesCount < 0 || axesCount * 4 > left()) throw new Error(`Invalid tySh design axes count`);
176
+ skipBytes(reader, axesCount * 4);
177
+ data.faces.push({ mark, name, familyName, styleName, script });
178
+ }
179
+
180
+ const stylesCount = readInt16(reader);
181
+
182
+ for (let i = 0; i < stylesCount; i++) {
183
+ const mark = readInt16(reader);
184
+ const faceMark = readInt16(reader);
185
+ const size = readInt32(reader);
186
+ const tracking = readInt32(reader);
187
+ const kerning = readInt32(reader);
188
+ const leading = readInt32(reader);
189
+ const baselineShift = readInt32(reader);
190
+ const autoKerning = !!readUint8(reader);
191
+ readUint8(reader); // rotate
192
+ data.styles.push({ mark, faceMark, size, tracking, kerning, leading, baselineShift, autoKerning });
193
+ }
194
+
195
+ readInt16(reader); // type (0 = point, 1 = paragraph)
196
+ readInt32(reader); // scaling factor
197
+ readInt32(reader); // character count
198
+ readInt32(reader); // horizontal placement
199
+ readInt32(reader); // vertical placement
200
+ readInt32(reader); // select start
201
+ readInt32(reader); // select end
202
+
203
+ const linesCount = readInt16(reader);
204
+
205
+ for (let i = 0; i < linesCount; i++) {
206
+ const charCount = readInt32(reader);
207
+ const orientation = readInt16(reader);
208
+ const alignment = readInt16(reader);
209
+ if (charCount < 0 || charCount * 4 > left()) throw new Error(`Invalid tySh character count`);
210
+ const chars: { code: number; style: number; }[] = [];
211
+
212
+ for (let j = 0; j < charCount; j++) {
213
+ const code = readUint16(reader);
214
+ const style = readInt16(reader);
215
+ chars.push({ code, style });
216
+ }
217
+
218
+ data.lines.push({ orientation, alignment, chars });
219
+ }
220
+
221
+ data.color = readColor(reader);
222
+ data.antiAlias = !!readUint8(reader);
223
+
224
+ if (!target.text) target.text = decodeLegacyTextData(data);
225
+
226
+ skipBytes(reader, left());
227
+ },
228
+ () => { },
229
+ );
230
+
150
231
  // vector fills
151
232
 
152
233
  addHandler(
153
234
  'SoCo',
154
235
  target => target.vectorFill !== undefined && target.vectorStroke === undefined &&
155
236
  target.vectorFill.type === 'color',
156
- (reader, target) => {
237
+ (reader, target, left) => {
157
238
  const descriptor = readVersionAndDescriptor(reader);
158
239
  target.vectorFill = parseVectorContent(descriptor);
240
+ skipBytes(reader, left());
159
241
  },
160
242
  (writer, target) => {
161
243
  const { descriptor } = serializeVectorContent(target.vectorFill!);
@@ -545,12 +627,12 @@ addHandler(
545
627
  // console.log('READ', require('util').inspect(desc, false, 99, true));
546
628
 
547
629
  // discard if read in 'lrFX' or 'lfx2' section
548
- target.effects = parseEffects(desc, !!reader.logMissingFeatures);
630
+ target.effects = parseEffects(desc, reader);
549
631
 
550
632
  skipBytes(reader, left());
551
633
  },
552
- (writer, target, _, options) => {
553
- const desc = serializeEffects(target.effects!, !!options.logMissingFeatures, true);
634
+ (writer, target) => {
635
+ const desc = serializeEffects(target.effects!, writer, true);
554
636
  // console.log('WRITE', require('util').inspect(desc, false, 99, true));
555
637
 
556
638
  writeUint32(writer, 0); // version
@@ -745,6 +827,82 @@ addHandler(
745
827
  },
746
828
  );
747
829
 
830
+ addHandler(
831
+ 'vmgm',
832
+ hasKey('vectorMaskAsGlobalMask'),
833
+ (reader, target) => {
834
+ target.vectorMaskAsGlobalMask = !!readUint8(reader);
835
+ skipBytes(reader, 3);
836
+ },
837
+ (writer, target) => {
838
+ writeUint8(writer, target.vectorMaskAsGlobalMask ? 1 : 0);
839
+ writeZeros(writer, 3);
840
+ },
841
+ );
842
+
843
+ addHandler(
844
+ 'ffxi',
845
+ hasKey('foreignEffectId'),
846
+ (reader, target) => {
847
+ target.foreignEffectId = readInt32(reader);
848
+ },
849
+ (writer, target) => {
850
+ writeInt32(writer, target.foreignEffectId!);
851
+ },
852
+ );
853
+
854
+ addHandler(
855
+ 'shpa',
856
+ hasKey('patternData'),
857
+ (reader, target, left) => {
858
+ if (readInt32(reader) !== 0) throw new Error(`Invalid shpa version`);
859
+
860
+ const count = readInt32(reader);
861
+ target.patternData = [];
862
+
863
+ for (let i = 0; i < count && left() > 0; i++) {
864
+ const signature = readSignature(reader);
865
+ const key = readSignature(reader);
866
+ const patternsCount = readInt32(reader);
867
+ const copyOnSheetDuplication = !!readUint8(reader);
868
+ skipBytes(reader, 3);
869
+ const patterns: NonNullable<LayerAdditionalInfo['patternData']>[number]['patterns'] = [];
870
+
871
+ for (let j = 0; j < patternsCount && left() > 0; j++) {
872
+ const colorHandling = patternColorHandling.decode('.' + readSignature(reader));
873
+ readPascalString(reader, 1); // name
874
+ const name = readUnicodeString(reader);
875
+ const id = readPascalString(reader, 1);
876
+ patterns.push({ colorHandling, name, id });
877
+ }
878
+
879
+ target.patternData.push({ signature, key, copyOnSheetDuplication, patterns });
880
+ }
881
+
882
+ skipBytes(reader, left());
883
+ },
884
+ (writer, target) => {
885
+ const sets = target.patternData!;
886
+ writeInt32(writer, 0); // version
887
+ writeInt32(writer, sets.length);
888
+
889
+ for (const set of sets) {
890
+ writeSignature(writer, set.signature);
891
+ writeSignature(writer, set.key);
892
+ writeInt32(writer, set.patterns.length);
893
+ writeUint8(writer, set.copyOnSheetDuplication ? 1 : 0);
894
+ writeZeros(writer, 3);
895
+
896
+ for (const pattern of set.patterns) {
897
+ writeSignature(writer, patternColorHandling.encode(pattern.colorHandling).split('.')[1]);
898
+ writePascalString(writer, pattern.name.replace(/[^\x20-\x7e]/g, '?'), 1);
899
+ writeUnicodeString(writer, pattern.name);
900
+ writePascalString(writer, pattern.id, 1);
901
+ }
902
+ }
903
+ },
904
+ );
905
+
748
906
  addHandler(
749
907
  'lspf',
750
908
  hasKey('protected'),
@@ -891,7 +1049,7 @@ addHandler(
891
1049
  if (f.enab !== undefined) frame.enable = f.enab;
892
1050
  if (f.Ofst) frame.offset = horzVrtcToXY(f.Ofst);
893
1051
  if (f.FXRf) frame.referencePoint = horzVrtcToXY(f.FXRf);
894
- if (f.Lefx) frame.effects = parseEffects(f.Lefx, !!reader.logMissingFeatures);
1052
+ if (f.Lefx) frame.effects = parseEffects(f.Lefx, reader);
895
1053
  if (f.blendOptions && f.blendOptions.Opct) frame.opacity = parsePercent(f.blendOptions.Opct);
896
1054
  target.animationFrames.push(frame);
897
1055
  }
@@ -922,7 +1080,7 @@ addHandler(
922
1080
  };
923
1081
 
924
1082
  if (desc.trackList) {
925
- timeline.tracks = parseTrackList(desc.trackList, !!reader.logMissingFeatures);
1083
+ timeline.tracks = parseTrackList(desc.trackList, reader);
926
1084
  }
927
1085
 
928
1086
  target.timeline = timeline;
@@ -994,7 +1152,7 @@ addHandler(
994
1152
  frame.FrLs = f.frames;
995
1153
  if (f.offset) frame.Ofst = xyToHorzVrtc(f.offset);
996
1154
  if (f.referencePoint) frame.FXRf = xyToHorzVrtc(f.referencePoint);
997
- if (f.effects) frame.Lefx = serializeEffects(f.effects, false, hasMultiEffects(f.effects));
1155
+ if (f.effects) frame.Lefx = serializeEffects(f.effects, writer, hasMultiEffects(f.effects));
998
1156
  if (f.opacity !== undefined) frame.blendOptions = { Opct: unitsPercentF(f.opacity) };
999
1157
  desc.LaSt.push(frame);
1000
1158
  }
@@ -1038,7 +1196,7 @@ addHandler(
1038
1196
  } as any;
1039
1197
 
1040
1198
  if (timeline.tracks) {
1041
- desc.trackList = serializeTrackList(timeline.tracks);
1199
+ desc.trackList = serializeTrackList(timeline.tracks, writer);
1042
1200
  }
1043
1201
 
1044
1202
  desc.LyrI = getLayerId(target, options);
@@ -2244,6 +2402,91 @@ function pointToHrznVrtc(point: { x: UnitsValue; y: UnitsValue; }): HrznVrtcDesc
2244
2402
  };
2245
2403
  }
2246
2404
 
2405
+ // Camera Raw Filter setting paths (in CameraRawFilterSettings) and their descriptor keys
2406
+ const cameraRawColorKeys: [keyof CameraRawColors, string][] = [
2407
+ ['red', 'R'], ['orange', 'O'], ['yellow', 'Y'], ['green', 'G'], ['aqua', 'A'], ['blue', 'B'], ['purple', 'P'], ['magenta', 'M'],
2408
+ ];
2409
+
2410
+ const cameraRawSettings: [string, string][] = [
2411
+ ['version', 'CrVe'], ['processVersion', 'PrVN'], ['processVersionCode', 'PrVe'], ['whiteBalance', 'WBal'],
2412
+ ['temperature', 'Temp'], ['tint', 'Tint'], ['exposure', 'Ex12'], ['contrast', 'Cr12'], ['highlights', 'Hi12'],
2413
+ ['shadows', 'Sh12'], ['whites', 'Wh12'], ['blacks', 'Bk12'], ['texture', 'CrTx'], ['clarity', 'Cl12'],
2414
+ ['dehaze', 'Dhze'], ['vibrance', 'Vibr'], ['saturation', 'Strt'],
2415
+ ['parametricCurve.shadows', 'PC_S'], ['parametricCurve.darks', 'PC_D'], ['parametricCurve.lights', 'PC_L'],
2416
+ ['parametricCurve.highlights', 'PC_H'], ['parametricCurve.shadowSplit', 'PC_1'], ['parametricCurve.midtoneSplit', 'PC_2'],
2417
+ ['parametricCurve.highlightSplit', 'PC_3'],
2418
+ ['pointCurve.composite', 'Crv '], ['pointCurve.red', 'CrvR'], ['pointCurve.green', 'CrvG'], ['pointCurve.blue', 'CrvB'],
2419
+ ...cameraRawColorKeys.map(([name, key]) => [`hsl.hue.${name}`, `HA_${key}`] as [string, string]),
2420
+ ...cameraRawColorKeys.map(([name, key]) => [`hsl.saturation.${name}`, `SA_${key}`] as [string, string]),
2421
+ ...cameraRawColorKeys.map(([name, key]) => [`hsl.luminance.${name}`, `LA_${key}`] as [string, string]),
2422
+ ['colorGrading.shadows.hue', 'STSH'], ['colorGrading.shadows.saturation', 'STSS'], ['colorGrading.shadows.luminance', 'CgSL'],
2423
+ ['colorGrading.midtones.hue', 'CgMH'], ['colorGrading.midtones.saturation', 'CgMS'], ['colorGrading.midtones.luminance', 'CgML'],
2424
+ ['colorGrading.highlights.hue', 'STHH'], ['colorGrading.highlights.saturation', 'STHS'], ['colorGrading.highlights.luminance', 'CgHL'],
2425
+ ['colorGrading.global.hue', 'CgGH'], ['colorGrading.global.saturation', 'CgGS'], ['colorGrading.global.luminance', 'CgGL'],
2426
+ ['colorGrading.blending', 'CgBl'], ['colorGrading.balance', 'STB '],
2427
+ ['sharpening.amount', 'Shrp'], ['sharpening.radius', 'ShpR'], ['sharpening.detail', 'ShpD'], ['sharpening.masking', 'ShpM'],
2428
+ ['noiseReduction.luminance', 'LNR '], ['noiseReduction.luminanceDetail', 'LNRD'], ['noiseReduction.luminanceContrast', 'LNRC'],
2429
+ ['noiseReduction.color', 'CNR '], ['noiseReduction.colorDetail', 'CNRD'], ['noiseReduction.colorSmoothness', 'CNRS'],
2430
+ ['grain.amount', 'GRNA'], ['grain.size', 'GRNS'], ['grain.roughness', 'GRNF'],
2431
+ ['postCropVignette.amount', 'PCVA'], ['postCropVignette.midpoint', 'PCVM'], ['postCropVignette.feather', 'PCVF'],
2432
+ ['postCropVignette.roundness', 'PCVR'],
2433
+ ];
2434
+
2435
+ const cameraRawCurveKeys = ['Crv ', 'CrvR', 'CrvG', 'CrvB'];
2436
+ const cameraRawFilterID = 2783;
2437
+
2438
+ function parseCameraRawSettings(desc: any, reader: PsdReader) {
2439
+ const settings: any = {};
2440
+
2441
+ for (const [path, key] of cameraRawSettings) {
2442
+ let value = desc[key];
2443
+ if (value === undefined) continue;
2444
+
2445
+ if (key === 'WBal') {
2446
+ value = (value as string).split('.')[1];
2447
+ } else if (cameraRawCurveKeys.includes(key)) {
2448
+ const values = value as number[];
2449
+ value = [];
2450
+ for (let i = 0; i + 1 < values.length; i += 2) value.push({ input: values[i], output: values[i + 1] });
2451
+ }
2452
+
2453
+ const parts = path.split('.');
2454
+ let target = settings;
2455
+ for (let i = 0; i < parts.length - 1; i++) target = target[parts[i]] = target[parts[i]] ?? {};
2456
+ target[parts[parts.length - 1]] = value;
2457
+ }
2458
+
2459
+ for (const key of Object.keys(desc)) {
2460
+ if (key !== '_name' && key !== '_classID' && !cameraRawSettings.some(s => s[1] === key)) {
2461
+ reader.logMissingFeatures && reader.log(`Unhandled Camera Raw Filter setting: ${key}`);
2462
+ }
2463
+ }
2464
+
2465
+ return settings as CameraRawFilterSettings;
2466
+ }
2467
+
2468
+ function serializeCameraRawSettings(settings: CameraRawFilterSettings) {
2469
+ const desc: any = { _name: '', _classID: cameraRawFilterClassID };
2470
+
2471
+ for (const [path, key] of cameraRawSettings) {
2472
+ let value: any = settings;
2473
+ for (const part of path.split('.')) value = value?.[part];
2474
+ if (value === undefined) continue;
2475
+
2476
+ if (key === 'WBal') {
2477
+ value = `WBal.${value}`;
2478
+ } else if (cameraRawCurveKeys.includes(key)) {
2479
+ value = (value as CameraRawCurvePoint[]).flatMap(p => [Math.round(p.input), Math.round(p.output)]);
2480
+ } else if (typeof value === 'number' && key !== 'Ex12' && key !== 'ShpR') {
2481
+ value = Math.round(value);
2482
+ }
2483
+
2484
+ desc[key] = value;
2485
+ }
2486
+
2487
+ return desc;
2488
+ }
2489
+
2247
2490
  function parseFilterFXItem(f: SoLdDescriptorFilterItem, reader: PsdReader): Filter | undefined {
2248
2491
  const base: Omit<Filter, 'type' | 'filter'> = {
2249
2492
  name: f['Nm '],
@@ -2255,6 +2498,10 @@ function parseFilterFXItem(f: SoLdDescriptorFilterItem, reader: PsdReader): Filt
2255
2498
  backgroundColor: parseColor(f.BckC),
2256
2499
  };
2257
2500
 
2501
+ if ('Fltr' in f && (f.Fltr._classID as string) === cameraRawFilterClassID) {
2502
+ return { ...base, type: 'camera raw', filter: parseCameraRawSettings(f.Fltr, reader) };
2503
+ }
2504
+
2258
2505
  if ('Fltr' in f) {
2259
2506
  switch (f.Fltr._classID) {
2260
2507
  case 'boxblur': return {
@@ -3594,6 +3841,11 @@ function serializeFilterFXItem(f: Filter): SoLdDescriptorFilterItem {
3594
3841
  },
3595
3842
  filterID: 1282829427,
3596
3843
  };
3844
+ case 'camera raw': return {
3845
+ ...base,
3846
+ Fltr: serializeCameraRawSettings(f.filter),
3847
+ filterID: cameraRawFilterID,
3848
+ } as any;
3597
3849
  case 'brightness/contrast': return {
3598
3850
  ...base,
3599
3851
  Fltr: {
@@ -3914,36 +4166,24 @@ addHandler(
3914
4166
  },
3915
4167
  );
3916
4168
 
3917
- if (MOCK_HANDLERS) {
3918
- addHandler(
3919
- 'Patt',
3920
- target => (target as any)._Patt !== undefined,
3921
- (reader, target, left) => {
3922
- // console.log('additional info: Patt');
3923
- (target as any)._Patt = readBytes(reader, left());
3924
- },
3925
- (writer, target) => false && writeBytes(writer, (target as any)._Patt),
3926
- );
3927
- } else {
3928
- addHandler(
3929
- 'Patt', // 'Pat2' and 'Pat3' (16 and 32 bit documents) are aliases, patterns are always written as 8 bit
3930
- target => !!(target.patterns && target.patterns.length > 0),
3931
- (reader, target, left) => {
3932
- while (left() > 0) {
3933
- const pattern = readPatternSafe(reader);
3934
- if (!pattern) continue;
3935
- if (target.patterns === undefined) target.patterns = [];
3936
- target.patterns.push(pattern);
3937
- }
3938
- },
3939
- (writer, target, _, _options) => {
3940
- const patterns = target.patterns || [];
3941
- for (const pattern of patterns) {
3942
- writePattern(writer, pattern);
3943
- }
3944
- },
3945
- );
3946
- }
4169
+ addHandler(
4170
+ 'Patt', // 'Pat2' and 'Pat3' (16 and 32 bit documents) are aliases, patterns are always written as 8 bit
4171
+ target => !!(target.patterns && target.patterns.length > 0),
4172
+ (reader, target, left) => {
4173
+ while (left() > 0) {
4174
+ const pattern = readPatternSafe(reader);
4175
+ if (!pattern) continue;
4176
+ if (target.patterns === undefined) target.patterns = [];
4177
+ target.patterns.push(pattern);
4178
+ }
4179
+ },
4180
+ (writer, target, _, _options) => {
4181
+ const patterns = target.patterns || [];
4182
+ for (const pattern of patterns) {
4183
+ writePattern(writer, pattern);
4184
+ }
4185
+ },
4186
+ );
3947
4187
 
3948
4188
  addHandlerAlias('Pat2', 'Patt');
3949
4189
  addHandlerAlias('Pat3', 'Patt');
@@ -4419,12 +4659,12 @@ addHandler(
4419
4659
  if (version !== 0) throw new Error(`Invalid lfxs version`);
4420
4660
 
4421
4661
  const desc: Lfx2Descriptor & LmfxDescriptor = readVersionAndDescriptor(reader);
4422
- target.effects = parseEffects(desc, !!reader.logMissingFeatures);
4662
+ target.effects = parseEffects(desc, reader);
4423
4663
 
4424
4664
  skipBytes(reader, left());
4425
4665
  },
4426
- (writer, target, _, options) => {
4427
- const desc = serializeEffects(target.effects!, !!options.logMissingFeatures, true);
4666
+ (writer, target) => {
4667
+ const desc = serializeEffects(target.effects!, writer, true);
4428
4668
  writeUint32(writer, 0); // version
4429
4669
  writeVersionAndDescriptor(writer, '', 'null', desc);
4430
4670
  },
@@ -4497,10 +4737,17 @@ addHandler(
4497
4737
  }
4498
4738
 
4499
4739
  if (left() >= 8) {
4500
- if (readSignature(reader) !== 'Lvls') throw new Error(`Invalid levl signature`);
4501
- if (readUint16(reader) !== 3) throw new Error(`Invalid levl version`);
4740
+ const signature = readSignature(reader);
4741
+ let totalChannelRecords: number;
4502
4742
 
4503
- const totalChannelRecords = readUint16(reader);
4743
+ if (signature === 'ls\x00\x03') {
4744
+ // Clip Studio Paint writes 'ls' instead of 'Lvls' signature (version is part of the read signature)
4745
+ totalChannelRecords = readUint16(reader);
4746
+ } else {
4747
+ if (signature !== 'Lvls') throw new Error(`Invalid levl signature`);
4748
+ if (readUint16(reader) !== 3) throw new Error(`Invalid levl version`);
4749
+ totalChannelRecords = readUint16(reader);
4750
+ }
4504
4751
 
4505
4752
  while (channels.length < totalChannelRecords && left() >= 10) {
4506
4753
  channels.push(readLevelsChannel(reader));
@@ -4563,71 +4810,81 @@ addHandler(
4563
4810
  'curv',
4564
4811
  adjustmentType('curves'),
4565
4812
  (reader, target, left) => {
4566
- readUint8(reader);
4567
- if (readUint16(reader) !== 1) throw new Error('Invalid curv version');
4568
- readUint16(reader);
4569
- const channelsMask = readUint16(reader);
4570
- const channels: CurvesAdjustmentChannel[] = [];
4571
-
4572
- for (let i = 0; i < 16; i++) {
4573
- if (channelsMask & (1 << i)) {
4574
- while (channels.length < i) channels.push([]);
4575
- channels.push(readCurveChannel(reader));
4813
+ const isMap = !!readUint8(reader); // curves drawn with pencil tool (lookup tables instead of points)
4814
+ const version = readUint16(reader);
4815
+ if (version !== 1 && version !== 4) throw new Error('Invalid curv version');
4816
+ const countOrMask = readUint32(reader);
4817
+ const channels: (CurvesAdjustmentChannel | number[])[] = [];
4818
+ const readChannel = () => isMap ? Array.from(readBytes(reader, 256)) : readCurveChannel(reader);
4819
+
4820
+ if (version === 1) {
4821
+ for (let i = 0; i < 32; i++) {
4822
+ if (countOrMask & (1 << i)) {
4823
+ while (channels.length < i) channels.push([]);
4824
+ channels.push(readChannel());
4825
+ }
4826
+ }
4827
+ } else {
4828
+ if (countOrMask > 64) throw new Error('Invalid curv channel count');
4829
+ for (let i = 0; i < countOrMask; i++) channels.push(readChannel());
4830
+ }
4831
+
4832
+ // extra section stores curves for all channels (including ones that don't fit in the mask)
4833
+ if (version === 1 && left() >= 10 && readSignature(reader) === 'Crv ') {
4834
+ readUint16(reader); // version
4835
+ const count = readUint32(reader);
4836
+
4837
+ for (let i = 0; i < count && left() >= 2; i++) {
4838
+ const index = readUint16(reader);
4839
+ const channel = readChannel();
4840
+ while (channels.length < index) channels.push([]);
4841
+ channels[index] = channel;
4576
4842
  }
4577
4843
  }
4578
4844
 
4579
4845
  target.adjustment = {
4580
4846
  ...target.adjustment as PresetInfo,
4581
4847
  type: 'curves',
4582
- channels,
4848
+ ...(isMap ? { values: channels as number[][] } : { channels: channels as CurvesAdjustmentChannel[] }),
4583
4849
  };
4584
4850
 
4585
- // ignoring, duplicate information
4586
- // checkSignature(reader, 'Crv ');
4587
-
4588
- // const cVersion = readUint16(reader);
4589
- // readUint16(reader);
4590
- // const channelCount = readUint16(reader);
4591
-
4592
- // for (let i = 0; i < channelCount; i++) {
4593
- // const index = readUint16(reader);
4594
- // const nodes = readUint16(reader);
4595
-
4596
- // for (let j = 0; j < nodes; j++) {
4597
- // const output = readInt16(reader);
4598
- // const input = readInt16(reader);
4599
- // }
4600
- // }
4601
-
4602
4851
  skipBytes(reader, left());
4603
4852
  },
4604
4853
  (writer, target) => {
4605
- const channels = ((target.adjustment as CurvesAdjustment).channels || []).slice(0, 16);
4854
+ const adjustment = target.adjustment as CurvesAdjustment;
4855
+ const isMap = !!adjustment.values;
4856
+ const channels: (CurvesAdjustmentChannel | number[])[] = (isMap ? adjustment.values : adjustment.channels) || [];
4606
4857
  const used: number[] = [];
4607
4858
  let channelsMask = 0;
4608
4859
 
4609
4860
  for (let i = 0; i < channels.length; i++) {
4610
4861
  if (channels[i]?.length) {
4611
- channelsMask |= 1 << i;
4862
+ if (i < 32) channelsMask |= 1 << i;
4612
4863
  used.push(i);
4613
4864
  }
4614
4865
  }
4615
4866
 
4616
- writeUint8(writer, 0);
4867
+ const writeChannel = (channel: CurvesAdjustmentChannel | number[]) => {
4868
+ if (isMap) {
4869
+ for (let i = 0; i < 256; i++) writeUint8(writer, clamp(Math.round((channel as number[])[i] ?? i), 0, 255));
4870
+ } else {
4871
+ writeCurveChannel(writer, channel as CurvesAdjustmentChannel);
4872
+ }
4873
+ };
4874
+
4875
+ writeUint8(writer, isMap ? 1 : 0);
4617
4876
  writeUint16(writer, 1); // version
4618
- writeUint16(writer, 0);
4619
- writeUint16(writer, channelsMask);
4877
+ writeUint32(writer, channelsMask >>> 0);
4620
4878
 
4621
- for (const i of used) writeCurveChannel(writer, channels[i]);
4879
+ for (const i of used) if (i < 32) writeChannel(channels[i]);
4622
4880
 
4623
4881
  writeSignature(writer, 'Crv ');
4624
4882
  writeUint16(writer, 4); // version
4625
- writeUint16(writer, 0);
4626
- writeUint16(writer, used.length);
4883
+ writeUint32(writer, used.length);
4627
4884
 
4628
4885
  for (const i of used) {
4629
4886
  writeUint16(writer, i);
4630
- writeCurveChannel(writer, channels[i]);
4887
+ writeChannel(channels[i]);
4631
4888
  }
4632
4889
  },
4633
4890
  );
@@ -5567,6 +5824,10 @@ addHandler(
5567
5824
  );
5568
5825
 
5569
5826
  addHandlerAlias('FXid', 'FEid');
5827
+ addHandlerAlias('FELS', 'FEid'); // filter effects 3
5828
+ addHandlerAlias('hue ', 'hue2'); // old hue/saturation (Photoshop 4.0), same structure as 'hue2'
5829
+ addHandlerAlias('plLd', 'PlLd'); // old placed layer key
5830
+ addHandlerAlias('abdd', 'artb'); // artboard data 3
5570
5831
 
5571
5832
  addHandler(
5572
5833
  'FMsk',
@@ -5648,12 +5909,12 @@ addHandler(
5648
5909
  // console.log('READ', require('util').inspect(desc, false, 99, true));
5649
5910
 
5650
5911
  // discard if read in 'lrFX' section ('lmfx' and 'lfx2' are never stored together)
5651
- target.effects = parseEffects(desc, !!reader.logMissingFeatures);
5912
+ target.effects = parseEffects(desc, reader);
5652
5913
 
5653
5914
  skipBytes(reader, left());
5654
5915
  },
5655
- (writer, target, _, options) => {
5656
- const desc = serializeEffects(target.effects!, !!options.logMissingFeatures, true);
5916
+ (writer, target) => {
5917
+ const desc = serializeEffects(target.effects!, writer, true);
5657
5918
  // console.log('WRITE', require('util').inspect(desc, false, 99, true));
5658
5919
 
5659
5920
  writeUint32(writer, 0); // version
@@ -5735,6 +5996,91 @@ addHandler(
5735
5996
  },
5736
5997
  );
5737
5998
 
5999
+ // generator settings stored by older versions of Photoshop (written back in 'shmd' or 'extn' section)
6000
+ addHandler(
6001
+ 'extd',
6002
+ () => false,
6003
+ (reader, target, left) => {
6004
+ const desc = readVersionAndDescriptor(reader) as GeneratorSettingsDescriptor;
6005
+ if (desc.generatorSettings && target.generatorSettings === undefined) {
6006
+ target.generatorSettings = parseGeneratorSettings(desc.generatorSettings);
6007
+ }
6008
+ skipBytes(reader, left());
6009
+ },
6010
+ () => { },
6011
+ );
6012
+
6013
+ interface FrgbDescriptor {
6014
+ framedGroupRect: { _name: ''; _classID: 'classFloatRect'; 'Top ': number; Left: number; Btom: number; Rght: number; };
6015
+ framedGroupType: number;
6016
+ LockPPI?: boolean;
6017
+ PreferredResolution?: number;
6018
+ SavedIteratedName?: string;
6019
+ }
6020
+
6021
+ addHandler(
6022
+ 'frgb', // frame tool group
6023
+ hasKey('framedGroup'),
6024
+ (reader, target, left) => {
6025
+ const desc = readVersionAndDescriptor(reader) as FrgbDescriptor;
6026
+ const rect = desc.framedGroupRect;
6027
+ target.framedGroup = {
6028
+ rect: { top: rect['Top '], left: rect.Left, bottom: rect.Btom, right: rect.Rght },
6029
+ type: desc.framedGroupType,
6030
+ };
6031
+ if (desc.LockPPI !== undefined) target.framedGroup.lockPPI = desc.LockPPI;
6032
+ if (desc.PreferredResolution !== undefined) target.framedGroup.preferredResolution = desc.PreferredResolution;
6033
+ if (desc.SavedIteratedName !== undefined) target.framedGroup.savedIteratedName = desc.SavedIteratedName;
6034
+ skipBytes(reader, left());
6035
+ },
6036
+ (writer, target) => {
6037
+ const group = target.framedGroup!;
6038
+ const rect = group.rect;
6039
+ const desc: FrgbDescriptor = {
6040
+ framedGroupRect: { _name: '', _classID: 'classFloatRect', 'Top ': rect.top, Left: rect.left, Btom: rect.bottom, Rght: rect.right },
6041
+ framedGroupType: group.type,
6042
+ };
6043
+ if (group.lockPPI !== undefined) desc.LockPPI = group.lockPPI;
6044
+ if (group.preferredResolution !== undefined) desc.PreferredResolution = group.preferredResolution;
6045
+ if (group.savedIteratedName !== undefined) desc.SavedIteratedName = group.savedIteratedName;
6046
+ writeVersionAndDescriptor(writer, '', 'framedGroup', desc);
6047
+ },
6048
+ );
6049
+
6050
+ addHandler(
6051
+ 'anFX',
6052
+ hasKey('animationEffects'),
6053
+ (reader, target, left) => {
6054
+ target.animationEffects = readBytes(reader, left());
6055
+ },
6056
+ (writer, target) => {
6057
+ writeBytes(writer, target.animationEffects);
6058
+ },
6059
+ );
6060
+
6061
+ addHandler(
6062
+ 'PxSD', // pixel source data 2 (video layer frames)
6063
+ hasKey('pixelSourceData'),
6064
+ (reader, target, left) => {
6065
+ target.pixelSourceData = [];
6066
+
6067
+ while (left() >= 8) {
6068
+ const length = readLength64(reader);
6069
+ if (length > left()) throw new Error('Invalid PxSD data length');
6070
+ target.pixelSourceData.push(readBytes(reader, length));
6071
+ }
6072
+
6073
+ skipBytes(reader, left());
6074
+ },
6075
+ (writer, target) => {
6076
+ for (const data of target.pixelSourceData!) {
6077
+ writeUint32(writer, 0);
6078
+ writeUint32(writer, data.byteLength);
6079
+ writeBytes(writer, data);
6080
+ }
6081
+ },
6082
+ );
6083
+
5738
6084
  addHandler(
5739
6085
  'iOpa',
5740
6086
  hasKey('fillOpacity'),