@pixerate/editor 0.112.0 → 0.114.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 (129) hide show
  1. package/THIRD_PARTY_NOTICES.md +11 -0
  2. package/dist/animation-editor/index.cjs +60 -54
  3. package/dist/animation-editor/index.cjs.map +1 -1
  4. package/dist/animation-editor/index.d.cts +3 -3
  5. package/dist/animation-editor/index.d.ts +3 -3
  6. package/dist/animation-editor/index.js +14 -8
  7. package/dist/animation-editor/index.js.map +1 -1
  8. package/dist/builtin-CY97ppKM.d.cts +354 -0
  9. package/dist/builtin-CY97ppKM.d.ts +354 -0
  10. package/dist/canvas/index.cjs +3 -3
  11. package/dist/canvas/index.cjs.map +1 -1
  12. package/dist/canvas/index.js +9 -9
  13. package/dist/chunk-26PGXHY7.cjs +753 -0
  14. package/dist/chunk-26PGXHY7.cjs.map +1 -0
  15. package/dist/chunk-2OJACTPR.cjs +929 -0
  16. package/dist/chunk-2OJACTPR.cjs.map +1 -0
  17. package/dist/chunk-3UFGNHSP.js +929 -0
  18. package/dist/chunk-3UFGNHSP.js.map +1 -0
  19. package/dist/{chunk-YGIMK4NX.cjs → chunk-5OVJHMZQ.cjs} +757 -825
  20. package/dist/chunk-5OVJHMZQ.cjs.map +1 -0
  21. package/dist/chunk-643B2HKC.cjs +534 -0
  22. package/dist/chunk-643B2HKC.cjs.map +1 -0
  23. package/dist/chunk-6FCKOS2B.js +534 -0
  24. package/dist/chunk-6FCKOS2B.js.map +1 -0
  25. package/dist/chunk-BRLYOPLT.js +2574 -0
  26. package/dist/chunk-BRLYOPLT.js.map +1 -0
  27. package/dist/{chunk-LQEEDHOP.js → chunk-C6C5PDKU.js} +759 -761
  28. package/dist/chunk-C6C5PDKU.js.map +1 -0
  29. package/dist/chunk-DFQEX3AM.cjs +781 -0
  30. package/dist/chunk-DFQEX3AM.cjs.map +1 -0
  31. package/dist/{chunk-CZOXTEMN.js → chunk-EUVSQ25B.js} +336 -404
  32. package/dist/chunk-EUVSQ25B.js.map +1 -0
  33. package/dist/chunk-G5NMLUVF.cjs +2574 -0
  34. package/dist/chunk-G5NMLUVF.cjs.map +1 -0
  35. package/dist/chunk-LKCYANMW.js +781 -0
  36. package/dist/chunk-LKCYANMW.js.map +1 -0
  37. package/dist/{chunk-W3GXSUSQ.cjs → chunk-LPLCWZTM.cjs} +5087 -1283
  38. package/dist/chunk-LPLCWZTM.cjs.map +1 -0
  39. package/dist/chunk-MMVHBKMM.js +753 -0
  40. package/dist/chunk-MMVHBKMM.js.map +1 -0
  41. package/dist/{chunk-POGBUYWE.cjs → chunk-ST7E3NEE.cjs} +857 -859
  42. package/dist/chunk-ST7E3NEE.cjs.map +1 -0
  43. package/dist/{chunk-7VOHOPLW.cjs → chunk-TCKIXI46.cjs} +59 -59
  44. package/dist/{chunk-7VOHOPLW.cjs.map → chunk-TCKIXI46.cjs.map} +1 -1
  45. package/dist/{chunk-XIOMMGPD.cjs → chunk-TFOPHZ3U.cjs} +1 -1
  46. package/dist/chunk-TFOPHZ3U.cjs.map +1 -0
  47. package/dist/{chunk-Z4BC6M74.js → chunk-UBIXVXPR.js} +2 -2
  48. package/dist/{chunk-BLWCAQCW.js → chunk-VXR7XWMF.js} +4574 -770
  49. package/dist/chunk-VXR7XWMF.js.map +1 -0
  50. package/dist/{chunk-HNS7UHCM.js → chunk-XK5LZKBD.js} +1 -1
  51. package/dist/chunk-XK5LZKBD.js.map +1 -0
  52. package/dist/cms/index.cjs +146 -0
  53. package/dist/cms/index.cjs.map +1 -0
  54. package/dist/cms/index.d.cts +51 -0
  55. package/dist/cms/index.d.ts +51 -0
  56. package/dist/cms/index.js +146 -0
  57. package/dist/{export-DNu_Jr8p.d.ts → export-_1dUKwpF.d.ts} +326 -195
  58. package/dist/{export-DBba-buV.d.cts → export-b6NjCZ5-.d.cts} +326 -195
  59. package/dist/image-editor/index.cjs +2377 -1291
  60. package/dist/image-editor/index.cjs.map +1 -1
  61. package/dist/image-editor/index.d.cts +149 -97
  62. package/dist/image-editor/index.d.ts +149 -97
  63. package/dist/image-editor/index.js +1343 -257
  64. package/dist/image-editor/index.js.map +1 -1
  65. package/dist/opentype-BZEOSOX3.d.ts +188 -0
  66. package/dist/opentype-Dpe75vL5.d.cts +188 -0
  67. package/dist/paint/index.cjs +63 -4
  68. package/dist/paint/index.cjs.map +1 -1
  69. package/dist/paint/index.d.cts +30 -3
  70. package/dist/paint/index.d.ts +30 -3
  71. package/dist/paint/index.js +62 -3
  72. package/dist/paint-editor/index.cjs +146 -145
  73. package/dist/paint-editor/index.cjs.map +1 -1
  74. package/dist/paint-editor/index.d.cts +9 -8
  75. package/dist/paint-editor/index.d.ts +9 -8
  76. package/dist/paint-editor/index.js +6 -5
  77. package/dist/paint-editor/index.js.map +1 -1
  78. package/dist/raster/index.cjs +53 -3
  79. package/dist/raster/index.cjs.map +1 -1
  80. package/dist/raster/index.d.cts +62 -91
  81. package/dist/raster/index.d.ts +62 -91
  82. package/dist/raster/index.js +59 -9
  83. package/dist/{render-BEriFuqK.d.cts → render-B1K_OoLy.d.cts} +87 -2
  84. package/dist/{render-tjzKaik-.d.ts → render-CrmHOgpo.d.ts} +87 -2
  85. package/dist/{blending-aOE6Y3UE.d.cts → retouch-Bxkuc0KH.d.cts} +219 -2
  86. package/dist/{blending-DXl06NSx.d.ts → retouch-Do6tBs2H.d.ts} +219 -2
  87. package/dist/{surface-C8RRg_Bh.d.ts → surface-Cn30xIV6.d.cts} +6 -1
  88. package/dist/{surface-C8RRg_Bh.d.cts → surface-Cn30xIV6.d.ts} +6 -1
  89. package/dist/{svg-D_cfkhlB.d.cts → svg-BnRj0q8i.d.cts} +16 -4
  90. package/dist/{svg-wIaKLrU0.d.ts → svg-CmmBFfGT.d.ts} +16 -4
  91. package/dist/{types-DtL_qLFz.d.ts → types-1SUQKDAw.d.ts} +273 -4
  92. package/dist/{types-DyQq-WPY.d.cts → types-yINF17Ta.d.cts} +273 -4
  93. package/dist/vector/index.cjs +12170 -7343
  94. package/dist/vector/index.cjs.map +1 -1
  95. package/dist/vector/index.d.cts +1579 -16
  96. package/dist/vector/index.d.ts +1579 -16
  97. package/dist/vector/index.js +6303 -1476
  98. package/dist/vector/index.js.map +1 -1
  99. package/dist/warp-B39VsOtd.d.cts +348 -0
  100. package/dist/warp-DMN30aY6.d.ts +348 -0
  101. package/dist/zlib-dxQdgHyN.d.cts +95 -0
  102. package/dist/zlib-dxQdgHyN.d.ts +95 -0
  103. package/package.json +6 -1
  104. package/skills/pixerate-image-editor/SKILL.md +34 -34
  105. package/skills/pixerate-image-editor/references/color-management.md +37 -0
  106. package/skills/pixerate-image-editor/references/photoshop-files.md +39 -0
  107. package/skills/pixerate-image-editor/references/retouching.md +36 -0
  108. package/skills/pixerate-vector/SKILL.md +154 -5
  109. package/dist/chunk-BLWCAQCW.js.map +0 -1
  110. package/dist/chunk-CZOXTEMN.js.map +0 -1
  111. package/dist/chunk-FTBZPI77.cjs +0 -1
  112. package/dist/chunk-FTBZPI77.cjs.map +0 -1
  113. package/dist/chunk-HNS7UHCM.js.map +0 -1
  114. package/dist/chunk-JLYX4V57.cjs +0 -2001
  115. package/dist/chunk-JLYX4V57.cjs.map +0 -1
  116. package/dist/chunk-KB2YBCVS.js +0 -2001
  117. package/dist/chunk-KB2YBCVS.js.map +0 -1
  118. package/dist/chunk-LQEEDHOP.js.map +0 -1
  119. package/dist/chunk-POGBUYWE.cjs.map +0 -1
  120. package/dist/chunk-VKK5VS2T.js +0 -1
  121. package/dist/chunk-W3GXSUSQ.cjs.map +0 -1
  122. package/dist/chunk-XIOMMGPD.cjs.map +0 -1
  123. package/dist/chunk-YGIMK4NX.cjs.map +0 -1
  124. package/dist/homography-CZ-tFjay.d.cts +0 -50
  125. package/dist/homography-Ctfjoc1b.d.ts +0 -50
  126. package/dist/warp-CWHZzE3x.d.ts +0 -118
  127. package/dist/warp-D5jz_tN7.d.cts +0 -118
  128. /package/dist/{chunk-Z4BC6M74.js.map → chunk-UBIXVXPR.js.map} +0 -0
  129. /package/dist/{chunk-VKK5VS2T.js.map → cms/index.js.map} +0 -0
