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
package/src/descriptor.ts CHANGED
@@ -44,11 +44,6 @@ const unitsMap: Dict = {
44
44
  };
45
45
 
46
46
  const unitsMapRev = revMap(unitsMap);
47
- let logErrors = false;
48
-
49
- export function setLogErrors(value: boolean) {
50
- logErrors = value;
51
- }
52
47
 
53
48
  function makeType(name: string, classID: string) {
54
49
  return { name, classID };
@@ -98,6 +93,7 @@ const fieldToExtType: ExtTypeDict = {
98
93
  psVersion: nullType,
99
94
  docDefaultNewArtboardBackgroundColor: makeType('', 'RGBC'),
100
95
  artboardRect: makeType('', 'classFloatRect'),
96
+ framedGroupRect: makeType('', 'classFloatRect'),
101
97
  keyOriginRRectRadii: makeType('', 'radii'),
102
98
  keyOriginBoxCorners: nullType,
103
99
  rectangleCornerA: makeType('', 'Pnt '),
@@ -185,7 +181,7 @@ const fieldToArrayExtType: ExtTypeDict = {
185
181
  const typeToField: { [key: string]: string[]; } = {
186
182
  'TEXT': [
187
183
  'Txt ', 'printerName', 'Nm ', 'Idnt', 'blackAndWhitePresetFileName', 'LUT3DFileName', 'exportOption', 'scaleSettings',
188
- 'BackgroundImagePath', 'ImageSubfolderName',
184
+ 'BackgroundImagePath', 'ImageSubfolderName', 'SavedIteratedName',
189
185
  'presetFileName', 'curvesPresetFileName', 'mixerPresetFileName', 'placed', 'description', 'reason',
190
186
  'artboardPresetName', 'json', 'clipID', 'relPath', 'fullPath', 'mediaDescriptor', 'Msge',
191
187
  'altTag', 'url', 'cellText', 'preset', 'KnNm', 'FPth', 'comment', 'originalPath', 'Bk ', 'pathUnicodeName',
@@ -212,7 +208,7 @@ const typeToField: { [key: string]: string[]; } = {
212
208
  'meshExpansion', 'meshRigidity', 'VrsM', 'VrsN', 'NmbG', 'WLMn', 'WLMx', 'AmMn', 'AmMx', 'SclH', 'SclV',
213
209
  'Lvl ', 'TlNm', 'TlOf', 'FlRs', 'Thsh', 'ShrS', 'ShrE', 'FlRs', 'Vrnc', 'Strg', 'ExtS', 'ExtD',
214
210
  'HrzS', 'VrtS', 'NmbR', 'EdgF', 'Ang1', 'Ang2', 'Ang3', 'Ang4', 'lastAppliedComp', 'capturedInfo',
215
- 'pixelSourceType',
211
+ 'pixelSourceType', 'framedGroupType',
216
212
  ],
217
213
  'enum': [
218
214
  'textGridding', 'Ornt', 'warpStyle', 'warpRotate', 'Inte', 'Bltn', 'ClrS', 'BlrQ',
@@ -236,7 +232,7 @@ const typeToField: { [key: string]: string[]; } = {
236
232
  'materialsDisclosed', 'hasMotion', 'muted', 'Effc', 'selected', 'autoScope', 'fillCanvas',
237
233
  'cellTextIsHTML', 'Smoo', 'Clsp', 'validAtPosition', 'rigidType', 'hasoptions', 'filterMaskEnable',
238
234
  'filterMaskLinked', 'filterMaskExtendWithWhite', 'removeJPEGArtifact', 'Mnch', 'ExtF', 'ExtM',
239
- 'moreAccurate', 'GpuY', 'LIWy', 'Cnty', 'Lnkd', 'showAlteredVideo', 'lightingOn',
235
+ 'moreAccurate', 'GpuY', 'LIWy', 'Cnty', 'Lnkd', 'showAlteredVideo', 'lightingOn', 'LockPPI',
240
236
  ],
241
237
  'doub': [
242
238
  'warpValue', 'warpPerspective', 'warpPerspectiveOther', 'Intr', 'Wdth', 'Hght', 'Gmm ',
@@ -244,7 +240,7 @@ const typeToField: { [key: string]: string[]; } = {
244
240
  'xx', 'xy', 'yx', 'yy', 'tx', 'ty', 'FrGA', 'frameRate', 'audioLevel', 'rotation',
245
241
  'X ', 'Y ', 'redFloat', 'greenFloat', 'blueFloat', 'imageResolution',
246
242
  'PuX0', 'PuX1', 'PuX2', 'PuX3', 'PuY0', 'PuY1', 'PuY2', 'PuY3',
247
- 'stylization', 'cleanliness', 'brushScale', 'microBrush', 'specularity',
243
+ 'stylization', 'cleanliness', 'brushScale', 'microBrush', 'specularity', 'PreferredResolution',
248
244
  ],
249
245
  'UntF': [
250
246
  'sdwO', 'hglO', 'lagl', 'Lald', 'srgR', 'blur', 'Sftn', 'Opct', 'Dstn', 'Angl',
@@ -276,6 +272,14 @@ const channels = [
276
272
  const doubleColorClasses = ['Grsc', 'LbCl', 'GRYC', 'LABC'];
277
273
 
278
274
  // fallback for unknown keys, based on the type of the value
275
+ export const cameraRawFilterClassID = 'Adobe Camera Raw Filter';
276
+
277
+ // types of Camera Raw Filter settings (keys clash with other descriptors so they're typed by class)
278
+ export const cameraRawFilterKeyTypes: Dict = {
279
+ CrVe: 'TEXT', PrVN: 'long', PrVe: 'long', WBal: 'enum', Ex12: 'doub', ShpR: 'doub',
280
+ 'Crv ': 'VlLs', CrvR: 'VlLs', CrvG: 'VlLs', CrvB: 'VlLs',
281
+ };
282
+
279
283
  function guessOSType(value: any): string | undefined {
280
284
  if (typeof value === 'boolean') return 'bool';
281
285
  if (typeof value === 'number' && !Number.isInteger(value)) return 'doub';
@@ -441,7 +445,7 @@ export function readDescriptorStructure(reader: PsdReader, includeClass: boolean
441
445
  export const outputSettingsRoot = 'outputSettings';
442
446
 
443
447
  export function writeDescriptorStructure(writer: PsdWriter, name: string, classId: string, value: any, root: string) {
444
- if (logErrors && !classId) console.log('Missing classId for: ', name, classId, value);
448
+ if (writer.logMissingFeatures && !classId) writer.log('Missing classId for: ', name, classId, value);
445
449
 
446
450
  // write class structure
447
451
  writeUnicodeStringWithPadding(writer, name);
@@ -464,6 +468,11 @@ export function writeDescriptorStructure(writer: PsdWriter, name: string, classI
464
468
  let childRoot = root;
465
469
  let extType = fieldToExtType[key];
466
470
 
471
+ if (classId === cameraRawFilterClassID) {
472
+ type = cameraRawFilterKeyTypes[key] ?? 'long'; // other settings are integer levels
473
+ childRoot = cameraRawFilterClassID; // lists (curves) contain integer levels
474
+ }
475
+
467
476
  if (key === 'bounds' && root === 'text') {
468
477
  extType = makeType('', 'bounds');
469
478
  } else if (key === 'origin') {
@@ -533,7 +542,7 @@ export function writeDescriptorStructure(writer: PsdWriter, name: string, classI
533
542
  } else if (value[key].Ptrn) {
534
543
  extType = makeType('', 'patternLayer');
535
544
  } else {
536
- logErrors && console.log('Invalid strokeStyleContent value', value[key]);
545
+ writer.logMissingFeatures && writer.log('Invalid strokeStyleContent value', value[key]);
537
546
  }
538
547
  } else if (key === 'bounds' && root === 'quiltWarp') {
539
548
  extType = makeType('', 'classFloatRect');
@@ -552,7 +561,7 @@ export function writeDescriptorStructure(writer: PsdWriter, name: string, classI
552
561
  writeAsciiStringOrClassId(writer, key);
553
562
  writeSignature(writer, type || 'long');
554
563
  writeOSType(writer, type || 'long', value[key], key, extType, childRoot);
555
- if (logErrors && !type) console.log(`Missing descriptor field type for: '${key}' in`, value);
564
+ if (writer.logMissingFeatures && !type) writer.log(`Missing descriptor field type for: '${key}' in`, value);
556
565
  }
557
566
  }
558
567
 
@@ -676,10 +685,10 @@ function writeOSType(writer: PsdWriter, type: string, value: any, key: string, e
676
685
  writeInt32(writer, value.length);
677
686
 
678
687
  for (let i = 0; i < value.length; i++) {
679
- const type = fieldToArrayType[key] ?? guessOSType(value[i]);
688
+ const type = root === cameraRawFilterClassID ? 'long' : (fieldToArrayType[key] ?? guessOSType(value[i]));
680
689
  writeSignature(writer, type || 'long');
681
690
  writeOSType(writer, type || 'long', value[i], `${key}[]`, fieldToArrayExtType[key], root);
682
- if (logErrors && !type) console.log(`Missing descriptor array type for: '${key}' in`, value);
691
+ if (writer.logMissingFeatures && !type) writer.log(`Missing descriptor array type for: '${key}' in`, value);
683
692
  }
684
693
  break;
685
694
  case 'doub': // Double
@@ -1353,7 +1362,7 @@ function serializeFxObject(stroke: LayerEffectStroke) {
1353
1362
  return FrFX;
1354
1363
  }
1355
1364
 
1356
- export function serializeEffects(e: LayerEffectsInfo, log: boolean, multi: boolean) {
1365
+ export function serializeEffects(e: LayerEffectsInfo, writer: PsdWriter, multi: boolean) {
1357
1366
  const info: Lfx2Descriptor & LmfxDescriptor = multi ? {
1358
1367
  'Scl ': unitsPercentF(e.scale ?? 1),
1359
1368
  masterFXSwitch: !e.disabled,
@@ -1370,20 +1379,20 @@ export function serializeEffects(e: LayerEffectsInfo, log: boolean, multi: boole
1370
1379
  const useMulti = <T>(arr: undefined | T[]): arr is T[] => !!arr && arr.length > 1 && multi;
1371
1380
  const useSingle = <T>(arr: undefined | T[]): arr is T[] => !!arr && arr.length >= 1 && (!multi || arr.length === 1);
1372
1381
 
1373
- if (useSingle(e.dropShadow)) info.DrSh = serializeEffectObject(e.dropShadow[0], 'dropShadow', log);
1374
- if (useMulti(e.dropShadow)) info.dropShadowMulti = e.dropShadow.map(i => serializeEffectObject(i, 'dropShadow', log));
1375
- if (useSingle(e.innerShadow)) info.IrSh = serializeEffectObject(e.innerShadow[0], 'innerShadow', log);
1376
- if (useMulti(e.innerShadow)) info.innerShadowMulti = e.innerShadow.map(i => serializeEffectObject(i, 'innerShadow', log));
1377
- if (e.outerGlow) info.OrGl = serializeEffectObject(e.outerGlow, 'outerGlow', log);
1378
- if (useMulti(e.solidFill)) info.solidFillMulti = e.solidFill.map(i => serializeEffectObject(i, 'solidFill', log));
1379
- if (useMulti(e.gradientOverlay)) info.gradientFillMulti = e.gradientOverlay.map(i => serializeEffectObject(i, 'gradientOverlay', log));
1382
+ if (useSingle(e.dropShadow)) info.DrSh = serializeEffectObject(e.dropShadow[0], 'dropShadow', writer);
1383
+ if (useMulti(e.dropShadow)) info.dropShadowMulti = e.dropShadow.map(i => serializeEffectObject(i, 'dropShadow', writer));
1384
+ if (useSingle(e.innerShadow)) info.IrSh = serializeEffectObject(e.innerShadow[0], 'innerShadow', writer);
1385
+ if (useMulti(e.innerShadow)) info.innerShadowMulti = e.innerShadow.map(i => serializeEffectObject(i, 'innerShadow', writer));
1386
+ if (e.outerGlow) info.OrGl = serializeEffectObject(e.outerGlow, 'outerGlow', writer);
1387
+ if (useMulti(e.solidFill)) info.solidFillMulti = e.solidFill.map(i => serializeEffectObject(i, 'solidFill', writer));
1388
+ if (useMulti(e.gradientOverlay)) info.gradientFillMulti = e.gradientOverlay.map(i => serializeEffectObject(i, 'gradientOverlay', writer));
1380
1389
  if (useMulti(e.stroke)) info.frameFXMulti = e.stroke.map(i => serializeFxObject(i));
1381
- if (e.innerGlow) info.IrGl = serializeEffectObject(e.innerGlow, 'innerGlow', log);
1382
- if (e.bevel) info.ebbl = serializeEffectObject(e.bevel, 'bevel', log);
1383
- if (useSingle(e.solidFill)) info.SoFi = serializeEffectObject(e.solidFill[0], 'solidFill', log);
1384
- if (e.patternOverlay) info.patternFill = serializeEffectObject(e.patternOverlay, 'patternOverlay', log);
1385
- if (useSingle(e.gradientOverlay)) info.GrFl = serializeEffectObject(e.gradientOverlay[0], 'gradientOverlay', log);
1386
- if (e.satin) info.ChFX = serializeEffectObject(e.satin, 'satin', log);
1390
+ if (e.innerGlow) info.IrGl = serializeEffectObject(e.innerGlow, 'innerGlow', writer);
1391
+ if (e.bevel) info.ebbl = serializeEffectObject(e.bevel, 'bevel', writer);
1392
+ if (useSingle(e.solidFill)) info.SoFi = serializeEffectObject(e.solidFill[0], 'solidFill', writer);
1393
+ if (e.patternOverlay) info.patternFill = serializeEffectObject(e.patternOverlay, 'patternOverlay', writer);
1394
+ if (useSingle(e.gradientOverlay)) info.GrFl = serializeEffectObject(e.gradientOverlay[0], 'gradientOverlay', writer);
1395
+ if (e.satin) info.ChFX = serializeEffectObject(e.satin, 'satin', writer);
1387
1396
  if (useSingle(e.stroke)) info.FrFX = serializeFxObject(e.stroke?.[0]);
1388
1397
 
1389
1398
  if (multi) {
@@ -1406,31 +1415,31 @@ export function serializeEffects(e: LayerEffectsInfo, log: boolean, multi: boole
1406
1415
  return info;
1407
1416
  }
1408
1417
 
1409
- export function parseEffects(info: Lfx2Descriptor & LmfxDescriptor, log: boolean) {
1418
+ export function parseEffects(info: Lfx2Descriptor & LmfxDescriptor, reader: PsdReader) {
1410
1419
  const effects: LayerEffectsInfo = {};
1411
1420
  const { _name, _classID, 'Scl ': scale, masterFXSwitch, DrSh, dropShadowMulti, IrSh, innerShadowMulti, OrGl, IrGl, ebbl, SoFi, solidFillMulti, patternFill, GrFl, gradientFillMulti, ChFX, FrFX, frameFXMulti, numModifyingFX, ...rest } = info as typeof info & { _name?: string; _classID?: string; };
1412
1421
  if (!masterFXSwitch) effects.disabled = true;
1413
1422
  if (scale) effects.scale = parsePercent(scale);
1414
- if (DrSh) effects.dropShadow = [parseEffectObject(DrSh, log)];
1415
- if (dropShadowMulti) effects.dropShadow = dropShadowMulti.map(i => parseEffectObject(i, log));
1416
- if (IrSh) effects.innerShadow = [parseEffectObject(IrSh, log)];
1417
- if (innerShadowMulti) effects.innerShadow = innerShadowMulti.map(i => parseEffectObject(i, log));
1418
- if (OrGl) effects.outerGlow = parseEffectObject(OrGl, log);
1419
- if (IrGl) effects.innerGlow = parseEffectObject(IrGl, log);
1420
- if (ebbl) effects.bevel = parseEffectObject(ebbl, log, 'bevel');
1421
- if (SoFi) effects.solidFill = [parseEffectObject(SoFi, log)];
1422
- if (solidFillMulti) effects.solidFill = solidFillMulti.map(i => parseEffectObject(i, log));
1423
- if (patternFill) effects.patternOverlay = parseEffectObject(patternFill, log);
1424
- if (GrFl) effects.gradientOverlay = [parseEffectObject(GrFl, log)];
1425
- if (gradientFillMulti) effects.gradientOverlay = gradientFillMulti.map(i => parseEffectObject(i, log));
1426
- if (ChFX) effects.satin = parseEffectObject(ChFX, log);
1423
+ if (DrSh) effects.dropShadow = [parseEffectObject(DrSh, reader)];
1424
+ if (dropShadowMulti) effects.dropShadow = dropShadowMulti.map(i => parseEffectObject(i, reader));
1425
+ if (IrSh) effects.innerShadow = [parseEffectObject(IrSh, reader)];
1426
+ if (innerShadowMulti) effects.innerShadow = innerShadowMulti.map(i => parseEffectObject(i, reader));
1427
+ if (OrGl) effects.outerGlow = parseEffectObject(OrGl, reader);
1428
+ if (IrGl) effects.innerGlow = parseEffectObject(IrGl, reader);
1429
+ if (ebbl) effects.bevel = parseEffectObject(ebbl, reader, 'bevel');
1430
+ if (SoFi) effects.solidFill = [parseEffectObject(SoFi, reader)];
1431
+ if (solidFillMulti) effects.solidFill = solidFillMulti.map(i => parseEffectObject(i, reader));
1432
+ if (patternFill) effects.patternOverlay = parseEffectObject(patternFill, reader);
1433
+ if (GrFl) effects.gradientOverlay = [parseEffectObject(GrFl, reader)];
1434
+ if (gradientFillMulti) effects.gradientOverlay = gradientFillMulti.map(i => parseEffectObject(i, reader));
1435
+ if (ChFX) effects.satin = parseEffectObject(ChFX, reader);
1427
1436
  if (FrFX) effects.stroke = [parseFxObject(FrFX)];
1428
1437
  if (frameFXMulti) effects.stroke = frameFXMulti.map(i => parseFxObject(i));
1429
- if (log && Object.keys(rest).length) console.log('Unhandled effect keys:', rest);
1438
+ if (reader.logMissingFeatures && Object.keys(rest).length) reader.log('Unhandled effect keys:', rest);
1430
1439
  return effects;
1431
1440
  }
1432
1441
 
1433
- function parseKeyList(keyList: TimelineKeyDescriptor[], logMissingFeatures: boolean) {
1442
+ function parseKeyList(keyList: TimelineKeyDescriptor[], reader: PsdReader) {
1434
1443
  const keys: TimelineKey[] = [];
1435
1444
 
1436
1445
  for (let j = 0; j < keyList.length; j++) {
@@ -1454,7 +1463,7 @@ function parseKeyList(keyList: TimelineKeyDescriptor[], logMissingFeatures: bool
1454
1463
  break;
1455
1464
  case 'keyType.sheetStyle': {
1456
1465
  const key: TimelineKey = { interpolation, time, selected, type: 'style' };
1457
- if (animKey.sheetStyle.Lefx) key.style = parseEffects(animKey.sheetStyle.Lefx, logMissingFeatures);
1466
+ if (animKey.sheetStyle.Lefx) key.style = parseEffects(animKey.sheetStyle.Lefx, reader);
1458
1467
  keys.push(key);
1459
1468
  break;
1460
1469
  }
@@ -1472,7 +1481,7 @@ function parseKeyList(keyList: TimelineKeyDescriptor[], logMissingFeatures: bool
1472
1481
  return keys;
1473
1482
  }
1474
1483
 
1475
- function serializeKeyList(keys: TimelineKey[]): TimelineKeyDescriptor[] {
1484
+ function serializeKeyList(keys: TimelineKey[], writer: PsdWriter): TimelineKeyDescriptor[] {
1476
1485
  const keyList: TimelineKeyDescriptor[] = [];
1477
1486
 
1478
1487
  for (let j = 0; j < keys.length; j++) {
@@ -1493,7 +1502,7 @@ function serializeKeyList(keys: TimelineKey[]): TimelineKeyDescriptor[] {
1493
1502
  break;
1494
1503
  case 'style':
1495
1504
  animKey = { Type: 'keyType.sheetStyle', sheetStyle: { Vrsn: 1, blendOptions: {} } };
1496
- if (key.style) animKey.sheetStyle = { Vrsn: 1, Lefx: serializeEffects(key.style, false, false), blendOptions: {} };
1505
+ if (key.style) animKey.sheetStyle = { Vrsn: 1, Lefx: serializeEffects(key.style, writer, false), blendOptions: {} };
1497
1506
  break;
1498
1507
  case 'globalLighting': {
1499
1508
  animKey = { Type: 'keyType.globalLighting', gblA: key.globalAngle, globalAltitude: key.globalAltitude };
@@ -1508,7 +1517,7 @@ function serializeKeyList(keys: TimelineKey[]): TimelineKeyDescriptor[] {
1508
1517
  return keyList;
1509
1518
  }
1510
1519
 
1511
- export function parseTrackList(trackList: TimelineTrackDescriptor[], logMissingFeatures: boolean) {
1520
+ export function parseTrackList(trackList: TimelineTrackDescriptor[], reader: PsdReader) {
1512
1521
  const tracks: TimelineTrack[] = [];
1513
1522
 
1514
1523
  for (let i = 0; i < trackList.length; i++) {
@@ -1516,14 +1525,14 @@ export function parseTrackList(trackList: TimelineTrackDescriptor[], logMissingF
1516
1525
  const track: TimelineTrack = {
1517
1526
  type: stdTrackID.decode(tr.trackID),
1518
1527
  enabled: tr.enab,
1519
- keys: parseKeyList(tr.keyList, logMissingFeatures),
1528
+ keys: parseKeyList(tr.keyList, reader),
1520
1529
  };
1521
1530
 
1522
1531
  if (tr.effectParams) {
1523
1532
  track.effectParams = {
1524
1533
  fillCanvas: tr.effectParams.fillCanvas,
1525
1534
  zoomOrigin: tr.effectParams.zoomOrigin,
1526
- keys: parseKeyList(tr.effectParams.keyList, logMissingFeatures),
1535
+ keys: parseKeyList(tr.effectParams.keyList, reader),
1527
1536
  };
1528
1537
  }
1529
1538
 
@@ -1533,7 +1542,7 @@ export function parseTrackList(trackList: TimelineTrackDescriptor[], logMissingF
1533
1542
  return tracks;
1534
1543
  }
1535
1544
 
1536
- export function serializeTrackList(tracks: TimelineTrack[]): TimelineTrackDescriptor[] {
1545
+ export function serializeTrackList(tracks: TimelineTrack[], writer: PsdWriter): TimelineTrackDescriptor[] {
1537
1546
  const trackList: TimelineTrackDescriptor[] = [];
1538
1547
 
1539
1548
  for (let i = 0; i < tracks.length; i++) {
@@ -1545,12 +1554,12 @@ export function serializeTrackList(tracks: TimelineTrack[]): TimelineTrackDescri
1545
1554
  Effc: !!t.effectParams,
1546
1555
  ...(t.effectParams ? {
1547
1556
  effectParams: {
1548
- keyList: serializeKeyList(t.effectParams.keys),
1557
+ keyList: serializeKeyList(t.effectParams.keys, writer),
1549
1558
  fillCanvas: t.effectParams.fillCanvas,
1550
1559
  zoomOrigin: t.effectParams.zoomOrigin,
1551
1560
  }
1552
1561
  } : {}),
1553
- keyList: serializeKeyList(t.keys),
1562
+ keyList: serializeKeyList(t.keys, writer),
1554
1563
  });
1555
1564
  }
1556
1565
 
@@ -1561,7 +1570,7 @@ type AllEffects = LayerEffectShadow & LayerEffectsOuterGlow & LayerEffectStroke
1561
1570
  LayerEffectInnerGlow & LayerEffectBevel & LayerEffectSolidFill &
1562
1571
  LayerEffectPatternOverlay & LayerEffectSatin & LayerEffectGradientOverlay;
1563
1572
 
1564
- function parseEffectObject(obj: any, reportErrors: boolean, objName?: string) {
1573
+ function parseEffectObject(obj: any, reader: PsdReader, objName?: string) {
1565
1574
  const result: AllEffects = {} as any;
1566
1575
 
1567
1576
  for (const key of Object.keys(obj)) {
@@ -1636,14 +1645,14 @@ function parseEffectObject(obj: any, reportErrors: boolean, objName?: string) {
1636
1645
  case '_classID':
1637
1646
  break;
1638
1647
  default:
1639
- reportErrors && console.log(`Invalid effect key: '${key}', value:`, val);
1648
+ reader.logMissingFeatures && reader.log(`Invalid effect key: '${key}', value:`, val);
1640
1649
  }
1641
1650
  }
1642
1651
 
1643
1652
  return result;
1644
1653
  }
1645
1654
 
1646
- function serializeEffectObject(obj: any, objName: string, reportErrors: boolean) {
1655
+ function serializeEffectObject(obj: any, objName: string, writer: PsdWriter) {
1647
1656
  const result: any = {
1648
1657
  enab: false,
1649
1658
  };
@@ -1720,7 +1729,7 @@ function serializeEffectObject(obj: any, objName: string, reportErrors: boolean)
1720
1729
  result[key] = val;
1721
1730
  break;
1722
1731
  default:
1723
- reportErrors && console.log(`Invalid effect key: '${key}', value:`, val);
1732
+ writer.logMissingFeatures && writer.log(`Invalid effect key: '${key}', value:`, val);
1724
1733
  }
1725
1734
  }
1726
1735
 
@@ -1948,6 +1957,23 @@ function isBookColor(color: any) {
1948
1957
  }
1949
1958
 
1950
1959
  export function parseColor(color: DescriptorColor): Color {
1960
+ const result = parseColorValues(color);
1961
+
1962
+ if (isBookColor(color)) {
1963
+ const book = color as any as { 'Bk '?: string; 'Nm '?: string; bookID?: number; bookKey?: Uint8Array | string; };
1964
+ const key = book.bookKey;
1965
+ result.book = {
1966
+ book: book['Bk '] ?? '',
1967
+ name: book['Nm '] ?? '',
1968
+ bookID: book.bookID ?? 0,
1969
+ bookKey: typeof key === 'string' ? key : (key ? String.fromCharCode(...Array.from(key)) : ''),
1970
+ };
1971
+ }
1972
+
1973
+ return result;
1974
+ }
1975
+
1976
+ function parseColorValues(color: DescriptorColor): Color {
1951
1977
  if ('H ' in color) {
1952
1978
  return { h: parsePercentOrAngle(color['H ']), s: color.Strt / 100, b: color.Brgh / 100 };
1953
1979
  } else if ('Rd ' in color) {
@@ -1966,13 +1992,26 @@ export function parseColor(color: DescriptorColor): Color {
1966
1992
  } else if ('redFloat' in color) {
1967
1993
  return { fr: color.redFloat, fg: color.greenFloat, fb: color.blueFloat };
1968
1994
  } else if (isBookColor(color)) {
1969
- return { r: 0, g: 0, b: 0 }; // book (Pantone) colors are not supported
1995
+ return { r: 0, g: 0, b: 0 }; // book (Pantone) color without color values
1970
1996
  } else {
1971
1997
  throw new Error('Unsupported color descriptor');
1972
1998
  }
1973
1999
  }
1974
2000
 
1975
2001
  export function serializeColor(color: Color | undefined): DescriptorColor {
2002
+ const result = serializeColorValues(color);
2003
+
2004
+ if (color?.book) {
2005
+ const { book, name, bookID, bookKey } = color.book;
2006
+ const key = new Uint8Array(bookKey.length);
2007
+ for (let i = 0; i < key.length; i++) key[i] = bookKey.charCodeAt(i) & 0xff;
2008
+ Object.assign(result, { 'Bk ': book, 'Nm ': name, bookID, bookKey: key });
2009
+ }
2010
+
2011
+ return result;
2012
+ }
2013
+
2014
+ function serializeColorValues(color: Color | undefined): DescriptorColor {
1976
2015
  if (!color) {
1977
2016
  return { _name: '', _classID: 'RGBC', 'Rd ': 0, 'Grn ': 0, 'Bl ': 0 };
1978
2017
  } else if ('r' in color) {
@@ -2455,3 +2494,9 @@ export const presetKindType = createEnum<'custom' | 'default'>('presetKindType',
2455
2494
  custom: 'presetKindCustom',
2456
2495
  default: 'presetKindDefault',
2457
2496
  });
2497
+
2498
+ export const patternColorHandling = createEnum<'convert' | 'avoid conversion' | 'luminance only'>('', 'convert', {
2499
+ 'convert': 'conv',
2500
+ 'avoid conversion': 'avod',
2501
+ 'luminance only': 'lumi',
2502
+ });
package/src/helpers.ts CHANGED
@@ -40,7 +40,7 @@ export const toBlendMode: { [key: string]: BlendMode } = {
40
40
  Object.keys(toBlendMode).forEach(key => fromBlendMode[toBlendMode[key]] = key);
41
41
 
42
42
  export const layerColors: LayerColor[] = [
43
- 'none', 'red', 'orange', 'yellow', 'green', 'blue', 'violet', 'gray'
43
+ 'none', 'red', 'orange', 'yellow', 'green', 'blue', 'violet', 'gray', 'seafoam', 'indigo', 'magenta', 'fuchsia',
44
44
  ];
45
45
 
46
46
  // keys not listed in documentation that Photoshop writes with 64bit length in PSB files (with 8B64 signature)
@@ -165,9 +165,10 @@ export function clamp(value: number, min: number, max: number) {
165
165
 
166
166
  export function hasAlpha(data: PixelData) {
167
167
  const size = data.width * data.height * 4;
168
+ const max = data.data instanceof Float32Array ? 1 : (data.data instanceof Uint16Array ? 0xffff : 0xff);
168
169
 
169
170
  for (let i = 3; i < size; i += 4) {
170
- if (data.data[i] !== 255) {
171
+ if (data.data[i] !== max) {
171
172
  return true;
172
173
  }
173
174
  }
@@ -175,6 +176,154 @@ export function hasAlpha(data: PixelData) {
175
176
  return false;
176
177
  }
177
178
 
179
+ // Polynomial approximation of U.S. Web Coated (SWOP) v2 CMYK to RGB conversion (default CMYK profile of Photoshop).
180
+ // Coefficients are taken from pdf.js (DeviceCmykCS in src/core/colorspace.js, https://github.com/mozilla/pdf.js),
181
+ // Copyright 2012 Mozilla Foundation, licensed under the Apache License, Version 2.0
182
+ // (http://www.apache.org/licenses/LICENSE-2.0)
183
+ // c, m, y, k - ink values (0 - 1), output - RGB values (0 - 255, not rounded or clamped)
184
+ export function cmykToRgbValues(c: number, m: number, y: number, k: number, out: number[]) {
185
+ out[0] = 255 +
186
+ c * (-4.387332384609988 * c + 54.48615194189176 * m + 18.82290502165302 * y + 212.25662451639585 * k - 285.2331026137004) +
187
+ m * (1.7149763477362134 * m - 5.6096736904047315 * y - 17.873870861415444 * k - 5.497006427196366) +
188
+ y * (-2.5217340131683033 * y - 21.248923337353073 * k + 17.5119270841813) +
189
+ k * (-21.86122147463605 * k - 189.48180835922747);
190
+ out[1] = 255 +
191
+ c * (8.841041422036149 * c + 60.118027045597366 * m + 6.871425592049007 * y + 31.159100130055922 * k - 79.2970844816548) +
192
+ m * (-15.310361306967817 * m + 17.575251261109482 * y + 131.35250912493976 * k - 190.9453302588951) +
193
+ y * (4.444339102852739 * y + 9.8632861493405 * k - 24.86741582555878) +
194
+ k * (-20.737325471181034 * k - 187.80453709719578);
195
+ out[2] = 255 +
196
+ c * (0.8842522430003296 * c + 8.078677503112928 * m + 30.89978309703729 * y - 0.23883238689178934 * k - 14.183576799673286) +
197
+ m * (10.49593273432072 * m + 63.02378494754052 * y + 50.606957656360734 * k - 112.23884253719248) +
198
+ y * (0.03296041114873217 * y + 115.60384449646641 * k - 193.58209356861505) +
199
+ k * (-22.33816807309886 * k - 180.12613974708367);
200
+ }
201
+
202
+ const clamp01 = (v: number) => v < 0 ? 0 : (v > 1 ? 1 : v);
203
+
204
+ // finds c, m, y (with fixed k) for given RGB color using Gauss-Newton method, returns squared error
205
+ function solveCmy(r: number, g: number, b: number, k: number, x: number[]) {
206
+ const f = [0, 0, 0], f2 = [0, 0, 0], e = [0, 0, 0];
207
+ const J = [[0, 0, 0], [0, 0, 0], [0, 0, 0]];
208
+ const h = 1e-4;
209
+
210
+ for (let iteration = 0; iteration < 20; iteration++) {
211
+ cmykToRgbValues(x[0], x[1], x[2], k, f);
212
+ e[0] = f[0] - r; e[1] = f[1] - g; e[2] = f[2] - b;
213
+ if ((e[0] * e[0] + e[1] * e[1] + e[2] * e[2]) < 1e-6) break;
214
+
215
+ for (let j = 0; j < 3; j++) {
216
+ const s = x[j];
217
+ x[j] = s + h;
218
+ cmykToRgbValues(x[0], x[1], x[2], k, f2);
219
+ x[j] = s;
220
+ for (let i = 0; i < 3; i++) J[i][j] = (f2[i] - f[i]) / h;
221
+ }
222
+
223
+ const [[a, b_, c], [d, e_, f_], [g_, h_, i_]] = J;
224
+ const det = a * (e_ * i_ - f_ * h_) - b_ * (d * i_ - f_ * g_) + c * (d * h_ - e_ * g_);
225
+ if (Math.abs(det) < 1e-12) break;
226
+
227
+ const inverse = [
228
+ [(e_ * i_ - f_ * h_), (c * h_ - b_ * i_), (b_ * f_ - c * e_)],
229
+ [(f_ * g_ - d * i_), (a * i_ - c * g_), (c * d - a * f_)],
230
+ [(d * h_ - e_ * g_), (b_ * g_ - a * h_), (a * e_ - b_ * d)],
231
+ ];
232
+
233
+ let changed = 0;
234
+
235
+ for (let j = 0; j < 3; j++) {
236
+ const delta = -(inverse[j][0] * e[0] + inverse[j][1] * e[1] + inverse[j][2] * e[2]) / det;
237
+ const value = clamp01(x[j] + delta);
238
+ changed += Math.abs(value - x[j]);
239
+ x[j] = value;
240
+ }
241
+
242
+ if (changed < 1e-9) break;
243
+ }
244
+
245
+ cmykToRgbValues(x[0], x[1], x[2], k, f);
246
+ return (f[0] - r) * (f[0] - r) + (f[1] - g) * (f[1] - g) + (f[2] - b) * (f[2] - b);
247
+ }
248
+
249
+ // finds CMYK ink values (0 - 1) for RGB color (0 - 255), out of gamut colors are approximated
250
+ function rgbToCmykExact(r: number, g: number, b: number, out: number[]) {
251
+ const max = Math.max(r, g, b) / 255;
252
+ const blackGeneration = Math.pow(clamp01((1 - max - 0.2) / 0.8), 1.3);
253
+ const x = [0, 0, 0];
254
+ let bestScore = Infinity, bestError = Infinity;
255
+
256
+ const tryBlack = (k: number) => {
257
+ x[0] = clamp01(1 - r / 255 - k);
258
+ x[1] = clamp01(1 - g / 255 - k);
259
+ x[2] = clamp01(1 - b / 255 - k);
260
+ const error = solveCmy(r, g, b, k, x);
261
+ // prefer solutions close to black generation value if the error is small enough
262
+ const score = Math.max(error, 0.1) + Math.abs(k - blackGeneration) * 0.01;
263
+
264
+ if (score < bestScore) {
265
+ bestScore = score;
266
+ bestError = error;
267
+ out[0] = x[0]; out[1] = x[1]; out[2] = x[2]; out[3] = k;
268
+ }
269
+ };
270
+
271
+ tryBlack(blackGeneration);
272
+
273
+ if (bestError > 0.1) {
274
+ for (let i = 0; i <= 10; i++) tryBlack(i / 10);
275
+ }
276
+
277
+ if (bestError > 0.1) {
278
+ const k = out[3];
279
+ for (let i = -5; i <= 5; i++) tryBlack(clamp01(k + i / 100));
280
+ }
281
+ }
282
+
283
+ const cmykLutSize = 33;
284
+ let cmykLut: Float32Array | undefined;
285
+
286
+ function getCmykLut() {
287
+ if (!cmykLut) {
288
+ const n = cmykLutSize;
289
+ const lut = new Float32Array(n * n * n * 4);
290
+ const out = [0, 0, 0, 0];
291
+
292
+ for (let r = 0, o = 0; r < n; r++) {
293
+ for (let g = 0; g < n; g++) {
294
+ for (let b = 0; b < n; b++, o += 4) {
295
+ rgbToCmykExact(r * 255 / (n - 1), g * 255 / (n - 1), b * 255 / (n - 1), out);
296
+ lut[o] = out[0]; lut[o + 1] = out[1]; lut[o + 2] = out[2]; lut[o + 3] = out[3];
297
+ }
298
+ }
299
+ }
300
+
301
+ cmykLut = lut;
302
+ }
303
+
304
+ return cmykLut;
305
+ }
306
+
307
+ // inverse of cmykToRgbValues, r, g, b - RGB values (0 - 1), output - CMYK ink values (0 - 1)
308
+ export function rgbToCmykValues(r: number, g: number, b: number, out: number[]) {
309
+ const lut = getCmykLut();
310
+ const n1 = cmykLutSize - 1;
311
+ const fr = clamp01(r) * n1, fg = clamp01(g) * n1, fb = clamp01(b) * n1;
312
+ const r0 = Math.min(n1 - 1, Math.floor(fr)), g0 = Math.min(n1 - 1, Math.floor(fg)), b0 = Math.min(n1 - 1, Math.floor(fb));
313
+ const dr = fr - r0, dg = fg - g0, db = fb - b0;
314
+ out[0] = out[1] = out[2] = out[3] = 0;
315
+
316
+ for (let i = 0; i < 8; i++) {
317
+ const ir = i >> 2, ig = (i >> 1) & 1, ib = i & 1;
318
+ const w = (ir ? dr : 1 - dr) * (ig ? dg : 1 - dg) * (ib ? db : 1 - db);
319
+ const o = (((r0 + ir) * cmykLutSize + g0 + ig) * cmykLutSize + b0 + ib) * 4;
320
+ out[0] += w * lut[o];
321
+ out[1] += w * lut[o + 1];
322
+ out[2] += w * lut[o + 2];
323
+ out[3] += w * lut[o + 3];
324
+ }
325
+ }
326
+
178
327
  export function resetImageData({ data }: PixelData) {
179
328
  const alpha = (data instanceof Float32Array) ? 1.0 : ((data instanceof Uint16Array) ? 0xffff : 0xff);
180
329
 
@@ -353,6 +502,117 @@ export function writeDataRLE(buffer: Uint8Array, { data, width, height }: PixelD
353
502
  return buffer.subarray(0, o);
354
503
  }
355
504
 
505
+ // single channel of image data (8bit, 16bit or 32bit values)
506
+ export type PlanarData = Uint8Array | Uint16Array | Float32Array;
507
+
508
+ // converts channel values to big-endian bytes
509
+ export function planarToBytes(values: PlanarData): Uint8Array {
510
+ if (values instanceof Uint8Array) return values;
511
+
512
+ const bytes = new Uint8Array(values.byteLength);
513
+ const view = new DataView(bytes.buffer);
514
+
515
+ if (values instanceof Uint16Array) {
516
+ for (let i = 0; i < values.length; i++) view.setUint16(i * 2, values[i], false);
517
+ } else {
518
+ for (let i = 0; i < values.length; i++) view.setFloat32(i * 4, values[i], false);
519
+ }
520
+
521
+ return bytes;
522
+ }
523
+
524
+ // RLE (PackBits) compression of rows of bytes, prefixed with byte counts of each row
525
+ export function writeDataRLEBytes(bytes: Uint8Array, rowBytes: number, height: number, large: boolean) {
526
+ const countSize = large ? 4 : 2;
527
+ const buffer = new Uint8Array(height * (countSize + rowBytes + Math.ceil(rowBytes / 128)));
528
+ let o = countSize * height;
529
+
530
+ for (let y = 0; y < height; y++) {
531
+ const start = o;
532
+ const rowStart = y * rowBytes;
533
+ const rowEnd = rowStart + rowBytes;
534
+ let i = rowStart;
535
+
536
+ while (i < rowEnd) {
537
+ let run = 1;
538
+ while (i + run < rowEnd && run < 128 && bytes[i + run] === bytes[i]) run++;
539
+
540
+ if (run > 1) {
541
+ buffer[o++] = 257 - run;
542
+ buffer[o++] = bytes[i];
543
+ i += run;
544
+ } else {
545
+ // literal bytes until a run of at least 3 equal bytes
546
+ const countIndex = o++;
547
+ let count = 0;
548
+
549
+ while (i < rowEnd && count < 128) {
550
+ if (i + 2 < rowEnd && bytes[i] === bytes[i + 1] && bytes[i] === bytes[i + 2]) break;
551
+ buffer[o++] = bytes[i++];
552
+ count++;
553
+ }
554
+
555
+ buffer[countIndex] = count - 1;
556
+ }
557
+ }
558
+
559
+ const length = o - start;
560
+
561
+ for (let j = 0; j < countSize; j++) {
562
+ buffer[y * countSize + j] = (length >>> ((countSize - j - 1) * 8)) & 0xff;
563
+ }
564
+ }
565
+
566
+ return buffer.subarray(0, o);
567
+ }
568
+
569
+ // zip compression with prediction (values are delta encoded per row, 32bit values are split into byte planes first)
570
+ export function writeDataZipWithPrediction(values: PlanarData, width: number, height: number) {
571
+ let bytes: Uint8Array;
572
+
573
+ if (values instanceof Uint8Array) {
574
+ bytes = new Uint8Array(width * height);
575
+
576
+ for (let y = 0, i = 0; y < height; y++) {
577
+ for (let x = 0, prev = 0; x < width; x++, i++) {
578
+ bytes[i] = values[i] - prev; // Uint8Array wraps values on assignment
579
+ prev = values[i];
580
+ }
581
+ }
582
+ } else if (values instanceof Uint16Array) {
583
+ bytes = new Uint8Array(width * height * 2);
584
+
585
+ for (let y = 0, i = 0; y < height; y++) {
586
+ for (let x = 0, prev = 0; x < width; x++, i++) {
587
+ const delta = (values[i] - prev) & 0xffff;
588
+ bytes[i * 2] = delta >> 8;
589
+ bytes[i * 2 + 1] = delta & 0xff;
590
+ prev = values[i];
591
+ }
592
+ }
593
+ } else {
594
+ const rowBytes = width * 4;
595
+ const source = planarToBytes(values);
596
+ bytes = new Uint8Array(rowBytes * height);
597
+
598
+ for (let y = 0; y < height; y++) {
599
+ const row = y * rowBytes;
600
+
601
+ for (let x = 0; x < width; x++) {
602
+ for (let b = 0; b < 4; b++) {
603
+ bytes[row + b * width + x] = source[row + x * 4 + b];
604
+ }
605
+ }
606
+
607
+ for (let x = rowBytes - 1; x > 0; x--) {
608
+ bytes[row + x] -= bytes[row + x - 1];
609
+ }
610
+ }
611
+ }
612
+
613
+ return deflateSync(bytes);
614
+ }
615
+
356
616
  export function writeDataZipWithoutPrediction({ data, width, height }: PixelData, offsets: number[]) {
357
617
  const size = width * height;
358
618
  const channel = new Uint8Array(size);