@pixerate/editor 0.113.0 → 0.115.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 (125) hide show
  1. package/THIRD_PARTY_NOTICES.md +8 -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-643B2HKC.cjs +534 -0
  20. package/dist/chunk-643B2HKC.cjs.map +1 -0
  21. package/dist/chunk-6FCKOS2B.js +534 -0
  22. package/dist/chunk-6FCKOS2B.js.map +1 -0
  23. package/dist/chunk-BRLYOPLT.js +2574 -0
  24. package/dist/chunk-BRLYOPLT.js.map +1 -0
  25. package/dist/{chunk-LQEEDHOP.js → chunk-C6C5PDKU.js} +759 -761
  26. package/dist/chunk-C6C5PDKU.js.map +1 -0
  27. package/dist/{chunk-4KLT7ZYS.cjs → chunk-DFQEX3AM.cjs} +29 -29
  28. package/dist/{chunk-4KLT7ZYS.cjs.map → chunk-DFQEX3AM.cjs.map} +1 -1
  29. package/dist/chunk-G5NMLUVF.cjs +2574 -0
  30. package/dist/chunk-G5NMLUVF.cjs.map +1 -0
  31. package/dist/{chunk-YGIMK4NX.cjs → chunk-I7FQBWMQ.cjs} +813 -842
  32. package/dist/chunk-I7FQBWMQ.cjs.map +1 -0
  33. package/dist/{chunk-LPYOIVLA.js → chunk-LKCYANMW.js} +3 -3
  34. package/dist/{chunk-W3GXSUSQ.cjs → chunk-LPLCWZTM.cjs} +5087 -1283
  35. package/dist/chunk-LPLCWZTM.cjs.map +1 -0
  36. package/dist/chunk-MMVHBKMM.js +753 -0
  37. package/dist/chunk-MMVHBKMM.js.map +1 -0
  38. package/dist/{chunk-POGBUYWE.cjs → chunk-ST7E3NEE.cjs} +857 -859
  39. package/dist/chunk-ST7E3NEE.cjs.map +1 -0
  40. package/dist/{chunk-7VOHOPLW.cjs → chunk-TCKIXI46.cjs} +59 -59
  41. package/dist/{chunk-7VOHOPLW.cjs.map → chunk-TCKIXI46.cjs.map} +1 -1
  42. package/dist/{chunk-XIOMMGPD.cjs → chunk-TFOPHZ3U.cjs} +1 -1
  43. package/dist/chunk-TFOPHZ3U.cjs.map +1 -0
  44. package/dist/{chunk-Z4BC6M74.js → chunk-UBIXVXPR.js} +2 -2
  45. package/dist/{chunk-BLWCAQCW.js → chunk-VXR7XWMF.js} +4574 -770
  46. package/dist/chunk-VXR7XWMF.js.map +1 -0
  47. package/dist/{chunk-CZOXTEMN.js → chunk-XICA4PVO.js} +392 -421
  48. package/dist/chunk-XICA4PVO.js.map +1 -0
  49. package/dist/{chunk-HNS7UHCM.js → chunk-XK5LZKBD.js} +1 -1
  50. package/dist/chunk-XK5LZKBD.js.map +1 -0
  51. package/dist/cms/index.cjs +146 -0
  52. package/dist/cms/index.cjs.map +1 -0
  53. package/dist/cms/index.d.cts +51 -0
  54. package/dist/cms/index.d.ts +51 -0
  55. package/dist/cms/index.js +146 -0
  56. package/dist/cms/index.js.map +1 -0
  57. package/dist/{export-CEXEzqBf.d.cts → export-BJE8T0YA.d.cts} +279 -191
  58. package/dist/{export-DIkoK8-r.d.ts → export-Con3UKkm.d.ts} +279 -191
  59. package/dist/image-editor/index.cjs +1848 -1218
  60. package/dist/image-editor/index.cjs.map +1 -1
  61. package/dist/image-editor/index.d.cts +85 -93
  62. package/dist/image-editor/index.d.ts +85 -93
  63. package/dist/image-editor/index.js +871 -241
  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 +4 -4
  68. package/dist/paint/index.d.cts +2 -2
  69. package/dist/paint/index.d.ts +2 -2
  70. package/dist/paint/index.js +3 -3
  71. package/dist/paint-editor/index.cjs +147 -145
  72. package/dist/paint-editor/index.cjs.map +1 -1
  73. package/dist/paint-editor/index.d.cts +9 -8
  74. package/dist/paint-editor/index.d.ts +9 -8
  75. package/dist/paint-editor/index.js +7 -5
  76. package/dist/paint-editor/index.js.map +1 -1
  77. package/dist/raster/index.cjs +53 -3
  78. package/dist/raster/index.cjs.map +1 -1
  79. package/dist/raster/index.d.cts +62 -91
  80. package/dist/raster/index.d.ts +62 -91
  81. package/dist/raster/index.js +59 -9
  82. package/dist/{render-BEriFuqK.d.cts → render-B1K_OoLy.d.cts} +87 -2
  83. package/dist/{render-tjzKaik-.d.ts → render-CrmHOgpo.d.ts} +87 -2
  84. package/dist/{retouch-CEIKR22W.d.cts → retouch-Bxkuc0KH.d.cts} +1 -1
  85. package/dist/{retouch-BXmeSCaB.d.ts → retouch-Do6tBs2H.d.ts} +1 -1
  86. package/dist/{surface-C8RRg_Bh.d.cts → surface-Cn30xIV6.d.cts} +6 -1
  87. package/dist/{surface-C8RRg_Bh.d.ts → surface-Cn30xIV6.d.ts} +6 -1
  88. package/dist/{svg-D_cfkhlB.d.cts → svg-BnRj0q8i.d.cts} +16 -4
  89. package/dist/{svg-wIaKLrU0.d.ts → svg-CmmBFfGT.d.ts} +16 -4
  90. package/dist/{types-DtL_qLFz.d.ts → types-1SUQKDAw.d.ts} +273 -4
  91. package/dist/{types-DyQq-WPY.d.cts → types-yINF17Ta.d.cts} +273 -4
  92. package/dist/vector/index.cjs +12170 -7343
  93. package/dist/vector/index.cjs.map +1 -1
  94. package/dist/vector/index.d.cts +1579 -16
  95. package/dist/vector/index.d.ts +1579 -16
  96. package/dist/vector/index.js +6303 -1476
  97. package/dist/vector/index.js.map +1 -1
  98. package/dist/warp-B39VsOtd.d.cts +348 -0
  99. package/dist/warp-DMN30aY6.d.ts +348 -0
  100. package/dist/zlib-dxQdgHyN.d.cts +95 -0
  101. package/dist/zlib-dxQdgHyN.d.ts +95 -0
  102. package/package.json +9 -2
  103. package/skills/pixerate-image-editor/SKILL.md +13 -36
  104. package/skills/pixerate-image-editor/references/color-management.md +42 -0
  105. package/skills/pixerate-image-editor/references/photoshop-files.md +41 -0
  106. package/skills/pixerate-paint-editor/SKILL.md +136 -4
  107. package/skills/pixerate-vector/SKILL.md +72 -2
  108. package/dist/chunk-BLWCAQCW.js.map +0 -1
  109. package/dist/chunk-CZOXTEMN.js.map +0 -1
  110. package/dist/chunk-HNS7UHCM.js.map +0 -1
  111. package/dist/chunk-JLYX4V57.cjs +0 -2001
  112. package/dist/chunk-JLYX4V57.cjs.map +0 -1
  113. package/dist/chunk-KB2YBCVS.js +0 -2001
  114. package/dist/chunk-KB2YBCVS.js.map +0 -1
  115. package/dist/chunk-LQEEDHOP.js.map +0 -1
  116. package/dist/chunk-POGBUYWE.cjs.map +0 -1
  117. package/dist/chunk-W3GXSUSQ.cjs.map +0 -1
  118. package/dist/chunk-XIOMMGPD.cjs.map +0 -1
  119. package/dist/chunk-YGIMK4NX.cjs.map +0 -1
  120. package/dist/homography-CZ-tFjay.d.cts +0 -50
  121. package/dist/homography-Ctfjoc1b.d.ts +0 -50
  122. package/dist/warp-CWHZzE3x.d.ts +0 -118
  123. package/dist/warp-D5jz_tN7.d.cts +0 -118
  124. /package/dist/{chunk-LPYOIVLA.js.map → chunk-LKCYANMW.js.map} +0 -0
  125. /package/dist/{chunk-Z4BC6M74.js.map → chunk-UBIXVXPR.js.map} +0 -0