@@ -1,10 +1,7 @@
1
1
  import {
2
- loadDocument
3
- } from "../chunk-U5HZSFCS.js";
4
- import {
5
- createCaptureTool
6
- } from "../chunk-REOJJIY3.js";
7
- import "../chunk-VKK5VS2T.js";
2
+ loadOpenTypeFont,
3
+ parseOpenTypeFont
4
+ } from "../chunk-3UFGNHSP.js";
8
5
  import {
9
6
  ADJUSTMENT_KEYS,
10
7
  ADJUSTMENT_KINDS,
@@ -17,10 +14,8 @@ import {
17
14
  D50,
18
15
  D65,
19
16
  DEFAULT_ADJUSTMENTS_VALUES,
20
- DEFAULT_AUTO_LEADING,
21
17
  DEFAULT_DEPTH_MASK_VALUES,
22
18
  DEFAULT_MITER_LIMIT,
23
- DEFAULT_TEXT_STYLE,
24
19
  DEFAULT_TRANSFORM_VALUES,
25
20
  DEVELOP_FILTER_ID,
26
21
  EMPTY_DISTANCE,
@@ -72,6 +67,7 @@ import {
72
67
  annotationOfShape,
73
68
  annotationOfType,
74
69
  applyAdjustmentLayers,
70
+ applyAdjustmentLayersFloat,
75
71
  applyAdjustments,
76
72
  applyBoxBlur,
77
73
  applyDepthMask,
@@ -91,12 +87,12 @@ import {
91
87
  buildToneLut,
92
88
  calculateCropDrag,
93
89
  canvasToImagePoint,
94
- caretAt,
95
90
  clampCropToImage,
96
91
  colorBalanceChannel,
97
92
  combineSelections,
98
93
  compileAdjustmentLayer,
99
94
  componentPath,
95
+ compositeDeep,
100
96
  computeSelectionBounds,
101
97
  contentsBox,
102
98
  contentsSurface,
@@ -110,6 +106,7 @@ import {
110
106
  createShapeLayer,
111
107
  createTypeLayer,
112
108
  curveLutFromLevels,
109
+ decodeDeepSurface,
113
110
  decodeLayerChannel,
114
111
  decodeLayerMask,
115
112
  decodeMergedImage,
@@ -132,6 +129,7 @@ import {
132
129
  distanceTransform,
133
130
  documentContents,
134
131
  duplicateLayer,
132
+ encodeDeepSurface,
135
133
  encodeRle,
136
134
  encodeSelectionRle,
137
135
  encodeSurface,
@@ -140,11 +138,8 @@ import {
140
138
  exportPsd,
141
139
  featherCoverage,
142
140
  filterMaskCoverage,
143
- fitParagraphRuns,
144
- fitRuns,
145
141
  flattenPath,
146
142
  flipCorners,
147
- fontOf,
148
143
  fullSelection,
149
144
  geometryHomography,
150
145
  getAnnotationBounds,
@@ -195,7 +190,6 @@ import {
195
190
  layerTree,
196
191
  layerTypeLabel,
197
192
  layersFromLegacy,
198
- layoutText,
199
193
  legacyAdjustmentLayer,
200
194
  legacyMaskOf,
201
195
  legacyView,
@@ -232,7 +226,6 @@ import {
232
226
  offsetLayer,
233
227
  offsetShape,
234
228
  offsetSmartObject,
235
- paragraphRanges,
236
229
  parseAdjustmentBlock,
237
230
  parseBlend,
238
231
  parseCssColor,
@@ -287,7 +280,6 @@ import {
287
280
  rerenderText,
288
281
  resizeSelection,
289
282
  resolveExternalAssets,
290
- restyleRange,
291
283
  rgbToGray,
292
284
  rgbToHsl,
293
285
  runLayerFilters,
@@ -323,12 +315,9 @@ import {
323
315
  smartContentsFile,
324
316
  smartObjectLayers,
325
317
  smoothCoverage,
326
- spliceRuns,
327
318
  srgbToLinear,
328
- styleAt,
329
319
  tempTintToXy,
330
320
  temperatureToKelvin,
331
- textIndexAt,
332
321
  textMatrix,
333
322
  textOutlines,
334
323
  textRange,
@@ -369,8 +358,15 @@ import {
369
358
  writePsd,
370
359
  xyToTempTint,
371
360
  xyToUv
372
- } from "../chunk-CZOXTEMN.js";
361
+ } from "../chunk-EUVSQ25B.js";
362
+ import {
363
+ loadDocument
364
+ } from "../chunk-U5HZSFCS.js";
373
365
  import {
366
+ createCaptureTool
367
+ } from "../chunk-REOJJIY3.js";
368
+ import {
369
+ BIT_DEPTHS,
374
370
  DEFAULT_GLOBAL_LIGHT,
375
371
  DIRECT_GAUSSIAN_MAX_SIGMA,
376
372
  INTERPOLATIONS,
@@ -381,8 +377,6 @@ import {
381
377
  PatternPlacement,
382
378
  PatternTile,
383
379
  TransformTooLargeError,
384
- ZlibError,
385
- adler32,
386
380
  base64ToBytes,
387
381
  blendTile,
388
382
  builtinPattern,
@@ -390,12 +384,14 @@ import {
390
384
  bytesToBase64,
391
385
  changedTiles,
392
386
  compositeSurfaceOver,
387
+ convertSurfaceDepth,
393
388
  curveLut,
394
389
  decodePng,
390
+ deepBytes,
391
+ deepTile,
395
392
  defaultEffectGradient,
396
393
  defaultLayerEffect,
397
- deflate,
398
- deflateRaw,
394
+ depthOf,
399
395
  effectColorHex,
400
396
  effectReach,
401
397
  effectsFingerprint,
@@ -407,9 +403,8 @@ import {
407
403
  gaussianBlur,
408
404
  hasActiveEffects,
409
405
  hashNoise,
410
- inflate,
411
- inflateRaw,
412
406
  insertLayer,
407
+ isBitDepth,
413
408
  isInterpolation,
414
409
  isLayerEffectKind,
415
410
  isLayerLocked,
@@ -424,16 +419,33 @@ import {
424
419
  parseDataUrl,
425
420
  parseEffectColor,
426
421
  pngDataUrl,
422
+ proxyOf,
423
+ readDeepRegion,
427
424
  reduceSurface,
428
425
  renderLayerEffects,
429
426
  scaleLayerEffect,
427
+ srgbDecode,
428
+ srgbEncode,
429
+ surfaceDepth,
430
+ surfaceFromDeepPixels,
431
+ tileSamples,
432
+ tileSamples16,
433
+ tileSamples32,
434
+ to16From32,
435
+ to16From8,
436
+ to32From16,
437
+ to32From8,
438
+ to8From16,
439
+ to8From32,
430
440
  transformSurface,
431
441
  transformedBounds,
432
442
  validateEffectGradient,
433
443
  validateLayerEffect,
434
444
  validateLayerEffects,
435
- warpSurface
436
- } from "../chunk-LQEEDHOP.js";
445
+ warpSurface,
446
+ workingToSamples,
447
+ writeDeepRegion
448
+ } from "../chunk-C6C5PDKU.js";
437
449
  import {
438
450
  applyHomography,
439
451
  boxQuad,
@@ -443,6 +455,37 @@ import {
443
455
  multiplyHomographies,
444
456
  rectToQuad
445
457
  } from "../chunk-GX332W2C.js";
458
+ import {
459
+ DEFAULT_RETOUCH,
460
+ RETOUCH_SAMPLES,
461
+ RegionPixelSource,
462
+ RetouchDabStroke,
463
+ SPOT_HEAL_TYPES,
464
+ TONE_RANGES,
465
+ TilePixelSource,
466
+ applyRetouchToTile,
467
+ bestOffset,
468
+ dilateMask,
469
+ dodgeBurn,
470
+ dodgeBurnCurve,
471
+ healPixels,
472
+ luma,
473
+ maskBounds,
474
+ membraneFill,
475
+ normalizeRetouch,
476
+ perDabStrength,
477
+ readSourceRegion,
478
+ rgbaViewOfGrey,
479
+ saturationOf,
480
+ seamlessClone,
481
+ smudgeEffect,
482
+ solveMembrane,
483
+ sponge,
484
+ spongeEffect,
485
+ spotHealPixels,
486
+ spotHealWindow,
487
+ toneOf
488
+ } from "../chunk-LKCYANMW.js";
446
489
  import {
447
490
  CoverageMap,
448
491
  DEFAULT_BLENDER,
@@ -482,7 +525,7 @@ import {
482
525
  tipReach,
483
526
  tipShape,
484
527
  walkDabs
485
- } from "../chunk-Z4BC6M74.js";
528
+ } from "../chunk-UBIXVXPR.js";
486
529
  import {
487
530
  BLEND_MODES,
488
531
  PAINTERLY_BLEND_MODES,
@@ -536,21 +579,62 @@ import {
536
579
  translateSurface,
537
580
  uniformTile,
538
581
  writeRegion
539
- } from "../chunk-HNS7UHCM.js";
582
+ } from "../chunk-XK5LZKBD.js";
583
+ import {
584
+ BUILTIN_PROFILES,
585
+ CmsError,
586
+ GamutCheck,
587
+ INTENTS,
588
+ Transform,
589
+ builtinProfile,
590
+ builtinProfileId,
591
+ canBeDestination,
592
+ encodeProfile,
593
+ parseProfile,
594
+ sameColors,
595
+ srgbProfile,
596
+ writeJpegIcc,
597
+ writePngIcc
598
+ } from "../chunk-BRLYOPLT.js";
540
599
  import {
600
+ ZlibError,
601
+ adler32,
602
+ deflate,
603
+ deflateRaw,
604
+ inflate,
605
+ inflateRaw
606
+ } from "../chunk-6FCKOS2B.js";
607
+ import {
608
+ DEFAULT_AUTO_LEADING,
609
+ DEFAULT_TEXT_STYLE,
541
610
  WARP_STYLES,
542
611
  WARP_STYLE_LABELS,
543
612
  applyMatrix,
544
613
  bezierToSubpaths,
614
+ caretAt,
615
+ createCanvasTextShaper,
616
+ createMetricTextShaper,
617
+ createOutlineTextShaper,
618
+ cssFont,
619
+ cssFontFamily,
545
620
  dragWarpMeshPoint,
546
621
  evalWarpMesh,
622
+ fitParagraphRuns,
623
+ fitRuns,
624
+ fontOf,
547
625
  identityWarpMesh,
548
626
  invertMatrix,
627
+ layoutText,
549
628
  multiplyMatrices,
629
+ paragraphRanges,
550
630
  pullWarpMesh,
551
631
  resizeWarpMesh,
632
+ restyleRange,
552
633
  rotateMatrix,
553
634
  roundSubpaths,
635
+ spliceRuns,
636
+ styleAt,
637
+ textIndexAt,
554
638
  translateMatrix,
555
639
  warpMeshColumns,
556
640
  warpMeshEditPoints,
@@ -558,7 +642,7 @@ import {
558
642
  warpMeshParamAt,
559
643
  warpMeshRows,
560
644
  warpPresetMesh
561
- } from "../chunk-BLWCAQCW.js";
645
+ } from "../chunk-VXR7XWMF.js";
562
646
  import "../chunk-YCSFS3GK.js";
563
647
  import "../chunk-ZRJMJIQU.js";
564
648
  import "../chunk-TGD52FEZ.js";
@@ -913,6 +997,17 @@ var ImageEditorRenderer = class {
913
997
  /** Which path the last base render took. */
914
998
  lastBaseRoute = null;
915
999
  comp = null;
1000
+ /** Colour transform from the document to the display (see `color-management`), or none. */
1001
+ display = null;
1002
+ displayKey = 0;
1003
+ /** Sets the viewport's display transform (document profile → sRGB, or a soft proof); `null` for none. */
1004
+ setDisplayTransform(t) {
1005
+ if (t === this.display) return;
1006
+ this.display = t;
1007
+ this.displayKey++;
1008
+ this.comp = null;
1009
+ this.cachedBaseKey = "";
1010
+ }
916
1011
  depthMaps = /* @__PURE__ */ new Map();
917
1012
  assetVersion = 0;
918
1013
  scratch = null;
@@ -982,13 +1077,15 @@ var ImageEditorRenderer = class {
982
1077
  `${projection.angle},${transform.flipH},${transform.flipV}`,
983
1078
  `${adjustments.brightness},${adjustments.contrast},${adjustments.saturation},${adjustments.exposure},${adjustments.temperature},${adjustments.tint ?? 0},${adjustments.blur},${adjustments.opacity},p${normalizeProcessVersion(adjustments.process)}`,
984
1079
  adjustmentLayersKey(adjustments.layers),
985
- `${depthMask.enabled},${depthMask.maskSource},${depthMask.depthRange?.[0]}-${depthMask.depthRange?.[1]},${depthMask.softness},${depthMask.invert},${depthMask.mode}`
1080
+ `${depthMask.enabled},${depthMask.maskSource},${depthMask.depthRange?.[0]}-${depthMask.depthRange?.[1]},${depthMask.softness},${depthMask.invert},${depthMask.mode}`,
1081
+ `d${this.displayKey}`
986
1082
  ].join("|");
987
1083
  let baseCanvas = this.cachedBaseCanvas;
988
1084
  if (!baseCanvas || this.cachedBaseKey !== baseKey) {
989
1085
  let histogram = null;
990
1086
  baseCanvas = await this.renderBaseImage(img, naturalWidth, naturalHeight, region, transform, state, (pixels) => {
991
1087
  histogram = computeHistogram(pixels);
1088
+ this.display?.(pixels.data);
992
1089
  });
993
1090
  this.cachedBaseCanvas = baseCanvas;
994
1091
  this.cachedBaseKey = baseKey;
@@ -1339,6 +1436,7 @@ var ImageEditorRenderer = class {
1339
1436
  if (x1 <= x0 || y1 <= y0) return;
1340
1437
  const img = ctx.createImageData(TILE, TILE);
1341
1438
  img.data.set(data);
1439
+ this.display?.(img.data);
1342
1440
  ctx.putImageData(img, ox, oy, x0, y0, x1 - x0, y1 - y0);
1343
1441
  }
1344
1442
  /**
@@ -1381,7 +1479,7 @@ var ImageEditorRenderer = class {
1381
1479
  const rotated = getRotatedSize(region.width, region.height, transform);
1382
1480
  const baseW = Math.max(1, Math.round(rotated.width));
1383
1481
  const baseH = Math.max(1, Math.round(rotated.height));
1384
- const key = `${comp.version}|${region.x},${region.y},${region.width},${region.height}|${normalizeAngle(transform.rotate)},${transform.flipH},${transform.flipV}|${opacity}|${blur}`;
1482
+ const key = `${this.displayKey}:${comp.version}|${region.x},${region.y},${region.width},${region.height}|${normalizeAngle(transform.rotate)},${transform.flipH},${transform.flipV}|${opacity}|${blur}`;
1385
1483
  if (this.cachedPostCanvas && this.cachedPostKey === key) return { canvas: this.cachedPostCanvas, histogram: this.cachedPostHistogram };
1386
1484
  const canvas = this.cachedPostCanvas && this.cachedPostCanvas.width === baseW && this.cachedPostCanvas.height === baseH ? this.cachedPostCanvas : this.createCanvas(baseW, baseH);
1387
1485
  const ctx = canvas.getContext("2d");
@@ -2518,159 +2616,6 @@ function refineSelection(pixels, mask, params = {}) {
2518
2616
  return createSelectionMask(mask.width, mask.height, out);
2519
2617
  }
2520
2618
 
2521
- // src/image-editor/layers/text-shapers.ts
2522
- var GENERIC = /* @__PURE__ */ new Set(["serif", "sans-serif", "monospace", "cursive", "fantasy", "system-ui", "ui-serif", "ui-sans-serif", "ui-monospace", "ui-rounded", "math", "emoji", "fangsong"]);
2523
- function cssFontFamily(family) {
2524
- const f = family.trim();
2525
- if (GENERIC.has(f.toLowerCase()) || f.includes(",") || /^["']/.test(f)) return f;
2526
- return /^[A-Za-z_-][\w -]*$/.test(f) && !/\s{2}/.test(f) ? f : `"${f.replace(/"/g, '\\"')}"`;
2527
- }
2528
- function cssFont(font, family = font.family) {
2529
- return `${font.italic ? "italic " : ""}${font.weight} ${font.size}px ${cssFontFamily(family)}`;
2530
- }
2531
- function createCanvasTextShaper(options = {}) {
2532
- const make = options.createCanvas ?? ((w, h) => {
2533
- if (typeof OffscreenCanvas !== "undefined") return new OffscreenCanvas(w, h);
2534
- if (typeof document !== "undefined") {
2535
- const c = document.createElement("canvas");
2536
- c.width = w;
2537
- c.height = h;
2538
- return c;
2539
- }
2540
- return null;
2541
- });
2542
- const canvas = make(1, 1);
2543
- const ctx = canvas?.getContext("2d");
2544
- if (!ctx || typeof ctx.measureText !== "function") return null;
2545
- const fallback = options.fallbackFamily ?? "sans-serif";
2546
- const available = /* @__PURE__ */ new Map();
2547
- const widths = /* @__PURE__ */ new Map();
2548
- const metrics = /* @__PURE__ */ new Map();
2549
- const probe = "mmmmmmmmmmlli1WQ@#";
2550
- const width = (font, text) => {
2551
- ctx.font = font;
2552
- return ctx.measureText(text).width;
2553
- };
2554
- const isAvailable = (family) => {
2555
- const f = family.trim();
2556
- if (GENERIC.has(f.toLowerCase()) || f.includes(",")) return true;
2557
- let hit = available.get(f);
2558
- if (hit === void 0) {
2559
- const q = cssFontFamily(f);
2560
- hit = width(`72px ${q}, monospace`, probe) !== width("72px monospace", probe) || width(`72px ${q}, serif`, probe) !== width("72px serif", probe);
2561
- available.set(f, hit);
2562
- }
2563
- return hit;
2564
- };
2565
- return {
2566
- id: "canvas",
2567
- resolve(font) {
2568
- const ok = isAvailable(font.family);
2569
- return { family: ok ? font.family : fallback, available: ok };
2570
- },
2571
- metrics(font) {
2572
- const key = cssFont(font);
2573
- let m = metrics.get(key);
2574
- if (!m) {
2575
- ctx.font = key;
2576
- ctx.textBaseline = "alphabetic";
2577
- const t = ctx.measureText("Hg");
2578
- const ascent = t.emHeightAscent ?? t.fontBoundingBoxAscent ?? font.size * 0.8;
2579
- const descent = t.emHeightDescent ?? t.fontBoundingBoxDescent ?? font.size * 0.2;
2580
- m = { ascent: Number.isFinite(ascent) ? ascent : font.size * 0.8, descent: Number.isFinite(descent) ? descent : font.size * 0.2 };
2581
- metrics.set(key, m);
2582
- }
2583
- return m;
2584
- },
2585
- measure(text, font) {
2586
- const key = `${cssFont(font)}|${text}`;
2587
- let w = widths.get(key);
2588
- if (!w) {
2589
- ctx.font = cssFont(font);
2590
- w = [0];
2591
- for (let i = 1; i <= text.length; i++) w.push(ctx.measureText(text.slice(0, i)).width);
2592
- if (widths.size > 2e3) widths.clear();
2593
- widths.set(key, w);
2594
- }
2595
- return w;
2596
- },
2597
- draw(target, text, font, x, y) {
2598
- target.font = cssFont(font);
2599
- target.textBaseline = "alphabetic";
2600
- target.textAlign = "left";
2601
- target.fillText(text, x, y);
2602
- },
2603
- families() {
2604
- const out = /* @__PURE__ */ new Set(["sans-serif", "serif", "monospace"]);
2605
- const fonts = typeof document !== "undefined" ? document.fonts : void 0;
2606
- if (fonts && typeof fonts.forEach === "function") fonts.forEach((f) => out.add(f.family.replace(/^["']|["']$/g, "")));
2607
- return [...out];
2608
- }
2609
- };
2610
- }
2611
- function createOutlineTextShaper(fonts, options = {}) {
2612
- if (!fonts.length) throw new Error("createOutlineTextShaper needs at least one font.");
2613
- const entries = fonts.map((f) => {
2614
- const e = "face" in f ? f : { face: f };
2615
- const names = e.face.names;
2616
- const family = e.family ?? names?.fontFamily?.en ?? Object.values(names?.fontFamily ?? {})[0] ?? "Font";
2617
- const weight = e.weight ?? e.face.tables?.os2?.usWeightClass ?? 400;
2618
- const italic = e.italic ?? Boolean((e.face.tables?.os2?.fsSelection ?? 0) & 1);
2619
- const postScript = names?.postScriptName?.en ?? Object.values(names?.postScriptName ?? {})[0];
2620
- return { face: e.face, family, key: family.toLowerCase(), weight, italic, postScript };
2621
- });
2622
- const fallbackKey = (options.fallback ?? entries[0].family).toLowerCase();
2623
- const pick2 = (font) => {
2624
- if (font.postScriptName) {
2625
- const exact = entries.find((e) => e.postScript === font.postScriptName);
2626
- if (exact) return { entry: exact, available: true };
2627
- }
2628
- const key = font.family.trim().toLowerCase();
2629
- let pool = entries.filter((e) => e.key === key);
2630
- const available = pool.length > 0;
2631
- if (!available) pool = entries.filter((e) => e.key === fallbackKey);
2632
- if (!pool.length) pool = entries;
2633
- const styled = pool.filter((e) => e.italic === font.italic);
2634
- const list = styled.length ? styled : pool;
2635
- const entry = list.reduce((best, e) => Math.abs(e.weight - font.weight) < Math.abs(best.weight - font.weight) ? e : best);
2636
- return { entry, available };
2637
- };
2638
- const toPath = (cmds) => {
2639
- const out = [];
2640
- for (const c of cmds) {
2641
- if (c.type === "M" || c.type === "L") out.push({ type: c.type, x: c.x, y: c.y });
2642
- else if (c.type === "C") out.push({ type: "C", x1: c.x1, y1: c.y1, x2: c.x2, y2: c.y2, x: c.x, y: c.y });
2643
- else if (c.type === "Q") out.push({ type: "Q", x1: c.x1, y1: c.y1, x: c.x, y: c.y });
2644
- else out.push({ type: "Z" });
2645
- }
2646
- return out;
2647
- };
2648
- return {
2649
- id: options.id ?? `outline:${entries.map((e) => `${e.family}/${e.weight}${e.italic ? "i" : ""}`).join(",")}`,
2650
- resolve(font) {
2651
- const { entry, available } = pick2(font);
2652
- return { family: entry.family, available };
2653
- },
2654
- metrics(font) {
2655
- const { face } = pick2(font).entry;
2656
- const k = font.size / face.unitsPerEm;
2657
- return { ascent: face.ascender * k, descent: -face.descender * k };
2658
- },
2659
- measure(text, font) {
2660
- const { face } = pick2(font).entry;
2661
- const out = [0];
2662
- for (let i = 1; i <= text.length; i++) out.push(face.getAdvanceWidth(text.slice(0, i), font.size, { kerning: true }));
2663
- return out;
2664
- },
2665
- outline(text, font, x, y) {
2666
- return toPath(pick2(font).entry.face.getPath(text, x, y, font.size, { kerning: true }).commands);
2667
- },
2668
- families() {
2669
- return [...new Set(entries.map((e) => e.family))];
2670
- }
2671
- };
2672
- }
2673
-
2674
2619
  // src/image-editor/free-transform.ts
2675
2620
  var FREE_TRANSFORM_CONTENTS = ["auto", "layer", "selection", "selection-copy", "selection-outline"];
2676
2621
  var fail = (message) => {
@@ -3151,8 +3096,146 @@ var FreeTransformSession = class {
3151
3096
  }
3152
3097
  };
3153
3098
 
3099
+ // src/image-editor/color-management.ts
3100
+ var DEFAULT_PROOF = { intent: "relative", bpc: true, simulatePaper: false, gamutWarning: false };
3101
+ var resolved = /* @__PURE__ */ new WeakMap();
3102
+ var fromBytes = /* @__PURE__ */ new Map();
3103
+ function resolveProfile(doc) {
3104
+ if (!doc) return srgbProfile();
3105
+ const hit = resolved.get(doc);
3106
+ if (hit) return hit;
3107
+ let p;
3108
+ if (doc.builtin) p = builtinProfile(doc.builtin);
3109
+ else if (doc.icc) {
3110
+ const key = bytesToBase64(doc.icc);
3111
+ p = fromBytes.get(key) ?? parseProfile(doc.icc);
3112
+ if (fromBytes.size > 32) fromBytes.clear();
3113
+ fromBytes.set(key, p);
3114
+ } else p = srgbProfile();
3115
+ resolved.set(doc, p);
3116
+ return p;
3117
+ }
3118
+ function profileBytes(doc) {
3119
+ if (doc?.icc) return doc.icc;
3120
+ const p = resolveProfile(doc);
3121
+ return p.bytes ?? encodeProfile(p);
3122
+ }
3123
+ function documentProfile(input) {
3124
+ if (typeof input === "string") {
3125
+ const id = builtinProfileId(input);
3126
+ if (!id) throw new CmsError(`Unknown colour profile "${input}"; use one of ${BUILTIN_PROFILES.map((b) => b.id).join(", ")}, or ICC bytes.`);
3127
+ return { builtin: id, name: BUILTIN_PROFILES.find((b) => b.id === id).description };
3128
+ }
3129
+ const p = parseProfile(input);
3130
+ return { name: p.description || "Embedded profile", icc: input.slice() };
3131
+ }
3132
+ function isSrgb(doc) {
3133
+ if (!doc || doc.builtin === "srgb") return true;
3134
+ try {
3135
+ return sameColors(resolveProfile(doc), srgbProfile(), builtinProfile("lab-d50"));
3136
+ } catch {
3137
+ return false;
3138
+ }
3139
+ }
3140
+ function serializeColorProfile(doc) {
3141
+ return doc.builtin ? { builtin: doc.builtin, name: doc.name } : { name: doc.name, icc: bytesToBase64(doc.icc ?? new Uint8Array(0)) };
3142
+ }
3143
+ function deserializeColorProfile(raw, problems) {
3144
+ const r = raw;
3145
+ if (!r || typeof r !== "object") return null;
3146
+ try {
3147
+ if (r.builtin) {
3148
+ const id = builtinProfileId(r.builtin);
3149
+ if (!id) throw new CmsError(`unknown built-in profile "${r.builtin}"`);
3150
+ return documentProfile(id);
3151
+ }
3152
+ if (typeof r.icc === "string") {
3153
+ const icc = base64ToBytes(r.icc);
3154
+ parseProfile(icc);
3155
+ return { name: typeof r.name === "string" ? r.name : "Embedded profile", icc };
3156
+ }
3157
+ } catch (e) {
3158
+ problems.push(`The colour profile could not be read (${e instanceof Error ? e.message : String(e)}); the document is treated as sRGB`);
3159
+ }
3160
+ return null;
3161
+ }
3162
+ function displayTransform(doc, proof) {
3163
+ const src = resolveProfile(doc);
3164
+ const display = srgbProfile();
3165
+ let t = null;
3166
+ let gamut = null;
3167
+ if (proof) {
3168
+ const p = resolveProfile(proof.profile);
3169
+ t = Transform.proof(src, p, display, proof.intent, proof.bpc, proof.simulatePaper);
3170
+ if (proof.gamutWarning) gamut = new GamutCheck(src, p, builtinProfile("lab-d50"));
3171
+ } else if (!isSrgb(doc)) t = Transform.create(src, display, { intent: "relative", bpc: true });
3172
+ if (!t) return null;
3173
+ const tr = t;
3174
+ const g = gamut;
3175
+ return (rgba) => {
3176
+ if (g) {
3177
+ const px2 = [0, 0, 0];
3178
+ const flags = new Uint8Array(rgba.length >> 2);
3179
+ for (let i = 0, p = 0; i < rgba.length; i += 4, p++) {
3180
+ if (rgba[i + 3] === 0) continue;
3181
+ px2[0] = rgba[i] / 255;
3182
+ px2[1] = rgba[i + 1] / 255;
3183
+ px2[2] = rgba[i + 2] / 255;
3184
+ if (g.outOfGamut(px2)) flags[p] = 1;
3185
+ }
3186
+ tr.convert8(rgba, 4, rgba, 4);
3187
+ for (let i = 0, p = 0; i < rgba.length; i += 4, p++) if (flags[p]) rgba[i] = rgba[i + 1] = rgba[i + 2] = 128;
3188
+ return;
3189
+ }
3190
+ tr.convert8(rgba, 4, rgba, 4);
3191
+ };
3192
+ }
3193
+ function convertHex(hex, from, to, intent = "relative", bpc = true) {
3194
+ const m = /^#?([0-9a-f]{6})$/i.exec(hex.trim());
3195
+ if (!m) return hex;
3196
+ const n = parseInt(m[1], 16);
3197
+ const out = new Float32Array(4);
3198
+ Transform.create(from, to, { intent, bpc }).eval([(n >> 16 & 255) / 255, (n >> 8 & 255) / 255, (n & 255) / 255], out);
3199
+ const c = (v) => Math.round(Math.min(1, Math.max(0, v)) * 255).toString(16).padStart(2, "0");
3200
+ return `#${c(out[0])}${c(out[1])}${c(out[2])}`;
3201
+ }
3202
+ function convertSurfaceColors(surface, t) {
3203
+ if (surface.channels !== 4 || t.inputs !== 3 || t.outputs !== 3) return surface;
3204
+ const tiles = /* @__PURE__ */ new Map();
3205
+ const memo = /* @__PURE__ */ new Map();
3206
+ for (const [key, tile] of surface.tiles) {
3207
+ let next = memo.get(tile);
3208
+ if (next === void 0) {
3209
+ if (tile.deep instanceof Uint16Array) {
3210
+ const out = new Uint16Array(tile.deep.length);
3211
+ t.convert16(tile.deep, 4, out, 4);
3212
+ next = deepTile(out, 4, surface.defaultValue);
3213
+ } else if (tile.deep instanceof Float32Array) {
3214
+ const enc = new Float32Array(tile.deep.length);
3215
+ for (let i = 0; i < enc.length; i += 4) {
3216
+ for (let c = 0; c < 3; c++) enc[i + c] = srgbEncode(tile.deep[i + c]);
3217
+ enc[i + 3] = tile.deep[i + 3];
3218
+ }
3219
+ t.convertFloat(enc, 4, enc, 4);
3220
+ for (let i = 0; i < enc.length; i += 4) for (let c = 0; c < 3; c++) enc[i + c] = srgbDecode(enc[i + c]);
3221
+ next = deepTile(enc, 4, surface.defaultValue);
3222
+ } else {
3223
+ const out = new Uint8ClampedArray(tile.data.length);
3224
+ t.convert8(tile.data, 4, out, 4);
3225
+ next = finishTile(out, 4, surface.defaultValue);
3226
+ }
3227
+ memo.set(tile, next);
3228
+ }
3229
+ if (next) tiles.set(key, next);
3230
+ }
3231
+ return new TiledSurface(4, tiles, surface.defaultValue);
3232
+ }
3233
+
3154
3234
  // src/image-editor/controller.ts
3155
3235
  var IMAGE_EDITOR_SCHEMA_VERSION = LAYERED_SCHEMA_VERSION;
3236
+ var RETOUCH_TOOLS = ["clone", "heal", "spot-heal", "smudge", "dodge", "burn", "sponge"];
3237
+ var isRetouchTool = (tool) => RETOUCH_TOOLS.includes(tool);
3238
+ var STROKE_TOOLS = ["brush", "eraser", "mixer", ...RETOUCH_TOOLS];
3156
3239
  function paintCoverageInto(base, coverage, apply, rects) {
3157
3240
  const tiles = new Map(base.tiles);
3158
3241
  const done = /* @__PURE__ */ new Set();
@@ -3186,6 +3269,18 @@ var PaintStrokeSession = class {
3186
3269
  if (stroke instanceof StrokeCoverage) {
3187
3270
  this.stroke = stroke;
3188
3271
  this.mixer = null;
3272
+ } else if ("kind" in stroke && stroke.kind === "coverage") {
3273
+ this.stroke = stroke.coverage;
3274
+ this.mixer = null;
3275
+ this.retouch = stroke;
3276
+ } else if ("kind" in stroke) {
3277
+ this.stroke = null;
3278
+ this.retouch = stroke;
3279
+ if (this.base.channels === 1) {
3280
+ const view = rgbaViewOfGrey(this.mixerPixels());
3281
+ this.mixer = stroke.create(view.target);
3282
+ this.flushView = view.flush;
3283
+ } else this.mixer = stroke.create(this.mixerPixels());
3189
3284
  } else {
3190
3285
  this.stroke = null;
3191
3286
  this.mixer = stroke.create(this.mixerPixels());
@@ -3202,10 +3297,20 @@ var PaintStrokeSession = class {
3202
3297
  stroke;
3203
3298
  mixer;
3204
3299
  mixerInit = null;
3300
+ retouch = null;
3301
+ /** Writes a mask's RGBA view back (sequential retouching on a mask). */
3302
+ flushView = null;
3303
+ started = false;
3205
3304
  /** Mixer tiles written since the last update: they get new tile objects so the compositor sees them change. */
3206
3305
  fresh = /* @__PURE__ */ new Set();
3207
3306
  last = null;
3208
3307
  closed = false;
3308
+ /** The new tile of a coverage stroke over the pre-stroke tile `src`. */
3309
+ compositeTile(src, tx, ty, options = {}, paint) {
3310
+ const r = this.retouch;
3311
+ if (r && r.kind === "coverage") return applyRetouchToTile(this.stroke.coverage, src, this.base.channels, tx, ty, paint ?? r.paint(), r.apply, options);
3312
+ return applyStrokeToTile(this.stroke.coverage, src, this.base.channels, tx, ty, this.apply, options);
3313
+ }
3209
3314
  /** The live pixels a mixer stroke reads and writes; base tiles are copied on first write. */
3210
3315
  mixerPixels() {
3211
3316
  const a = this.apply;
@@ -3232,8 +3337,14 @@ var PaintStrokeSession = class {
3232
3337
  /** Adds input points (document pixels); the stack shows the stroke so far. */
3233
3338
  push(points) {
3234
3339
  if (this.closed || !points.length) return;
3340
+ if (!this.started) {
3341
+ this.started = true;
3342
+ if (this.retouch?.kind === "coverage") this.retouch.start(points[0]);
3343
+ }
3235
3344
  this.last = points[points.length - 1];
3236
- this.update(this.mixer ? this.mixer.push(points) : this.stroke.push(points));
3345
+ const rects = this.mixer ? this.mixer.push(points) : this.stroke.push(points);
3346
+ this.flushView?.();
3347
+ this.update(rects);
3237
3348
  }
3238
3349
  update(rects) {
3239
3350
  if (!rects.length) return;
@@ -3257,7 +3368,7 @@ var PaintStrokeSession = class {
3257
3368
  const src = this.base.tiles.get(key)?.data ?? defaultTile(this.base);
3258
3369
  const prev = this.touched.has(key) ? this.live.get(key)?.data : void 0;
3259
3370
  this.touched.add(key);
3260
- const data = applyStrokeToTile(stroke.coverage, src, ch, tx, ty, this.apply, prev ? { into: prev, region: r } : {});
3371
+ const data = this.compositeTile(src, tx, ty, prev ? { into: prev, region: r } : {});
3261
3372
  this.live.set(key, { data, uniform: null });
3262
3373
  }
3263
3374
  this.controller._strokeUpdate(this.target.id, this.target.channel, new TiledSurface(ch, new Map(this.live), this.base.defaultValue), rects);
@@ -3266,9 +3377,22 @@ var PaintStrokeSession = class {
3266
3377
  end() {
3267
3378
  if (this.closed) throw new LayerOperationError("This stroke has already ended.");
3268
3379
  const last = this.last ?? void 0;
3269
- this.update(this.mixer ? this.mixer.finish(last) : this.stroke.finish(last));
3380
+ const rects = this.mixer ? this.mixer.finish(last) : this.stroke.finish(last);
3381
+ this.flushView?.();
3382
+ this.update(rects);
3270
3383
  this.closed = true;
3271
3384
  const ch = this.base.channels;
3385
+ const r = this.retouch;
3386
+ if (r?.kind === "coverage" && r.finalPaint) {
3387
+ const final = r.finalPaint(this.stroke.coverage);
3388
+ if (final) {
3389
+ for (const key of this.touched) {
3390
+ const { tx, ty } = tileCoord(key);
3391
+ const src = this.base.tiles.get(key)?.data ?? defaultTile(this.base);
3392
+ this.live.set(key, { data: this.compositeTile(src, tx, ty, {}, final), uniform: null });
3393
+ }
3394
+ }
3395
+ }
3272
3396
  const tiles = new Map(this.base.tiles);
3273
3397
  let changed = false;
3274
3398
  for (const key of this.touched) {
@@ -3280,11 +3404,12 @@ var PaintStrokeSession = class {
3280
3404
  if (!sameTile(prev, tile)) changed = true;
3281
3405
  }
3282
3406
  const surface = changed ? new TiledSurface(ch, tiles, this.base.defaultValue) : null;
3283
- const mixerLoad = this.mixer ? this.mixerInit.end(this.mixer.load) : void 0;
3407
+ const mixerLoad = this.mixerInit ? this.mixerInit.end(this.mixer.load) : void 0;
3284
3408
  this.controller._strokeCommit(this.before, this.target.id, this.target.channel, surface);
3285
3409
  const b = this.mixer ? this.mixer.bounds : this.stroke.coverage.bounds;
3286
3410
  const result = { layerId: this.target.id, channel: this.target.channel, bounds: changed ? b : null, dabs: (this.mixer ?? this.stroke).dabCount };
3287
3411
  if (mixerLoad) result.mixerLoad = mixerLoad;
3412
+ if (r?.result) Object.assign(result, r.result());
3288
3413
  return result;
3289
3414
  }
3290
3415
  /** Abandons the stroke and restores the layer. */
@@ -3423,7 +3548,7 @@ var ImageEditorController = class {
3423
3548
  activeLayerId: null,
3424
3549
  activeTarget: "pixels",
3425
3550
  outputOpacity: 1,
3426
- paint: { brush: { ...DEFAULT_BRUSH }, foreground: "#000000", background: "#ffffff", mixer: { ...DEFAULT_MIXER }, mixerLoad: EMPTY_MIXER_LOAD },
3551
+ paint: { brush: { ...DEFAULT_BRUSH }, foreground: "#000000", background: "#ffffff", mixer: { ...DEFAULT_MIXER }, mixerLoad: EMPTY_MIXER_LOAD, retouch: { ...DEFAULT_RETOUCH }, cloneSource: null },
3427
3552
  fit: options.fit ?? "margin"
3428
3553
  };
3429
3554
  if (options.image instanceof Object && "width" in options.image) {
@@ -3518,10 +3643,23 @@ var ImageEditorController = class {
3518
3643
  if (strokeRects && this.pendingDirty !== "all") this.pendingDirty.push(...strokeRects);
3519
3644
  else if (!strokeRects) this.pendingDirty = "all";
3520
3645
  this.syncViews();
3646
+ this.syncDisplay();
3521
3647
  const currentState = { ...this.state };
3522
3648
  this.onStateChange?.(currentState);
3523
3649
  this.listeners.forEach((l) => l(currentState));
3524
3650
  }
3651
+ displayFor = null;
3652
+ /** Keeps the renderer's display transform in step with the document profile and Proof Setup. */
3653
+ syncDisplay() {
3654
+ const s = this.state;
3655
+ if (this.displayFor && this.displayFor[0] === s.colorProfile && this.displayFor[1] === s.proofSetup) return;
3656
+ this.displayFor = [s.colorProfile, s.proofSetup];
3657
+ try {
3658
+ this.renderer.setDisplayTransform(displayTransform(s.colorProfile, s.proofSetup));
3659
+ } catch {
3660
+ this.renderer.setDisplayTransform(null);
3661
+ }
3662
+ }
3525
3663
  /** Recomputes the compatibility views (`adjustments`, `depthMask`, `annotations`) when the stack changed. */
3526
3664
  syncViews() {
3527
3665
  const s = this.state;
@@ -3559,6 +3697,8 @@ var ImageEditorController = class {
3559
3697
  if (s.legacyOutputBlur !== void 0) entry.legacyOutputBlur = s.legacyOutputBlur;
3560
3698
  if (s.globalLight) entry.globalLight = { ...s.globalLight };
3561
3699
  if (s.patterns) entry.patterns = s.patterns;
3700
+ if (s.colorProfile) entry.colorProfile = s.colorProfile;
3701
+ if (s.bitDepth && s.bitDepth !== 8) entry.bitDepth = s.bitDepth;
3562
3702
  return entry;
3563
3703
  }
3564
3704
  restoreHistoryEntry(entry) {
@@ -3575,6 +3715,10 @@ var ImageEditorController = class {
3575
3715
  else delete s.globalLight;
3576
3716
  if (entry.patterns) s.patterns = entry.patterns;
3577
3717
  else delete s.patterns;
3718
+ if (entry.bitDepth) s.bitDepth = entry.bitDepth;
3719
+ else delete s.bitDepth;
3720
+ if (entry.colorProfile) s.colorProfile = entry.colorProfile;
3721
+ else delete s.colorProfile;
3578
3722
  s.selection = entry.selection;
3579
3723
  if (this.imageElement !== entry.source.element && !this.samplePixelsOverride) this.samplePixels = null;
3580
3724
  this.imageElement = entry.source.element;
@@ -4858,9 +5002,28 @@ var ImageEditorController = class {
4858
5002
  throw new LayerOperationError("Merging annotation layers needs a canvas (a browser); hide or delete them first, or merge in the browser.");
4859
5003
  }
4860
5004
  await this.renderer.prepareLayerAssets(layers, width, height);
4861
- const data = new LayerCompositor().composite(layers, rect, this.renderer.compositorSources(width, height));
5005
+ const sources = this.renderer.compositorSources(width, height);
5006
+ const depth = this.bitDepth;
5007
+ if (depth !== 8) {
5008
+ const samples = workingToSamples(compositeDeep(layers, rect, sources, depth), depth);
5009
+ return surfaceFromDeepPixels({ width: rect.width, height: rect.height, data: samples }, 4, rect.x, rect.y);
5010
+ }
5011
+ const data = new LayerCompositor().composite(layers, rect, sources);
4862
5012
  return surfaceFromPixels({ width: rect.width, height: rect.height, data }, 4, rect.x, rect.y);
4863
5013
  }
5014
+ /**
5015
+ * The composite of the layer stack at the document's bit depth (as `compositePixels`, no
5016
+ * canvas): 16-bit sRGB samples, or 32-bit linear float, over `rect` (default the canvas). At 8
5017
+ * bits, the 8-bit composite widened to 16 bits.
5018
+ */
5019
+ compositeDeepPixels(rect) {
5020
+ const { width, height } = this.naturalSize;
5021
+ const r = rect ?? { x: 0, y: 0, width, height };
5022
+ const depth = this.bitDepth;
5023
+ if (depth === 8) return { width: r.width, height: r.height, depth: 16, data: to16From8(this.compositePixels(r).data) };
5024
+ const data = workingToSamples(compositeDeep(this.state.layers, r, this.compositorSources(), depth), depth);
5025
+ return { width: r.width, height: r.height, depth, data };
5026
+ }
4864
5027
  /** Document bounds a layer can paint into (pixel content, annotations, or the canvas for the rest). */
4865
5028
  paintBounds(layer) {
4866
5029
  const { width, height } = this.naturalSize;
@@ -5018,12 +5181,16 @@ var ImageEditorController = class {
5018
5181
  /** Brush settings, colours and tool options of the paint tools (view state, no undo). */
5019
5182
  setPaintOptions(options) {
5020
5183
  const cur = this.state.paint;
5184
+ const retouch = options.retouch ? normalizeRetouch(options.retouch, cur.retouch) : cur.retouch;
5185
+ const cloneSource = cur.cloneSource && retouch.aligned !== cur.retouch.aligned ? { ...cur.cloneSource, offset: null } : cur.cloneSource;
5021
5186
  const next = {
5022
5187
  brush: options.brush ? normalizeBrush({ ...cur.brush, ...options.brush }) : cur.brush,
5023
5188
  foreground: options.foreground !== void 0 ? toHex(parseColor(options.foreground)) : cur.foreground,
5024
5189
  background: options.background !== void 0 ? toHex(parseColor(options.background)) : cur.background,
5025
5190
  mixer: options.mixer ? normalizeMixer({ ...cur.mixer, ...options.mixer }) : cur.mixer,
5026
- mixerLoad: cur.mixerLoad
5191
+ mixerLoad: cur.mixerLoad,
5192
+ retouch,
5193
+ cloneSource
5027
5194
  };
5028
5195
  this.state.paint = next;
5029
5196
  this.notify();
@@ -5053,6 +5220,17 @@ var ImageEditorController = class {
5053
5220
  this.notify();
5054
5221
  return EMPTY_MIXER_LOAD;
5055
5222
  }
5223
+ /**
5224
+ * Sets (Alt-click) or clears the Clone Stamp and Healing Brush source point, in document
5225
+ * pixels. The next stroke fixes the offset from its first point. View state, no undo.
5226
+ */
5227
+ setCloneSource(point) {
5228
+ if (point && (!Number.isFinite(point.x) || !Number.isFinite(point.y))) throw new LayerOperationError("The clone source needs finite x and y (document pixels).");
5229
+ const source = point ? { x: Math.round(point.x), y: Math.round(point.y), offset: null } : null;
5230
+ this.state.paint = { ...this.state.paint, cloneSource: source };
5231
+ this.notify();
5232
+ return source;
5233
+ }
5056
5234
  /** D: black foreground, white background. */
5057
5235
  resetPaintColors() {
5058
5236
  this.setPaintOptions({ foreground: "#000000", background: "#ffffff" });
@@ -5107,8 +5285,8 @@ var ImageEditorController = class {
5107
5285
  */
5108
5286
  beginStroke(init = {}) {
5109
5287
  const tool = init.tool ?? "brush";
5110
- if (tool !== "brush" && tool !== "eraser" && tool !== "mixer") throw new LayerOperationError(`Unknown paint tool "${String(tool)}"; use brush, eraser or mixer.`);
5111
- const target = this.paintTarget(init.layerId, init.channel, tool === "eraser" ? "erase" : "paint");
5288
+ if (!STROKE_TOOLS.includes(tool)) throw new LayerOperationError(`Unknown paint tool "${String(tool)}"; use one of ${STROKE_TOOLS.join(", ")}.`);
5289
+ const target = this.paintTarget(init.layerId, init.channel, tool === "eraser" ? "erase" : isRetouchTool(tool) ? "retouch" : "paint");
5112
5290
  if (tool === "mixer" && target.channel !== "pixels") {
5113
5291
  throw new LayerOperationError("The Mixer Brush paints colour on pixels, not on masks; target pixels (channel pixels), or paint the mask with the brush.");
5114
5292
  }
@@ -5130,8 +5308,101 @@ var ImageEditorController = class {
5130
5308
  offset: target.offset
5131
5309
  };
5132
5310
  if (tool === "mixer") return new PaintStrokeSession(this, target, this.mixerStroke(init, brush), apply);
5311
+ if (isRetouchTool(tool)) return new PaintStrokeSession(this, target, this.retouchStroke(tool, init, brush, target, apply), apply);
5133
5312
  return new PaintStrokeSession(this, target, new StrokeCoverage(brush), apply);
5134
5313
  }
5314
+ /** A retouching stroke's start: its settings, what it samples and how it composites. */
5315
+ retouchStroke(tool, init, brush, target, apply) {
5316
+ const settings = normalizeRetouch(init.retouch ?? {}, this.state.paint.retouch);
5317
+ const mask = target.channel !== "pixels";
5318
+ if (tool === "sponge" && mask) throw new LayerOperationError("The Sponge changes saturation, and a mask has none; target pixels (channel pixels), or use dodge or burn on the mask.");
5319
+ if (tool === "smudge") return { kind: "dabs", create: (pixels) => new RetouchDabStroke(brush, pixels, smudgeEffect(brush, settings.strength)) };
5320
+ if (tool === "sponge") return { kind: "dabs", create: (pixels) => new RetouchDabStroke(brush, pixels, spongeEffect(brush, settings.spongeMode === "saturate", settings.vibrance)) };
5321
+ const retouchApply = {
5322
+ opacity: tool === "clone" || tool === "heal" ? brush.opacity : 1,
5323
+ blend: tool === "clone" && !mask ? brush.blend : "normal",
5324
+ lockTransparency: target.lockTransparency,
5325
+ selection: apply.selection,
5326
+ offset: target.offset
5327
+ };
5328
+ const coverage = new StrokeCoverage(brush);
5329
+ if (tool === "dodge" || tool === "burn") {
5330
+ const paint2 = { kind: "tone", tone: toneOf(tool, settings) };
5331
+ return { kind: "coverage", coverage, apply: retouchApply, start: () => {
5332
+ }, paint: () => paint2 };
5333
+ }
5334
+ const sample = mask ? "current" : settings.sample;
5335
+ const own = this.surfaceSource(target.surface, target.offset);
5336
+ const source = sample === "current" ? own : this.compositeSource(sample === "all" ? null : target.id);
5337
+ const pad = (r, m) => ({ x: r.x - m, y: r.y - m, width: r.width + 2 * m, height: r.height + 2 * m });
5338
+ const covered = (cov, r) => {
5339
+ const m = new Uint8Array(r.width * r.height);
5340
+ for (let y = 0; y < r.height; y++) for (let x = 0; x < r.width; x++) m[y * r.width + x] = cov.get(r.x + x, r.y + y) > 0 ? 1 : 0;
5341
+ return m;
5342
+ };
5343
+ if (tool === "spot-heal") {
5344
+ const { width, height } = this.naturalSize;
5345
+ let patch = null;
5346
+ const trail = { kind: "color", color: [0, 0, 0, 96] };
5347
+ return {
5348
+ kind: "coverage",
5349
+ coverage,
5350
+ apply: retouchApply,
5351
+ start: () => {
5352
+ },
5353
+ paint: () => trail,
5354
+ finalPaint: (cov) => {
5355
+ if (!cov.bounds) return null;
5356
+ const win = spotHealWindow(cov.bounds, { width, height });
5357
+ if (win.width <= 0 || win.height <= 0) return null;
5358
+ const img = readSourceRegion(source, win);
5359
+ const hole = covered(cov, win);
5360
+ const healed = spotHealPixels(win.width, win.height, img, hole, brush.size, settings.spotType, (off) => {
5361
+ patch = off ? { x: off[0], y: off[1] } : null;
5362
+ });
5363
+ return { kind: "source", source: new RegionPixelSource(win, healed), dx: 0, dy: 0 };
5364
+ },
5365
+ result: () => ({ spotHeal: { offset: patch } })
5366
+ };
5367
+ }
5368
+ if (init.source) this.setCloneSource(init.source);
5369
+ const cs = this.state.paint.cloneSource;
5370
+ if (!cs) throw new LayerOperationError(`The ${tool === "clone" ? "Clone Stamp" : "Healing Brush"} needs a source point; Alt-click the canvas, call set_clone_source, or pass source { x, y }.`);
5371
+ let offset = { x: 0, y: 0 };
5372
+ let first = { x: 0, y: 0 };
5373
+ const paint = () => ({ kind: "source", source, dx: offset.x, dy: offset.y });
5374
+ return {
5375
+ kind: "coverage",
5376
+ coverage,
5377
+ apply: retouchApply,
5378
+ start: (p) => {
5379
+ first = { x: Math.round(p.x), y: Math.round(p.y) };
5380
+ offset = settings.aligned && cs.offset ? { ...cs.offset } : { x: cs.x - first.x, y: cs.y - first.y };
5381
+ if (settings.aligned) this.state.paint = { ...this.state.paint, cloneSource: { x: cs.x, y: cs.y, offset: { ...offset } } };
5382
+ },
5383
+ paint,
5384
+ finalPaint: tool === "heal" ? (cov) => {
5385
+ if (!cov.bounds) return null;
5386
+ const g = pad(cov.bounds, 2);
5387
+ const src = readSourceRegion(source, g, offset.x, offset.y);
5388
+ const dst = readSourceRegion(source, g);
5389
+ return { kind: "source", source: new RegionPixelSource(g, healPixels(g.width, g.height, src, dst, covered(cov, g))), dx: 0, dy: 0 };
5390
+ } : void 0,
5391
+ result: () => ({ clone: { offset: { ...offset }, source: { x: first.x + offset.x, y: first.y + offset.y }, aligned: settings.aligned } })
5392
+ };
5393
+ }
5394
+ /** A pixel source over a surface (layer pixels or a mask) in layer space at `offset`. */
5395
+ surfaceSource(surface, offset) {
5396
+ return new TilePixelSource((tx, ty) => surface.tiles.get(tileKey(tx, ty))?.data ?? (surface.defaultValue ? defaultTile(surface) : null), surface.channels, offset);
5397
+ }
5398
+ /** A pixel source over the composite of the current layers (through `throughId` and below, or all of them), as it is now. */
5399
+ compositeSource(throughId) {
5400
+ const stack = withoutAnnotations(this.state.layers);
5401
+ const layers = throughId ? layersThrough(stack, throughId) : stack;
5402
+ const compositor = new LayerCompositor();
5403
+ const sources = this.compositorSources();
5404
+ return new TilePixelSource((tx, ty) => compositor.tile(layers, tx, ty, sources), 4);
5405
+ }
5135
5406
  /** A Mixer Brush stroke's start: the settings, and the load (refilled when asked or set to). */
5136
5407
  mixerStroke(init, brush) {
5137
5408
  const settings = normalizeMixer({ ...this.state.paint.mixer, ...init.mixer ?? {} });
@@ -5955,16 +6226,37 @@ var ImageEditorController = class {
5955
6226
  const canvas = await this.renderExport(options);
5956
6227
  const format = options.format || "image/png";
5957
6228
  const quality = options.quality ?? 0.92;
5958
- return canvas.toDataURL(format, quality);
6229
+ const url = canvas.toDataURL(format, quality);
6230
+ if (!this.state.colorProfile) return url;
6231
+ const parsed = parseDataUrl(url);
6232
+ if (!parsed) return url;
6233
+ const bytes = this.withEmbeddedProfile(parsed.bytes, parsed.mime);
6234
+ return bytes === parsed.bytes ? url : `data:${parsed.mime};base64,${bytesToBase64(bytes)}`;
6235
+ }
6236
+ /** PNG or JPEG bytes with the document's ICC profile embedded (other formats unchanged). */
6237
+ withEmbeddedProfile(bytes, mime) {
6238
+ const icc = profileBytes(this.state.colorProfile);
6239
+ try {
6240
+ if (mime === "image/png") return writePngIcc(bytes, icc, this.getColorProfile().name);
6241
+ if (mime === "image/jpeg") return writeJpegIcc(bytes, icc);
6242
+ } catch {
6243
+ }
6244
+ return bytes;
5959
6245
  }
5960
6246
  async toBlob(options = {}) {
5961
6247
  const canvas = await this.renderExport(options);
5962
6248
  const format = options.format || "image/png";
5963
6249
  const quality = options.quality ?? 0.92;
6250
+ const tag = async (blob) => {
6251
+ if (!this.state.colorProfile || typeof blob.arrayBuffer !== "function") return blob;
6252
+ const bytes = new Uint8Array(await blob.arrayBuffer());
6253
+ const out = this.withEmbeddedProfile(bytes, blob.type || format);
6254
+ return out === bytes ? blob : new Blob([out], { type: blob.type || format });
6255
+ };
5964
6256
  return new Promise((resolve, reject) => {
5965
6257
  if (canvas.toBlob) {
5966
6258
  canvas.toBlob((blob) => {
5967
- if (blob) resolve(blob);
6259
+ if (blob) tag(blob).then(resolve, reject);
5968
6260
  else reject(new Error("Failed to create Blob from canvas"));
5969
6261
  }, format, quality);
5970
6262
  } else {
@@ -5977,7 +6269,7 @@ var ImageEditorController = class {
5977
6269
  for (let i = 0; i < byteStr.length; i++) {
5978
6270
  ia[i] = byteStr.charCodeAt(i);
5979
6271
  }
5980
- resolve(new Blob([ab], { type: format }));
6272
+ tag(new Blob([ab], { type: format })).then(resolve, reject);
5981
6273
  } catch (err) {
5982
6274
  reject(err);
5983
6275
  }
@@ -6497,6 +6789,162 @@ var ImageEditorController = class {
6497
6789
  return id;
6498
6790
  }
6499
6791
  // --- PSD ---
6792
+ // --- Colour management ---
6793
+ /** The document's colour profile: its name, built-in id (or `null` for an embedded profile) and colour space. */
6794
+ getColorProfile() {
6795
+ const doc = this.state.colorProfile;
6796
+ const p = resolveProfile(doc);
6797
+ return { name: doc?.name ?? p.description, builtin: doc ? doc.builtin ?? null : "srgb", colorSpace: p.colorSpace, srgb: isSrgb(doc) };
6798
+ }
6799
+ /** Resolves a profile argument (built-in id or alias, or ICC bytes) as an RGB document profile. */
6800
+ rgbProfileArg(profile, action) {
6801
+ let doc;
6802
+ try {
6803
+ doc = documentProfile(profile);
6804
+ } catch (e) {
6805
+ throw new LayerOperationError(e instanceof Error ? e.message : String(e));
6806
+ }
6807
+ const cs = resolveProfile(doc).colorSpace;
6808
+ if (cs !== "rgb") throw new LayerOperationError(`${action} needs an RGB profile; "${doc.name}" is ${cs.toUpperCase()}. The document is RGB: soft-proof CMYK with set_proof_setup, or export to it.`);
6809
+ return doc;
6810
+ }
6811
+ /**
6812
+ * Edit › Assign Profile (one undo step): the document's numbers now mean colours in
6813
+ * `profile` (a built-in id such as `display-p3`, or ICC bytes); no pixel changes, so colours
6814
+ * look different unless the profiles match. Returns the new profile.
6815
+ */
6816
+ assignProfile(profile) {
6817
+ const doc = this.rgbProfileArg(profile, "Assign Profile");
6818
+ this.recordHistory();
6819
+ if (doc.builtin === "srgb") delete this.state.colorProfile;
6820
+ else this.state.colorProfile = doc;
6821
+ this.notify();
6822
+ return this.getColorProfile();
6823
+ }
6824
+ /**
6825
+ * Edit › Convert to Profile (one undo step): every pixel layer's numbers are converted so its
6826
+ * colours stay the same in `profile` (with `intent` and black point compensation), and
6827
+ * `profile` becomes the document's. Shape and type layers, layer effects and adjustment layers
6828
+ * keep their numbers (they are listed in `unconvertedLayerIds`). Returns the profile and the
6829
+ * layers changed.
6830
+ */
6831
+ convertToProfile(profile, options = {}) {
6832
+ const doc = this.rgbProfileArg(profile, "Convert to Profile");
6833
+ const intent = options.intent ?? "relative";
6834
+ if (!INTENTS.includes(intent)) throw new LayerOperationError(`Unknown rendering intent "${String(intent)}"; use ${INTENTS.join(", ")}.`);
6835
+ const t = Transform.create(resolveProfile(this.state.colorProfile), resolveProfile(doc), { intent, bpc: options.bpc ?? true });
6836
+ const converted = [];
6837
+ const unconverted = [];
6838
+ const walk = (layers) => layers.map((l) => {
6839
+ if (l.type === "group") return { ...l, children: walk(l.children) };
6840
+ if (l.type === "pixel" && l.source !== "document") {
6841
+ converted.push(l.id);
6842
+ return { ...l, surface: convertSurfaceColors(l.surface, t) };
6843
+ }
6844
+ if (l.type === "pixel" && l.source === "document") {
6845
+ converted.push(l.id);
6846
+ return { ...l, surface: convertSurfaceColors(this.surfaceOf(l), t), source: void 0 };
6847
+ }
6848
+ if (l.type === "shape" || l.type === "type" || l.type === "smart" || l.effects?.items.length) unconverted.push(l.id);
6849
+ return l;
6850
+ });
6851
+ const next = walk(this.state.layers);
6852
+ this.commitLayers(next, () => {
6853
+ if (doc.builtin === "srgb") delete this.state.colorProfile;
6854
+ else this.state.colorProfile = doc;
6855
+ });
6856
+ return { profile: this.getColorProfile(), convertedLayerIds: converted, unconvertedLayerIds: unconverted };
6857
+ }
6858
+ /**
6859
+ * View › Proof Setup (view state, not an undo step): shows the document as `profile` would
6860
+ * reproduce it, or turns proofing off with `null`. Returns the setup.
6861
+ */
6862
+ setProofSetup(setup) {
6863
+ if (!setup) {
6864
+ this.state.proofSetup = null;
6865
+ this.notify();
6866
+ return null;
6867
+ }
6868
+ let doc;
6869
+ try {
6870
+ doc = documentProfile(setup.profile);
6871
+ } catch (e) {
6872
+ throw new LayerOperationError(e instanceof Error ? e.message : String(e));
6873
+ }
6874
+ const intent = setup.intent ?? DEFAULT_PROOF.intent;
6875
+ if (!INTENTS.includes(intent)) throw new LayerOperationError(`Unknown rendering intent "${String(intent)}"; use ${INTENTS.join(", ")}.`);
6876
+ const next = { ...DEFAULT_PROOF, ...setup, intent, profile: doc };
6877
+ if (!canBeDestination(resolveProfile(doc))) throw new LayerOperationError(`"${doc.name}" describes a source only, so colours cannot be proofed through it.`);
6878
+ this.state.proofSetup = next;
6879
+ this.notify();
6880
+ return next;
6881
+ }
6882
+ /**
6883
+ * Converts colours (`#rrggbb`) from one profile to another (default from the document's), with
6884
+ * `intent`. For RGB targets returns hex; for others the channel values 0..1 (CMYK, grey, Lab
6885
+ * encoded as L/100, (a+128)/255, (b+128)/255) and, for Lab, L*a*b*.
6886
+ */
6887
+ convertColors(colors, to, options = {}) {
6888
+ let src;
6889
+ let dst;
6890
+ try {
6891
+ src = options.from !== void 0 ? resolveProfile(documentProfile(options.from)) : resolveProfile(this.state.colorProfile);
6892
+ dst = resolveProfile(documentProfile(to));
6893
+ } catch (e) {
6894
+ throw new LayerOperationError(e instanceof Error ? e.message : String(e));
6895
+ }
6896
+ if (src.colorSpace !== "rgb") throw new LayerOperationError("Colours are given as #rrggbb, so the source profile must be RGB.");
6897
+ const t = Transform.create(src, dst, { intent: options.intent ?? "relative", bpc: options.bpc ?? true });
6898
+ const toLab = Transform.create(src, builtinProfile("lab-d50"), { intent: "relative", bpc: false });
6899
+ return colors.map((input) => {
6900
+ const m = /^#?([0-9a-f]{6})$/i.exec(input.trim());
6901
+ if (!m) throw new LayerOperationError(`Invalid colour "${input}"; use #rrggbb.`);
6902
+ const n = parseInt(m[1], 16);
6903
+ const rgb = [(n >> 16 & 255) / 255, (n >> 8 & 255) / 255, (n & 255) / 255];
6904
+ const out = new Float32Array(16);
6905
+ t.eval(rgb, out);
6906
+ const values = Array.from(out.subarray(0, t.outputs), (v) => Math.round(Math.min(1, Math.max(0, v)) * 1e4) / 1e4);
6907
+ const l = new Float32Array(3);
6908
+ toLab.eval(rgb, l);
6909
+ const lab = [Math.round(l[0] * 1e4) / 100, Math.round((l[1] * 255 - 128) * 100) / 100, Math.round((l[2] * 255 - 128) * 100) / 100];
6910
+ const res = { input, values, lab };
6911
+ if (dst.colorSpace === "rgb") res.hex = `#${values.map((v) => Math.round(v * 255).toString(16).padStart(2, "0")).join("")}`;
6912
+ return res;
6913
+ });
6914
+ }
6915
+ /** The document's bits per channel (8 unless set to 16 or 32). */
6916
+ get bitDepth() {
6917
+ return this.state.bitDepth ?? 8;
6918
+ }
6919
+ /**
6920
+ * Image › Mode › 8 / 16 / 32 Bits/Channel (one undo step). Going up costs nothing: layers keep
6921
+ * their pixels and read them at the new depth, and edits that understand depth keep the extra
6922
+ * precision from then on. Going down rounds every pixel layer's deep samples to the new depth
6923
+ * (8 bits: their 8-bit rendering; 32 → 16: colour above 1 is clipped). Returns the depth and
6924
+ * the ids of the layers whose pixels changed.
6925
+ */
6926
+ setBitDepth(depth) {
6927
+ if (!isBitDepth(depth)) throw new LayerOperationError(`Bit depth must be 8, 16 or 32, not ${String(depth)}.`);
6928
+ const changed = [];
6929
+ const convert = (layers) => layers.map((l) => {
6930
+ if (l.type === "group") {
6931
+ const children = convert(l.children);
6932
+ return children.some((c, i) => c !== l.children[i]) ? { ...l, children } : l;
6933
+ }
6934
+ if (l.type !== "pixel") return l;
6935
+ const surface = convertSurfaceDepth(l.surface, depth);
6936
+ if (surface === l.surface) return l;
6937
+ changed.push(l.id);
6938
+ return { ...l, surface };
6939
+ });
6940
+ if (depth === this.bitDepth) return { bitDepth: depth, changedLayerIds: [] };
6941
+ const next = convert(this.state.layers);
6942
+ this.commitLayers(next, () => {
6943
+ if (depth === 8) delete this.state.bitDepth;
6944
+ else this.state.bitDepth = depth;
6945
+ });
6946
+ return { bitDepth: depth, changedLayerIds: changed };
6947
+ }
6500
6948
  /** Pixels per inch of the last opened PSD (written back by `toPsd`). */
6501
6949
  psdResolution;
6502
6950
  /**
@@ -6529,6 +6977,20 @@ var ImageEditorController = class {
6529
6977
  else delete s.patterns;
6530
6978
  if (result.globalLight) s.globalLight = result.globalLight;
6531
6979
  else delete s.globalLight;
6980
+ if (result.bitDepth !== 8) s.bitDepth = result.bitDepth;
6981
+ else delete s.bitDepth;
6982
+ delete s.colorProfile;
6983
+ if (result.iccProfile) {
6984
+ try {
6985
+ const doc = documentProfile(result.iccProfile);
6986
+ const cs = resolveProfile(doc).colorSpace;
6987
+ if (cs === "rgb") {
6988
+ if (!isSrgb(doc)) s.colorProfile = doc;
6989
+ } else result.warnings.push(`The file's ${cs.toUpperCase()} profile "${doc.name}" was not used; its colours were converted to sRGB numerically.`);
6990
+ } catch (e) {
6991
+ result.warnings.push(`The embedded ICC profile could not be read (${e instanceof Error ? e.message : String(e)}); colours are read as sRGB.`);
6992
+ }
6993
+ }
6532
6994
  this.lastLoadProblems = result.warnings;
6533
6995
  this.notify();
6534
6996
  return {
@@ -6564,7 +7026,7 @@ var ImageEditorController = class {
6564
7026
  return layers;
6565
7027
  }
6566
7028
  /**
6567
- * The document as an 8-bit RGB PSD (PSB above 30 000 px) with real layers, groups, adjustment
7029
+ * The document as an RGB PSD at its bit depth (8, 16 or 32) (PSB above 30 000 px) with real layers, groups, adjustment
6568
7030
  * layers and masks, in the source frame. Layer filters and parametric (depth) masks are baked
6569
7031
  * into pixels and masks, annotation layers are rasterized (in a browser; elsewhere they are left
6570
7032
  * out), and the merged image is our composite. `warnings` lists every such change.
@@ -6690,7 +7152,9 @@ var ImageEditorController = class {
6690
7152
  if (s.legacyOutputBlur) warnings.push("The document-level blur of an old document is not written.");
6691
7153
  for (const [msg, names] of note) warnings.push(`${msg} (${names.length === 1 ? "layer" : "layers"} ${names.map((n) => `"${n}"`).join(", ")}).`);
6692
7154
  const merged = compositor.composite(prepared, canvas, { ...sources, version: `psd:${Date.now()}` });
6693
- const result = exportPsd({ width, height, layers: prepared, merged, resolution: this.psdResolution, patterns: this.state.patterns, globalLight: this.state.globalLight });
7155
+ const depth = this.bitDepth;
7156
+ const mergedDeep = depth === 8 ? void 0 : workingToSamples(compositeDeep(prepared, canvas, sources, depth), depth);
7157
+ const result = exportPsd({ width, height, layers: prepared, merged, mergedDeep, resolution: this.psdResolution, patterns: this.state.patterns, globalLight: this.state.globalLight, depth, iccProfile: profileBytes(this.state.colorProfile) });
6694
7158
  warnings.push(...result.warnings);
6695
7159
  return { bytes: result.bytes, warnings, psb: result.psb, width, height };
6696
7160
  }
@@ -6836,6 +7300,11 @@ var ImageEditorController = class {
6836
7300
  const patterns = deserializePatterns(data.patterns, problems);
6837
7301
  if (patterns.length) s.patterns = patterns;
6838
7302
  else delete s.patterns;
7303
+ if (data.bitDepth === 16 || data.bitDepth === 32) s.bitDepth = data.bitDepth;
7304
+ else delete s.bitDepth;
7305
+ const profile = data.colorProfile ? deserializeColorProfile(data.colorProfile, problems) : null;
7306
+ if (profile) s.colorProfile = profile;
7307
+ else delete s.colorProfile;
6839
7308
  if (!this.imageElement && typeof data.width === "number" && typeof data.height === "number" && data.width > 0 && data.height > 0) {
6840
7309
  s.imageDimensions = { width: data.width, height: data.height };
6841
7310
  }
@@ -6851,6 +7320,8 @@ var ImageEditorController = class {
6851
7320
  s.activeLayerId = null;
6852
7321
  delete s.globalLight;
6853
7322
  delete s.patterns;
7323
+ delete s.bitDepth;
7324
+ delete s.colorProfile;
6854
7325
  }
6855
7326
  s.activeTarget = "pixels";
6856
7327
  this.viewKey = [];
@@ -6860,7 +7331,7 @@ var ImageEditorController = class {
6860
7331
  serializeState() {
6861
7332
  const { width, height } = this.naturalSize;
6862
7333
  const view = legacyView(this.stack(), width, height, null);
6863
- if (view.exact && !this.state.globalLight && !this.state.patterns?.length) {
7334
+ if (view.exact && !this.state.globalLight && !this.state.patterns?.length && this.bitDepth === 8 && !this.state.colorProfile) {
6864
7335
  return {
6865
7336
  // States without adjustment layers keep schema 1, so they round-trip unchanged.
6866
7337
  version: view.adjustments.layers?.length ? 2 : 1,
@@ -6890,6 +7361,8 @@ var ImageEditorController = class {
6890
7361
  if (this.state.globalLight) out.globalLight = { ...this.state.globalLight };
6891
7362
  if (this.state.patterns?.length) out.patterns = serializePatterns(this.state.patterns);
6892
7363
  if (smartContents) out.smartContents = smartContents;
7364
+ if (this.state.bitDepth === 16 || this.state.bitDepth === 32) out.bitDepth = this.state.bitDepth;
7365
+ if (this.state.colorProfile) out.colorProfile = serializeColorProfile(this.state.colorProfile);
6893
7366
  return out;
6894
7367
  }
6895
7368
  };
@@ -7033,6 +7506,27 @@ function collectStateTiles(layers, into) {
7033
7506
  }
7034
7507
  }
7035
7508
  }
7509
+ function layersThrough(layers, id) {
7510
+ const walk = (list) => {
7511
+ const out = [];
7512
+ for (const l of list) {
7513
+ if (l.id === id) {
7514
+ out.push(l);
7515
+ return { out, found: true };
7516
+ }
7517
+ if (l.type === "group") {
7518
+ const sub2 = walk(l.children);
7519
+ if (sub2.found) {
7520
+ out.push({ ...l, children: sub2.out });
7521
+ return { out, found: true };
7522
+ }
7523
+ }
7524
+ out.push(l);
7525
+ }
7526
+ return { out, found: false };
7527
+ };
7528
+ return walk(layers).out;
7529
+ }
7036
7530
  function pixelsOnly(layers) {
7037
7531
  const out = [];
7038
7532
  for (const l of layers) {
@@ -8077,10 +8571,23 @@ function createImageEditTools(controller) {
8077
8571
  const getImageInfo = {
8078
8572
  name: "get_image_info",
8079
8573
  title: "Read image geometry",
8080
- description: "Returns the source image size in pixels, the crop box (natural image pixels, or null for the whole image), rotation in degrees, flips, the output size after crop and rotation, the number of adjustment layers and whether undo / redo are available.",
8574
+ description: "Returns the source image size in pixels, the crop box (natural image pixels, or null for the whole image), rotation in degrees, flips, the output size after crop and rotation, the bits per channel (bitDepth 8, 16 or 32), the number of adjustment layers and whether undo / redo are available.",
8081
8575
  inputSchema: { type: "object", properties: {}, additionalProperties: false },
8082
8576
  annotations: { title: "Read image geometry", ...READ },
8083
- handler: () => ({ ...geometry2(), layers: controller.getAdjustmentLayers().length, ...history() })
8577
+ handler: () => ({ ...geometry2(), bitDepth: controller.bitDepth, layers: controller.getAdjustmentLayers().length, ...history() })
8578
+ };
8579
+ const setBitDepth = {
8580
+ name: "set_bit_depth",
8581
+ title: "Set the bit depth",
8582
+ description: "Image \u203A Mode \u203A 8 / 16 / 32 Bits/Channel. 16-bit keeps smooth gradients and survives strong adjustments without banding; 32-bit holds linear-light float values (HDR, colour above 1). Going up costs nothing and changes no pixels; going down rounds every pixel layer's extra precision away (32 \u2192 16 also clips colour above 1). The viewport always shows an 8-bit rendering. One undo step. Returns the bitDepth and changedLayerIds (the layers whose samples were rounded).",
8583
+ inputSchema: {
8584
+ type: "object",
8585
+ properties: { depth: { type: "number", enum: [8, 16, 32], description: "Bits per channel: 8, 16 or 32 (float, linear light)." } },
8586
+ required: ["depth"],
8587
+ additionalProperties: false
8588
+ },
8589
+ annotations: { title: "Set the bit depth", readOnlyHint: false, destructiveHint: true, idempotentHint: true, openWorldHint: false },
8590
+ handler: (args) => ({ ...controller.setBitDepth(args.depth), ...history() })
8084
8591
  };
8085
8592
  const getHistogram = {
8086
8593
  name: "get_histogram",
@@ -8256,7 +8763,7 @@ function createImageEditTools(controller) {
8256
8763
  return history();
8257
8764
  }
8258
8765
  };
8259
- return [getImageInfo, getHistogram, updateLayer, removeLayer2, moveLayer2, setCrop, resetCrop, setRotation, flipImage, undo, redo];
8766
+ return [getImageInfo, getHistogram, updateLayer, removeLayer2, moveLayer2, setCrop, resetCrop, setRotation, flipImage, setBitDepth, undo, redo];
8260
8767
  }
8261
8768
 
8262
8769
  // src/image-editor/layer-tools.ts
@@ -9175,9 +9682,73 @@ var PAINT_TOOLS = [
9175
9682
  { id: "move", label: "Move", shortcut: "V" },
9176
9683
  { id: "brush", label: "Brush", shortcut: "B" },
9177
9684
  { id: "mixer", label: "Mixer", shortcut: "Shift+B" },
9178
- { id: "eraser", label: "Eraser", shortcut: "E" }
9685
+ { id: "eraser", label: "Eraser", shortcut: "E" },
9686
+ { id: "clone", label: "Clone", shortcut: "S" },
9687
+ { id: "heal", label: "Heal", shortcut: "Shift+J" },
9688
+ { id: "spot-heal", label: "Spot Heal", shortcut: "J" },
9689
+ { id: "smudge", label: "Smudge", shortcut: "" },
9690
+ { id: "dodge", label: "Dodge", shortcut: "O" },
9691
+ { id: "burn", label: "Burn", shortcut: "Shift+O" },
9692
+ { id: "sponge", label: "Sponge", shortcut: "Shift+O" }
9693
+ ];
9694
+ var isPaintTool = (tool) => PAINT_TOOLS.some((t) => t.id === tool);
9695
+ var isRetouchPaintTool = (tool) => tool === "clone" || tool === "heal" || tool === "spot-heal" || tool === "smudge" || tool === "dodge" || tool === "burn" || tool === "sponge";
9696
+ var RETOUCH_OPTION_CONTROLS = [
9697
+ { id: "exposure", label: "Exposure", section: "layer", min: 0, max: 1, step: 0.01, default: 0.5, decimals: 0, track: plain2, displayScale: 100, unit: "%", tools: ["dodge", "burn"] },
9698
+ { id: "strength", label: "Strength", section: "layer", min: 0, max: 1, step: 0.01, default: 0.5, decimals: 0, track: plain2, displayScale: 100, unit: "%", tools: ["smudge"] }
9699
+ ];
9700
+ var RETOUCH_CHOICES = [
9701
+ {
9702
+ id: "sample",
9703
+ label: "Sample",
9704
+ tools: ["clone", "heal", "spot-heal"],
9705
+ options: [
9706
+ { id: "current", label: "Current", title: "Sample the layer being retouched" },
9707
+ { id: "current-below", label: "Current & Below", title: "Sample this layer and the layers under it" },
9708
+ { id: "all", label: "All", title: "Sample every layer (retouch onto an empty layer)" }
9709
+ ]
9710
+ },
9711
+ {
9712
+ id: "spotType",
9713
+ label: "Type",
9714
+ tools: ["spot-heal"],
9715
+ options: [
9716
+ { id: "proximity-match", label: "Proximity Match", title: "Copy the best matching patch nearby" },
9717
+ { id: "smooth", label: "Smooth", title: "Fill smoothly from the edges" }
9718
+ ]
9719
+ },
9720
+ {
9721
+ id: "range",
9722
+ label: "Range",
9723
+ tools: ["dodge", "burn"],
9724
+ options: [
9725
+ { id: "shadows", label: "Shadows", title: "Change the dark tones most" },
9726
+ { id: "midtones", label: "Midtones", title: "Change the middle tones most" },
9727
+ { id: "highlights", label: "Highlights", title: "Change the light tones most" }
9728
+ ]
9729
+ },
9730
+ {
9731
+ id: "spongeMode",
9732
+ label: "Mode",
9733
+ tools: ["sponge"],
9734
+ options: [
9735
+ { id: "desaturate", label: "Desaturate", title: "Take colour out" },
9736
+ { id: "saturate", label: "Saturate", title: "Add colour" }
9737
+ ]
9738
+ }
9739
+ ];
9740
+ var RETOUCH_TOGGLES = [
9741
+ { id: "aligned", label: "Aligned", tools: ["clone", "heal"] },
9742
+ { id: "protectTones", label: "Protect tones", tools: ["dodge", "burn"] },
9743
+ { id: "vibrance", label: "Vibrance", tools: ["sponge"] }
9179
9744
  ];
9180
- var isPaintTool = (tool) => tool === "move" || tool === "brush" || tool === "mixer" || tool === "eraser";
9745
+ function setRetouchChoice(controller, id, value) {
9746
+ controller.setPaintOptions({ retouch: { [id]: value } });
9747
+ }
9748
+ function cloneSourceLabel(controller) {
9749
+ const cs = controller.getState().paint.cloneSource;
9750
+ return cs ? `Source ${cs.x}, ${cs.y}${cs.offset ? ` (offset ${cs.offset.x}, ${cs.offset.y})` : ""}` : "Alt-click to set the source";
9751
+ }
9181
9752
  function rgbUnitToHex(c) {
9182
9753
  return `#${c.map((v) => Math.round(Math.min(1, Math.max(0, v)) * 255).toString(16).padStart(2, "0")).join("")}`;
9183
9754
  }
@@ -9203,6 +9774,18 @@ function handlePaintShortcut(controller, e) {
9203
9774
  case "v":
9204
9775
  controller.setTool("move");
9205
9776
  return true;
9777
+ case "s":
9778
+ controller.setTool("clone");
9779
+ return true;
9780
+ case "j":
9781
+ controller.setTool(e.shiftKey || key === "J" ? controller.getState().activeTool === "heal" ? "spot-heal" : "heal" : "spot-heal");
9782
+ return true;
9783
+ case "o": {
9784
+ const cur = controller.getState().activeTool;
9785
+ const next = e.shiftKey || key === "O" ? cur === "dodge" ? "burn" : cur === "burn" ? "sponge" : "dodge" : "dodge";
9786
+ controller.setTool(next);
9787
+ return true;
9788
+ }
9206
9789
  case "d":
9207
9790
  controller.resetPaintColors();
9208
9791
  return true;
@@ -9258,9 +9841,13 @@ var PaintGesture = class {
9258
9841
  this.move = { id: layer.id, start: { x: sample.x, y: sample.y }, origin, moved: false };
9259
9842
  return null;
9260
9843
  }
9261
- if (tool === "brush" || tool === "eraser" || tool === "mixer") {
9844
+ if ((tool === "clone" || tool === "heal") && sample.altKey) {
9845
+ this.controller.setCloneSource({ x: sample.x, y: sample.y });
9846
+ return null;
9847
+ }
9848
+ if (isPaintTool(tool)) {
9262
9849
  this.stroke = this.controller.beginStroke({ tool });
9263
- this.stroke.push([sample]);
9850
+ this.stroke.push([{ x: sample.x, y: sample.y, pressure: sample.pressure }]);
9264
9851
  return null;
9265
9852
  }
9266
9853
  } catch (err) {
@@ -9491,8 +10078,396 @@ function createImagePaintTools(controller) {
9491
10078
  return [paintStroke, fill, clear, getMixer, loadMixerTool, cleanMixerTool];
9492
10079
  }
9493
10080
 
9494
- // src/image-editor/psd-tools.ts
10081
+ // src/image-editor/color-tools.ts
9495
10082
  var READ5 = { readOnlyHint: true, destructiveHint: false, idempotentHint: true, openWorldHint: false };
10083
+ var PROFILE = {
10084
+ type: "string",
10085
+ description: `A built-in profile id (${BUILTIN_PROFILES.map((b) => b.id).join(", ")}; names like "Adobe RGB (1998)" work too), or "icc:<base64>" for ICC profile bytes.`
10086
+ };
10087
+ var INTENT = {
10088
+ type: "string",
10089
+ enum: [...INTENTS],
10090
+ description: "Rendering intent (default relative): relative (colorimetric, white to white, clips out-of-gamut colours: best for most photos), perceptual (compresses the whole gamut smoothly), saturation (keeps colours vivid, for graphics), absolute (keeps the source white too: for proofing a paper colour)."
10091
+ };
10092
+ function profileArg(v) {
10093
+ const s = String(v);
10094
+ if (s.startsWith("icc:")) {
10095
+ try {
10096
+ return base64ToBytes(s.slice(4));
10097
+ } catch {
10098
+ throw new LayerOperationError("The icc: value is not valid base64.");
10099
+ }
10100
+ }
10101
+ return s;
10102
+ }
10103
+ function createImageColorTools(controller) {
10104
+ const getProfile = {
10105
+ name: "get_color_profile",
10106
+ title: "Get the colour profile",
10107
+ description: "Reads the document's colour profile (its working space: name, builtin id or null for an embedded profile, colorSpace, srgb true when it means sRGB), the Proof Setup (profile, intent, bpc, simulatePaper, gamutWarning, or null) and the built-in profiles you can assign, convert to or proof with ({id, description, colorSpace}).",
10108
+ inputSchema: { type: "object", properties: {}, additionalProperties: false },
10109
+ annotations: { title: "Get the colour profile", ...READ5 },
10110
+ handler: () => {
10111
+ const proof2 = controller.getState().proofSetup;
10112
+ return {
10113
+ profile: controller.getColorProfile(),
10114
+ proof: proof2 ? { profile: proof2.profile.name, builtin: proof2.profile.builtin ?? null, intent: proof2.intent, bpc: proof2.bpc, simulatePaper: proof2.simulatePaper, gamutWarning: proof2.gamutWarning } : null,
10115
+ builtins: BUILTIN_PROFILES.map((b) => ({ ...b }))
10116
+ };
10117
+ }
10118
+ };
10119
+ const assign = {
10120
+ name: "assign_profile",
10121
+ title: "Assign a colour profile",
10122
+ description: "Edit \u203A Assign Profile: the document's RGB numbers now mean colours in `profile`; no pixel changes, so the image looks different unless the profiles match. Use it when a file was saved without its profile, or with the wrong one. To keep the look and change the numbers, use convert_to_profile. RGB profiles only. One undo step. Returns the new profile.",
10123
+ inputSchema: { type: "object", properties: { profile: PROFILE }, required: ["profile"], additionalProperties: false },
10124
+ annotations: { title: "Assign a colour profile", readOnlyHint: false, destructiveHint: false, idempotentHint: true, openWorldHint: false },
10125
+ handler: (args) => controller.assignProfile(profileArg(args.profile))
10126
+ };
10127
+ const convert = {
10128
+ name: "convert_to_profile",
10129
+ title: "Convert to a colour profile",
10130
+ description: "Edit \u203A Convert to Profile: converts every pixel layer's numbers so its colours stay the same in `profile`, then makes `profile` the document's. Colours outside the new space are mapped by `intent`. Shape and type layers, layer effects and smart objects keep their numbers and are listed in unconvertedLayerIds. RGB profiles only (proof CMYK with set_proof_setup). One undo step. Returns the profile, convertedLayerIds and unconvertedLayerIds.",
10131
+ inputSchema: {
10132
+ type: "object",
10133
+ properties: {
10134
+ profile: PROFILE,
10135
+ intent: INTENT,
10136
+ bpc: { type: "boolean", description: "Black point compensation: map black to black (default true)." }
10137
+ },
10138
+ required: ["profile"],
10139
+ additionalProperties: false
10140
+ },
10141
+ annotations: { title: "Convert to a colour profile", readOnlyHint: false, destructiveHint: true, idempotentHint: false, openWorldHint: false },
10142
+ handler: (args) => controller.convertToProfile(profileArg(args.profile), { intent: args.intent, bpc: args.bpc })
10143
+ };
10144
+ const proof = {
10145
+ name: "set_proof_setup",
10146
+ title: "Set up soft proofing",
10147
+ description: "View \u203A Proof Setup: shows the document in the viewport as `profile` would reproduce it (for example coated-cmyk for a press), with `intent`; `simulatePaper` shows the paper white and ink black; `gamutWarning` greys out colours the profile cannot reproduce. Pass off true to stop proofing. View state, not an undo step; pixels never change. Check the result with capture_view. Returns the setup (or null).",
10148
+ inputSchema: {
10149
+ type: "object",
10150
+ properties: {
10151
+ profile: PROFILE,
10152
+ intent: INTENT,
10153
+ bpc: { type: "boolean", description: "Black point compensation (default true)." },
10154
+ simulatePaper: { type: "boolean", description: "Simulate paper colour and black ink (default false)." },
10155
+ gamutWarning: { type: "boolean", description: "Grey out out-of-gamut colours (default false)." },
10156
+ off: { type: "boolean", description: "Turn proofing off instead (default false)." }
10157
+ },
10158
+ additionalProperties: false
10159
+ },
10160
+ annotations: { title: "Set up soft proofing", readOnlyHint: false, destructiveHint: false, idempotentHint: true, openWorldHint: false },
10161
+ handler: (args) => {
10162
+ if (args.off === true) {
10163
+ if (args.profile !== void 0) throw new LayerOperationError("Give either off true or a profile, not both.");
10164
+ return controller.setProofSetup(null);
10165
+ }
10166
+ if (args.profile === void 0) throw new LayerOperationError("Pass the profile to proof with (e.g. coated-cmyk), or off true.");
10167
+ const s = controller.setProofSetup({
10168
+ profile: profileArg(args.profile),
10169
+ ...args.intent !== void 0 ? { intent: args.intent } : {},
10170
+ ...args.bpc !== void 0 ? { bpc: args.bpc } : {},
10171
+ ...args.simulatePaper !== void 0 ? { simulatePaper: args.simulatePaper } : {},
10172
+ ...args.gamutWarning !== void 0 ? { gamutWarning: args.gamutWarning } : {}
10173
+ });
10174
+ return { profile: s.profile.name, builtin: s.profile.builtin ?? null, intent: s.intent, bpc: s.bpc, simulatePaper: s.simulatePaper, gamutWarning: s.gamutWarning };
10175
+ }
10176
+ };
10177
+ const convertColors = {
10178
+ name: "convert_colors",
10179
+ title: "Convert colours between profiles",
10180
+ description: 'Converts 1 to 64 #rrggbb colours from the document\'s profile (or `from`) into `to` with `intent`: returns per colour the channel values 0..1 (RGB, CMYK, grey; Lab as L/100, (a+128)/255, (b+128)/255), a hex for RGB targets, and L*a*b* (D50). Use it to answer "what are these CMYK values" or to pick colours that survive printing. Changes nothing.',
10181
+ inputSchema: {
10182
+ type: "object",
10183
+ properties: {
10184
+ colors: { type: "array", minItems: 1, maxItems: 64, items: { type: "string", description: "A colour #rrggbb." }, description: "Colours in the source profile." },
10185
+ to: PROFILE,
10186
+ from: { ...PROFILE, description: `Source profile (default the document's). ${PROFILE.description}` },
10187
+ intent: INTENT,
10188
+ bpc: { type: "boolean", description: "Black point compensation (default true)." }
10189
+ },
10190
+ required: ["colors", "to"],
10191
+ additionalProperties: false
10192
+ },
10193
+ annotations: { title: "Convert colours between profiles", ...READ5 },
10194
+ handler: (args) => ({
10195
+ colors: controller.convertColors(args.colors, profileArg(args.to), {
10196
+ ...args.from !== void 0 ? { from: profileArg(args.from) } : {},
10197
+ intent: args.intent,
10198
+ bpc: args.bpc
10199
+ })
10200
+ })
10201
+ };
10202
+ return [getProfile, assign, convert, proof, convertColors];
10203
+ }
10204
+
10205
+ // src/image-editor/retouch-tools.ts
10206
+ var READ6 = { readOnlyHint: true, destructiveHint: false, idempotentHint: true, openWorldHint: false };
10207
+ var WRITE5 = { readOnlyHint: false, destructiveHint: false, idempotentHint: false, openWorldHint: false };
10208
+ var POINTS = {
10209
+ type: "array",
10210
+ minItems: 1,
10211
+ maxItems: 1e4,
10212
+ description: "The stroke path in document pixels; one point is one dab.",
10213
+ items: {
10214
+ type: "object",
10215
+ properties: {
10216
+ x: { type: "number", description: "Document x in px." },
10217
+ y: { type: "number", description: "Document y in px." },
10218
+ pressure: { type: "number", minimum: 0, maximum: 1, description: "Pen pressure (default 1)." }
10219
+ },
10220
+ required: ["x", "y"],
10221
+ additionalProperties: false
10222
+ }
10223
+ };
10224
+ var BRUSH2 = {
10225
+ type: "object",
10226
+ description: "Brush tip; missing fields use the current paint options (default round, hard, 20 px).",
10227
+ properties: {
10228
+ size: { type: "number", minimum: 1, maximum: MAX_BRUSH_SIZE, description: "Diameter in px (default 20)." },
10229
+ hardness: { type: "number", minimum: 0, maximum: 1, description: "Edge hardness 0 (soft) .. 1 (hard, default). Retouching usually wants a soft tip (0..0.5)." },
10230
+ spacing: { type: "number", minimum: 0.01, maximum: 10, description: "Dab spacing as a fraction of the diameter (default 0.1)." },
10231
+ opacity: { type: "number", minimum: 0, maximum: 1, description: "Stroke opacity (Clone Stamp and Healing Brush only; default 1)." },
10232
+ flow: { type: "number", minimum: 0, maximum: 1, description: "Build-up per dab (default 1). For the Sponge, how strongly each pass moves saturation." },
10233
+ smoothing: { type: "number", minimum: 0, maximum: 0.95, description: "Point smoothing 0..0.95 (default 0)." },
10234
+ pressureSize: { type: "boolean", description: "Point pressure scales the size (default true)." },
10235
+ pressureOpacity: { type: "boolean", description: "Point pressure scales the opacity (default false)." }
10236
+ },
10237
+ additionalProperties: false
10238
+ };
10239
+ var TARGET2 = {
10240
+ layerId: { type: "string", description: "Layer to retouch (default the active layer; see list_layers)." },
10241
+ channel: {
10242
+ type: "string",
10243
+ enum: ["pixels", "mask", "filter-mask"],
10244
+ description: "pixels (default for a pixel layer), the layer's mask, or a smart object's filter mask. Masks are grey values and sample only themselves."
10245
+ }
10246
+ };
10247
+ var SAMPLE = {
10248
+ type: "string",
10249
+ enum: [...RETOUCH_SAMPLES],
10250
+ description: "What is sampled: current (default: the target layer), current-below (the composite of this layer and everything under it), or all (the composite of every layer). Sample current-below or all to retouch onto an empty layer above the image."
10251
+ };
10252
+ function readTarget2(args) {
10253
+ return { layerId: args.layerId, channel: args.channel };
10254
+ }
10255
+ function checkSource(controller, source) {
10256
+ if (source === void 0) return void 0;
10257
+ const p = source;
10258
+ const { width, height } = controller.getState().imageDimensions;
10259
+ if (!(p.x >= 0 && p.y >= 0 && p.x < width && p.y < height)) {
10260
+ throw new LayerOperationError(`source (${p.x}, ${p.y}) is outside the ${width} \xD7 ${height} canvas; pick a point on the image to sample.`);
10261
+ }
10262
+ return p;
10263
+ }
10264
+ var SOURCE = {
10265
+ type: "object",
10266
+ description: "Source point in document pixels (an Alt-click). Given, it replaces the clone source and the offset starts afresh at this stroke; missing, the current clone source is used (set_clone_source).",
10267
+ properties: { x: { type: "number", description: "Source x in px." }, y: { type: "number", description: "Source y in px." } },
10268
+ required: ["x", "y"],
10269
+ additionalProperties: false
10270
+ };
10271
+ function createImageRetouchTools(controller) {
10272
+ const stroke = (tool, args, retouch) => controller.paintStroke({
10273
+ tool,
10274
+ points: args.points,
10275
+ brush: args.brush,
10276
+ retouch,
10277
+ source: checkSource(controller, args.source),
10278
+ ...readTarget2(args)
10279
+ });
10280
+ const cloneStamp = {
10281
+ name: "clone_stamp",
10282
+ title: "Clone Stamp stroke",
10283
+ description: "Copies pixels from the clone source along a stroke (Photoshop's Clone Stamp): each point paints what lies at point + offset, where the offset is source \u2212 the stroke's first point. With aligned on, later strokes keep that offset; off, every stroke starts again at the source point. Samples the layer, the layers up to it, or all layers, from before the stroke. The selection limits it; a transparency-locked layer keeps its alpha. One undo step. Returns the layerId, channel, changed bounds, dab count and clone {offset, source, aligned}.",
10284
+ inputSchema: {
10285
+ type: "object",
10286
+ properties: {
10287
+ points: POINTS,
10288
+ source: SOURCE,
10289
+ aligned: { type: "boolean", description: "Keep the offset for the next strokes (default the current option, true)." },
10290
+ sample: SAMPLE,
10291
+ brush: BRUSH2,
10292
+ ...TARGET2
10293
+ },
10294
+ required: ["points"],
10295
+ additionalProperties: false
10296
+ },
10297
+ annotations: { title: "Clone Stamp stroke", ...WRITE5 },
10298
+ handler: (args) => stroke("clone", args, pick(args, ["aligned", "sample"]))
10299
+ };
10300
+ const healBrush = {
10301
+ name: "heal_brush",
10302
+ title: "Healing Brush stroke",
10303
+ description: "Heals along a stroke from the clone source (Photoshop's Healing Brush): the source's texture is cloned, then blended so its colour and light match the pixels around the stroke (Poisson seamless cloning, applied when the stroke ends). Use it for blemishes and dust where the area around them is the right tone; use clone_stamp to copy exactly. Same source, offset and aligned rules as clone_stamp. One undo step. Returns the layerId, channel, changed bounds, dab count and clone {offset, source, aligned}.",
10304
+ inputSchema: {
10305
+ type: "object",
10306
+ properties: {
10307
+ points: POINTS,
10308
+ source: SOURCE,
10309
+ aligned: { type: "boolean", description: "Keep the offset for the next strokes (default the current option, true)." },
10310
+ sample: SAMPLE,
10311
+ brush: BRUSH2,
10312
+ ...TARGET2
10313
+ },
10314
+ required: ["points"],
10315
+ additionalProperties: false
10316
+ },
10317
+ annotations: { title: "Healing Brush stroke", ...WRITE5 },
10318
+ handler: (args) => stroke("heal", args, pick(args, ["aligned", "sample"]))
10319
+ };
10320
+ const spotHeal = {
10321
+ name: "spot_heal",
10322
+ title: "Spot Healing stroke",
10323
+ description: "Removes what a stroke covers without a source point (Photoshop's Spot Healing Brush): proximity-match copies the best matching patch nearby and blends it in; smooth fills from the edges inwards (for spots on smooth areas). Paint just over the spot with a brush a little larger than it. One undo step. Returns the layerId, channel, changed bounds, dab count and spotHeal {offset} (the patch offset copied, or null for a smooth fill).",
10324
+ inputSchema: {
10325
+ type: "object",
10326
+ properties: {
10327
+ points: POINTS,
10328
+ type: { type: "string", enum: [...SPOT_HEAL_TYPES], description: "proximity-match (default) or smooth." },
10329
+ sample: SAMPLE,
10330
+ brush: BRUSH2,
10331
+ ...TARGET2
10332
+ },
10333
+ required: ["points"],
10334
+ additionalProperties: false
10335
+ },
10336
+ annotations: { title: "Spot Healing stroke", ...WRITE5 },
10337
+ handler: (args) => stroke("spot-heal", args, { ...pick(args, ["sample"]), ...args.type !== void 0 ? { spotType: args.type } : {} })
10338
+ };
10339
+ const smudge = {
10340
+ name: "smudge",
10341
+ title: "Smudge stroke",
10342
+ description: "Drags pixels along a stroke like a finger through wet paint (Photoshop's Smudge): each dab lays what the finger carries and picks up what was under it, so colour is pulled in the stroke's direction and fades with distance. One undo step. Returns the layerId, channel, changed bounds and dab count.",
10343
+ inputSchema: {
10344
+ type: "object",
10345
+ properties: {
10346
+ points: POINTS,
10347
+ strength: { type: "number", minimum: 0, maximum: 1, description: "How far colour is dragged, 0..1 (default the current option, 0.5)." },
10348
+ brush: BRUSH2,
10349
+ ...TARGET2
10350
+ },
10351
+ required: ["points"],
10352
+ additionalProperties: false
10353
+ },
10354
+ annotations: { title: "Smudge stroke", ...WRITE5 },
10355
+ handler: (args) => stroke("smudge", args, pick(args, ["strength"]))
10356
+ };
10357
+ const toneSchema = (verb) => ({
10358
+ type: "object",
10359
+ properties: {
10360
+ points: POINTS,
10361
+ range: { type: "string", enum: [...TONE_RANGES], description: `Which tones to ${verb} most: shadows, midtones (default the current option, midtones) or highlights.` },
10362
+ exposure: { type: "number", minimum: 0, maximum: 1, description: "Strength 0..1 (default the current option, 0.5). Overlapping dabs in one stroke never go past it; another stroke builds on it." },
10363
+ protectTones: { type: "boolean", description: "Change luma only, keeping hue and saturation (default true)." },
10364
+ brush: BRUSH2,
10365
+ ...TARGET2
10366
+ },
10367
+ required: ["points"],
10368
+ additionalProperties: false
10369
+ });
10370
+ const dodge = {
10371
+ name: "dodge",
10372
+ title: "Dodge stroke",
10373
+ description: "Lightens the pixels a stroke covers (Photoshop's Dodge), most in the chosen tonal range. Alpha is kept. One undo step. Returns the layerId, channel, changed bounds and dab count. Inspect the result with capture_view or get_histogram; several light strokes look more natural than one strong one.",
10374
+ inputSchema: toneSchema("lighten"),
10375
+ annotations: { title: "Dodge stroke", ...WRITE5 },
10376
+ handler: (args) => stroke("dodge", args, pick(args, ["range", "exposure", "protectTones"]))
10377
+ };
10378
+ const burn = {
10379
+ name: "burn",
10380
+ title: "Burn stroke",
10381
+ description: "Darkens the pixels a stroke covers (Photoshop's Burn), most in the chosen tonal range. Alpha is kept. One undo step. Returns the layerId, channel, changed bounds and dab count.",
10382
+ inputSchema: toneSchema("darken"),
10383
+ annotations: { title: "Burn stroke", ...WRITE5 },
10384
+ handler: (args) => stroke("burn", args, pick(args, ["range", "exposure", "protectTones"]))
10385
+ };
10386
+ const spongeTool = {
10387
+ name: "sponge",
10388
+ title: "Sponge stroke",
10389
+ description: "Saturates or desaturates the pixels a stroke covers (Photoshop's Sponge); passes compound, and brush.flow sets how much each moves. Pixels only (a mask has no colour). One undo step. Returns the layerId, channel, changed bounds and dab count.",
10390
+ inputSchema: {
10391
+ type: "object",
10392
+ properties: {
10393
+ points: POINTS,
10394
+ mode: { type: "string", enum: ["desaturate", "saturate"], description: "desaturate (default the current option, desaturate) or saturate." },
10395
+ vibrance: { type: "boolean", description: "Damp the change where colours would clip or collapse to grey (default true)." },
10396
+ brush: BRUSH2,
10397
+ layerId: TARGET2.layerId
10398
+ },
10399
+ required: ["points"],
10400
+ additionalProperties: false
10401
+ },
10402
+ annotations: { title: "Sponge stroke", ...WRITE5 },
10403
+ handler: (args) => stroke("sponge", { ...args, channel: "pixels" }, { ...pick(args, ["vibrance"]), ...args.mode !== void 0 ? { spongeMode: args.mode } : {} })
10404
+ };
10405
+ const getOptions = {
10406
+ name: "get_retouch_options",
10407
+ title: "Get retouch options",
10408
+ description: "Reads the retouching tools' options (aligned, sample, spotType, range, exposure, protectTones, spongeMode, vibrance, strength) and the clone source ({x, y, offset} or null; offset is the aligned offset the last clone or heal stroke fixed). Call it before clone_stamp or heal_brush without a source to see where they will sample.",
10409
+ inputSchema: { type: "object", properties: {}, additionalProperties: false },
10410
+ annotations: { title: "Get retouch options", ...READ6 },
10411
+ handler: () => {
10412
+ const { retouch, cloneSource } = controller.getState().paint;
10413
+ return { options: { ...retouch }, cloneSource: cloneSource ? { ...cloneSource } : null };
10414
+ }
10415
+ };
10416
+ const setOptions = {
10417
+ name: "set_retouch_options",
10418
+ title: "Set retouch options",
10419
+ description: "Sets the retouching tools' defaults (view state, not an undo step); each stroke tool can also override them per call. Changing aligned clears the aligned offset. Returns the options after the change.",
10420
+ inputSchema: {
10421
+ type: "object",
10422
+ properties: {
10423
+ aligned: { type: "boolean", description: "Clone / heal: keep the offset from stroke to stroke (default true)." },
10424
+ sample: SAMPLE,
10425
+ spotType: { type: "string", enum: [...SPOT_HEAL_TYPES], description: "Spot heal: proximity-match (default) or smooth." },
10426
+ range: { type: "string", enum: [...TONE_RANGES], description: "Dodge / burn: shadows, midtones (default) or highlights." },
10427
+ exposure: { type: "number", minimum: 0, maximum: 1, description: "Dodge / burn strength 0..1 (default 0.5)." },
10428
+ protectTones: { type: "boolean", description: "Dodge / burn: keep hue and saturation (default true)." },
10429
+ spongeMode: { type: "string", enum: ["desaturate", "saturate"], description: "Sponge: desaturate (default) or saturate." },
10430
+ vibrance: { type: "boolean", description: "Sponge: damp clipping (default true)." },
10431
+ strength: { type: "number", minimum: 0, maximum: 1, description: "Smudge strength 0..1 (default 0.5)." }
10432
+ },
10433
+ additionalProperties: false
10434
+ },
10435
+ annotations: { title: "Set retouch options", ...WRITE5, idempotentHint: true },
10436
+ handler: (args) => ({ ...controller.setPaintOptions({ retouch: args }).retouch })
10437
+ };
10438
+ const setSource = {
10439
+ name: "set_clone_source",
10440
+ title: "Set the clone source",
10441
+ description: "Sets the Clone Stamp and Healing Brush source point (Photoshop's Alt-click), in document pixels, or clears it with clear true. The next clone_stamp or heal_brush stroke fixes the offset from its first point. View state, not an undo step. Returns the clone source {x, y, offset} (or null).",
10442
+ inputSchema: {
10443
+ type: "object",
10444
+ properties: {
10445
+ x: { type: "number", description: "Source x in px (required unless clear)." },
10446
+ y: { type: "number", description: "Source y in px (required unless clear)." },
10447
+ clear: { type: "boolean", description: "Remove the source point instead (default false)." }
10448
+ },
10449
+ additionalProperties: false
10450
+ },
10451
+ annotations: { title: "Set the clone source", ...WRITE5, idempotentHint: true },
10452
+ handler: (args) => {
10453
+ if (args.clear === true) {
10454
+ if (args.x !== void 0 || args.y !== void 0) throw new LayerOperationError("Give either clear true or x and y, not both.");
10455
+ return controller.setCloneSource(null);
10456
+ }
10457
+ if (typeof args.x !== "number" || typeof args.y !== "number") throw new LayerOperationError("Pass x and y (document pixels) for the source point, or clear true to remove it.");
10458
+ return controller.setCloneSource(checkSource(controller, { x: args.x, y: args.y }));
10459
+ }
10460
+ };
10461
+ return [cloneStamp, healBrush, spotHeal, smudge, dodge, burn, spongeTool, getOptions, setOptions, setSource];
10462
+ }
10463
+ function pick(args, keys) {
10464
+ const out = {};
10465
+ for (const k of keys) if (args[k] !== void 0) out[k] = args[k];
10466
+ return out;
10467
+ }
10468
+
10469
+ // src/image-editor/psd-tools.ts
10470
+ var READ7 = { readOnlyHint: true, destructiveHint: false, idempotentHint: true, openWorldHint: false };
9496
10471
  var DESTRUCTIVE4 = { readOnlyHint: false, destructiveHint: true, idempotentHint: false, openWorldHint: false };
9497
10472
  var DATA = {
9498
10473
  type: "string",
@@ -9513,7 +10488,7 @@ function createImagePsdTools(controller) {
9513
10488
  title: "Inspect a PSD",
9514
10489
  description: "Reads a PSD / PSB without opening it: size, bit depth, colour mode, resolution, and every layer (bottom first, a group before its children) with its PSD kind, the layer type it would become and its status (mapped, approximated or dropped, with notes), plus the import warnings. Changes nothing. Call before import_psd to see what would be lost.",
9515
10490
  inputSchema: { type: "object", properties: { data: DATA }, required: ["data"], additionalProperties: false },
9516
- annotations: { title: "Inspect a PSD", ...READ5 },
10491
+ annotations: { title: "Inspect a PSD", ...READ7 },
9517
10492
  handler: ({ data }) => {
9518
10493
  const res = importPsd(psdBytesFromBase64(data), { skipPixels: true });
9519
10494
  return {
@@ -9548,9 +10523,9 @@ function createImagePsdTools(controller) {
9548
10523
  const save = {
9549
10524
  name: "export_psd",
9550
10525
  title: "Export the document as a PSD",
9551
- description: "Writes the document as an 8-bit RGB PSD (a PSB above 30000 px) with real layers, groups, adjustment layers, masks and layer effects (an effects block opened from a PSD is written back unchanged while its effects are; the patterns effects use are embedded), in the source frame (crop and rotation are not applied). Smart objects are written as Photoshop smart objects (contents embedded once per linked set, placement, warp, smart filters and filter mask; a Develop smart filter has no Photoshop filter and is left out of the list). Shape layers are written as Photoshop shape layers (path, fill, stroke, live shapes) and type layers as Photoshop type layers (text, runs, paragraphs, frame; blocks opened from a PSD are written back unchanged while the layer is). Layer filters and depth masks are baked, annotation layers are rasterized (in a browser; elsewhere left out), and the merged image is the editor's composite. Changes nothing. Returns the file as base64, its size in bytes, whether it is a PSB, and warnings for everything that was changed or left out.",
10526
+ description: "Writes the document as an RGB PSD at its bit depth (8, 16 or 32; see set_bit_depth; a PSB above 30000 px) with real layers, groups, adjustment layers, masks and layer effects (an effects block opened from a PSD is written back unchanged while its effects are; the patterns effects use are embedded), in the source frame (crop and rotation are not applied). Smart objects are written as Photoshop smart objects (contents embedded once per linked set, placement, warp, smart filters and filter mask; a Develop smart filter has no Photoshop filter and is left out of the list). Shape layers are written as Photoshop shape layers (path, fill, stroke, live shapes) and type layers as Photoshop type layers (text, runs, paragraphs, frame; blocks opened from a PSD are written back unchanged while the layer is). Layer filters and depth masks are baked, annotation layers are rasterized (in a browser; elsewhere left out), and the merged image is the editor's composite. Changes nothing. Returns the file as base64, its size in bytes, whether it is a PSB, and warnings for everything that was changed or left out.",
9552
10527
  inputSchema: { type: "object", properties: {}, additionalProperties: false },
9553
- annotations: { title: "Export the document as a PSD", ...READ5 },
10528
+ annotations: { title: "Export the document as a PSD", ...READ7 },
9554
10529
  handler: async () => {
9555
10530
  const res = await controller.toPsd();
9556
10531
  return {
@@ -9568,8 +10543,8 @@ function createImagePsdTools(controller) {
9568
10543
  }
9569
10544
 
9570
10545
  // src/image-editor/transform-tools.ts
9571
- var READ6 = { readOnlyHint: true, destructiveHint: false, idempotentHint: true, openWorldHint: false };
9572
- var WRITE5 = { readOnlyHint: false, destructiveHint: false, idempotentHint: false, openWorldHint: false };
10546
+ var READ8 = { readOnlyHint: true, destructiveHint: false, idempotentHint: true, openWorldHint: false };
10547
+ var WRITE6 = { readOnlyHint: false, destructiveHint: false, idempotentHint: false, openWorldHint: false };
9573
10548
  var round4 = (n, digits = 3) => Math.round(n * 10 ** digits) / 10 ** digits;
9574
10549
  var roundPoint = (p) => ({ x: round4(p.x), y: round4(p.y) });
9575
10550
  var POINT = {
@@ -9578,7 +10553,7 @@ var POINT = {
9578
10553
  required: ["x", "y"],
9579
10554
  additionalProperties: false
9580
10555
  };
9581
- var TARGET2 = {
10556
+ var TARGET3 = {
9582
10557
  layerId: { type: "string", description: "Layer to transform (from list_layers; default the active layer)." },
9583
10558
  content: {
9584
10559
  type: "string",
@@ -9660,15 +10635,15 @@ function createImageTransformTools(controller) {
9660
10635
  inputSchema: {
9661
10636
  type: "object",
9662
10637
  properties: {
9663
- layerId: TARGET2.layerId,
9664
- content: { type: "string", enum: FREE_TRANSFORM_CONTENTS, description: `${TARGET2.content.description} selection-outline frames the selection outline alone.` },
9665
- channel: TARGET2.channel,
10638
+ layerId: TARGET3.layerId,
10639
+ content: { type: "string", enum: FREE_TRANSFORM_CONTENTS, description: `${TARGET3.content.description} selection-outline frames the selection outline alone.` },
10640
+ channel: TARGET3.channel,
9666
10641
  meshColumns: { type: "integer", minimum: 1, maximum: 16, description: "Patch columns of an identity warp mesh to return (omit for none)." },
9667
10642
  meshRows: { type: "integer", minimum: 1, maximum: 16, description: "Patch rows of an identity warp mesh to return (default meshColumns)." }
9668
10643
  },
9669
10644
  additionalProperties: false
9670
10645
  },
9671
- annotations: { title: "Get the Free Transform frame", ...READ6 },
10646
+ annotations: { title: "Get the Free Transform frame", ...READ8 },
9672
10647
  handler: (args) => {
9673
10648
  const init = targetFrom(args);
9674
10649
  if (init.content === "selection-copy") init.content = "selection";
@@ -9698,10 +10673,10 @@ function createImageTransformTools(controller) {
9698
10673
  description: "Edit \u203A Free Transform: scales, rotates, skews, moves, distorts or puts in perspective a pixel layer (with its linked mask), the selected pixels, a copy of them on a new layer, or an unlinked mask. Give the transform one way: the numeric fields (as Photoshop\u2019s options bar, about a reference point), a matrix, or the frame\u2019s new corners (distort / perspective; read the frame with get_transform_frame). Resamples once, tile by tile; one undo step. Returns layerId (the new layer for selection-copy), what moved, the frame, the corners and the new document bounds.",
9699
10674
  inputSchema: {
9700
10675
  type: "object",
9701
- properties: { ...TARGET2, ...GEOMETRY },
10676
+ properties: { ...TARGET3, ...GEOMETRY },
9702
10677
  additionalProperties: false
9703
10678
  },
9704
- annotations: { title: "Free Transform a layer", ...WRITE5 },
10679
+ annotations: { title: "Free Transform a layer", ...WRITE6 },
9705
10680
  handler: (args) => summary2(controller.transformLayer({ ...targetFrom(args), ...geometryFrom(args) }))
9706
10681
  };
9707
10682
  const warpLayer = {
@@ -9711,7 +10686,7 @@ function createImageTransformTools(controller) {
9711
10686
  inputSchema: {
9712
10687
  type: "object",
9713
10688
  properties: {
9714
- ...TARGET2,
10689
+ ...TARGET3,
9715
10690
  style: { type: "string", enum: WARP_STYLES, description: "Preset warp (Photoshop\u2019s menu): arc, arcLower, arcUpper, arch, bulge, shellLower, shellUpper, flag, wave, fish, rise, fisheye, inflate, squeeze, twist." },
9716
10691
  bend: { type: "number", minimum: -100, maximum: 100, description: "Preset bend in percent, \u2212100..100 (default 50)." },
9717
10692
  horizontal: { type: "number", minimum: -100, maximum: 100, description: "Preset horizontal distortion in percent, \u2212100..100 (default 0)." },
@@ -9731,7 +10706,7 @@ function createImageTransformTools(controller) {
9731
10706
  },
9732
10707
  additionalProperties: false
9733
10708
  },
9734
- annotations: { title: "Warp a layer", ...WRITE5 },
10709
+ annotations: { title: "Warp a layer", ...WRITE6 },
9735
10710
  handler: (args) => {
9736
10711
  const hasPreset = ["style", "bend", "horizontal", "vertical", "orientation"].some((k) => args[k] !== void 0);
9737
10712
  if (hasPreset && args.mesh !== void 0) throw new LayerOperationError("Give a preset (style, bend, horizontal, vertical, orientation) or a mesh, not both.");
@@ -9762,10 +10737,10 @@ function createImageTransformTools(controller) {
9762
10737
  description: "Select \u203A Transform Selection: scales, rotates, skews, moves or distorts only the selection outline; no pixels change. Give the transform one way: the numeric fields (about a reference point of the selection\u2019s bounds), a matrix, or the new corners of its bounds. Needs a selection. One undo step. Returns the new selection bounds.",
9763
10738
  inputSchema: {
9764
10739
  type: "object",
9765
- properties: { interpolation: { ...TARGET2.interpolation, description: "Resampling of soft edges: nearest, bilinear or bicubic (default)." }, ...GEOMETRY },
10740
+ properties: { interpolation: { ...TARGET3.interpolation, description: "Resampling of soft edges: nearest, bilinear or bicubic (default)." }, ...GEOMETRY },
9766
10741
  additionalProperties: false
9767
10742
  },
9768
- annotations: { title: "Transform the selection outline", ...WRITE5 },
10743
+ annotations: { title: "Transform the selection outline", ...WRITE6 },
9769
10744
  handler: (args) => {
9770
10745
  const res = controller.transformLayer({ content: "selection-outline", interpolation: args.interpolation, ...geometryFrom(args) });
9771
10746
  return { changed: res.changed, frame: { ...res.box }, corners: res.corners.map(roundPoint), bounds: res.bounds };
@@ -9779,14 +10754,14 @@ function createImageTransformTools(controller) {
9779
10754
  type: "object",
9780
10755
  properties: {
9781
10756
  axis: { type: "string", enum: ["horizontal", "vertical"], description: "horizontal mirrors left \u2194 right, vertical top \u2194 bottom." },
9782
- layerId: TARGET2.layerId,
9783
- content: TARGET2.content,
9784
- channel: TARGET2.channel
10757
+ layerId: TARGET3.layerId,
10758
+ content: TARGET3.content,
10759
+ channel: TARGET3.channel
9785
10760
  },
9786
10761
  required: ["axis"],
9787
10762
  additionalProperties: false
9788
10763
  },
9789
- annotations: { title: "Flip a layer", ...WRITE5 },
10764
+ annotations: { title: "Flip a layer", ...WRITE6 },
9790
10765
  handler: (args) => summary2(controller.flipLayer(args.axis, targetFrom(args)))
9791
10766
  };
9792
10767
  const rotate90 = {
@@ -9797,22 +10772,22 @@ function createImageTransformTools(controller) {
9797
10772
  type: "object",
9798
10773
  properties: {
9799
10774
  turn: { type: "integer", enum: [90, -90, 180], description: "Degrees: 90 clockwise, -90 counter-clockwise, or 180." },
9800
- layerId: TARGET2.layerId,
9801
- content: TARGET2.content,
9802
- channel: TARGET2.channel
10775
+ layerId: TARGET3.layerId,
10776
+ content: TARGET3.content,
10777
+ channel: TARGET3.channel
9803
10778
  },
9804
10779
  required: ["turn"],
9805
10780
  additionalProperties: false
9806
10781
  },
9807
- annotations: { title: "Rotate a layer by quarter turns", ...WRITE5 },
10782
+ annotations: { title: "Rotate a layer by quarter turns", ...WRITE6 },
9808
10783
  handler: (args) => summary2(controller.rotateLayer90(args.turn, targetFrom(args)))
9809
10784
  };
9810
10785
  return [getFrame, transformLayer, warpLayer, transformSelection, flipLayer, rotate90];
9811
10786
  }
9812
10787
 
9813
10788
  // src/image-editor/smart-tools.ts
9814
- var READ7 = { readOnlyHint: true, destructiveHint: false, idempotentHint: true, openWorldHint: false };
9815
- var WRITE6 = { readOnlyHint: false, destructiveHint: false, idempotentHint: false, openWorldHint: false };
10789
+ var READ9 = { readOnlyHint: true, destructiveHint: false, idempotentHint: true, openWorldHint: false };
10790
+ var WRITE7 = { readOnlyHint: false, destructiveHint: false, idempotentHint: false, openWorldHint: false };
9816
10791
  var WRITE_IDEMPOTENT4 = { readOnlyHint: false, destructiveHint: false, idempotentHint: true, openWorldHint: false };
9817
10792
  var DESTRUCTIVE5 = { readOnlyHint: false, destructiveHint: true, idempotentHint: false, openWorldHint: false };
9818
10793
  var round5 = (n, digits = 3) => Math.round(n * 10 ** digits) / 10 ** digits;
@@ -9870,7 +10845,7 @@ function createImageSmartObjectTools(controller) {
9870
10845
  title: "Read a smart object",
9871
10846
  description: "Returns a smart object\u2019s contents (id shared by linked instances, name, kind raster or document, pixel size, the file type it came from, the instances placed from it), its placement (corners in document pixels, warp mesh in contents pixels, interpolation, and scale / rotate / skew in percent and degrees of the contents\u2019 own size when affine), its smart filters bottom to top (ids, kind, settings, enabled, blend, opacity; kind psd marks a Photoshop filter kept but not drawn) with the filter mask, and the rendered bounds. Changes nothing.",
9872
10847
  inputSchema: { type: "object", properties: { layerId: LAYER_ID4 }, required: ["layerId"], additionalProperties: false },
9873
- annotations: { title: "Read a smart object", ...READ7 },
10848
+ annotations: { title: "Read a smart object", ...READ9 },
9874
10849
  handler: ({ layerId }) => info(smartId(layerId))
9875
10850
  };
9876
10851
  const convert = {
@@ -9885,7 +10860,7 @@ function createImageSmartObjectTools(controller) {
9885
10860
  },
9886
10861
  additionalProperties: false
9887
10862
  },
9888
- annotations: { title: "Convert to Smart Object", ...WRITE6 },
10863
+ annotations: { title: "Convert to Smart Object", ...WRITE7 },
9889
10864
  handler: ({ layerIds, name }) => {
9890
10865
  for (const id2 of layerIds ?? []) requireLayer(controller.getLayers(), id2);
9891
10866
  const id = controller.convertToSmartObject(layerIds, { name });
@@ -10004,7 +10979,7 @@ function createImageSmartObjectTools(controller) {
10004
10979
  title: "Export a smart object\u2019s contents",
10005
10980
  description: "Layer \u203A Smart Objects \u203A Export Contents: the contents as a file, base64: the file they came from while unedited (a PSB, PNG, JPEG\u2026), else a PNG for raster contents or a PSD / PSB of a nested document. Returns name, mimeType, byteLength and base64. Changes nothing.",
10006
10981
  inputSchema: { type: "object", properties: { layerId: LAYER_ID4 }, required: ["layerId"], additionalProperties: false },
10007
- annotations: { title: "Export a smart object\u2019s contents", ...READ7 },
10982
+ annotations: { title: "Export a smart object\u2019s contents", ...READ9 },
10008
10983
  handler: ({ layerId }) => {
10009
10984
  const f = controller.exportSmartObjectContents(smartId(layerId));
10010
10985
  return { name: f.name, mimeType: f.mimeType, byteLength: f.bytes.length, base64: bytesToBase64(f.bytes) };
@@ -10024,7 +10999,7 @@ function createImageSmartObjectTools(controller) {
10024
10999
  required: ["layerId"],
10025
11000
  additionalProperties: false
10026
11001
  },
10027
- annotations: { title: "Copy a smart object", ...WRITE6 },
11002
+ annotations: { title: "Copy a smart object", ...WRITE7 },
10028
11003
  handler: ({ layerId, linked, name }) => {
10029
11004
  const id = controller.copySmartObject(smartId(layerId), linked === true, name);
10030
11005
  return { layer: layerSummary(id), contentsId: controller.getSmartObject(id).contents.id };
@@ -10035,7 +11010,7 @@ function createImageSmartObjectTools(controller) {
10035
11010
  title: "Edit a smart object\u2019s contents",
10036
11011
  description: 'Layer \u203A Smart Objects \u203A Edit Contents: opens the contents as a document of their own (a layer document, or raster contents as one pixel layer "Layer 0") with their own undo history. Every other tool (list_layers, paint, adjustments\u2026) then works inside the contents until close_smart_object_contents saves them back. Contents can nest. Returns the contents\u2019 size and layers.',
10037
11012
  inputSchema: { type: "object", properties: { layerId: LAYER_ID4 }, required: ["layerId"], additionalProperties: false },
10038
- annotations: { title: "Edit a smart object\u2019s contents", ...WRITE6 },
11013
+ annotations: { title: "Edit a smart object\u2019s contents", ...WRITE7 },
10039
11014
  handler: ({ layerId }) => {
10040
11015
  const res = controller.editSmartObjectContents(smartId(layerId));
10041
11016
  const s = controller.getState();
@@ -10051,7 +11026,7 @@ function createImageSmartObjectTools(controller) {
10051
11026
  properties: { save: { type: "boolean", description: "true (default) saves the edits into the smart object; false discards them." } },
10052
11027
  additionalProperties: false
10053
11028
  },
10054
- annotations: { title: "Close a smart object\u2019s contents", ...WRITE6 },
11029
+ annotations: { title: "Close a smart object\u2019s contents", ...WRITE7 },
10055
11030
  handler: ({ save }) => ({ ...controller.closeSmartObjectContents(save !== false), editing: controller.getEditingContents() })
10056
11031
  };
10057
11032
  const addFilter = {
@@ -10069,7 +11044,7 @@ function createImageSmartObjectTools(controller) {
10069
11044
  required: ["layerId", "kind"],
10070
11045
  additionalProperties: false
10071
11046
  },
10072
- annotations: { title: "Add a smart filter", ...WRITE6 },
11047
+ annotations: { title: "Add a smart filter", ...WRITE7 },
10073
11048
  handler: (args) => {
10074
11049
  const id = smartId(args.layerId);
10075
11050
  const filter = controller.addSmartFilter(id, { kind: args.kind, ...settingsFrom(args) }, args.index);
@@ -10158,7 +11133,7 @@ function createImageSmartObjectTools(controller) {
10158
11133
  required: ["layerId", "from"],
10159
11134
  additionalProperties: false
10160
11135
  },
10161
- annotations: { title: "Set the smart filter mask", ...WRITE6 },
11136
+ annotations: { title: "Set the smart filter mask", ...WRITE7 },
10162
11137
  handler: (args) => {
10163
11138
  const id = smartId(args.layerId);
10164
11139
  const props = {};
@@ -10171,8 +11146,8 @@ function createImageSmartObjectTools(controller) {
10171
11146
  }
10172
11147
 
10173
11148
  // src/image-editor/shape-tools.ts
10174
- var READ8 = { readOnlyHint: true, destructiveHint: false, idempotentHint: true, openWorldHint: false };
10175
- var WRITE7 = { readOnlyHint: false, destructiveHint: false, idempotentHint: false, openWorldHint: false };
11149
+ var READ10 = { readOnlyHint: true, destructiveHint: false, idempotentHint: true, openWorldHint: false };
11150
+ var WRITE8 = { readOnlyHint: false, destructiveHint: false, idempotentHint: false, openWorldHint: false };
10176
11151
  var WRITE_IDEMPOTENT5 = { readOnlyHint: false, destructiveHint: false, idempotentHint: true, openWorldHint: false };
10177
11152
  var DESTRUCTIVE6 = { readOnlyHint: false, destructiveHint: true, idempotentHint: false, openWorldHint: false };
10178
11153
  var LAYER_ID5 = { type: "string", description: "Shape layer id from list_layers (type shape) or add_shape_layer." };
@@ -10425,7 +11400,7 @@ function createImageShapeTools(controller) {
10425
11400
  title: "Read a shape layer",
10426
11401
  description: "Returns a shape layer\u2019s components bottom to top (index, path operation, the live shape it was drawn as or null, its subpaths with absolute anchors and handles, bounds), the fill rule, fill and stroke paints and settings, the path bounds and the rendered bounds (stroke included). Changes nothing.",
10427
11402
  inputSchema: { type: "object", properties: { layerId: LAYER_ID5 }, required: ["layerId"], additionalProperties: false },
10428
- annotations: { title: "Read a shape layer", ...READ8 },
11403
+ annotations: { title: "Read a shape layer", ...READ10 },
10429
11404
  handler: ({ layerId }) => controller.getShapeLayer(shapeId(layerId))
10430
11405
  };
10431
11406
  const add2 = {
@@ -10456,7 +11431,7 @@ function createImageShapeTools(controller) {
10456
11431
  },
10457
11432
  additionalProperties: false
10458
11433
  },
10459
- annotations: { title: "Add a shape layer", ...WRITE7 },
11434
+ annotations: { title: "Add a shape layer", ...WRITE8 },
10460
11435
  handler: (args) => {
10461
11436
  const init = { ...geometry(args) };
10462
11437
  if (args.components !== void 0) init.components = args.components.map(geometry);
@@ -10551,7 +11526,7 @@ function createImageShapeTools(controller) {
10551
11526
  required: ["layerId"],
10552
11527
  additionalProperties: false
10553
11528
  },
10554
- annotations: { title: "Add a shape component", ...WRITE7 },
11529
+ annotations: { title: "Add a shape component", ...WRITE8 },
10555
11530
  handler: ({ layerId, index, ...rest }) => controller.addShapeComponent(shapeId(layerId), geometry(rest), index)
10556
11531
  };
10557
11532
  const removeComponent = {
@@ -10574,8 +11549,8 @@ function createImageShapeTools(controller) {
10574
11549
  }
10575
11550
 
10576
11551
  // src/image-editor/type-tools.ts
10577
- var READ9 = { readOnlyHint: true, destructiveHint: false, idempotentHint: true, openWorldHint: false };
10578
- var WRITE8 = { readOnlyHint: false, destructiveHint: false, idempotentHint: false, openWorldHint: false };
11552
+ var READ11 = { readOnlyHint: true, destructiveHint: false, idempotentHint: true, openWorldHint: false };
11553
+ var WRITE9 = { readOnlyHint: false, destructiveHint: false, idempotentHint: false, openWorldHint: false };
10579
11554
  var WRITE_IDEMPOTENT6 = { readOnlyHint: false, destructiveHint: false, idempotentHint: true, openWorldHint: false };
10580
11555
  var DESTRUCTIVE7 = { readOnlyHint: false, destructiveHint: true, idempotentHint: false, openWorldHint: false };
10581
11556
  var LAYER_ID6 = { type: "string", description: "Type layer id from list_layers (type type) or add_text_layer." };
@@ -10603,7 +11578,7 @@ var PARAGRAPH = {
10603
11578
  spaceBefore: { type: ["number", "null"], description: "Space above the paragraph in pixels (not before the first)." },
10604
11579
  spaceAfter: { type: ["number", "null"], description: "Space below the paragraph in pixels." }
10605
11580
  };
10606
- function pick(args, keys) {
11581
+ function pick2(args, keys) {
10607
11582
  const out = {};
10608
11583
  for (const k of keys) if (args[k] !== void 0) out[k] = args[k];
10609
11584
  return out;
@@ -10625,7 +11600,7 @@ function createImageTypeTools(controller) {
10625
11600
  title: "List fonts",
10626
11601
  description: "Returns the font families type layers can use here (generic families such as sans-serif always work). Changes nothing.",
10627
11602
  inputSchema: { type: "object", properties: {}, additionalProperties: false },
10628
- annotations: { title: "List fonts", ...READ9 },
11603
+ annotations: { title: "List fonts", ...READ11 },
10629
11604
  handler: () => ({ families: controller.listFonts(), shaper: controller.getTextShaper()?.id ?? null })
10630
11605
  };
10631
11606
  const get = {
@@ -10633,7 +11608,7 @@ function createImageTypeTools(controller) {
10633
11608
  title: "Read a type layer",
10634
11609
  description: "Returns a type layer\u2019s text, its character runs and paragraphs as [start, end) ranges in UTF-16 code units with their styles, the frame (point text, or a paragraph box with its size), the text-to-document transform, the laid-out lines (range, baseline, width), the rendered bounds, missing fonts and whether the pixels are live or stored from a file. Call before editing text you did not write, to get the ranges. Changes nothing.",
10635
11610
  inputSchema: { type: "object", properties: { layerId: LAYER_ID6 }, required: ["layerId"], additionalProperties: false },
10636
- annotations: { title: "Read a type layer", ...READ9 },
11611
+ annotations: { title: "Read a type layer", ...READ11 },
10637
11612
  handler: ({ layerId }) => controller.getTextLayer(typeId(layerId))
10638
11613
  };
10639
11614
  const add2 = {
@@ -10667,9 +11642,9 @@ function createImageTypeTools(controller) {
10667
11642
  required: ["text", "x", "y"],
10668
11643
  additionalProperties: false
10669
11644
  },
10670
- annotations: { title: "Add a type layer", ...WRITE8 },
11645
+ annotations: { title: "Add a type layer", ...WRITE9 },
10671
11646
  handler: (args) => {
10672
- const init = pick(args, ["text", "x", "y", "box", "style", "paragraph", "rotation", "name", "opacity", "blend", "aboveId", "belowId", "parentId", "index"]);
11647
+ const init = pick2(args, ["text", "x", "y", "box", "style", "paragraph", "rotation", "name", "opacity", "blend", "aboveId", "belowId", "parentId", "index"]);
10673
11648
  if (init.blend !== void 0) init.blend = init.blend;
10674
11649
  const id = controller.addTextLayer(init);
10675
11650
  return { layer: layerSummary(id), text: controller.getTextLayer(id) };
@@ -10680,7 +11655,7 @@ function createImageTypeTools(controller) {
10680
11655
  title: "Edit text",
10681
11656
  description: "Replaces the text in [start, end) (default all of it) with text; inserted text takes the style of the character at start. Use start = end to insert. Returns the text layer with its new runs.",
10682
11657
  inputSchema: { type: "object", properties: { layerId: LAYER_ID6, text: { type: "string", description: "Replacement text (\\n starts a paragraph; empty deletes the range)." }, start: START, end: END }, required: ["layerId", "text"], additionalProperties: false },
10683
- annotations: { title: "Edit text", ...WRITE8 },
11658
+ annotations: { title: "Edit text", ...WRITE9 },
10684
11659
  handler: (args) => controller.setText(typeId(args.layerId), args.text, range(args))
10685
11660
  };
10686
11661
  const setStyle = {
@@ -10690,7 +11665,7 @@ function createImageTypeTools(controller) {
10690
11665
  inputSchema: { type: "object", properties: { layerId: LAYER_ID6, start: START, end: END, ...STYLE }, required: ["layerId"], additionalProperties: false },
10691
11666
  annotations: { title: "Style a range of text", ...WRITE_IDEMPOTENT6 },
10692
11667
  handler: (args) => {
10693
- const style = pick(args, Object.keys(STYLE));
11668
+ const style = pick2(args, Object.keys(STYLE));
10694
11669
  if (!Object.keys(style).length) throw new LayerOperationError(`Give at least one style field: ${Object.keys(STYLE).join(", ")}.`);
10695
11670
  return controller.setTextStyle(typeId(args.layerId), style, range(args));
10696
11671
  }
@@ -10702,7 +11677,7 @@ function createImageTypeTools(controller) {
10702
11677
  inputSchema: { type: "object", properties: { layerId: LAYER_ID6, start: START, end: END, ...PARAGRAPH }, required: ["layerId"], additionalProperties: false },
10703
11678
  annotations: { title: "Style paragraphs", ...WRITE_IDEMPOTENT6 },
10704
11679
  handler: (args) => {
10705
- const style = pick(args, Object.keys(PARAGRAPH));
11680
+ const style = pick2(args, Object.keys(PARAGRAPH));
10706
11681
  if (!Object.keys(style).length) throw new LayerOperationError(`Give at least one paragraph field: ${Object.keys(PARAGRAPH).join(", ")}.`);
10707
11682
  return controller.setParagraphStyle(typeId(args.layerId), style, range(args));
10708
11683
  }
@@ -10986,6 +11961,8 @@ function createImageEditorToolProvider(controller, options = {}) {
10986
11961
  ...createImageTypeTools(controller),
10987
11962
  ...createImageEffectTools(controller),
10988
11963
  ...createImagePaintTools(controller),
11964
+ ...createImageColorTools(controller),
11965
+ ...createImageRetouchTools(controller),
10989
11966
  ...createPigmentTools(),
10990
11967
  ...createImagePsdTools(controller)
10991
11968
  ]
@@ -11980,6 +12957,11 @@ function extractNormalizedLayerItems(controller, fallbackImageUrl = "") {
11980
12957
  };
11981
12958
  });
11982
12959
  }
12960
+ var BIT_DEPTH_OPTIONS = [
12961
+ { depth: 8, label: "8-bit", title: "8 bits per channel" },
12962
+ { depth: 16, label: "16-bit", title: "16 bits per channel: smooth gradients, no banding under strong adjustments" },
12963
+ { depth: 32, label: "32-bit", title: "32-bit float per channel, linear light (HDR)" }
12964
+ ];
11983
12965
 
11984
12966
  // src/image-editor/layers/effects-ui.ts
11985
12967
  function layerEffectsPanel(layer) {
@@ -12154,6 +13136,25 @@ function effectControlChange(effect, key, value, patterns = []) {
12154
13136
  return { [top]: setPath(getPath(effect, top), rest, value) };
12155
13137
  }
12156
13138
 
13139
+ // src/image-editor/layers/color-ui.ts
13140
+ var WORKING_SPACE_OPTIONS = BUILTIN_PROFILES.filter((b) => b.colorSpace === "rgb").map((b) => ({ id: b.id, label: b.description }));
13141
+ var PROOF_PROFILE_OPTIONS = BUILTIN_PROFILES.map((b) => ({ id: b.id, label: b.description }));
13142
+ var INTENT_OPTIONS = [
13143
+ { id: "relative", label: "Relative Colorimetric", title: "Keep in-gamut colours exact, clip the rest (photos)" },
13144
+ { id: "perceptual", label: "Perceptual", title: "Compress the whole gamut smoothly" },
13145
+ { id: "saturation", label: "Saturation", title: "Keep colours vivid (graphics)" },
13146
+ { id: "absolute", label: "Absolute Colorimetric", title: "Keep the source white too (paper proofs)" }
13147
+ ];
13148
+ function colorPanelView(controller) {
13149
+ const profile = controller.getColorProfile();
13150
+ const proof = controller.getState().proofSetup ?? null;
13151
+ return {
13152
+ profileName: profile.name,
13153
+ workingSpace: profile.builtin,
13154
+ proof: proof ? { id: proof.profile.builtin ?? null, name: proof.profile.name, intent: proof.intent, simulatePaper: proof.simulatePaper, gamutWarning: proof.gamutWarning, bpc: proof.bpc } : null
13155
+ };
13156
+ }
13157
+
12157
13158
  // src/image-editor/psd/ui.ts
12158
13159
  var PSD_FILE_ACCEPT = ".psd,.psb,image/vnd.adobe.photoshop,application/x-photoshop";
12159
13160
  function blobBytes(file) {
@@ -12766,9 +13767,12 @@ export {
12766
13767
  AS_SHOT_KELVIN,
12767
13768
  BACKGROUND_LAYER_ID,
12768
13769
  BACKGROUND_LAYER_NAME,
13770
+ BIT_DEPTHS,
13771
+ BIT_DEPTH_OPTIONS,
12769
13772
  BLEND_MODES,
12770
13773
  BLUR_FILTER_ID,
12771
13774
  BRUSH_OPTION_CONTROLS,
13775
+ BUILTIN_PROFILES,
12772
13776
  CoverageMap,
12773
13777
  D50,
12774
13778
  D65,
@@ -12780,7 +13784,9 @@ export {
12780
13784
  DEFAULT_GLOBAL_LIGHT,
12781
13785
  DEFAULT_MITER_LIMIT,
12782
13786
  DEFAULT_MIXER,
13787
+ DEFAULT_PROOF,
12783
13788
  DEFAULT_REFINE_EDGE_PARAMS,
13789
+ DEFAULT_RETOUCH,
12784
13790
  DEFAULT_SELECTION_TOOL_OPTIONS,
12785
13791
  DEFAULT_SHAPE_TOOL_OPTIONS,
12786
13792
  DEFAULT_SMUDGE,
@@ -12808,6 +13814,8 @@ export {
12808
13814
  IMAGE_EDITOR_SCHEMA_VERSION,
12809
13815
  IMAGE_EDITOR_STATE_VERSION,
12810
13816
  IMAGE_FILTER_PRESETS,
13817
+ INTENTS,
13818
+ INTENT_OPTIONS,
12811
13819
  INTERPOLATIONS,
12812
13820
  ImageEditorController,
12813
13821
  ImageEditorRenderer,
@@ -12841,6 +13849,7 @@ export {
12841
13849
  PIGMENT_LAMBDA_MIN,
12842
13850
  PIGMENT_LAMBDA_STEP,
12843
13851
  PIGMENT_WAVELENGTHS,
13852
+ PROOF_PROFILE_OPTIONS,
12844
13853
  PSD_BLEND_KEYS,
12845
13854
  PSD_FILE_ACCEPT,
12846
13855
  PaintExchange,
@@ -12850,6 +13859,13 @@ export {
12850
13859
  PatternTile,
12851
13860
  PsdError,
12852
13861
  REFINE_EDGE_CONTROLS,
13862
+ RETOUCH_CHOICES,
13863
+ RETOUCH_OPTION_CONTROLS,
13864
+ RETOUCH_SAMPLES,
13865
+ RETOUCH_TOGGLES,
13866
+ RETOUCH_TOOLS,
13867
+ RegionPixelSource,
13868
+ RetouchDabStroke,
12853
13869
  SELECTION_MODES,
12854
13870
  SELECTION_MODE_LABELS,
12855
13871
  SELECTION_MODIFY_OPS,
@@ -12859,6 +13875,7 @@ export {
12859
13875
  SHAPE_OPERATIONS,
12860
13876
  SHAPE_RENDER_MARGIN,
12861
13877
  SMART_RENDER_MARGIN,
13878
+ SPOT_HEAL_TYPES,
12862
13879
  SUPPORTED_ADJUSTMENT_KEYS,
12863
13880
  SelectionGesture,
12864
13881
  ShapeDrawGesture,
@@ -12872,10 +13889,12 @@ export {
12872
13889
  TILE_SHIFT,
12873
13890
  TINT_SCALE,
12874
13891
  TONE_LUT_SIZE,
13892
+ TONE_RANGES,
12875
13893
  TONE_RANGE_PRESETS,
12876
13894
  TRANSFORM_ANCHORS,
12877
13895
  TextEditSession,
12878
13896
  TileMedium,
13897
+ TilePixelSource,
12879
13898
  TiledSurface,
12880
13899
  TransformTooLargeError,
12881
13900
  TypeToolGesture,
@@ -12884,6 +13903,7 @@ export {
12884
13903
  WARP_STYLES,
12885
13904
  WARP_STYLE_LABELS,
12886
13905
  WARP_STYLE_OPTIONS,
13906
+ WORKING_SPACE_OPTIONS,
12887
13907
  XYZ_D50_TO_SRGB,
12888
13908
  ZlibError,
12889
13909
  activeAdjustmentLayers,
@@ -12905,6 +13925,7 @@ export {
12905
13925
  annotationOfShape,
12906
13926
  annotationOfType,
12907
13927
  applyAdjustmentLayers,
13928
+ applyAdjustmentLayersFloat,
12908
13929
  applyAdjustments,
12909
13930
  applyBoxBlur,
12910
13931
  applyDepthMask,
@@ -12917,12 +13938,14 @@ export {
12917
13938
  applyProcess2Adjustments,
12918
13939
  applyRegionBlur,
12919
13940
  applyRegionMosaic,
13941
+ applyRetouchToTile,
12920
13942
  applyShapeOptions,
12921
13943
  applySmartFilters,
12922
13944
  applyStrokeToTile,
12923
13945
  backgroundLayer,
12924
13946
  base64ToBytes,
12925
13947
  baseProjection,
13948
+ bestOffset,
12926
13949
  blackWhiteGray,
12927
13950
  blendChannel,
12928
13951
  blendImageData,
@@ -12952,12 +13975,15 @@ export {
12952
13975
  clampControlValue,
12953
13976
  clampCropToImage,
12954
13977
  cleanMixer,
13978
+ cloneSourceLabel,
12955
13979
  collectTiles,
12956
13980
  colorBalanceChannel,
13981
+ colorPanelView,
12957
13982
  colorRange,
12958
13983
  combineSelections,
12959
13984
  compileAdjustmentLayer,
12960
13985
  componentPath,
13986
+ compositeDeep,
12961
13987
  compositeSurfaceOver,
12962
13988
  computeHistogram,
12963
13989
  computeSelectionBounds,
@@ -12967,6 +13993,9 @@ export {
12967
13993
  contractCoverage,
12968
13994
  controlPosition,
12969
13995
  controlTrackBackground,
13996
+ convertHex,
13997
+ convertSurfaceColors,
13998
+ convertSurfaceDepth,
12970
13999
  convertToSmartObject,
12971
14000
  copySmartObject,
12972
14001
  cornersFromMatrix,
@@ -12977,17 +14006,20 @@ export {
12977
14006
  createBlenderScratch,
12978
14007
  createCanvasTextShaper,
12979
14008
  createImageCaptureSurface,
14009
+ createImageColorTools,
12980
14010
  createImageEditTools,
12981
14011
  createImageEditorToolProvider,
12982
14012
  createImageEffectTools,
12983
14013
  createImageLayerTools,
12984
14014
  createImagePaintTools,
12985
14015
  createImagePsdTools,
14016
+ createImageRetouchTools,
12986
14017
  createImageSelectionTools,
12987
14018
  createImageShapeTools,
12988
14019
  createImageSmartObjectTools,
12989
14020
  createImageTransformTools,
12990
14021
  createImageTypeTools,
14022
+ createMetricTextShaper,
12991
14023
  createOutlineTextShaper,
12992
14024
  createPigment,
12993
14025
  createPigmentTools,
@@ -13002,6 +14034,7 @@ export {
13002
14034
  curveSvgPath,
13003
14035
  dabCoverage,
13004
14036
  dabRect,
14037
+ decodeDeepSurface,
13005
14038
  decodeLayerChannel,
13006
14039
  decodeLayerMask,
13007
14040
  decodeMergedImage,
@@ -13009,6 +14042,8 @@ export {
13009
14042
  decodePng,
13010
14043
  decodePngDataUrl,
13011
14044
  decodeSelectionRle,
14045
+ deepBytes,
14046
+ deepTile,
13012
14047
  defaultAdjustmentParams,
13013
14048
  defaultEffectGradient,
13014
14049
  defaultLayerEffect,
@@ -13016,11 +14051,13 @@ export {
13016
14051
  deflate,
13017
14052
  deflateRaw,
13018
14053
  depthMaskAlpha,
14054
+ depthOf,
13019
14055
  depthRasterMask,
13020
14056
  describeLayer,
13021
14057
  describeLayerEffects,
13022
14058
  describeSelection,
13023
14059
  describeTransform,
14060
+ deserializeColorProfile,
13024
14061
  deserializeEffects,
13025
14062
  deserializeLayer,
13026
14063
  deserializeLayers,
@@ -13029,8 +14066,13 @@ export {
13029
14066
  developFilter,
13030
14067
  developFromAdjustments,
13031
14068
  developTarget,
14069
+ dilateMask,
14070
+ displayTransform,
13032
14071
  distanceTransform,
13033
14072
  documentContents,
14073
+ documentProfile,
14074
+ dodgeBurn,
14075
+ dodgeBurnCurve,
13034
14076
  downloadPsd,
13035
14077
  dragWarpMeshPoint,
13036
14078
  dropPlacement,
@@ -13047,6 +14089,7 @@ export {
13047
14089
  ellipseSelection,
13048
14090
  emptyHistogram,
13049
14091
  emptySurface,
14092
+ encodeDeepSurface,
13050
14093
  encodePng,
13051
14094
  encodeRle,
13052
14095
  encodeSelectionRle,
@@ -13092,6 +14135,7 @@ export {
13092
14135
  hasWarp,
13093
14136
  hashMaskContent,
13094
14137
  hashNoise,
14138
+ healPixels,
13095
14139
  hexToRgb,
13096
14140
  histogramPath,
13097
14141
  hitTestAnnotation,
@@ -13121,6 +14165,7 @@ export {
13121
14165
  invertSelection,
13122
14166
  isActiveFilter,
13123
14167
  isAdjustmentKind,
14168
+ isBitDepth,
13124
14169
  isBlendMode,
13125
14170
  isContentsDecoded,
13126
14171
  isDefaultDepthMask,
@@ -13135,10 +14180,13 @@ export {
13135
14180
  isQuarterTurn,
13136
14181
  isQuarterTurnSwapped,
13137
14182
  isRenderedLayer,
14183
+ isRetouchPaintTool,
14184
+ isRetouchTool,
13138
14185
  isSelectionEmpty,
13139
14186
  isSelectionMode,
13140
14187
  isSeparableBlendMode,
13141
14188
  isSpatialFilter,
14189
+ isSrgb,
13142
14190
  isTransformAnchor,
13143
14191
  isValidWarp,
13144
14192
  kelvinToTemperature,
@@ -13176,13 +14224,17 @@ export {
13176
14224
  liveShapeSubpaths,
13177
14225
  loadImageEditorDocument,
13178
14226
  loadMixer,
14227
+ loadOpenTypeFont,
14228
+ luma,
13179
14229
  magicWand,
13180
14230
  mapCropHandle,
13181
14231
  mapLayer,
13182
14232
  mapSubpaths,
14233
+ maskBounds,
13183
14234
  maskSummary,
13184
14235
  matrixFromCorners,
13185
14236
  matrixFromParams,
14237
+ membraneFill,
13186
14238
  mergeLayerEffect,
13187
14239
  mergeShapeComponents,
13188
14240
  migrateSerializedImageEditorState,
@@ -13217,6 +14269,7 @@ export {
13217
14269
  normalizeProcessVersion,
13218
14270
  normalizeRect,
13219
14271
  normalizeRefineEdgeParams,
14272
+ normalizeRetouch,
13220
14273
  normalizeShapePaint,
13221
14274
  normalizeShapeStroke,
13222
14275
  normalizeSmartFilter,
@@ -13238,11 +14291,13 @@ export {
13238
14291
  parseEffectColor,
13239
14292
  parseLfx2,
13240
14293
  parseLrfx,
14294
+ parseOpenTypeFont,
13241
14295
  parsePatternBlock,
13242
14296
  parseSelectionMode,
13243
14297
  parseVersionedDescriptor,
13244
14298
  patchParagraphStyle,
13245
14299
  patchTextStyle,
14300
+ perDabStrength,
13246
14301
  photoFilterMatrix,
13247
14302
  pigment8,
13248
14303
  pixelLayerBounds,
@@ -13259,10 +14314,12 @@ export {
13259
14314
  posterizeValue,
13260
14315
  presetWarp,
13261
14316
  previewStep,
14317
+ profileBytes,
13262
14318
  projectImagePoint,
13263
14319
  projectImageRect,
13264
14320
  projectMaskCoverage,
13265
14321
  projectQuarterTurn,
14322
+ proxyOf,
13266
14323
  psdBytesFromBase64,
13267
14324
  psdFileName,
13268
14325
  psdPatternToEffectPattern,
@@ -13276,9 +14333,11 @@ export {
13276
14333
  rasterizeDab,
13277
14334
  rasterizeSmartObject,
13278
14335
  readAdjustmentControls,
14336
+ readDeepRegion,
13279
14337
  readPixel,
13280
14338
  readPsd,
13281
14339
  readRegion,
14340
+ readSourceRegion,
13282
14341
  rectSelection,
13283
14342
  rectToQuad,
13284
14343
  reduceSurface,
@@ -13315,16 +14374,20 @@ export {
13315
14374
  resizeWarpMesh,
13316
14375
  resolveExternalAssets,
13317
14376
  resolveMosaicRegionBounds,
14377
+ resolveProfile,
13318
14378
  restyleRange,
13319
14379
  rgbToGray,
13320
14380
  rgbToHsl,
13321
14381
  rgbToReflectance,
13322
14382
  rgbUnitToHex,
14383
+ rgbaViewOfGrey,
13323
14384
  runLayerFilters,
13324
14385
  runLayerMenuAction,
13325
14386
  sampleLut,
13326
14387
  sampleToneLut,
14388
+ saturationOf,
13327
14389
  scaleLayerEffect,
14390
+ seamlessClone,
13328
14391
  selectionArea,
13329
14392
  selectionFromCoverage,
13330
14393
  selectionFromPixels,
@@ -13333,6 +14396,7 @@ export {
13333
14396
  selectionToCoverage,
13334
14397
  selectionToPixels,
13335
14398
  selectionValueAt,
14399
+ serializeColorProfile,
13336
14400
  serializeContents,
13337
14401
  serializeEffects,
13338
14402
  serializeLayer,
@@ -13347,6 +14411,7 @@ export {
13347
14411
  setLayerProperties,
13348
14412
  setLum,
13349
14413
  setPigmentColor,
14414
+ setRetouchChoice,
13350
14415
  setSmartFilterMask,
13351
14416
  setSmartFiltersEnabled,
13352
14417
  setSmartPlacement,
@@ -13361,15 +14426,25 @@ export {
13361
14426
  smartObjectLayers,
13362
14427
  smoothCoverage,
13363
14428
  smoothPoints,
14429
+ smudgeEffect,
13364
14430
  solidSurface,
14431
+ solveMembrane,
13365
14432
  spliceRuns,
13366
14433
  splitOverlayAnnotations,
14434
+ sponge,
14435
+ spongeEffect,
14436
+ spotHealPixels,
14437
+ spotHealWindow,
13367
14438
  srgb8ToLab,
14439
+ srgbDecode,
14440
+ srgbEncode,
13368
14441
  srgbToLinear,
13369
14442
  standardBlendMode,
13370
14443
  styleAt,
13371
14444
  sumHistograms,
13372
14445
  surfaceBytes,
14446
+ surfaceDepth,
14447
+ surfaceFromDeepPixels,
13373
14448
  surfaceFromPixels,
13374
14449
  surfacesEqual,
13375
14450
  tempTintToXy,
@@ -13381,14 +14456,24 @@ export {
13381
14456
  tileCoord,
13382
14457
  tileKey,
13383
14458
  tileRect,
14459
+ tileSamples,
14460
+ tileSamples16,
14461
+ tileSamples32,
13384
14462
  tilesInRect,
13385
14463
  tipFalloff,
13386
14464
  tipReach,
13387
14465
  tipShape,
14466
+ to16From32,
14467
+ to16From8,
14468
+ to32From16,
14469
+ to32From8,
14470
+ to8From16,
14471
+ to8From32,
13388
14472
  toHex,
13389
14473
  toTextSpace,
13390
14474
  toneCurve,
13391
14475
  toneLuts,
14476
+ toneOf,
13392
14477
  toneRange,
13393
14478
  transformHomography,
13394
14479
  transformMap,
@@ -13433,6 +14518,7 @@ export {
13433
14518
  withFilterMaskSurface,
13434
14519
  withNewIds,
13435
14520
  writeAdjustmentBlock,
14521
+ writeDeepRegion,
13436
14522
  writeLfx2,
13437
14523
  writePatternBlock,
13438
14524
  writePsd,