@@ -1,40 +1,7 @@
1
1
  import {
2
- loadDocument
3
- } from "../chunk-U5HZSFCS.js";
4
- import {
5
- createCaptureTool
6
- } from "../chunk-REOJJIY3.js";
7
- import {
8
- DEFAULT_RETOUCH,
9
- RETOUCH_SAMPLES,
10
- RegionPixelSource,
11
- RetouchDabStroke,
12
- SPOT_HEAL_TYPES,
13
- TONE_RANGES,
14
- TilePixelSource,
15
- applyRetouchToTile,
16
- bestOffset,
17
- dilateMask,
18
- dodgeBurn,
19
- dodgeBurnCurve,
20
- healPixels,
21
- luma,
22
- maskBounds,
23
- membraneFill,
24
- normalizeRetouch,
25
- perDabStrength,
26
- readSourceRegion,
27
- rgbaViewOfGrey,
28
- saturationOf,
29
- seamlessClone,
30
- smudgeEffect,
31
- solveMembrane,
32
- sponge,
33
- spongeEffect,
34
- spotHealPixels,
35
- spotHealWindow,
36
- toneOf
37
- } from "../chunk-LPYOIVLA.js";
2
+ loadOpenTypeFont,
3
+ parseOpenTypeFont
4
+ } from "../chunk-3UFGNHSP.js";
38
5
  import {
39
6
  ADJUSTMENT_KEYS,
40
7
  ADJUSTMENT_KINDS,
@@ -47,10 +14,8 @@ import {
47
14
  D50,
48
15
  D65,
49
16
  DEFAULT_ADJUSTMENTS_VALUES,
50
- DEFAULT_AUTO_LEADING,
51
17
  DEFAULT_DEPTH_MASK_VALUES,
52
18
  DEFAULT_MITER_LIMIT,
53
- DEFAULT_TEXT_STYLE,
54
19
  DEFAULT_TRANSFORM_VALUES,
55
20
  DEVELOP_FILTER_ID,
56
21
  EMPTY_DISTANCE,
@@ -102,6 +67,7 @@ import {
102
67
  annotationOfShape,
103
68
  annotationOfType,
104
69
  applyAdjustmentLayers,
70
+ applyAdjustmentLayersFloat,
105
71
  applyAdjustments,
106
72
  applyBoxBlur,
107
73
  applyDepthMask,
@@ -121,12 +87,12 @@ import {
121
87
  buildToneLut,
122
88
  calculateCropDrag,
123
89
  canvasToImagePoint,
124
- caretAt,
125
90
  clampCropToImage,
126
91
  colorBalanceChannel,
127
92
  combineSelections,
128
93
  compileAdjustmentLayer,
129
94
  componentPath,
95
+ compositeDeep,
130
96
  computeSelectionBounds,
131
97
  contentsBox,
132
98
  contentsSurface,
@@ -140,6 +106,7 @@ import {
140
106
  createShapeLayer,
141
107
  createTypeLayer,
142
108
  curveLutFromLevels,
109
+ decodeDeepSurface,
143
110
  decodeLayerChannel,
144
111
  decodeLayerMask,
145
112
  decodeMergedImage,
@@ -162,6 +129,7 @@ import {
162
129
  distanceTransform,
163
130
  documentContents,
164
131
  duplicateLayer,
132
+ encodeDeepSurface,
165
133
  encodeRle,
166
134
  encodeSelectionRle,
167
135
  encodeSurface,
@@ -170,11 +138,8 @@ import {
170
138
  exportPsd,
171
139
  featherCoverage,
172
140
  filterMaskCoverage,
173
- fitParagraphRuns,
174
- fitRuns,
175
141
  flattenPath,
176
142
  flipCorners,
177
- fontOf,
178
143
  fullSelection,
179
144
  geometryHomography,
180
145
  getAnnotationBounds,
@@ -225,7 +190,6 @@ import {
225
190
  layerTree,
226
191
  layerTypeLabel,
227
192
  layersFromLegacy,
228
- layoutText,
229
193
  legacyAdjustmentLayer,
230
194
  legacyMaskOf,
231
195
  legacyView,
@@ -262,7 +226,6 @@ import {
262
226
  offsetLayer,
263
227
  offsetShape,
264
228
  offsetSmartObject,
265
- paragraphRanges,
266
229
  parseAdjustmentBlock,
267
230
  parseBlend,
268
231
  parseCssColor,
@@ -317,7 +280,6 @@ import {
317
280
  rerenderText,
318
281
  resizeSelection,
319
282
  resolveExternalAssets,
320
- restyleRange,
321
283
  rgbToGray,
322
284
  rgbToHsl,
323
285
  runLayerFilters,
@@ -353,12 +315,9 @@ import {
353
315
  smartContentsFile,
354
316
  smartObjectLayers,
355
317
  smoothCoverage,
356
- spliceRuns,
357
318
  srgbToLinear,
358
- styleAt,
359
319
  tempTintToXy,
360
320
  temperatureToKelvin,
361
- textIndexAt,
362
321
  textMatrix,
363
322
  textOutlines,
364
323
  textRange,
@@ -399,8 +358,15 @@ import {
399
358
  writePsd,
400
359
  xyToTempTint,
401
360
  xyToUv
402
- } from "../chunk-CZOXTEMN.js";
361
+ } from "../chunk-XICA4PVO.js";
362
+ import {
363
+ loadDocument
364
+ } from "../chunk-U5HZSFCS.js";
403
365
  import {
366
+ createCaptureTool
367
+ } from "../chunk-REOJJIY3.js";
368
+ import {
369
+ BIT_DEPTHS,
404
370
  DEFAULT_GLOBAL_LIGHT,
405
371
  DIRECT_GAUSSIAN_MAX_SIGMA,
406
372
  INTERPOLATIONS,
@@ -411,8 +377,6 @@ import {
411
377
  PatternPlacement,
412
378
  PatternTile,
413
379
  TransformTooLargeError,
414
- ZlibError,
415
- adler32,
416
380
  base64ToBytes,
417
381
  blendTile,
418
382
  builtinPattern,
@@ -420,12 +384,14 @@ import {
420
384
  bytesToBase64,
421
385
  changedTiles,
422
386
  compositeSurfaceOver,
387
+ convertSurfaceDepth,
423
388
  curveLut,
424
389
  decodePng,
390
+ deepBytes,
391
+ deepTile,
425
392
  defaultEffectGradient,
426
393
  defaultLayerEffect,
427
- deflate,
428
- deflateRaw,
394
+ depthOf,
429
395
  effectColorHex,
430
396
  effectReach,
431
397
  effectsFingerprint,
@@ -437,9 +403,8 @@ import {
437
403
  gaussianBlur,
438
404
  hasActiveEffects,
439
405
  hashNoise,
440
- inflate,
441
- inflateRaw,
442
406
  insertLayer,
407
+ isBitDepth,
443
408
  isInterpolation,
444
409
  isLayerEffectKind,
445
410
  isLayerLocked,
@@ -454,16 +419,33 @@ import {
454
419
  parseDataUrl,
455
420
  parseEffectColor,
456
421
  pngDataUrl,
422
+ proxyOf,
423
+ readDeepRegion,
457
424
  reduceSurface,
458
425
  renderLayerEffects,
459
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,
460
440
  transformSurface,
461
441
  transformedBounds,
462
442
  validateEffectGradient,
463
443
  validateLayerEffect,
464
444
  validateLayerEffects,
465
- warpSurface
466
- } from "../chunk-LQEEDHOP.js";
445
+ warpSurface,
446
+ workingToSamples,
447
+ writeDeepRegion
448
+ } from "../chunk-C6C5PDKU.js";
467
449
  import {
468
450
  applyHomography,
469
451
  boxQuad,
@@ -473,6 +455,37 @@ import {
473
455
  multiplyHomographies,
474
456
  rectToQuad
475
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";
476
489
  import {
477
490
  CoverageMap,
478
491
  DEFAULT_BLENDER,
@@ -512,7 +525,7 @@ import {
512
525
  tipReach,
513
526
  tipShape,
514
527
  walkDabs
515
- } from "../chunk-Z4BC6M74.js";
528
+ } from "../chunk-UBIXVXPR.js";
516
529
  import {
517
530
  BLEND_MODES,
518
531
  PAINTERLY_BLEND_MODES,
@@ -566,21 +579,62 @@ import {
566
579
  translateSurface,
567
580
  uniformTile,
568
581
  writeRegion
569
- } from "../chunk-HNS7UHCM.js";
582
+ } from "../chunk-XK5LZKBD.js";
570
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";
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,
571
610
  WARP_STYLES,
572
611
  WARP_STYLE_LABELS,
573
612
  applyMatrix,
574
613
  bezierToSubpaths,
614
+ caretAt,
615
+ createCanvasTextShaper,
616
+ createMetricTextShaper,
617
+ createOutlineTextShaper,
618
+ cssFont,
619
+ cssFontFamily,
575
620
  dragWarpMeshPoint,
576
621
  evalWarpMesh,
622
+ fitParagraphRuns,
623
+ fitRuns,
624
+ fontOf,
577
625
  identityWarpMesh,
578
626
  invertMatrix,
627
+ layoutText,
579
628
  multiplyMatrices,
629
+ paragraphRanges,
580
630
  pullWarpMesh,
581
631
  resizeWarpMesh,
632
+ restyleRange,
582
633
  rotateMatrix,
583
634
  roundSubpaths,
635
+ spliceRuns,
636
+ styleAt,
637
+ textIndexAt,
584
638
  translateMatrix,
585
639
  warpMeshColumns,
586
640
  warpMeshEditPoints,
@@ -588,7 +642,7 @@ import {
588
642
  warpMeshParamAt,
589
643
  warpMeshRows,
590
644
  warpPresetMesh
591
- } from "../chunk-BLWCAQCW.js";
645
+ } from "../chunk-VXR7XWMF.js";
592
646
  import "../chunk-YCSFS3GK.js";
593
647
  import "../chunk-ZRJMJIQU.js";
594
648
  import "../chunk-TGD52FEZ.js";
@@ -943,6 +997,17 @@ var ImageEditorRenderer = class {
943
997
  /** Which path the last base render took. */
944
998
  lastBaseRoute = null;
945
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
+ }
946
1011
  depthMaps = /* @__PURE__ */ new Map();
947
1012
  assetVersion = 0;
948
1013
  scratch = null;
@@ -1012,13 +1077,15 @@ var ImageEditorRenderer = class {
1012
1077
  `${projection.angle},${transform.flipH},${transform.flipV}`,
1013
1078
  `${adjustments.brightness},${adjustments.contrast},${adjustments.saturation},${adjustments.exposure},${adjustments.temperature},${adjustments.tint ?? 0},${adjustments.blur},${adjustments.opacity},p${normalizeProcessVersion(adjustments.process)}`,
1014
1079
  adjustmentLayersKey(adjustments.layers),
1015
- `${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}`
1016
1082
  ].join("|");
1017
1083
  let baseCanvas = this.cachedBaseCanvas;
1018
1084
  if (!baseCanvas || this.cachedBaseKey !== baseKey) {
1019
1085
  let histogram = null;
1020
1086
  baseCanvas = await this.renderBaseImage(img, naturalWidth, naturalHeight, region, transform, state, (pixels) => {
1021
1087
  histogram = computeHistogram(pixels);
1088
+ this.display?.(pixels.data);
1022
1089
  });
1023
1090
  this.cachedBaseCanvas = baseCanvas;
1024
1091
  this.cachedBaseKey = baseKey;
@@ -1369,6 +1436,7 @@ var ImageEditorRenderer = class {
1369
1436
  if (x1 <= x0 || y1 <= y0) return;
1370
1437
  const img = ctx.createImageData(TILE, TILE);
1371
1438
  img.data.set(data);
1439
+ this.display?.(img.data);
1372
1440
  ctx.putImageData(img, ox, oy, x0, y0, x1 - x0, y1 - y0);
1373
1441
  }
1374
1442
  /**
@@ -1411,7 +1479,7 @@ var ImageEditorRenderer = class {
1411
1479
  const rotated = getRotatedSize(region.width, region.height, transform);
1412
1480
  const baseW = Math.max(1, Math.round(rotated.width));
1413
1481
  const baseH = Math.max(1, Math.round(rotated.height));
1414
- 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}`;
1415
1483
  if (this.cachedPostCanvas && this.cachedPostKey === key) return { canvas: this.cachedPostCanvas, histogram: this.cachedPostHistogram };
1416
1484
  const canvas = this.cachedPostCanvas && this.cachedPostCanvas.width === baseW && this.cachedPostCanvas.height === baseH ? this.cachedPostCanvas : this.createCanvas(baseW, baseH);
1417
1485
  const ctx = canvas.getContext("2d");
@@ -2548,159 +2616,6 @@ function refineSelection(pixels, mask, params = {}) {
2548
2616
  return createSelectionMask(mask.width, mask.height, out);
2549
2617
  }
2550
2618
 
2551
- // src/image-editor/layers/text-shapers.ts
2552
- 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"]);
2553
- function cssFontFamily(family) {
2554
- const f = family.trim();
2555
- if (GENERIC.has(f.toLowerCase()) || f.includes(",") || /^["']/.test(f)) return f;
2556
- return /^[A-Za-z_-][\w -]*$/.test(f) && !/\s{2}/.test(f) ? f : `"${f.replace(/"/g, '\\"')}"`;
2557
- }
2558
- function cssFont(font, family = font.family) {
2559
- return `${font.italic ? "italic " : ""}${font.weight} ${font.size}px ${cssFontFamily(family)}`;
2560
- }
2561
- function createCanvasTextShaper(options = {}) {
2562
- const make = options.createCanvas ?? ((w, h) => {
2563
- if (typeof OffscreenCanvas !== "undefined") return new OffscreenCanvas(w, h);
2564
- if (typeof document !== "undefined") {
2565
- const c = document.createElement("canvas");
2566
- c.width = w;
2567
- c.height = h;
2568
- return c;
2569
- }
2570
- return null;
2571
- });
2572
- const canvas = make(1, 1);
2573
- const ctx = canvas?.getContext("2d");
2574
- if (!ctx || typeof ctx.measureText !== "function") return null;
2575
- const fallback = options.fallbackFamily ?? "sans-serif";
2576
- const available = /* @__PURE__ */ new Map();
2577
- const widths = /* @__PURE__ */ new Map();
2578
- const metrics = /* @__PURE__ */ new Map();
2579
- const probe = "mmmmmmmmmmlli1WQ@#";
2580
- const width = (font, text) => {
2581
- ctx.font = font;
2582
- return ctx.measureText(text).width;
2583
- };
2584
- const isAvailable = (family) => {
2585
- const f = family.trim();
2586
- if (GENERIC.has(f.toLowerCase()) || f.includes(",")) return true;
2587
- let hit = available.get(f);
2588
- if (hit === void 0) {
2589
- const q = cssFontFamily(f);
2590
- hit = width(`72px ${q}, monospace`, probe) !== width("72px monospace", probe) || width(`72px ${q}, serif`, probe) !== width("72px serif", probe);
2591
- available.set(f, hit);
2592
- }
2593
- return hit;
2594
- };
2595
- return {
2596
- id: "canvas",
2597
- resolve(font) {
2598
- const ok = isAvailable(font.family);
2599
- return { family: ok ? font.family : fallback, available: ok };
2600
- },
2601
- metrics(font) {
2602
- const key = cssFont(font);
2603
- let m = metrics.get(key);
2604
- if (!m) {
2605
- ctx.font = key;
2606
- ctx.textBaseline = "alphabetic";
2607
- const t = ctx.measureText("Hg");
2608
- const ascent = t.emHeightAscent ?? t.fontBoundingBoxAscent ?? font.size * 0.8;
2609
- const descent = t.emHeightDescent ?? t.fontBoundingBoxDescent ?? font.size * 0.2;
2610
- m = { ascent: Number.isFinite(ascent) ? ascent : font.size * 0.8, descent: Number.isFinite(descent) ? descent : font.size * 0.2 };
2611
- metrics.set(key, m);
2612
- }
2613
- return m;
2614
- },
2615
- measure(text, font) {
2616
- const key = `${cssFont(font)}|${text}`;
2617
- let w = widths.get(key);
2618
- if (!w) {
2619
- ctx.font = cssFont(font);
2620
- w = [0];
2621
- for (let i = 1; i <= text.length; i++) w.push(ctx.measureText(text.slice(0, i)).width);
2622
- if (widths.size > 2e3) widths.clear();
2623
- widths.set(key, w);
2624
- }
2625
- return w;
2626
- },
2627
- draw(target, text, font, x, y) {
2628
- target.font = cssFont(font);
2629
- target.textBaseline = "alphabetic";
2630
- target.textAlign = "left";
2631
- target.fillText(text, x, y);
2632
- },
2633
- families() {
2634
- const out = /* @__PURE__ */ new Set(["sans-serif", "serif", "monospace"]);
2635
- const fonts = typeof document !== "undefined" ? document.fonts : void 0;
2636
- if (fonts && typeof fonts.forEach === "function") fonts.forEach((f) => out.add(f.family.replace(/^["']|["']$/g, "")));
2637
- return [...out];
2638
- }
2639
- };
2640
- }
2641
- function createOutlineTextShaper(fonts, options = {}) {
2642
- if (!fonts.length) throw new Error("createOutlineTextShaper needs at least one font.");
2643
- const entries = fonts.map((f) => {
2644
- const e = "face" in f ? f : { face: f };
2645
- const names = e.face.names;
2646
- const family = e.family ?? names?.fontFamily?.en ?? Object.values(names?.fontFamily ?? {})[0] ?? "Font";
2647
- const weight = e.weight ?? e.face.tables?.os2?.usWeightClass ?? 400;
2648
- const italic = e.italic ?? Boolean((e.face.tables?.os2?.fsSelection ?? 0) & 1);
2649
- const postScript = names?.postScriptName?.en ?? Object.values(names?.postScriptName ?? {})[0];
2650
- return { face: e.face, family, key: family.toLowerCase(), weight, italic, postScript };
2651
- });
2652
- const fallbackKey = (options.fallback ?? entries[0].family).toLowerCase();
2653
- const pick3 = (font) => {
2654
- if (font.postScriptName) {
2655
- const exact = entries.find((e) => e.postScript === font.postScriptName);
2656
- if (exact) return { entry: exact, available: true };
2657
- }
2658
- const key = font.family.trim().toLowerCase();
2659
- let pool = entries.filter((e) => e.key === key);
2660
- const available = pool.length > 0;
2661
- if (!available) pool = entries.filter((e) => e.key === fallbackKey);
2662
- if (!pool.length) pool = entries;
2663
- const styled = pool.filter((e) => e.italic === font.italic);
2664
- const list = styled.length ? styled : pool;
2665
- const entry = list.reduce((best, e) => Math.abs(e.weight - font.weight) < Math.abs(best.weight - font.weight) ? e : best);
2666
- return { entry, available };
2667
- };
2668
- const toPath = (cmds) => {
2669
- const out = [];
2670
- for (const c of cmds) {
2671
- if (c.type === "M" || c.type === "L") out.push({ type: c.type, x: c.x, y: c.y });
2672
- 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 });
2673
- else if (c.type === "Q") out.push({ type: "Q", x1: c.x1, y1: c.y1, x: c.x, y: c.y });
2674
- else out.push({ type: "Z" });
2675
- }
2676
- return out;
2677
- };
2678
- return {
2679
- id: options.id ?? `outline:${entries.map((e) => `${e.family}/${e.weight}${e.italic ? "i" : ""}`).join(",")}`,
2680
- resolve(font) {
2681
- const { entry, available } = pick3(font);
2682
- return { family: entry.family, available };
2683
- },
2684
- metrics(font) {
2685
- const { face } = pick3(font).entry;
2686
- const k = font.size / face.unitsPerEm;
2687
- return { ascent: face.ascender * k, descent: -face.descender * k };
2688
- },
2689
- measure(text, font) {
2690
- const { face } = pick3(font).entry;
2691
- const out = [0];
2692
- for (let i = 1; i <= text.length; i++) out.push(face.getAdvanceWidth(text.slice(0, i), font.size, { kerning: true }));
2693
- return out;
2694
- },
2695
- outline(text, font, x, y) {
2696
- return toPath(pick3(font).entry.face.getPath(text, x, y, font.size, { kerning: true }).commands);
2697
- },
2698
- families() {
2699
- return [...new Set(entries.map((e) => e.family))];
2700
- }
2701
- };
2702
- }
2703
-
2704
2619
  // src/image-editor/free-transform.ts
2705
2620
  var FREE_TRANSFORM_CONTENTS = ["auto", "layer", "selection", "selection-copy", "selection-outline"];
2706
2621
  var fail = (message) => {
@@ -3181,6 +3096,179 @@ var FreeTransformSession = class {
3181
3096
  }
3182
3097
  };
3183
3098
 
3099
+ // src/image-editor/color-management.ts
3100
+ var DEFAULT_PROOF = { intent: "relative", bpc: true, simulatePaper: false, gamutWarning: false };
3101
+ function serializeColorMode(m) {
3102
+ return m.mode === "cmyk" ? { mode: "cmyk", profile: serializeColorProfile(m.profile), intent: m.intent } : { mode: "lab" };
3103
+ }
3104
+ function deserializeColorMode(raw, problems) {
3105
+ const r = raw;
3106
+ if (!r || typeof r !== "object") return null;
3107
+ if (r.mode === "lab") return { mode: "lab" };
3108
+ if (r.mode !== "cmyk") return null;
3109
+ const profile = r.profile && deserializeColorProfile(r.profile, problems) || documentProfile("coated-cmyk");
3110
+ const intent = ["perceptual", "relative", "saturation", "absolute"].includes(r.intent) ? r.intent : "relative";
3111
+ return { mode: "cmyk", profile, intent };
3112
+ }
3113
+ function effectiveProof(proof, mode) {
3114
+ if (proof) return proof;
3115
+ if (mode?.mode === "cmyk") return { ...DEFAULT_PROOF, profile: mode.profile, intent: mode.intent };
3116
+ return null;
3117
+ }
3118
+ function colorModePlanes(doc, mode) {
3119
+ const src = resolveProfile(doc);
3120
+ if (mode.mode === "cmyk") {
3121
+ const t2 = Transform.create(src, resolveProfile(mode.profile), { intent: mode.intent, bpc: true });
3122
+ return (rgba, n) => {
3123
+ const ink = new Uint8Array(n * 4);
3124
+ t2.convert8(rgba, 4, ink, 4, false);
3125
+ const planes = [0, 1, 2, 3].map(() => new Uint8Array(n));
3126
+ for (let i = 0; i < n; i++) for (let c = 0; c < 4; c++) planes[c][i] = 255 - ink[i * 4 + c];
3127
+ return planes;
3128
+ };
3129
+ }
3130
+ const t = Transform.create(src, builtinProfile("lab-d50"), { intent: "relative", bpc: false });
3131
+ return (rgba, n) => {
3132
+ const lab = new Uint8Array(n * 4);
3133
+ t.convert8(rgba, 4, lab, 4, false);
3134
+ const planes = [0, 1, 2].map(() => new Uint8Array(n));
3135
+ for (let i = 0; i < n; i++) for (let c = 0; c < 3; c++) planes[c][i] = lab[i * 4 + c];
3136
+ return planes;
3137
+ };
3138
+ }
3139
+ var resolved = /* @__PURE__ */ new WeakMap();
3140
+ var fromBytes = /* @__PURE__ */ new Map();
3141
+ function resolveProfile(doc) {
3142
+ if (!doc) return srgbProfile();
3143
+ const hit = resolved.get(doc);
3144
+ if (hit) return hit;
3145
+ let p;
3146
+ if (doc.builtin) p = builtinProfile(doc.builtin);
3147
+ else if (doc.icc) {
3148
+ const key = bytesToBase64(doc.icc);
3149
+ p = fromBytes.get(key) ?? parseProfile(doc.icc);
3150
+ if (fromBytes.size > 32) fromBytes.clear();
3151
+ fromBytes.set(key, p);
3152
+ } else p = srgbProfile();
3153
+ resolved.set(doc, p);
3154
+ return p;
3155
+ }
3156
+ function profileBytes(doc) {
3157
+ if (doc?.icc) return doc.icc;
3158
+ const p = resolveProfile(doc);
3159
+ return p.bytes ?? encodeProfile(p);
3160
+ }
3161
+ function documentProfile(input) {
3162
+ if (typeof input === "string") {
3163
+ const id = builtinProfileId(input);
3164
+ if (!id) throw new CmsError(`Unknown colour profile "${input}"; use one of ${BUILTIN_PROFILES.map((b) => b.id).join(", ")}, or ICC bytes.`);
3165
+ return { builtin: id, name: BUILTIN_PROFILES.find((b) => b.id === id).description };
3166
+ }
3167
+ const p = parseProfile(input);
3168
+ return { name: p.description || "Embedded profile", icc: input.slice() };
3169
+ }
3170
+ function isSrgb(doc) {
3171
+ if (!doc || doc.builtin === "srgb") return true;
3172
+ try {
3173
+ return sameColors(resolveProfile(doc), srgbProfile(), builtinProfile("lab-d50"));
3174
+ } catch {
3175
+ return false;
3176
+ }
3177
+ }
3178
+ function serializeColorProfile(doc) {
3179
+ return doc.builtin ? { builtin: doc.builtin, name: doc.name } : { name: doc.name, icc: bytesToBase64(doc.icc ?? new Uint8Array(0)) };
3180
+ }
3181
+ function deserializeColorProfile(raw, problems) {
3182
+ const r = raw;
3183
+ if (!r || typeof r !== "object") return null;
3184
+ try {
3185
+ if (r.builtin) {
3186
+ const id = builtinProfileId(r.builtin);
3187
+ if (!id) throw new CmsError(`unknown built-in profile "${r.builtin}"`);
3188
+ return documentProfile(id);
3189
+ }
3190
+ if (typeof r.icc === "string") {
3191
+ const icc = base64ToBytes(r.icc);
3192
+ parseProfile(icc);
3193
+ return { name: typeof r.name === "string" ? r.name : "Embedded profile", icc };
3194
+ }
3195
+ } catch (e) {
3196
+ problems.push(`The colour profile could not be read (${e instanceof Error ? e.message : String(e)}); the document is treated as sRGB`);
3197
+ }
3198
+ return null;
3199
+ }
3200
+ function displayTransform(doc, proof) {
3201
+ const src = resolveProfile(doc);
3202
+ const display = srgbProfile();
3203
+ let t = null;
3204
+ let gamut = null;
3205
+ if (proof) {
3206
+ const p = resolveProfile(proof.profile);
3207
+ t = Transform.proof(src, p, display, proof.intent, proof.bpc, proof.simulatePaper);
3208
+ if (proof.gamutWarning) gamut = new GamutCheck(src, p, builtinProfile("lab-d50"));
3209
+ } else if (!isSrgb(doc)) t = Transform.create(src, display, { intent: "relative", bpc: true });
3210
+ if (!t) return null;
3211
+ const tr = t;
3212
+ const g = gamut;
3213
+ return (rgba) => {
3214
+ if (g) {
3215
+ const px2 = [0, 0, 0];
3216
+ const flags = new Uint8Array(rgba.length >> 2);
3217
+ for (let i = 0, p = 0; i < rgba.length; i += 4, p++) {
3218
+ if (rgba[i + 3] === 0) continue;
3219
+ px2[0] = rgba[i] / 255;
3220
+ px2[1] = rgba[i + 1] / 255;
3221
+ px2[2] = rgba[i + 2] / 255;
3222
+ if (g.outOfGamut(px2)) flags[p] = 1;
3223
+ }
3224
+ tr.convert8(rgba, 4, rgba, 4);
3225
+ for (let i = 0, p = 0; i < rgba.length; i += 4, p++) if (flags[p]) rgba[i] = rgba[i + 1] = rgba[i + 2] = 128;
3226
+ return;
3227
+ }
3228
+ tr.convert8(rgba, 4, rgba, 4);
3229
+ };
3230
+ }
3231
+ function convertHex(hex, from, to, intent = "relative", bpc = true) {
3232
+ const m = /^#?([0-9a-f]{6})$/i.exec(hex.trim());
3233
+ if (!m) return hex;
3234
+ const n = parseInt(m[1], 16);
3235
+ const out = new Float32Array(4);
3236
+ Transform.create(from, to, { intent, bpc }).eval([(n >> 16 & 255) / 255, (n >> 8 & 255) / 255, (n & 255) / 255], out);
3237
+ const c = (v) => Math.round(Math.min(1, Math.max(0, v)) * 255).toString(16).padStart(2, "0");
3238
+ return `#${c(out[0])}${c(out[1])}${c(out[2])}`;
3239
+ }
3240
+ function convertSurfaceColors(surface, t) {
3241
+ if (surface.channels !== 4 || t.inputs !== 3 || t.outputs !== 3) return surface;
3242
+ const tiles = /* @__PURE__ */ new Map();
3243
+ const memo = /* @__PURE__ */ new Map();
3244
+ for (const [key, tile] of surface.tiles) {
3245
+ let next = memo.get(tile);
3246
+ if (next === void 0) {
3247
+ if (tile.deep instanceof Uint16Array) {
3248
+ const out = new Uint16Array(tile.deep.length);
3249
+ t.convert16(tile.deep, 4, out, 4);
3250
+ next = deepTile(out, 4, surface.defaultValue);
3251
+ } else if (tile.deep instanceof Float32Array) {
3252
+ const enc = new Float32Array(tile.deep.length);
3253
+ for (let i = 0; i < enc.length; i += 4) {
3254
+ for (let c = 0; c < 3; c++) enc[i + c] = srgbEncode(tile.deep[i + c]);
3255
+ enc[i + 3] = tile.deep[i + 3];
3256
+ }
3257
+ t.convertFloat(enc, 4, enc, 4);
3258
+ for (let i = 0; i < enc.length; i += 4) for (let c = 0; c < 3; c++) enc[i + c] = srgbDecode(enc[i + c]);
3259
+ next = deepTile(enc, 4, surface.defaultValue);
3260
+ } else {
3261
+ const out = new Uint8ClampedArray(tile.data.length);
3262
+ t.convert8(tile.data, 4, out, 4);
3263
+ next = finishTile(out, 4, surface.defaultValue);
3264
+ }
3265
+ memo.set(tile, next);
3266
+ }
3267
+ if (next) tiles.set(key, next);
3268
+ }
3269
+ return new TiledSurface(4, tiles, surface.defaultValue);
3270
+ }
3271
+
3184
3272
  // src/image-editor/controller.ts
3185
3273
  var IMAGE_EDITOR_SCHEMA_VERSION = LAYERED_SCHEMA_VERSION;
3186
3274
  var RETOUCH_TOOLS = ["clone", "heal", "spot-heal", "smudge", "dodge", "burn", "sponge"];
@@ -3593,10 +3681,24 @@ var ImageEditorController = class {
3593
3681
  if (strokeRects && this.pendingDirty !== "all") this.pendingDirty.push(...strokeRects);
3594
3682
  else if (!strokeRects) this.pendingDirty = "all";
3595
3683
  this.syncViews();
3684
+ this.syncDisplay();
3596
3685
  const currentState = { ...this.state };
3597
3686
  this.onStateChange?.(currentState);
3598
3687
  this.listeners.forEach((l) => l(currentState));
3599
3688
  }
3689
+ displayFor = null;
3690
+ /** Keeps the renderer's display transform in step with the document profile, mode and Proof Setup. */
3691
+ syncDisplay() {
3692
+ const s = this.state;
3693
+ const d = this.displayFor;
3694
+ if (d && d[0] === s.colorProfile && d[1] === s.proofSetup && d[2] === s.colorMode) return;
3695
+ this.displayFor = [s.colorProfile, s.proofSetup, s.colorMode];
3696
+ try {
3697
+ this.renderer.setDisplayTransform(displayTransform(s.colorProfile, effectiveProof(s.proofSetup, s.colorMode)));
3698
+ } catch {
3699
+ this.renderer.setDisplayTransform(null);
3700
+ }
3701
+ }
3600
3702
  /** Recomputes the compatibility views (`adjustments`, `depthMask`, `annotations`) when the stack changed. */
3601
3703
  syncViews() {
3602
3704
  const s = this.state;
@@ -3634,6 +3736,9 @@ var ImageEditorController = class {
3634
3736
  if (s.legacyOutputBlur !== void 0) entry.legacyOutputBlur = s.legacyOutputBlur;
3635
3737
  if (s.globalLight) entry.globalLight = { ...s.globalLight };
3636
3738
  if (s.patterns) entry.patterns = s.patterns;
3739
+ if (s.colorProfile) entry.colorProfile = s.colorProfile;
3740
+ if (s.colorMode) entry.colorMode = s.colorMode;
3741
+ if (s.bitDepth && s.bitDepth !== 8) entry.bitDepth = s.bitDepth;
3637
3742
  return entry;
3638
3743
  }
3639
3744
  restoreHistoryEntry(entry) {
@@ -3650,6 +3755,12 @@ var ImageEditorController = class {
3650
3755
  else delete s.globalLight;
3651
3756
  if (entry.patterns) s.patterns = entry.patterns;
3652
3757
  else delete s.patterns;
3758
+ if (entry.bitDepth) s.bitDepth = entry.bitDepth;
3759
+ else delete s.bitDepth;
3760
+ if (entry.colorProfile) s.colorProfile = entry.colorProfile;
3761
+ else delete s.colorProfile;
3762
+ if (entry.colorMode) s.colorMode = entry.colorMode;
3763
+ else delete s.colorMode;
3653
3764
  s.selection = entry.selection;
3654
3765
  if (this.imageElement !== entry.source.element && !this.samplePixelsOverride) this.samplePixels = null;
3655
3766
  this.imageElement = entry.source.element;
@@ -4933,9 +5044,28 @@ var ImageEditorController = class {
4933
5044
  throw new LayerOperationError("Merging annotation layers needs a canvas (a browser); hide or delete them first, or merge in the browser.");
4934
5045
  }
4935
5046
  await this.renderer.prepareLayerAssets(layers, width, height);
4936
- const data = new LayerCompositor().composite(layers, rect, this.renderer.compositorSources(width, height));
5047
+ const sources = this.renderer.compositorSources(width, height);
5048
+ const depth = this.bitDepth;
5049
+ if (depth !== 8) {
5050
+ const samples = workingToSamples(compositeDeep(layers, rect, sources, depth), depth);
5051
+ return surfaceFromDeepPixels({ width: rect.width, height: rect.height, data: samples }, 4, rect.x, rect.y);
5052
+ }
5053
+ const data = new LayerCompositor().composite(layers, rect, sources);
4937
5054
  return surfaceFromPixels({ width: rect.width, height: rect.height, data }, 4, rect.x, rect.y);
4938
5055
  }
5056
+ /**
5057
+ * The composite of the layer stack at the document's bit depth (as `compositePixels`, no
5058
+ * canvas): 16-bit sRGB samples, or 32-bit linear float, over `rect` (default the canvas). At 8
5059
+ * bits, the 8-bit composite widened to 16 bits.
5060
+ */
5061
+ compositeDeepPixels(rect) {
5062
+ const { width, height } = this.naturalSize;
5063
+ const r = rect ?? { x: 0, y: 0, width, height };
5064
+ const depth = this.bitDepth;
5065
+ if (depth === 8) return { width: r.width, height: r.height, depth: 16, data: to16From8(this.compositePixels(r).data) };
5066
+ const data = workingToSamples(compositeDeep(this.state.layers, r, this.compositorSources(), depth), depth);
5067
+ return { width: r.width, height: r.height, depth, data };
5068
+ }
4939
5069
  /** Document bounds a layer can paint into (pixel content, annotations, or the canvas for the rest). */
4940
5070
  paintBounds(layer) {
4941
5071
  const { width, height } = this.naturalSize;
@@ -6138,16 +6268,37 @@ var ImageEditorController = class {
6138
6268
  const canvas = await this.renderExport(options);
6139
6269
  const format = options.format || "image/png";
6140
6270
  const quality = options.quality ?? 0.92;
6141
- return canvas.toDataURL(format, quality);
6271
+ const url = canvas.toDataURL(format, quality);
6272
+ if (!this.state.colorProfile) return url;
6273
+ const parsed = parseDataUrl(url);
6274
+ if (!parsed) return url;
6275
+ const bytes = this.withEmbeddedProfile(parsed.bytes, parsed.mime);
6276
+ return bytes === parsed.bytes ? url : `data:${parsed.mime};base64,${bytesToBase64(bytes)}`;
6277
+ }
6278
+ /** PNG or JPEG bytes with the document's ICC profile embedded (other formats unchanged). */
6279
+ withEmbeddedProfile(bytes, mime) {
6280
+ const icc = profileBytes(this.state.colorProfile);
6281
+ try {
6282
+ if (mime === "image/png") return writePngIcc(bytes, icc, this.getColorProfile().name);
6283
+ if (mime === "image/jpeg") return writeJpegIcc(bytes, icc);
6284
+ } catch {
6285
+ }
6286
+ return bytes;
6142
6287
  }
6143
6288
  async toBlob(options = {}) {
6144
6289
  const canvas = await this.renderExport(options);
6145
6290
  const format = options.format || "image/png";
6146
6291
  const quality = options.quality ?? 0.92;
6292
+ const tag = async (blob) => {
6293
+ if (!this.state.colorProfile || typeof blob.arrayBuffer !== "function") return blob;
6294
+ const bytes = new Uint8Array(await blob.arrayBuffer());
6295
+ const out = this.withEmbeddedProfile(bytes, blob.type || format);
6296
+ return out === bytes ? blob : new Blob([out], { type: blob.type || format });
6297
+ };
6147
6298
  return new Promise((resolve, reject) => {
6148
6299
  if (canvas.toBlob) {
6149
6300
  canvas.toBlob((blob) => {
6150
- if (blob) resolve(blob);
6301
+ if (blob) tag(blob).then(resolve, reject);
6151
6302
  else reject(new Error("Failed to create Blob from canvas"));
6152
6303
  }, format, quality);
6153
6304
  } else {
@@ -6160,7 +6311,7 @@ var ImageEditorController = class {
6160
6311
  for (let i = 0; i < byteStr.length; i++) {
6161
6312
  ia[i] = byteStr.charCodeAt(i);
6162
6313
  }
6163
- resolve(new Blob([ab], { type: format }));
6314
+ tag(new Blob([ab], { type: format })).then(resolve, reject);
6164
6315
  } catch (err) {
6165
6316
  reject(err);
6166
6317
  }
@@ -6680,6 +6831,206 @@ var ImageEditorController = class {
6680
6831
  return id;
6681
6832
  }
6682
6833
  // --- PSD ---
6834
+ // --- Colour management ---
6835
+ /** The document's colour profile: its name, built-in id (or `null` for an embedded profile), colour space, and Image › Mode. */
6836
+ getColorProfile() {
6837
+ const doc = this.state.colorProfile;
6838
+ const p = resolveProfile(doc);
6839
+ const m = this.state.colorMode;
6840
+ const out = { name: doc?.name ?? p.description, builtin: doc ? doc.builtin ?? null : "srgb", colorSpace: p.colorSpace, srgb: isSrgb(doc), mode: m?.mode ?? "rgb" };
6841
+ if (m?.mode === "cmyk") out.cmykProfile = { name: m.profile.name, builtin: m.profile.builtin ?? null, intent: m.intent };
6842
+ return out;
6843
+ }
6844
+ /**
6845
+ * Image › Mode (one undo step): `rgb`, `cmyk` (with a CMYK `profile`, default the coated
6846
+ * CMYK, and the `intent` RGB is separated with) or `lab`. The pixels stay RGB in the working
6847
+ * space; a CMYK document is soft-proofed through its profile in the viewport, and PSD export
6848
+ * writes CMYK or Lab channels. Returns the profile summary.
6849
+ */
6850
+ setColorMode(mode, options = {}) {
6851
+ let next;
6852
+ if (mode === "rgb") {
6853
+ if (options.profile !== void 0) throw new LayerOperationError("An RGB document takes no CMYK profile; use assign_profile for its working space.");
6854
+ next = null;
6855
+ } else if (mode === "lab") next = { mode: "lab" };
6856
+ else if (mode === "cmyk") {
6857
+ let doc;
6858
+ try {
6859
+ doc = documentProfile(options.profile ?? "coated-cmyk");
6860
+ } catch (e) {
6861
+ throw new LayerOperationError(e instanceof Error ? e.message : String(e));
6862
+ }
6863
+ const cs = resolveProfile(doc).colorSpace;
6864
+ if (cs !== "cmyk") throw new LayerOperationError(`CMYK mode needs a CMYK profile; "${doc.name}" is ${cs.toUpperCase()}.`);
6865
+ const intent = options.intent ?? "relative";
6866
+ if (!INTENTS.includes(intent)) throw new LayerOperationError(`Unknown rendering intent "${String(intent)}"; use ${INTENTS.join(", ")}.`);
6867
+ next = { mode: "cmyk", profile: doc, intent };
6868
+ } else throw new LayerOperationError(`Unknown colour mode "${String(mode)}"; use rgb, cmyk or lab.`);
6869
+ this.recordHistory();
6870
+ if (next) this.state.colorMode = next;
6871
+ else delete this.state.colorMode;
6872
+ this.notify();
6873
+ return this.getColorProfile();
6874
+ }
6875
+ /** Resolves a profile argument (built-in id or alias, or ICC bytes) as an RGB document profile. */
6876
+ rgbProfileArg(profile, action) {
6877
+ let doc;
6878
+ try {
6879
+ doc = documentProfile(profile);
6880
+ } catch (e) {
6881
+ throw new LayerOperationError(e instanceof Error ? e.message : String(e));
6882
+ }
6883
+ const cs = resolveProfile(doc).colorSpace;
6884
+ 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.`);
6885
+ return doc;
6886
+ }
6887
+ /**
6888
+ * Edit › Assign Profile (one undo step): the document's numbers now mean colours in
6889
+ * `profile` (a built-in id such as `display-p3`, or ICC bytes); no pixel changes, so colours
6890
+ * look different unless the profiles match. Returns the new profile.
6891
+ */
6892
+ assignProfile(profile) {
6893
+ const doc = this.rgbProfileArg(profile, "Assign Profile");
6894
+ this.recordHistory();
6895
+ if (doc.builtin === "srgb") delete this.state.colorProfile;
6896
+ else this.state.colorProfile = doc;
6897
+ this.notify();
6898
+ return this.getColorProfile();
6899
+ }
6900
+ /**
6901
+ * Edit › Convert to Profile (one undo step): every pixel layer's numbers are converted so its
6902
+ * colours stay the same in `profile` (with `intent` and black point compensation), and
6903
+ * `profile` becomes the document's. Shape and type layers, layer effects and adjustment layers
6904
+ * keep their numbers (they are listed in `unconvertedLayerIds`). Returns the profile and the
6905
+ * layers changed.
6906
+ */
6907
+ convertToProfile(profile, options = {}) {
6908
+ const doc = this.rgbProfileArg(profile, "Convert to Profile");
6909
+ const intent = options.intent ?? "relative";
6910
+ if (!INTENTS.includes(intent)) throw new LayerOperationError(`Unknown rendering intent "${String(intent)}"; use ${INTENTS.join(", ")}.`);
6911
+ const t = Transform.create(resolveProfile(this.state.colorProfile), resolveProfile(doc), { intent, bpc: options.bpc ?? true });
6912
+ const converted = [];
6913
+ const unconverted = [];
6914
+ const walk = (layers) => layers.map((l) => {
6915
+ if (l.type === "group") return { ...l, children: walk(l.children) };
6916
+ if (l.type === "pixel" && l.source !== "document") {
6917
+ converted.push(l.id);
6918
+ return { ...l, surface: convertSurfaceColors(l.surface, t) };
6919
+ }
6920
+ if (l.type === "pixel" && l.source === "document") {
6921
+ converted.push(l.id);
6922
+ return { ...l, surface: convertSurfaceColors(this.surfaceOf(l), t), source: void 0 };
6923
+ }
6924
+ if (l.type === "shape" || l.type === "type" || l.type === "smart" || l.effects?.items.length) unconverted.push(l.id);
6925
+ return l;
6926
+ });
6927
+ const next = walk(this.state.layers);
6928
+ this.commitLayers(next, () => {
6929
+ if (doc.builtin === "srgb") delete this.state.colorProfile;
6930
+ else this.state.colorProfile = doc;
6931
+ });
6932
+ return { profile: this.getColorProfile(), convertedLayerIds: converted, unconvertedLayerIds: unconverted };
6933
+ }
6934
+ /**
6935
+ * View › Proof Setup (view state, not an undo step): shows the document as `profile` would
6936
+ * reproduce it, or turns proofing off with `null`. Returns the setup.
6937
+ */
6938
+ setProofSetup(setup) {
6939
+ if (!setup) {
6940
+ this.state.proofSetup = null;
6941
+ this.notify();
6942
+ return null;
6943
+ }
6944
+ let doc;
6945
+ try {
6946
+ doc = documentProfile(setup.profile);
6947
+ } catch (e) {
6948
+ throw new LayerOperationError(e instanceof Error ? e.message : String(e));
6949
+ }
6950
+ const intent = setup.intent ?? DEFAULT_PROOF.intent;
6951
+ if (!INTENTS.includes(intent)) throw new LayerOperationError(`Unknown rendering intent "${String(intent)}"; use ${INTENTS.join(", ")}.`);
6952
+ const next = { ...DEFAULT_PROOF, ...setup, intent, profile: doc };
6953
+ if (!canBeDestination(resolveProfile(doc))) throw new LayerOperationError(`"${doc.name}" describes a source only, so colours cannot be proofed through it.`);
6954
+ this.state.proofSetup = next;
6955
+ this.notify();
6956
+ return next;
6957
+ }
6958
+ /**
6959
+ * Converts colours (`#rrggbb`) from one profile to another (default from the document's), with
6960
+ * `intent`. For RGB targets returns hex; for others the channel values 0..1 (CMYK, grey, Lab
6961
+ * encoded as L/100, (a+128)/255, (b+128)/255) and, for Lab, L*a*b*.
6962
+ */
6963
+ convertColors(colors, to, options = {}) {
6964
+ let src;
6965
+ let dst;
6966
+ try {
6967
+ src = options.from !== void 0 ? resolveProfile(documentProfile(options.from)) : resolveProfile(this.state.colorProfile);
6968
+ dst = resolveProfile(documentProfile(to));
6969
+ } catch (e) {
6970
+ throw new LayerOperationError(e instanceof Error ? e.message : String(e));
6971
+ }
6972
+ if (src.colorSpace !== "rgb") throw new LayerOperationError("Colours are given as #rrggbb, so the source profile must be RGB.");
6973
+ const t = Transform.create(src, dst, { intent: options.intent ?? "relative", bpc: options.bpc ?? true });
6974
+ const toLab = Transform.create(src, builtinProfile("lab-d50"), { intent: "relative", bpc: false });
6975
+ return colors.map((input) => {
6976
+ const m = /^#?([0-9a-f]{6})$/i.exec(input.trim());
6977
+ if (!m) throw new LayerOperationError(`Invalid colour "${input}"; use #rrggbb.`);
6978
+ const n = parseInt(m[1], 16);
6979
+ const rgb = [(n >> 16 & 255) / 255, (n >> 8 & 255) / 255, (n & 255) / 255];
6980
+ const out = new Float32Array(16);
6981
+ t.eval(rgb, out);
6982
+ const values = Array.from(out.subarray(0, t.outputs), (v) => Math.round(Math.min(1, Math.max(0, v)) * 1e4) / 1e4);
6983
+ const l = new Float32Array(3);
6984
+ toLab.eval(rgb, l);
6985
+ const lab = [Math.round(l[0] * 1e4) / 100, Math.round((l[1] * 255 - 128) * 100) / 100, Math.round((l[2] * 255 - 128) * 100) / 100];
6986
+ const res = { input, values, lab };
6987
+ if (dst.colorSpace === "rgb") res.hex = `#${values.map((v) => Math.round(v * 255).toString(16).padStart(2, "0")).join("")}`;
6988
+ return res;
6989
+ });
6990
+ }
6991
+ /** The document's bits per channel (8 unless set to 16 or 32). */
6992
+ get bitDepth() {
6993
+ return this.state.bitDepth ?? 8;
6994
+ }
6995
+ /**
6996
+ * Image › Mode › 8 / 16 / 32 Bits/Channel (one undo step). Going up costs nothing: layers keep
6997
+ * their pixels and read them at the new depth, and edits that understand depth keep the extra
6998
+ * precision from then on. Going down rounds every pixel layer's deep samples to the new depth
6999
+ * (8 bits: their 8-bit rendering; 32 → 16: colour above 1 is clipped). Returns the depth and
7000
+ * the ids of the layers whose pixels changed.
7001
+ */
7002
+ setBitDepth(depth) {
7003
+ if (!isBitDepth(depth)) throw new LayerOperationError(`Bit depth must be 8, 16 or 32, not ${String(depth)}.`);
7004
+ const changed = [];
7005
+ const convert = (layers) => layers.map((l) => {
7006
+ if (l.type === "group") {
7007
+ const children = convert(l.children);
7008
+ return children.some((c, i) => c !== l.children[i]) ? { ...l, children } : l;
7009
+ }
7010
+ if (l.type !== "pixel") return l;
7011
+ const surface = convertSurfaceDepth(l.surface, depth);
7012
+ if (surface === l.surface) return l;
7013
+ changed.push(l.id);
7014
+ return { ...l, surface };
7015
+ });
7016
+ if (depth === this.bitDepth) return { bitDepth: depth, changedLayerIds: [] };
7017
+ const next = convert(this.state.layers);
7018
+ this.commitLayers(next, () => {
7019
+ if (depth === 8) delete this.state.bitDepth;
7020
+ else this.state.bitDepth = depth;
7021
+ });
7022
+ return { bitDepth: depth, changedLayerIds: changed };
7023
+ }
7024
+ /** How `toPsd` writes colour: RGB with the working space, or CMYK / Lab channels for those modes. */
7025
+ psdColorOptions(warnings) {
7026
+ const m = this.state.colorMode;
7027
+ if (!m) return { iccProfile: profileBytes(this.state.colorProfile) };
7028
+ if (flattenLayers(this.state.layers).some((l) => l.type === "adjustment" && l.visible)) {
7029
+ warnings.push(`Adjustment layers were written with their RGB settings; in a ${m.mode.toUpperCase()} file Photoshop applies them to its own channels, so they may look different there.`);
7030
+ }
7031
+ const planes = colorModePlanes(this.state.colorProfile, m);
7032
+ return m.mode === "cmyk" ? { colorMode: "cmyk", colorPlanes: planes, iccProfile: profileBytes(m.profile) } : { colorMode: "lab", colorPlanes: planes };
7033
+ }
6683
7034
  /** Pixels per inch of the last opened PSD (written back by `toPsd`). */
6684
7035
  psdResolution;
6685
7036
  /**
@@ -6712,6 +7063,23 @@ var ImageEditorController = class {
6712
7063
  else delete s.patterns;
6713
7064
  if (result.globalLight) s.globalLight = result.globalLight;
6714
7065
  else delete s.globalLight;
7066
+ if (result.bitDepth !== 8) s.bitDepth = result.bitDepth;
7067
+ else delete s.bitDepth;
7068
+ delete s.colorProfile;
7069
+ delete s.colorMode;
7070
+ if (result.sourceMode?.mode === "cmyk") s.colorMode = { mode: "cmyk", profile: result.sourceMode.profile, intent: "relative" };
7071
+ else if (result.sourceMode?.mode === "lab") s.colorMode = { mode: "lab" };
7072
+ else if (result.iccProfile) {
7073
+ try {
7074
+ const doc = documentProfile(result.iccProfile);
7075
+ const cs = resolveProfile(doc).colorSpace;
7076
+ if (cs === "rgb") {
7077
+ if (!isSrgb(doc)) s.colorProfile = doc;
7078
+ } else result.warnings.push(`The file's ${cs.toUpperCase()} profile "${doc.name}" was not used; its colours were read as sRGB.`);
7079
+ } catch (e) {
7080
+ result.warnings.push(`The embedded ICC profile could not be read (${e instanceof Error ? e.message : String(e)}); colours are read as sRGB.`);
7081
+ }
7082
+ }
6715
7083
  this.lastLoadProblems = result.warnings;
6716
7084
  this.notify();
6717
7085
  return {
@@ -6747,7 +7115,7 @@ var ImageEditorController = class {
6747
7115
  return layers;
6748
7116
  }
6749
7117
  /**
6750
- * The document as an 8-bit RGB PSD (PSB above 30 000 px) with real layers, groups, adjustment
7118
+ * The document as an RGB PSD at its bit depth (8, 16 or 32) (PSB above 30 000 px) with real layers, groups, adjustment
6751
7119
  * layers and masks, in the source frame. Layer filters and parametric (depth) masks are baked
6752
7120
  * into pixels and masks, annotation layers are rasterized (in a browser; elsewhere they are left
6753
7121
  * out), and the merged image is our composite. `warnings` lists every such change.
@@ -6873,7 +7241,11 @@ var ImageEditorController = class {
6873
7241
  if (s.legacyOutputBlur) warnings.push("The document-level blur of an old document is not written.");
6874
7242
  for (const [msg, names] of note) warnings.push(`${msg} (${names.length === 1 ? "layer" : "layers"} ${names.map((n) => `"${n}"`).join(", ")}).`);
6875
7243
  const merged = compositor.composite(prepared, canvas, { ...sources, version: `psd:${Date.now()}` });
6876
- const result = exportPsd({ width, height, layers: prepared, merged, resolution: this.psdResolution, patterns: this.state.patterns, globalLight: this.state.globalLight });
7244
+ const color = this.psdColorOptions(warnings);
7245
+ if (color.colorMode && this.bitDepth !== 8) warnings.push(`${color.colorMode.toUpperCase()} files are written at 8 bits per channel; the ${this.bitDepth}-bit samples were rounded.`);
7246
+ const depth = color.colorMode ? 8 : this.bitDepth;
7247
+ const mergedDeep = depth === 8 ? void 0 : workingToSamples(compositeDeep(prepared, canvas, sources, depth), depth);
7248
+ const result = exportPsd({ width, height, layers: prepared, merged, mergedDeep, resolution: this.psdResolution, patterns: this.state.patterns, globalLight: this.state.globalLight, depth, ...color });
6877
7249
  warnings.push(...result.warnings);
6878
7250
  return { bytes: result.bytes, warnings, psb: result.psb, width, height };
6879
7251
  }
@@ -7019,6 +7391,14 @@ var ImageEditorController = class {
7019
7391
  const patterns = deserializePatterns(data.patterns, problems);
7020
7392
  if (patterns.length) s.patterns = patterns;
7021
7393
  else delete s.patterns;
7394
+ if (data.bitDepth === 16 || data.bitDepth === 32) s.bitDepth = data.bitDepth;
7395
+ else delete s.bitDepth;
7396
+ const profile = data.colorProfile ? deserializeColorProfile(data.colorProfile, problems) : null;
7397
+ if (profile) s.colorProfile = profile;
7398
+ else delete s.colorProfile;
7399
+ const cmode = data.colorMode ? deserializeColorMode(data.colorMode, problems) : null;
7400
+ if (cmode) s.colorMode = cmode;
7401
+ else delete s.colorMode;
7022
7402
  if (!this.imageElement && typeof data.width === "number" && typeof data.height === "number" && data.width > 0 && data.height > 0) {
7023
7403
  s.imageDimensions = { width: data.width, height: data.height };
7024
7404
  }
@@ -7034,6 +7414,9 @@ var ImageEditorController = class {
7034
7414
  s.activeLayerId = null;
7035
7415
  delete s.globalLight;
7036
7416
  delete s.patterns;
7417
+ delete s.bitDepth;
7418
+ delete s.colorProfile;
7419
+ delete s.colorMode;
7037
7420
  }
7038
7421
  s.activeTarget = "pixels";
7039
7422
  this.viewKey = [];
@@ -7043,7 +7426,7 @@ var ImageEditorController = class {
7043
7426
  serializeState() {
7044
7427
  const { width, height } = this.naturalSize;
7045
7428
  const view = legacyView(this.stack(), width, height, null);
7046
- if (view.exact && !this.state.globalLight && !this.state.patterns?.length) {
7429
+ if (view.exact && !this.state.globalLight && !this.state.patterns?.length && this.bitDepth === 8 && !this.state.colorProfile && !this.state.colorMode) {
7047
7430
  return {
7048
7431
  // States without adjustment layers keep schema 1, so they round-trip unchanged.
7049
7432
  version: view.adjustments.layers?.length ? 2 : 1,
@@ -7073,6 +7456,9 @@ var ImageEditorController = class {
7073
7456
  if (this.state.globalLight) out.globalLight = { ...this.state.globalLight };
7074
7457
  if (this.state.patterns?.length) out.patterns = serializePatterns(this.state.patterns);
7075
7458
  if (smartContents) out.smartContents = smartContents;
7459
+ if (this.state.bitDepth === 16 || this.state.bitDepth === 32) out.bitDepth = this.state.bitDepth;
7460
+ if (this.state.colorProfile) out.colorProfile = serializeColorProfile(this.state.colorProfile);
7461
+ if (this.state.colorMode) out.colorMode = serializeColorMode(this.state.colorMode);
7076
7462
  return out;
7077
7463
  }
7078
7464
  };
@@ -8281,10 +8667,23 @@ function createImageEditTools(controller) {
8281
8667
  const getImageInfo = {
8282
8668
  name: "get_image_info",
8283
8669
  title: "Read image geometry",
8284
- 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.",
8670
+ 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.",
8285
8671
  inputSchema: { type: "object", properties: {}, additionalProperties: false },
8286
8672
  annotations: { title: "Read image geometry", ...READ },
8287
- handler: () => ({ ...geometry2(), layers: controller.getAdjustmentLayers().length, ...history() })
8673
+ handler: () => ({ ...geometry2(), bitDepth: controller.bitDepth, layers: controller.getAdjustmentLayers().length, ...history() })
8674
+ };
8675
+ const setBitDepth = {
8676
+ name: "set_bit_depth",
8677
+ title: "Set the bit depth",
8678
+ 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).",
8679
+ inputSchema: {
8680
+ type: "object",
8681
+ properties: { depth: { type: "number", enum: [8, 16, 32], description: "Bits per channel: 8, 16 or 32 (float, linear light)." } },
8682
+ required: ["depth"],
8683
+ additionalProperties: false
8684
+ },
8685
+ annotations: { title: "Set the bit depth", readOnlyHint: false, destructiveHint: true, idempotentHint: true, openWorldHint: false },
8686
+ handler: (args) => ({ ...controller.setBitDepth(args.depth), ...history() })
8288
8687
  };
8289
8688
  const getHistogram = {
8290
8689
  name: "get_histogram",
@@ -8460,7 +8859,7 @@ function createImageEditTools(controller) {
8460
8859
  return history();
8461
8860
  }
8462
8861
  };
8463
- return [getImageInfo, getHistogram, updateLayer, removeLayer2, moveLayer2, setCrop, resetCrop, setRotation, flipImage, undo, redo];
8862
+ return [getImageInfo, getHistogram, updateLayer, removeLayer2, moveLayer2, setCrop, resetCrop, setRotation, flipImage, setBitDepth, undo, redo];
8464
8863
  }
8465
8864
 
8466
8865
  // src/image-editor/layer-tools.ts
@@ -9775,8 +10174,155 @@ function createImagePaintTools(controller) {
9775
10174
  return [paintStroke, fill, clear, getMixer, loadMixerTool, cleanMixerTool];
9776
10175
  }
9777
10176
 
9778
- // src/image-editor/retouch-tools.ts
10177
+ // src/image-editor/color-tools.ts
9779
10178
  var READ5 = { readOnlyHint: true, destructiveHint: false, idempotentHint: true, openWorldHint: false };
10179
+ var PROFILE = {
10180
+ type: "string",
10181
+ 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.`
10182
+ };
10183
+ var INTENT = {
10184
+ type: "string",
10185
+ enum: [...INTENTS],
10186
+ 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)."
10187
+ };
10188
+ function profileArg(v) {
10189
+ const s = String(v);
10190
+ if (s.startsWith("icc:")) {
10191
+ try {
10192
+ return base64ToBytes(s.slice(4));
10193
+ } catch {
10194
+ throw new LayerOperationError("The icc: value is not valid base64.");
10195
+ }
10196
+ }
10197
+ return s;
10198
+ }
10199
+ function createImageColorTools(controller) {
10200
+ const getProfile = {
10201
+ name: "get_color_profile",
10202
+ title: "Get the colour profile",
10203
+ 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, mode rgb / cmyk / lab and, for cmyk, cmykProfile), 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}).",
10204
+ inputSchema: { type: "object", properties: {}, additionalProperties: false },
10205
+ annotations: { title: "Get the colour profile", ...READ5 },
10206
+ handler: () => {
10207
+ const proof2 = controller.getState().proofSetup;
10208
+ return {
10209
+ profile: controller.getColorProfile(),
10210
+ proof: proof2 ? { profile: proof2.profile.name, builtin: proof2.profile.builtin ?? null, intent: proof2.intent, bpc: proof2.bpc, simulatePaper: proof2.simulatePaper, gamutWarning: proof2.gamutWarning } : null,
10211
+ builtins: BUILTIN_PROFILES.map((b) => ({ ...b }))
10212
+ };
10213
+ }
10214
+ };
10215
+ const assign = {
10216
+ name: "assign_profile",
10217
+ title: "Assign a colour profile",
10218
+ 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.",
10219
+ inputSchema: { type: "object", properties: { profile: PROFILE }, required: ["profile"], additionalProperties: false },
10220
+ annotations: { title: "Assign a colour profile", readOnlyHint: false, destructiveHint: false, idempotentHint: true, openWorldHint: false },
10221
+ handler: (args) => controller.assignProfile(profileArg(args.profile))
10222
+ };
10223
+ const convert = {
10224
+ name: "convert_to_profile",
10225
+ title: "Convert to a colour profile",
10226
+ 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.",
10227
+ inputSchema: {
10228
+ type: "object",
10229
+ properties: {
10230
+ profile: PROFILE,
10231
+ intent: INTENT,
10232
+ bpc: { type: "boolean", description: "Black point compensation: map black to black (default true)." }
10233
+ },
10234
+ required: ["profile"],
10235
+ additionalProperties: false
10236
+ },
10237
+ annotations: { title: "Convert to a colour profile", readOnlyHint: false, destructiveHint: true, idempotentHint: false, openWorldHint: false },
10238
+ handler: (args) => controller.convertToProfile(profileArg(args.profile), { intent: args.intent, bpc: args.bpc })
10239
+ };
10240
+ const proof = {
10241
+ name: "set_proof_setup",
10242
+ title: "Set up soft proofing",
10243
+ 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).",
10244
+ inputSchema: {
10245
+ type: "object",
10246
+ properties: {
10247
+ profile: PROFILE,
10248
+ intent: INTENT,
10249
+ bpc: { type: "boolean", description: "Black point compensation (default true)." },
10250
+ simulatePaper: { type: "boolean", description: "Simulate paper colour and black ink (default false)." },
10251
+ gamutWarning: { type: "boolean", description: "Grey out out-of-gamut colours (default false)." },
10252
+ off: { type: "boolean", description: "Turn proofing off instead (default false)." }
10253
+ },
10254
+ additionalProperties: false
10255
+ },
10256
+ annotations: { title: "Set up soft proofing", readOnlyHint: false, destructiveHint: false, idempotentHint: true, openWorldHint: false },
10257
+ handler: (args) => {
10258
+ if (args.off === true) {
10259
+ if (args.profile !== void 0) throw new LayerOperationError("Give either off true or a profile, not both.");
10260
+ return controller.setProofSetup(null);
10261
+ }
10262
+ if (args.profile === void 0) throw new LayerOperationError("Pass the profile to proof with (e.g. coated-cmyk), or off true.");
10263
+ const s = controller.setProofSetup({
10264
+ profile: profileArg(args.profile),
10265
+ ...args.intent !== void 0 ? { intent: args.intent } : {},
10266
+ ...args.bpc !== void 0 ? { bpc: args.bpc } : {},
10267
+ ...args.simulatePaper !== void 0 ? { simulatePaper: args.simulatePaper } : {},
10268
+ ...args.gamutWarning !== void 0 ? { gamutWarning: args.gamutWarning } : {}
10269
+ });
10270
+ return { profile: s.profile.name, builtin: s.profile.builtin ?? null, intent: s.intent, bpc: s.bpc, simulatePaper: s.simulatePaper, gamutWarning: s.gamutWarning };
10271
+ }
10272
+ };
10273
+ const convertColors = {
10274
+ name: "convert_colors",
10275
+ title: "Convert colours between profiles",
10276
+ 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.',
10277
+ inputSchema: {
10278
+ type: "object",
10279
+ properties: {
10280
+ colors: { type: "array", minItems: 1, maxItems: 64, items: { type: "string", description: "A colour #rrggbb." }, description: "Colours in the source profile." },
10281
+ to: PROFILE,
10282
+ from: { ...PROFILE, description: `Source profile (default the document's). ${PROFILE.description}` },
10283
+ intent: INTENT,
10284
+ bpc: { type: "boolean", description: "Black point compensation (default true)." }
10285
+ },
10286
+ required: ["colors", "to"],
10287
+ additionalProperties: false
10288
+ },
10289
+ annotations: { title: "Convert colours between profiles", ...READ5 },
10290
+ handler: (args) => ({
10291
+ colors: controller.convertColors(args.colors, profileArg(args.to), {
10292
+ ...args.from !== void 0 ? { from: profileArg(args.from) } : {},
10293
+ intent: args.intent,
10294
+ bpc: args.bpc
10295
+ })
10296
+ })
10297
+ };
10298
+ const setMode = {
10299
+ name: "set_color_mode",
10300
+ title: "Set the colour mode",
10301
+ description: "Image \u203A Mode: rgb, cmyk (for print: give the press `profile`, default coated-cmyk, and the `intent` RGB is separated with) or lab. The document is still edited as RGB in its working space; a CMYK document is soft-proofed through its profile in the viewport (unless set_proof_setup says otherwise), and export_psd writes CMYK or Lab channels with the profile. Changes no pixels. One undo step. Returns the profile summary with mode and cmykProfile.",
10302
+ inputSchema: {
10303
+ type: "object",
10304
+ properties: {
10305
+ mode: { type: "string", enum: ["rgb", "cmyk", "lab"], description: "The colour mode." },
10306
+ profile: { ...PROFILE, description: `CMYK mode only: the CMYK profile (default coated-cmyk). ${PROFILE.description}` },
10307
+ intent: INTENT
10308
+ },
10309
+ required: ["mode"],
10310
+ additionalProperties: false
10311
+ },
10312
+ annotations: { title: "Set the colour mode", readOnlyHint: false, destructiveHint: false, idempotentHint: true, openWorldHint: false },
10313
+ handler: (args) => {
10314
+ if (args.mode !== "cmyk" && (args.profile !== void 0 || args.intent !== void 0)) throw new LayerOperationError("profile and intent apply to CMYK mode only.");
10315
+ return controller.setColorMode(args.mode, {
10316
+ ...args.profile !== void 0 ? { profile: profileArg(args.profile) } : {},
10317
+ ...args.intent !== void 0 ? { intent: args.intent } : {}
10318
+ });
10319
+ }
10320
+ };
10321
+ return [getProfile, assign, convert, proof, convertColors, setMode];
10322
+ }
10323
+
10324
+ // src/image-editor/retouch-tools.ts
10325
+ var READ6 = { readOnlyHint: true, destructiveHint: false, idempotentHint: true, openWorldHint: false };
9780
10326
  var WRITE5 = { readOnlyHint: false, destructiveHint: false, idempotentHint: false, openWorldHint: false };
9781
10327
  var POINTS = {
9782
10328
  type: "array",
@@ -9980,7 +10526,7 @@ function createImageRetouchTools(controller) {
9980
10526
  title: "Get retouch options",
9981
10527
  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.",
9982
10528
  inputSchema: { type: "object", properties: {}, additionalProperties: false },
9983
- annotations: { title: "Get retouch options", ...READ5 },
10529
+ annotations: { title: "Get retouch options", ...READ6 },
9984
10530
  handler: () => {
9985
10531
  const { retouch, cloneSource } = controller.getState().paint;
9986
10532
  return { options: { ...retouch }, cloneSource: cloneSource ? { ...cloneSource } : null };
@@ -10040,7 +10586,7 @@ function pick(args, keys) {
10040
10586
  }
10041
10587
 
10042
10588
  // src/image-editor/psd-tools.ts
10043
- var READ6 = { readOnlyHint: true, destructiveHint: false, idempotentHint: true, openWorldHint: false };
10589
+ var READ7 = { readOnlyHint: true, destructiveHint: false, idempotentHint: true, openWorldHint: false };
10044
10590
  var DESTRUCTIVE4 = { readOnlyHint: false, destructiveHint: true, idempotentHint: false, openWorldHint: false };
10045
10591
  var DATA = {
10046
10592
  type: "string",
@@ -10061,7 +10607,7 @@ function createImagePsdTools(controller) {
10061
10607
  title: "Inspect a PSD",
10062
10608
  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.",
10063
10609
  inputSchema: { type: "object", properties: { data: DATA }, required: ["data"], additionalProperties: false },
10064
- annotations: { title: "Inspect a PSD", ...READ6 },
10610
+ annotations: { title: "Inspect a PSD", ...READ7 },
10065
10611
  handler: ({ data }) => {
10066
10612
  const res = importPsd(psdBytesFromBase64(data), { skipPixels: true });
10067
10613
  return {
@@ -10096,9 +10642,9 @@ function createImagePsdTools(controller) {
10096
10642
  const save = {
10097
10643
  name: "export_psd",
10098
10644
  title: "Export the document as a PSD",
10099
- 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.",
10645
+ 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.",
10100
10646
  inputSchema: { type: "object", properties: {}, additionalProperties: false },
10101
- annotations: { title: "Export the document as a PSD", ...READ6 },
10647
+ annotations: { title: "Export the document as a PSD", ...READ7 },
10102
10648
  handler: async () => {
10103
10649
  const res = await controller.toPsd();
10104
10650
  return {
@@ -10116,7 +10662,7 @@ function createImagePsdTools(controller) {
10116
10662
  }
10117
10663
 
10118
10664
  // src/image-editor/transform-tools.ts
10119
- var READ7 = { readOnlyHint: true, destructiveHint: false, idempotentHint: true, openWorldHint: false };
10665
+ var READ8 = { readOnlyHint: true, destructiveHint: false, idempotentHint: true, openWorldHint: false };
10120
10666
  var WRITE6 = { readOnlyHint: false, destructiveHint: false, idempotentHint: false, openWorldHint: false };
10121
10667
  var round4 = (n, digits = 3) => Math.round(n * 10 ** digits) / 10 ** digits;
10122
10668
  var roundPoint = (p) => ({ x: round4(p.x), y: round4(p.y) });
@@ -10216,7 +10762,7 @@ function createImageTransformTools(controller) {
10216
10762
  },
10217
10763
  additionalProperties: false
10218
10764
  },
10219
- annotations: { title: "Get the Free Transform frame", ...READ7 },
10765
+ annotations: { title: "Get the Free Transform frame", ...READ8 },
10220
10766
  handler: (args) => {
10221
10767
  const init = targetFrom(args);
10222
10768
  if (init.content === "selection-copy") init.content = "selection";
@@ -10359,7 +10905,7 @@ function createImageTransformTools(controller) {
10359
10905
  }
10360
10906
 
10361
10907
  // src/image-editor/smart-tools.ts
10362
- var READ8 = { readOnlyHint: true, destructiveHint: false, idempotentHint: true, openWorldHint: false };
10908
+ var READ9 = { readOnlyHint: true, destructiveHint: false, idempotentHint: true, openWorldHint: false };
10363
10909
  var WRITE7 = { readOnlyHint: false, destructiveHint: false, idempotentHint: false, openWorldHint: false };
10364
10910
  var WRITE_IDEMPOTENT4 = { readOnlyHint: false, destructiveHint: false, idempotentHint: true, openWorldHint: false };
10365
10911
  var DESTRUCTIVE5 = { readOnlyHint: false, destructiveHint: true, idempotentHint: false, openWorldHint: false };
@@ -10418,7 +10964,7 @@ function createImageSmartObjectTools(controller) {
10418
10964
  title: "Read a smart object",
10419
10965
  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.",
10420
10966
  inputSchema: { type: "object", properties: { layerId: LAYER_ID4 }, required: ["layerId"], additionalProperties: false },
10421
- annotations: { title: "Read a smart object", ...READ8 },
10967
+ annotations: { title: "Read a smart object", ...READ9 },
10422
10968
  handler: ({ layerId }) => info(smartId(layerId))
10423
10969
  };
10424
10970
  const convert = {
@@ -10552,7 +11098,7 @@ function createImageSmartObjectTools(controller) {
10552
11098
  title: "Export a smart object\u2019s contents",
10553
11099
  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.",
10554
11100
  inputSchema: { type: "object", properties: { layerId: LAYER_ID4 }, required: ["layerId"], additionalProperties: false },
10555
- annotations: { title: "Export a smart object\u2019s contents", ...READ8 },
11101
+ annotations: { title: "Export a smart object\u2019s contents", ...READ9 },
10556
11102
  handler: ({ layerId }) => {
10557
11103
  const f = controller.exportSmartObjectContents(smartId(layerId));
10558
11104
  return { name: f.name, mimeType: f.mimeType, byteLength: f.bytes.length, base64: bytesToBase64(f.bytes) };
@@ -10719,7 +11265,7 @@ function createImageSmartObjectTools(controller) {
10719
11265
  }
10720
11266
 
10721
11267
  // src/image-editor/shape-tools.ts
10722
- var READ9 = { readOnlyHint: true, destructiveHint: false, idempotentHint: true, openWorldHint: false };
11268
+ var READ10 = { readOnlyHint: true, destructiveHint: false, idempotentHint: true, openWorldHint: false };
10723
11269
  var WRITE8 = { readOnlyHint: false, destructiveHint: false, idempotentHint: false, openWorldHint: false };
10724
11270
  var WRITE_IDEMPOTENT5 = { readOnlyHint: false, destructiveHint: false, idempotentHint: true, openWorldHint: false };
10725
11271
  var DESTRUCTIVE6 = { readOnlyHint: false, destructiveHint: true, idempotentHint: false, openWorldHint: false };
@@ -10973,7 +11519,7 @@ function createImageShapeTools(controller) {
10973
11519
  title: "Read a shape layer",
10974
11520
  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.",
10975
11521
  inputSchema: { type: "object", properties: { layerId: LAYER_ID5 }, required: ["layerId"], additionalProperties: false },
10976
- annotations: { title: "Read a shape layer", ...READ9 },
11522
+ annotations: { title: "Read a shape layer", ...READ10 },
10977
11523
  handler: ({ layerId }) => controller.getShapeLayer(shapeId(layerId))
10978
11524
  };
10979
11525
  const add2 = {
@@ -11122,7 +11668,7 @@ function createImageShapeTools(controller) {
11122
11668
  }
11123
11669
 
11124
11670
  // src/image-editor/type-tools.ts
11125
- var READ10 = { readOnlyHint: true, destructiveHint: false, idempotentHint: true, openWorldHint: false };
11671
+ var READ11 = { readOnlyHint: true, destructiveHint: false, idempotentHint: true, openWorldHint: false };
11126
11672
  var WRITE9 = { readOnlyHint: false, destructiveHint: false, idempotentHint: false, openWorldHint: false };
11127
11673
  var WRITE_IDEMPOTENT6 = { readOnlyHint: false, destructiveHint: false, idempotentHint: true, openWorldHint: false };
11128
11674
  var DESTRUCTIVE7 = { readOnlyHint: false, destructiveHint: true, idempotentHint: false, openWorldHint: false };
@@ -11173,7 +11719,7 @@ function createImageTypeTools(controller) {
11173
11719
  title: "List fonts",
11174
11720
  description: "Returns the font families type layers can use here (generic families such as sans-serif always work). Changes nothing.",
11175
11721
  inputSchema: { type: "object", properties: {}, additionalProperties: false },
11176
- annotations: { title: "List fonts", ...READ10 },
11722
+ annotations: { title: "List fonts", ...READ11 },
11177
11723
  handler: () => ({ families: controller.listFonts(), shaper: controller.getTextShaper()?.id ?? null })
11178
11724
  };
11179
11725
  const get = {
@@ -11181,7 +11727,7 @@ function createImageTypeTools(controller) {
11181
11727
  title: "Read a type layer",
11182
11728
  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.",
11183
11729
  inputSchema: { type: "object", properties: { layerId: LAYER_ID6 }, required: ["layerId"], additionalProperties: false },
11184
- annotations: { title: "Read a type layer", ...READ10 },
11730
+ annotations: { title: "Read a type layer", ...READ11 },
11185
11731
  handler: ({ layerId }) => controller.getTextLayer(typeId(layerId))
11186
11732
  };
11187
11733
  const add2 = {
@@ -11534,6 +12080,7 @@ function createImageEditorToolProvider(controller, options = {}) {
11534
12080
  ...createImageTypeTools(controller),
11535
12081
  ...createImageEffectTools(controller),
11536
12082
  ...createImagePaintTools(controller),
12083
+ ...createImageColorTools(controller),
11537
12084
  ...createImageRetouchTools(controller),
11538
12085
  ...createPigmentTools(),
11539
12086
  ...createImagePsdTools(controller)
@@ -12529,6 +13076,11 @@ function extractNormalizedLayerItems(controller, fallbackImageUrl = "") {
12529
13076
  };
12530
13077
  });
12531
13078
  }
13079
+ var BIT_DEPTH_OPTIONS = [
13080
+ { depth: 8, label: "8-bit", title: "8 bits per channel" },
13081
+ { depth: 16, label: "16-bit", title: "16 bits per channel: smooth gradients, no banding under strong adjustments" },
13082
+ { depth: 32, label: "32-bit", title: "32-bit float per channel, linear light (HDR)" }
13083
+ ];
12532
13084
 
12533
13085
  // src/image-editor/layers/effects-ui.ts
12534
13086
  function layerEffectsPanel(layer) {
@@ -12703,6 +13255,32 @@ function effectControlChange(effect, key, value, patterns = []) {
12703
13255
  return { [top]: setPath(getPath(effect, top), rest, value) };
12704
13256
  }
12705
13257
 
13258
+ // src/image-editor/layers/color-ui.ts
13259
+ var WORKING_SPACE_OPTIONS = BUILTIN_PROFILES.filter((b) => b.colorSpace === "rgb").map((b) => ({ id: b.id, label: b.description }));
13260
+ var PROOF_PROFILE_OPTIONS = BUILTIN_PROFILES.map((b) => ({ id: b.id, label: b.description }));
13261
+ var INTENT_OPTIONS = [
13262
+ { id: "relative", label: "Relative Colorimetric", title: "Keep in-gamut colours exact, clip the rest (photos)" },
13263
+ { id: "perceptual", label: "Perceptual", title: "Compress the whole gamut smoothly" },
13264
+ { id: "saturation", label: "Saturation", title: "Keep colours vivid (graphics)" },
13265
+ { id: "absolute", label: "Absolute Colorimetric", title: "Keep the source white too (paper proofs)" }
13266
+ ];
13267
+ var COLOR_MODE_OPTIONS = [
13268
+ { id: "rgb", label: "RGB", title: "Edit and export RGB in the working space" },
13269
+ { id: "cmyk", label: "CMYK", title: "For print: proof through the CMYK profile and export CMYK" },
13270
+ { id: "lab", label: "Lab", title: "Export Lab channels" }
13271
+ ];
13272
+ function colorPanelView(controller) {
13273
+ const profile = controller.getColorProfile();
13274
+ const proof = controller.getState().proofSetup ?? null;
13275
+ return {
13276
+ profileName: profile.name,
13277
+ workingSpace: profile.builtin,
13278
+ mode: profile.mode,
13279
+ cmykProfileName: profile.cmykProfile?.name ?? null,
13280
+ proof: proof ? { id: proof.profile.builtin ?? null, name: proof.profile.name, intent: proof.intent, simulatePaper: proof.simulatePaper, gamutWarning: proof.gamutWarning, bpc: proof.bpc } : null
13281
+ };
13282
+ }
13283
+
12706
13284
  // src/image-editor/psd/ui.ts
12707
13285
  var PSD_FILE_ACCEPT = ".psd,.psb,image/vnd.adobe.photoshop,application/x-photoshop";
12708
13286
  function blobBytes(file) {
@@ -13315,9 +13893,13 @@ export {
13315
13893
  AS_SHOT_KELVIN,
13316
13894
  BACKGROUND_LAYER_ID,
13317
13895
  BACKGROUND_LAYER_NAME,
13896
+ BIT_DEPTHS,
13897
+ BIT_DEPTH_OPTIONS,
13318
13898
  BLEND_MODES,
13319
13899
  BLUR_FILTER_ID,
13320
13900
  BRUSH_OPTION_CONTROLS,
13901
+ BUILTIN_PROFILES,
13902
+ COLOR_MODE_OPTIONS,
13321
13903
  CoverageMap,
13322
13904
  D50,
13323
13905
  D65,
@@ -13329,6 +13911,7 @@ export {
13329
13911
  DEFAULT_GLOBAL_LIGHT,
13330
13912
  DEFAULT_MITER_LIMIT,
13331
13913
  DEFAULT_MIXER,
13914
+ DEFAULT_PROOF,
13332
13915
  DEFAULT_REFINE_EDGE_PARAMS,
13333
13916
  DEFAULT_RETOUCH,
13334
13917
  DEFAULT_SELECTION_TOOL_OPTIONS,
@@ -13358,6 +13941,8 @@ export {
13358
13941
  IMAGE_EDITOR_SCHEMA_VERSION,
13359
13942
  IMAGE_EDITOR_STATE_VERSION,
13360
13943
  IMAGE_FILTER_PRESETS,
13944
+ INTENTS,
13945
+ INTENT_OPTIONS,
13361
13946
  INTERPOLATIONS,
13362
13947
  ImageEditorController,
13363
13948
  ImageEditorRenderer,
@@ -13391,6 +13976,7 @@ export {
13391
13976
  PIGMENT_LAMBDA_MIN,
13392
13977
  PIGMENT_LAMBDA_STEP,
13393
13978
  PIGMENT_WAVELENGTHS,
13979
+ PROOF_PROFILE_OPTIONS,
13394
13980
  PSD_BLEND_KEYS,
13395
13981
  PSD_FILE_ACCEPT,
13396
13982
  PaintExchange,
@@ -13444,6 +14030,7 @@ export {
13444
14030
  WARP_STYLES,
13445
14031
  WARP_STYLE_LABELS,
13446
14032
  WARP_STYLE_OPTIONS,
14033
+ WORKING_SPACE_OPTIONS,
13447
14034
  XYZ_D50_TO_SRGB,
13448
14035
  ZlibError,
13449
14036
  activeAdjustmentLayers,
@@ -13465,6 +14052,7 @@ export {
13465
14052
  annotationOfShape,
13466
14053
  annotationOfType,
13467
14054
  applyAdjustmentLayers,
14055
+ applyAdjustmentLayersFloat,
13468
14056
  applyAdjustments,
13469
14057
  applyBoxBlur,
13470
14058
  applyDepthMask,
@@ -13517,10 +14105,13 @@ export {
13517
14105
  cloneSourceLabel,
13518
14106
  collectTiles,
13519
14107
  colorBalanceChannel,
14108
+ colorModePlanes,
14109
+ colorPanelView,
13520
14110
  colorRange,
13521
14111
  combineSelections,
13522
14112
  compileAdjustmentLayer,
13523
14113
  componentPath,
14114
+ compositeDeep,
13524
14115
  compositeSurfaceOver,
13525
14116
  computeHistogram,
13526
14117
  computeSelectionBounds,
@@ -13530,6 +14121,9 @@ export {
13530
14121
  contractCoverage,
13531
14122
  controlPosition,
13532
14123
  controlTrackBackground,
14124
+ convertHex,
14125
+ convertSurfaceColors,
14126
+ convertSurfaceDepth,
13533
14127
  convertToSmartObject,
13534
14128
  copySmartObject,
13535
14129
  cornersFromMatrix,
@@ -13540,6 +14134,7 @@ export {
13540
14134
  createBlenderScratch,
13541
14135
  createCanvasTextShaper,
13542
14136
  createImageCaptureSurface,
14137
+ createImageColorTools,
13543
14138
  createImageEditTools,
13544
14139
  createImageEditorToolProvider,
13545
14140
  createImageEffectTools,
@@ -13552,6 +14147,7 @@ export {
13552
14147
  createImageSmartObjectTools,
13553
14148
  createImageTransformTools,
13554
14149
  createImageTypeTools,
14150
+ createMetricTextShaper,
13555
14151
  createOutlineTextShaper,
13556
14152
  createPigment,
13557
14153
  createPigmentTools,
@@ -13566,6 +14162,7 @@ export {
13566
14162
  curveSvgPath,
13567
14163
  dabCoverage,
13568
14164
  dabRect,
14165
+ decodeDeepSurface,
13569
14166
  decodeLayerChannel,
13570
14167
  decodeLayerMask,
13571
14168
  decodeMergedImage,
@@ -13573,6 +14170,8 @@ export {
13573
14170
  decodePng,
13574
14171
  decodePngDataUrl,
13575
14172
  decodeSelectionRle,
14173
+ deepBytes,
14174
+ deepTile,
13576
14175
  defaultAdjustmentParams,
13577
14176
  defaultEffectGradient,
13578
14177
  defaultLayerEffect,
@@ -13580,11 +14179,14 @@ export {
13580
14179
  deflate,
13581
14180
  deflateRaw,
13582
14181
  depthMaskAlpha,
14182
+ depthOf,
13583
14183
  depthRasterMask,
13584
14184
  describeLayer,
13585
14185
  describeLayerEffects,
13586
14186
  describeSelection,
13587
14187
  describeTransform,
14188
+ deserializeColorMode,
14189
+ deserializeColorProfile,
13588
14190
  deserializeEffects,
13589
14191
  deserializeLayer,
13590
14192
  deserializeLayers,
@@ -13594,8 +14196,10 @@ export {
13594
14196
  developFromAdjustments,
13595
14197
  developTarget,
13596
14198
  dilateMask,
14199
+ displayTransform,
13597
14200
  distanceTransform,
13598
14201
  documentContents,
14202
+ documentProfile,
13599
14203
  dodgeBurn,
13600
14204
  dodgeBurnCurve,
13601
14205
  downloadPsd,
@@ -13607,6 +14211,7 @@ export {
13607
14211
  effectColorHex,
13608
14212
  effectControlChange,
13609
14213
  effectReach,
14214
+ effectiveProof,
13610
14215
  effectsFingerprint,
13611
14216
  effectsMargin,
13612
14217
  effectsScale,
@@ -13614,6 +14219,7 @@ export {
13614
14219
  ellipseSelection,
13615
14220
  emptyHistogram,
13616
14221
  emptySurface,
14222
+ encodeDeepSurface,
13617
14223
  encodePng,
13618
14224
  encodeRle,
13619
14225
  encodeSelectionRle,
@@ -13689,6 +14295,7 @@ export {
13689
14295
  invertSelection,
13690
14296
  isActiveFilter,
13691
14297
  isAdjustmentKind,
14298
+ isBitDepth,
13692
14299
  isBlendMode,
13693
14300
  isContentsDecoded,
13694
14301
  isDefaultDepthMask,
@@ -13709,6 +14316,7 @@ export {
13709
14316
  isSelectionMode,
13710
14317
  isSeparableBlendMode,
13711
14318
  isSpatialFilter,
14319
+ isSrgb,
13712
14320
  isTransformAnchor,
13713
14321
  isValidWarp,
13714
14322
  kelvinToTemperature,
@@ -13746,6 +14354,7 @@ export {
13746
14354
  liveShapeSubpaths,
13747
14355
  loadImageEditorDocument,
13748
14356
  loadMixer,
14357
+ loadOpenTypeFont,
13749
14358
  luma,
13750
14359
  magicWand,
13751
14360
  mapCropHandle,
@@ -13812,6 +14421,7 @@ export {
13812
14421
  parseEffectColor,
13813
14422
  parseLfx2,
13814
14423
  parseLrfx,
14424
+ parseOpenTypeFont,
13815
14425
  parsePatternBlock,
13816
14426
  parseSelectionMode,
13817
14427
  parseVersionedDescriptor,
@@ -13834,10 +14444,12 @@ export {
13834
14444
  posterizeValue,
13835
14445
  presetWarp,
13836
14446
  previewStep,
14447
+ profileBytes,
13837
14448
  projectImagePoint,
13838
14449
  projectImageRect,
13839
14450
  projectMaskCoverage,
13840
14451
  projectQuarterTurn,
14452
+ proxyOf,
13841
14453
  psdBytesFromBase64,
13842
14454
  psdFileName,
13843
14455
  psdPatternToEffectPattern,
@@ -13851,6 +14463,7 @@ export {
13851
14463
  rasterizeDab,
13852
14464
  rasterizeSmartObject,
13853
14465
  readAdjustmentControls,
14466
+ readDeepRegion,
13854
14467
  readPixel,
13855
14468
  readPsd,
13856
14469
  readRegion,
@@ -13891,6 +14504,7 @@ export {
13891
14504
  resizeWarpMesh,
13892
14505
  resolveExternalAssets,
13893
14506
  resolveMosaicRegionBounds,
14507
+ resolveProfile,
13894
14508
  restyleRange,
13895
14509
  rgbToGray,
13896
14510
  rgbToHsl,
@@ -13912,6 +14526,8 @@ export {
13912
14526
  selectionToCoverage,
13913
14527
  selectionToPixels,
13914
14528
  selectionValueAt,
14529
+ serializeColorMode,
14530
+ serializeColorProfile,
13915
14531
  serializeContents,
13916
14532
  serializeEffects,
13917
14533
  serializeLayer,
@@ -13951,11 +14567,15 @@ export {
13951
14567
  spotHealPixels,
13952
14568
  spotHealWindow,
13953
14569
  srgb8ToLab,
14570
+ srgbDecode,
14571
+ srgbEncode,
13954
14572
  srgbToLinear,
13955
14573
  standardBlendMode,
13956
14574
  styleAt,
13957
14575
  sumHistograms,
13958
14576
  surfaceBytes,
14577
+ surfaceDepth,
14578
+ surfaceFromDeepPixels,
13959
14579
  surfaceFromPixels,
13960
14580
  surfacesEqual,
13961
14581
  tempTintToXy,
@@ -13967,10 +14587,19 @@ export {
13967
14587
  tileCoord,
13968
14588
  tileKey,
13969
14589
  tileRect,
14590
+ tileSamples,
14591
+ tileSamples16,
14592
+ tileSamples32,
13970
14593
  tilesInRect,
13971
14594
  tipFalloff,
13972
14595
  tipReach,
13973
14596
  tipShape,
14597
+ to16From32,
14598
+ to16From8,
14599
+ to32From16,
14600
+ to32From8,
14601
+ to8From16,
14602
+ to8From32,
13974
14603
  toHex,
13975
14604
  toTextSpace,
13976
14605
  toneCurve,
@@ -14020,6 +14649,7 @@ export {
14020
14649
  withFilterMaskSurface,
14021
14650
  withNewIds,
14022
14651
  writeAdjustmentBlock,
14652
+ writeDeepRegion,
14023
14653
  writeLfx2,
14024
14654
  writePatternBlock,
14025
14655
  writePsd,