@pixerate/editor 0.108.0 → 0.110.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 (105) hide show
  1. package/dist/animation/index.cjs +5 -5
  2. package/dist/animation/index.cjs.map +1 -1
  3. package/dist/animation/index.js +9 -9
  4. package/dist/animation-editor/index.cjs +59 -59
  5. package/dist/animation-editor/index.cjs.map +1 -1
  6. package/dist/animation-editor/index.d.cts +2 -1
  7. package/dist/animation-editor/index.d.ts +2 -1
  8. package/dist/animation-editor/index.js +29 -29
  9. package/dist/blending-DXl06NSx.d.ts +486 -0
  10. package/dist/blending-aOE6Y3UE.d.cts +486 -0
  11. package/dist/canvas/index.cjs +27 -27
  12. package/dist/canvas/index.cjs.map +1 -1
  13. package/dist/canvas/index.js +11 -11
  14. package/dist/{chunk-2FGRTYJN.cjs → chunk-477UVMNR.cjs} +3 -3
  15. package/dist/{chunk-2FGRTYJN.cjs.map → chunk-477UVMNR.cjs.map} +1 -1
  16. package/dist/chunk-7VOHOPLW.cjs +1136 -0
  17. package/dist/chunk-7VOHOPLW.cjs.map +1 -0
  18. package/dist/{chunk-HUQ7EHVI.js → chunk-BLWCAQCW.js} +723 -1975
  19. package/dist/chunk-BLWCAQCW.js.map +1 -0
  20. package/dist/{chunk-SOROZ2BF.js → chunk-DMRSJBJF.js} +1939 -318
  21. package/dist/chunk-DMRSJBJF.js.map +1 -0
  22. package/dist/chunk-FTBZPI77.cjs +1 -0
  23. package/dist/chunk-FTBZPI77.cjs.map +1 -0
  24. package/dist/{chunk-WMBIJCHA.js → chunk-HNS7UHCM.js} +5 -1
  25. package/dist/{chunk-WMBIJCHA.js.map → chunk-HNS7UHCM.js.map} +1 -1
  26. package/dist/chunk-JLYX4V57.cjs +2001 -0
  27. package/dist/chunk-JLYX4V57.cjs.map +1 -0
  28. package/dist/chunk-KB2YBCVS.js +2001 -0
  29. package/dist/chunk-KB2YBCVS.js.map +1 -0
  30. package/dist/{chunk-G4BIYE6R.js → chunk-LQEEDHOP.js} +40 -2
  31. package/dist/chunk-LQEEDHOP.js.map +1 -0
  32. package/dist/{chunk-FRD3FUXI.cjs → chunk-MGL4U5WA.cjs} +2144 -523
  33. package/dist/chunk-MGL4U5WA.cjs.map +1 -0
  34. package/dist/{chunk-T3QZ6S47.cjs → chunk-POGBUYWE.cjs} +131 -93
  35. package/dist/chunk-POGBUYWE.cjs.map +1 -0
  36. package/dist/chunk-VKK5VS2T.js +1 -0
  37. package/dist/{chunk-UTEVPQTS.cjs → chunk-W3GXSUSQ.cjs} +746 -1998
  38. package/dist/chunk-W3GXSUSQ.cjs.map +1 -0
  39. package/dist/{chunk-SZ6KMZNM.cjs → chunk-XIOMMGPD.cjs} +6 -2
  40. package/dist/chunk-XIOMMGPD.cjs.map +1 -0
  41. package/dist/{chunk-JK6M6V7G.js → chunk-Z3YWVUWT.js} +4 -4
  42. package/dist/{chunk-3PHA3UKI.js → chunk-Z4BC6M74.js} +523 -121
  43. package/dist/chunk-Z4BC6M74.js.map +1 -0
  44. package/dist/{export-a9bnF7Cl.d.ts → export-CO2ryu3I.d.ts} +419 -18
  45. package/dist/{export-JGWQEPJn.d.cts → export-DRiLzP_j.d.cts} +419 -18
  46. package/dist/image-editor/index.cjs +1665 -1113
  47. package/dist/image-editor/index.cjs.map +1 -1
  48. package/dist/image-editor/index.d.cts +78 -14
  49. package/dist/image-editor/index.d.ts +78 -14
  50. package/dist/image-editor/index.js +843 -291
  51. package/dist/image-editor/index.js.map +1 -1
  52. package/dist/paint/index.cjs +28 -4
  53. package/dist/paint/index.cjs.map +1 -1
  54. package/dist/paint/index.d.cts +3 -89
  55. package/dist/paint/index.d.ts +3 -89
  56. package/dist/paint/index.js +27 -3
  57. package/dist/paint-editor/index.cjs +1056 -287
  58. package/dist/paint-editor/index.cjs.map +1 -1
  59. package/dist/paint-editor/index.d.cts +304 -22
  60. package/dist/paint-editor/index.d.ts +304 -22
  61. package/dist/paint-editor/index.js +852 -83
  62. package/dist/paint-editor/index.js.map +1 -1
  63. package/dist/raster/index.cjs +7 -3
  64. package/dist/raster/index.cjs.map +1 -1
  65. package/dist/raster/index.d.cts +3 -129
  66. package/dist/raster/index.d.ts +3 -129
  67. package/dist/raster/index.js +6 -2
  68. package/dist/{compositor-D92URMlm.d.cts → render-BEriFuqK.d.cts} +136 -2
  69. package/dist/{compositor-Kt_M4ruo.d.ts → render-tjzKaik-.d.ts} +136 -2
  70. package/dist/{surface-CkKOHGPm.d.cts → surface-C8RRg_Bh.d.cts} +109 -1
  71. package/dist/{surface-CkKOHGPm.d.ts → surface-C8RRg_Bh.d.ts} +109 -1
  72. package/dist/{svg-SseJTIrA.d.ts → svg-D_cfkhlB.d.cts} +3 -629
  73. package/dist/{svg-ClVIG4T7.d.cts → svg-wIaKLrU0.d.ts} +3 -629
  74. package/dist/types-DtL_qLFz.d.ts +630 -0
  75. package/dist/types-DyQq-WPY.d.cts +630 -0
  76. package/dist/vector/index.cjs +563 -563
  77. package/dist/vector/index.cjs.map +1 -1
  78. package/dist/vector/index.d.cts +4 -2
  79. package/dist/vector/index.d.ts +4 -2
  80. package/dist/vector/index.js +69 -69
  81. package/dist/video-editor/index.cjs +3 -3
  82. package/dist/video-editor/index.js +2 -2
  83. package/package.json +1 -1
  84. package/skills/pixerate-paint-editor/SKILL.md +49 -13
  85. package/dist/chunk-3PHA3UKI.js.map +0 -1
  86. package/dist/chunk-6BU5WU4R.cjs +0 -1
  87. package/dist/chunk-6BU5WU4R.cjs.map +0 -1
  88. package/dist/chunk-DWGZUSES.cjs +0 -694
  89. package/dist/chunk-DWGZUSES.cjs.map +0 -1
  90. package/dist/chunk-FRD3FUXI.cjs.map +0 -1
  91. package/dist/chunk-G4BIYE6R.js.map +0 -1
  92. package/dist/chunk-HUFQD4CB.cjs +0 -734
  93. package/dist/chunk-HUFQD4CB.cjs.map +0 -1
  94. package/dist/chunk-HUQ7EHVI.js.map +0 -1
  95. package/dist/chunk-KKREERP2.js +0 -694
  96. package/dist/chunk-KKREERP2.js.map +0 -1
  97. package/dist/chunk-SOROZ2BF.js.map +0 -1
  98. package/dist/chunk-SZ6KMZNM.cjs.map +0 -1
  99. package/dist/chunk-T3QZ6S47.cjs.map +0 -1
  100. package/dist/chunk-UTEVPQTS.cjs.map +0 -1
  101. package/dist/chunk-XYBJKUAU.js +0 -1
  102. package/dist/mixer-5ziHU8OS.d.cts +0 -282
  103. package/dist/mixer-DNqiYXYQ.d.ts +0 -282
  104. /package/dist/{chunk-XYBJKUAU.js.map → chunk-VKK5VS2T.js.map} +0 -0
  105. /package/dist/{chunk-JK6M6V7G.js.map → chunk-Z3YWVUWT.js.map} +0 -0
@@ -4,18 +4,23 @@ import {
4
4
  import {
5
5
  createCaptureTool
6
6
  } from "../chunk-REOJJIY3.js";
7
- import "../chunk-XYBJKUAU.js";
7
+ import "../chunk-VKK5VS2T.js";
8
8
  import {
9
9
  ADJUSTMENT_KEYS,
10
10
  ADJUSTMENT_KINDS,
11
11
  ADJUSTMENT_LUT_SIZE,
12
12
  AS_SHOT_KELVIN,
13
+ BACKGROUND_LAYER_ID,
14
+ BACKGROUND_LAYER_NAME,
15
+ BLUR_FILTER_ID,
13
16
  BROWSER_IMAGE_TYPES,
14
17
  D50,
15
18
  D65,
16
19
  DEFAULT_ADJUSTMENTS_VALUES,
17
20
  DEFAULT_DEPTH_MASK_VALUES,
21
+ DEFAULT_MITER_LIMIT,
18
22
  DEFAULT_TRANSFORM_VALUES,
23
+ DEVELOP_FILTER_ID,
19
24
  EMPTY_DISTANCE,
20
25
  FREE_TRANSFORM_MODES,
21
26
  HUE_RANGE_LABELS,
@@ -23,6 +28,7 @@ import {
23
28
  LATEST_PROCESS_VERSION,
24
29
  LAYERED_SCHEMA_VERSION,
25
30
  LEGACY_PROCESS_VERSION,
31
+ LIVE_SHAPE_KINDS,
26
32
  LUMA_709,
27
33
  LayerCompositor,
28
34
  LayerOperationError,
@@ -34,6 +40,8 @@ import {
34
40
  PsdError,
35
41
  SELECTION_MODES,
36
42
  SELECTION_MODIFY_OPS,
43
+ SHAPE_OPERATIONS,
44
+ SHAPE_RENDER_MARGIN,
37
45
  SMART_RENDER_MARGIN,
38
46
  SUPPORTED_ADJUSTMENT_KEYS,
39
47
  TINT_SCALE,
@@ -49,7 +57,13 @@ import {
49
57
  adjustmentKindLabel,
50
58
  adjustmentLayersKey,
51
59
  adjustmentNodeAsLayer,
60
+ adjustmentNodeFromLegacy,
52
61
  anchorPoint,
62
+ annotationBlend,
63
+ annotationLayerFor,
64
+ annotationLayerName,
65
+ annotationOfLayer,
66
+ annotationOfShape,
53
67
  applyAdjustmentLayers,
54
68
  applyAdjustments,
55
69
  applyBoxBlur,
@@ -60,6 +74,7 @@ import {
60
74
  applyParams,
61
75
  applyProcess2Adjustments,
62
76
  applySmartFilters,
77
+ backgroundLayer,
63
78
  blackWhiteGray,
64
79
  blurFilter,
65
80
  borderCoverage,
@@ -73,6 +88,7 @@ import {
73
88
  colorBalanceChannel,
74
89
  combineSelections,
75
90
  compileAdjustmentLayer,
91
+ componentPath,
76
92
  computeSelectionBounds,
77
93
  contentsBox,
78
94
  contentsSurface,
@@ -83,6 +99,7 @@ import {
83
99
  coverageToByte,
84
100
  createAdjustmentLayer,
85
101
  createSelectionMask,
102
+ createShapeLayer,
86
103
  curveLutFromLevels,
87
104
  decodeLayerChannel,
88
105
  decodeLayerMask,
@@ -91,7 +108,9 @@ import {
91
108
  decodePngDataUrl,
92
109
  decodeSelectionRle,
93
110
  defaultAdjustmentParams,
111
+ defaultShapeName,
94
112
  depthMaskAlpha,
113
+ depthRasterMask,
95
114
  describeTransform,
96
115
  deserializeEffects,
97
116
  deserializeLayer,
@@ -99,6 +118,8 @@ import {
99
118
  deserializePatterns,
100
119
  deserializeSelection,
101
120
  developFilter,
121
+ developFromAdjustments,
122
+ developTarget,
102
123
  distanceTransform,
103
124
  documentContents,
104
125
  duplicateLayer,
@@ -110,6 +131,7 @@ import {
110
131
  exportPsd,
111
132
  featherCoverage,
112
133
  filterMaskCoverage,
134
+ flattenPath,
113
135
  flipCorners,
114
136
  fullSelection,
115
137
  geometryHomography,
@@ -120,6 +142,7 @@ import {
120
142
  getViewportProjection,
121
143
  greyMorphology,
122
144
  groupLayers,
145
+ hasResampledLegacyMask,
123
146
  hasSurface,
124
147
  hasWarp,
125
148
  hashMaskContent,
@@ -141,8 +164,11 @@ import {
141
164
  isActiveFilter,
142
165
  isAdjustmentKind,
143
166
  isContentsDecoded,
167
+ isDefaultDepthMask,
144
168
  isIdentityTransform,
169
+ isLegacyShape,
145
170
  isQuarterTurnSwapped,
171
+ isRenderedLayer,
146
172
  isSelectionEmpty,
147
173
  isSelectionMode,
148
174
  isSpatialFilter,
@@ -156,12 +182,20 @@ import {
156
182
  layerName,
157
183
  layerTree,
158
184
  layerTypeLabel,
185
+ layersFromLegacy,
186
+ legacyAdjustmentLayer,
187
+ legacyMaskOf,
188
+ legacyView,
159
189
  levelsValue,
160
190
  linearSrgbToXy,
161
191
  linearToSrgb,
192
+ liveComponent,
193
+ liveShapeSubpaths,
162
194
  mapCropHandle,
195
+ mapSubpaths,
163
196
  matrixFromCorners,
164
197
  matrixFromParams,
198
+ mergeShapeComponents,
165
199
  modifySelection,
166
200
  moveLayer,
167
201
  moveSmartFilter,
@@ -174,13 +208,19 @@ import {
174
208
  normalizeAngle,
175
209
  normalizeFilter,
176
210
  normalizeLayerMask,
211
+ normalizeLiveShape,
177
212
  normalizePlacement,
178
213
  normalizeProcessVersion,
214
+ normalizeShapePaint,
215
+ normalizeShapeStroke,
179
216
  normalizeSmartFilter,
217
+ normalizeSubpaths,
180
218
  offsetLayer,
219
+ offsetShape,
181
220
  offsetSmartObject,
182
221
  parseAdjustmentBlock,
183
222
  parseBlend,
223
+ parseCssColor,
184
224
  parseLfx2,
185
225
  parseLrfx,
186
226
  parsePatternBlock,
@@ -203,6 +243,8 @@ import {
203
243
  psdToLayers,
204
244
  rangeLightness,
205
245
  rasterContents,
246
+ rasterMaskFromLegacy,
247
+ rasterizeCoverage,
206
248
  rasterizeSmartObject,
207
249
  readPsd,
208
250
  referencePoint,
@@ -211,12 +253,17 @@ import {
211
253
  removeLayerEffect,
212
254
  removeLayerFilter,
213
255
  removeSmartFilter,
256
+ renderShape,
257
+ renderShapeRect,
214
258
  renderSmartObject,
215
259
  replaceContents,
216
260
  requireLayer,
261
+ requireOperation,
262
+ requireShapeLayer,
217
263
  requireSmartObject,
218
264
  requireUnlocked,
219
265
  rerender,
266
+ rerenderShape,
220
267
  resizeSelection,
221
268
  resolveExternalAssets,
222
269
  rgbToGray,
@@ -244,6 +291,12 @@ import {
244
291
  setSmartFilterMask,
245
292
  setSmartFiltersEnabled,
246
293
  setSmartPlacement,
294
+ shapeAsPixelLayer,
295
+ shapeFromAnnotation,
296
+ shapeGeometryBounds,
297
+ shapeLayers,
298
+ shapeOutline,
299
+ shapeRenderClip,
247
300
  shiftCoverage,
248
301
  smartContentsFile,
249
302
  smartObjectLayers,
@@ -255,6 +308,7 @@ import {
255
308
  toneLuts,
256
309
  transformHomography,
257
310
  transformMap,
311
+ transformShapeComponents,
258
312
  transformedBox,
259
313
  transformedPoint,
260
314
  ungroupLayer,
@@ -263,6 +317,7 @@ import {
263
317
  updateLayerEffect,
264
318
  updateLayerFilter,
265
319
  updateLayerMaskProps,
320
+ updateShapeLayer,
266
321
  updateSmartFilter,
267
322
  uvToXy,
268
323
  versionedDescriptorBytes,
@@ -279,7 +334,7 @@ import {
279
334
  writePsd,
280
335
  xyToTempTint,
281
336
  xyToUv
282
- } from "../chunk-SOROZ2BF.js";
337
+ } from "../chunk-DMRSJBJF.js";
283
338
  import {
284
339
  DEFAULT_GLOBAL_LIGHT,
285
340
  DIRECT_GAUSSIAN_MAX_SIGMA,
@@ -343,7 +398,7 @@ import {
343
398
  validateLayerEffect,
344
399
  validateLayerEffects,
345
400
  warpSurface
346
- } from "../chunk-G4BIYE6R.js";
401
+ } from "../chunk-LQEEDHOP.js";
347
402
  import {
348
403
  applyHomography,
349
404
  boxQuad,
@@ -355,8 +410,10 @@ import {
355
410
  } from "../chunk-GX332W2C.js";
356
411
  import {
357
412
  CoverageMap,
413
+ DEFAULT_BLENDER,
358
414
  DEFAULT_BRUSH,
359
415
  DEFAULT_MIXER,
416
+ DEFAULT_SMUDGE,
360
417
  DabWalker,
361
418
  EMPTY_MIXER_LOAD,
362
419
  MAX_BRUSH_SIZE,
@@ -364,23 +421,33 @@ import {
364
421
  MIXER_PICKUP_RATE,
365
422
  MIXING_MODELS,
366
423
  MixerStroke,
424
+ PaintExchange,
425
+ SmudgeTip,
367
426
  StrokeCoverage,
427
+ TileMedium,
368
428
  applyStrokeToTile,
429
+ blendRadius,
430
+ blenderDab,
369
431
  cleanMixer,
370
432
  coverageTileCoord,
433
+ createBlenderScratch,
371
434
  createPigmentTools,
372
435
  dabCoverage,
373
436
  dabRect,
437
+ exchangeRates,
374
438
  loadMixer,
439
+ mixerRates,
440
+ normalizeBlender,
375
441
  normalizeBrush,
376
442
  normalizeMixer,
443
+ normalizeSmudge,
377
444
  rasterizeDab,
378
445
  smoothPoints,
379
446
  tipFalloff,
380
447
  tipReach,
381
448
  tipShape,
382
449
  walkDabs
383
- } from "../chunk-3PHA3UKI.js";
450
+ } from "../chunk-Z4BC6M74.js";
384
451
  import {
385
452
  BLEND_MODES,
386
453
  PAINTERLY_BLEND_MODES,
@@ -415,6 +482,7 @@ import {
415
482
  mixPigments,
416
483
  normalizeBlendMode,
417
484
  packRgba,
485
+ pigment8,
418
486
  readPixel,
419
487
  readRegion,
420
488
  reflectanceToRgb,
@@ -433,7 +501,7 @@ import {
433
501
  translateSurface,
434
502
  uniformTile,
435
503
  writeRegion
436
- } from "../chunk-WMBIJCHA.js";
504
+ } from "../chunk-HNS7UHCM.js";
437
505
  import {
438
506
  WARP_STYLES,
439
507
  WARP_STYLE_LABELS,
@@ -444,6 +512,7 @@ import {
444
512
  multiplyMatrices,
445
513
  pullWarpMesh,
446
514
  resizeWarpMesh,
515
+ roundSubpaths,
447
516
  translateMatrix,
448
517
  warpMeshColumns,
449
518
  warpMeshEditPoints,
@@ -451,7 +520,10 @@ import {
451
520
  warpMeshParamAt,
452
521
  warpMeshRows,
453
522
  warpPresetMesh
454
- } from "../chunk-KKREERP2.js";
523
+ } from "../chunk-BLWCAQCW.js";
524
+ import "../chunk-YCSFS3GK.js";
525
+ import "../chunk-ZRJMJIQU.js";
526
+ import "../chunk-TGD52FEZ.js";
455
527
 
456
528
  // src/image-editor/histogram.ts
457
529
  var HISTOGRAM_BINS = 256;
@@ -554,249 +626,6 @@ function quickMaskPixels(mask, style = {}) {
554
626
  return out;
555
627
  }
556
628
 
557
- // src/image-editor/layers/migrate.ts
558
- var BACKGROUND_LAYER_ID = "background";
559
- var BACKGROUND_LAYER_NAME = "Background";
560
- var DEVELOP_FILTER_ID = "develop";
561
- var BLUR_FILTER_ID = "blur";
562
- var legacyMaskMemo = /* @__PURE__ */ new WeakMap();
563
- function rasterMaskFromLegacy(mask, width, height) {
564
- const sel = decodeLayerMask(mask);
565
- if (!sel) return null;
566
- const sized = sel.width === width && sel.height === height ? sel : resizeSelection(sel, width, height);
567
- const surface = surfaceFromPixels({ width, height, data: sized.data }, 1, 0, 0, 0);
568
- legacyMaskMemo.set(surface, { width: mask.width, height: mask.height, rle: mask.rle });
569
- const out = { surface, defaultValue: 0 };
570
- if (mask.density !== void 0 && mask.density !== 1) out.density = mask.density;
571
- if (mask.feather) out.feather = mask.feather;
572
- if (mask.enabled === false) out.enabled = false;
573
- return out;
574
- }
575
- function hasResampledLegacyMask(layers, width, height) {
576
- for (const l of layers) {
577
- if (l.type !== "adjustment" || !l.mask) continue;
578
- const memo = legacyMaskMemo.get(l.mask.surface);
579
- if (memo && (memo.width !== width || memo.height !== height)) return true;
580
- }
581
- return false;
582
- }
583
- function legacyMaskOf(mask, width, height) {
584
- let memo = legacyMaskMemo.get(mask.surface);
585
- if (!memo) {
586
- const data = readRegion(mask.surface, { x: 0, y: 0, width, height });
587
- memo = { width, height, rle: encodeSelectionRle(new Uint8Array(data.buffer, data.byteOffset, data.length)) };
588
- legacyMaskMemo.set(mask.surface, memo);
589
- }
590
- return normalizeLayerMask({ ...memo, density: mask.density, feather: mask.feather, enabled: mask.enabled });
591
- }
592
- function sameJson(a, b) {
593
- return JSON.stringify(a) === JSON.stringify(b);
594
- }
595
- function isDefaultDepthMask(mask) {
596
- if (!mask) return true;
597
- return sameJson({ ...DEFAULT_DEPTH_MASK_VALUES, ...mask, depthRange: [...mask.depthRange ?? []] }, DEFAULT_DEPTH_MASK_VALUES);
598
- }
599
- function developFromAdjustments(adjustments) {
600
- return {
601
- id: DEVELOP_FILTER_ID,
602
- kind: "develop",
603
- enabled: true,
604
- brightness: adjustments.brightness ?? 0,
605
- contrast: adjustments.contrast ?? 0,
606
- saturation: adjustments.saturation ?? 0,
607
- exposure: adjustments.exposure ?? 0,
608
- temperature: adjustments.temperature ?? 0,
609
- tint: adjustments.tint ?? 0,
610
- process: normalizeProcessVersion(adjustments.process)
611
- };
612
- }
613
- function annotationLayerName(annotation) {
614
- switch (annotation.type) {
615
- case "pen":
616
- return "Pen";
617
- case "rect":
618
- return "Rectangle";
619
- case "circle":
620
- return "Ellipse";
621
- case "arrow":
622
- return "Arrow";
623
- case "line":
624
- return "Line";
625
- case "text":
626
- return annotation.text ? annotation.text.slice(0, 32) : "Text";
627
- case "image":
628
- return "Image";
629
- }
630
- }
631
- function annotationLayerFor(annotation) {
632
- return {
633
- type: "annotation",
634
- id: annotation.id,
635
- name: annotationLayerName(annotation),
636
- visible: true,
637
- opacity: 1,
638
- blend: "normal",
639
- annotation
640
- };
641
- }
642
- function adjustmentNodeFromLegacy(layer, width, height) {
643
- const node = {
644
- type: "adjustment",
645
- id: layer.id,
646
- name: layer.name ?? "",
647
- visible: layer.enabled !== false,
648
- opacity: layer.opacity ?? 1,
649
- blend: layer.blend ?? "normal",
650
- kind: layer.kind,
651
- params: JSON.parse(JSON.stringify(layer.params))
652
- };
653
- if (layer.mask) {
654
- const mask = rasterMaskFromLegacy(layer.mask, width, height);
655
- if (mask) node.mask = mask;
656
- }
657
- return node;
658
- }
659
- function backgroundLayer(filters = [], mask) {
660
- const layer = {
661
- type: "pixel",
662
- id: BACKGROUND_LAYER_ID,
663
- name: BACKGROUND_LAYER_NAME,
664
- visible: true,
665
- opacity: 1,
666
- blend: "normal",
667
- surface: emptySurface(4),
668
- offset: { x: 0, y: 0 },
669
- source: "document",
670
- filters
671
- };
672
- if (mask) layer.mask = mask;
673
- return layer;
674
- }
675
- function depthRasterMask(source) {
676
- const out = {
677
- surface: emptySurface(1, 255),
678
- defaultValue: 255,
679
- source: { ...source, depthRange: [...source.depthRange] }
680
- };
681
- if (!source.enabled) out.enabled = false;
682
- return out;
683
- }
684
- function layersFromLegacy(parts, width, height) {
685
- const stored = parts.adjustments;
686
- const adjustments = stored ? { ...DEFAULT_ADJUSTMENTS_VALUES, ...stored, process: normalizeProcessVersion(stored.process) } : { ...DEFAULT_ADJUSTMENTS_VALUES };
687
- const adjLayers = normalizeAdjustmentLayers(adjustments.layers);
688
- const filters = [developFromAdjustments(adjustments)];
689
- const blur = typeof adjustments.blur === "number" && adjustments.blur > 0 ? adjustments.blur : 0;
690
- let legacyOutputBlur;
691
- if (blur > 0) {
692
- if (adjLayers.length) legacyOutputBlur = blur;
693
- else filters.push({ id: BLUR_FILTER_ID, kind: "blur", enabled: true, radius: blur });
694
- }
695
- const depth = parts.depthMask;
696
- const mask = depth && !isDefaultDepthMask(depth) ? depthRasterMask({ ...DEFAULT_DEPTH_MASK_VALUES, ...depth }) : void 0;
697
- const layers = [backgroundLayer(filters, mask)];
698
- for (const l of adjLayers) layers.push(adjustmentNodeFromLegacy(l, width, height));
699
- for (const a of parts.annotations ?? []) layers.push(annotationLayerFor(JSON.parse(JSON.stringify(a))));
700
- const out = { layers, outputOpacity: typeof adjustments.opacity === "number" ? adjustments.opacity : 1 };
701
- if (legacyOutputBlur !== void 0) out.legacyOutputBlur = legacyOutputBlur;
702
- return out;
703
- }
704
- function legacyAdjustmentLayer(node, width, height) {
705
- const out = {
706
- id: node.id,
707
- kind: node.kind,
708
- enabled: node.visible,
709
- opacity: node.opacity,
710
- blend: node.blend,
711
- params: node.params
712
- };
713
- if (node.name) out.name = node.name;
714
- if (node.mask && !node.mask.source) out.mask = legacyMaskOf(node.mask, width, height);
715
- return out;
716
- }
717
- function developTarget(layers, activeLayerId) {
718
- const pixels = [];
719
- const walk = (list) => {
720
- for (const l of list) {
721
- if (l.type === "pixel") pixels.push(l);
722
- else if (l.type === "group") walk(l.children);
723
- }
724
- };
725
- walk(layers);
726
- if (activeLayerId) {
727
- const active = pixels.find((l) => l.id === activeLayerId);
728
- if (active) return active;
729
- }
730
- return pixels.find((l) => l.source === "document") ?? pixels.find((l) => l.id === BACKGROUND_LAYER_ID) ?? pixels[0] ?? null;
731
- }
732
- var isPlain = (l) => l.visible && l.opacity === 1 && l.blend === "normal" && !l.clipped && !l.mask && !l.locks && !l.effects;
733
- function legacyView(stack, width, height, activeLayerId = null) {
734
- const { layers } = stack;
735
- const target = developTarget(layers, activeLayerId);
736
- const develop = target ? developFilter(target) : void 0;
737
- const blur = stack.legacyOutputBlur ?? (target ? blurFilter(target)?.radius : void 0) ?? 0;
738
- const adjustments = {
739
- ...DEFAULT_ADJUSTMENTS_VALUES,
740
- ...develop ? {
741
- brightness: develop.brightness,
742
- contrast: develop.contrast,
743
- saturation: develop.saturation,
744
- exposure: develop.exposure,
745
- temperature: develop.temperature,
746
- tint: develop.tint ?? 0
747
- } : {},
748
- blur,
749
- opacity: stack.outputOpacity,
750
- process: develop ? normalizeProcessVersion(develop.process) : DEFAULT_ADJUSTMENTS_VALUES.process
751
- };
752
- const adjNodes = [];
753
- const annotations = [];
754
- const walk = (list) => {
755
- for (const l of list) {
756
- if (l.type === "adjustment") adjNodes.push(l);
757
- else if (l.type === "annotation") annotations.push(l.annotation);
758
- else if (l.type === "group") walk(l.children);
759
- }
760
- };
761
- walk(layers);
762
- if (adjNodes.length) adjustments.layers = adjNodes.map((n) => legacyAdjustmentLayer(n, width, height));
763
- const depthSource = target?.mask?.source;
764
- const depthMask = depthSource ? { ...depthSource, depthRange: [...depthSource.depthRange] } : { ...DEFAULT_DEPTH_MASK_VALUES, depthRange: [...DEFAULT_DEPTH_MASK_VALUES.depthRange] };
765
- return { adjustments, depthMask, annotations, exact: isLegacyShape(stack) };
766
- }
767
- function isLegacyShape(stack) {
768
- const { layers } = stack;
769
- const bg = layers[0];
770
- if (!bg || bg.type !== "pixel" || bg.source !== "document") return false;
771
- if (bg.id !== BACKGROUND_LAYER_ID || bg.name !== BACKGROUND_LAYER_NAME) return false;
772
- if (!bg.visible || bg.opacity !== 1 || bg.blend !== "normal" || bg.clipped || bg.locks || bg.effects) return false;
773
- if ((bg.fillOpacity ?? 1) !== 1 || bg.offset.x !== 0 || bg.offset.y !== 0) return false;
774
- const filters = bg.filters ?? [];
775
- if (filters.length < 1 || filters.length > 2) return false;
776
- const [develop, blur] = filters;
777
- if (develop.kind !== "develop" || !develop.enabled || develop.id !== DEVELOP_FILTER_ID) return false;
778
- if (blur && (blur.kind !== "blur" || !blur.enabled || blur.id !== BLUR_FILTER_ID || !(blur.radius > 0))) return false;
779
- if (bg.mask) {
780
- const m = bg.mask;
781
- if (!m.source || m.density !== void 0 || m.feather !== void 0) return false;
782
- if (m.enabled !== false !== m.source.enabled) return false;
783
- }
784
- let i = 1;
785
- let adjustments = 0;
786
- for (; i < layers.length && layers[i].type === "adjustment"; i++) {
787
- const l = layers[i];
788
- if (l.clipped || l.locks || l.mask?.source || l.effects) return false;
789
- adjustments++;
790
- }
791
- for (; i < layers.length; i++) {
792
- const l = layers[i];
793
- if (l.type !== "annotation" || !isPlain(l) || l.id !== l.annotation.id || l.name !== annotationLayerName(l.annotation)) return false;
794
- }
795
- if (blur && adjustments) return false;
796
- if (stack.legacyOutputBlur !== void 0 && (!adjustments || !(stack.legacyOutputBlur > 0))) return false;
797
- return true;
798
- }
799
-
800
629
  // src/image-editor/layers/render.ts
801
630
  function isQuarterTurn(rotate) {
802
631
  return normalizeAngle(rotate) % 90 === 0;
@@ -871,17 +700,28 @@ function needsLegacyBase(input) {
871
700
  if (hasResampledLegacyMask(stack.layers, canvas.width, canvas.height)) return true;
872
701
  return false;
873
702
  }
874
- var isPlainAnnotation = (l) => l.type === "annotation" && l.opacity === 1 && l.blend === "normal" && !l.clipped && !l.mask;
703
+ var isPlainAnnotation = (l) => l.type === "annotation" ? l.opacity === 1 && l.blend === "normal" && !l.clipped && !l.mask : l.type === "shape" && Boolean(l.annotation) && l.opacity === 1 && l.blend === (l.annotation.blend ?? "normal") && !l.clipped && !l.mask && !l.effects && (l.fillOpacity ?? 1) === 1;
704
+ var overlayViews = /* @__PURE__ */ new WeakMap();
705
+ function overlayView(l) {
706
+ if (l.type === "annotation") return l;
707
+ let view = overlayViews.get(l);
708
+ if (!view) {
709
+ const shape = l;
710
+ view = { type: "annotation", id: shape.id, name: shape.name, visible: shape.visible, opacity: 1, blend: "normal", annotation: shape.annotation };
711
+ overlayViews.set(l, view);
712
+ }
713
+ return view;
714
+ }
875
715
  function splitOverlayAnnotations(layers) {
876
716
  let i = layers.length;
877
717
  while (i > 0) {
878
718
  const l = layers[i - 1];
879
- if (l.type === "annotation" && (!l.visible || isPlainAnnotation(l))) i--;
719
+ if ((l.type === "annotation" || l.type === "shape" && l.annotation) && (!l.visible || isPlainAnnotation(l))) i--;
880
720
  else break;
881
721
  }
882
722
  return {
883
723
  composite: layers.slice(0, i),
884
- overlay: layers.slice(i).filter((l) => l.visible)
724
+ overlay: layers.slice(i).filter((l) => l.visible).map(overlayView)
885
725
  };
886
726
  }
887
727
  function coveringRect(region) {
@@ -2672,7 +2512,7 @@ var FreeTransformSession = class {
2672
2512
  }
2673
2513
  const targetId = init.layerId ?? host.activeLayerId();
2674
2514
  const target = targetId && content !== "selection-outline" ? findLayer(host.layers(), targetId)?.layer : void 0;
2675
- const smartTarget = target?.type === "smart" && (init.channel ?? (target.id === host.activeLayerId() ? host.activeTarget() : "pixels")) !== "mask";
2515
+ const smartTarget = (target?.type === "smart" || target?.type === "shape") && (init.channel ?? (target.id === host.activeLayerId() ? host.activeTarget() : "pixels")) !== "mask";
2676
2516
  if (content === "auto") content = selected && !smartTarget ? "selection" : "layer";
2677
2517
  this.content = content;
2678
2518
  if (content !== "layer" && !selected) fail(`Nothing is selected; ${content} needs a selection. Select first, or use content layer.`);
@@ -2699,7 +2539,7 @@ var FreeTransformSession = class {
2699
2539
  const linked = mask?.linked !== false;
2700
2540
  const maskAlone = channel === "mask" && (!linked || layer.type !== "pixel");
2701
2541
  if (channel === "mask" && !mask) fail(layer.mask?.source ? `Layer "${layer.name || id}" has a depth mask, which is generated, not transformed.` : `Layer "${layer.name || id}" has no mask to transform; add one with add_layer_mask, or transform its pixels.`);
2702
- if (!maskAlone && layer.type !== "pixel" && layer.type !== "smart") fail(`Layer "${layer.name || id}" is a ${layer.type} layer; Free Transform works on pixel layers, smart objects, layer masks (channel mask) and selections.`);
2542
+ if (!maskAlone && layer.type !== "pixel" && layer.type !== "smart" && layer.type !== "shape") fail(`Layer "${layer.name || id}" is a ${layer.type} layer; Free Transform works on pixel, shape and smart object layers, layer masks (channel mask) and selections.`);
2703
2543
  if (!layer.visible && content !== "selection-copy") fail(`Layer "${layer.name || id}" is hidden; show it before you transform it.`);
2704
2544
  if (content !== "selection-copy") {
2705
2545
  requireUnlocked(layer, "position", "transform it");
@@ -2721,7 +2561,21 @@ var FreeTransformSession = class {
2721
2561
  this.shown = this.startLayers;
2722
2562
  return;
2723
2563
  }
2724
- if (maskAlone) {
2564
+ if (layer.type === "shape" && !maskAlone) {
2565
+ if (content !== "layer") fail(`Layer "${layer.name || id}" is a shape layer, which transforms as a whole; use content layer (or rasterize it to transform part of it).`);
2566
+ const g = shapeGeometryBounds(layer);
2567
+ if (!g) fail(`Shape layer "${layer.name || id}" has no path to transform.`);
2568
+ const w = Math.max(g.width, 1);
2569
+ const h = Math.max(g.height, 1);
2570
+ box = { x: g.x + (g.width - w) / 2, y: g.y + (g.height - h) / 2, width: w, height: h };
2571
+ this.shape = layer;
2572
+ this.shapeLayer = true;
2573
+ moves.push("pixels");
2574
+ if (mask && linked) {
2575
+ this.parts.push({ what: "mask", offset: o, moving: mask.surface, base: null, reduced: /* @__PURE__ */ new Map() });
2576
+ moves.push("mask");
2577
+ }
2578
+ } else if (maskAlone) {
2725
2579
  if (content === "selection-copy") fail("Layer via Copy copies pixels; transform a mask with content layer or selection.");
2726
2580
  if (content === "selection") {
2727
2581
  const { lifted, rest } = liftMask(mask.surface, sel, o);
@@ -2806,6 +2660,9 @@ var FreeTransformSession = class {
2806
2660
  startGeometry;
2807
2661
  /** True when the layer is a smart object (the transform sets its placement). */
2808
2662
  smartObject = false;
2663
+ /** True when the layer is a shape layer (the transform maps its paths and renders them again). */
2664
+ shapeLayer = false;
2665
+ shape = null;
2809
2666
  geometryValue;
2810
2667
  smart = null;
2811
2668
  smartReduced = /* @__PURE__ */ new Map();
@@ -2871,6 +2728,7 @@ var FreeTransformSession = class {
2871
2728
  let layers = this.startLayers;
2872
2729
  let selection = this.startSelection;
2873
2730
  const targetId = this.copyId ?? this.layerId;
2731
+ if (this.shape) layers = this.resampleShape(final, preview, warp ? f : null, h);
2874
2732
  for (const part of this.parts) {
2875
2733
  const o = part.offset;
2876
2734
  let moved;
@@ -2922,6 +2780,24 @@ var FreeTransformSession = class {
2922
2780
  }
2923
2781
  return { layers, selection };
2924
2782
  }
2783
+ /**
2784
+ * A shape with its paths mapped through the current geometry and rendered again (live shapes
2785
+ * stay live under an affine map; perspective and warps turn them into plain paths). Previews
2786
+ * render only the visible part.
2787
+ */
2788
+ resampleShape(final, preview, warpMap, h) {
2789
+ const layer = this.shape;
2790
+ let components = layer.components;
2791
+ if (!isIdentityTransform(this.geometryValue)) {
2792
+ const affine = !warpMap && h && Math.abs(h[6]) < 1e-12 && Math.abs(h[7]) < 1e-12 && Math.abs(h[8] - 1) < 1e-12 ? [h[0], h[3], h[1], h[4], h[2], h[5]] : void 0;
2793
+ const map = warpMap ?? ((p) => h ? applyHomography(h, p) : p);
2794
+ components = transformShapeComponents(layer.components, (p) => map(p) ?? p, affine);
2795
+ }
2796
+ const moved = { ...layer, components };
2797
+ delete moved.annotation;
2798
+ const surface = renderShape(moved, this.host.shapeEnv(), final ? {} : { clip: preview.clip });
2799
+ return mapLayer(this.startLayers, layer.id, () => ({ ...moved, surface }));
2800
+ }
2925
2801
  /** A smart object re-rendered from its contents for the current geometry (previews leave the smart filters out). */
2926
2802
  resampleSmart(final, preview) {
2927
2803
  const layer = this.smart;
@@ -2982,7 +2858,7 @@ var FreeTransformSession = class {
2982
2858
  const id = this.copyId ?? this.layerId;
2983
2859
  const layer = id ? findLayer(layers, id)?.layer : void 0;
2984
2860
  if (this.moves.includes("pixels") && layer?.type === "pixel") bounds = toDoc(contentBounds(this.host.pixelsOf(layer)), layer.offset);
2985
- else if (this.moves.includes("pixels") && layer?.type === "smart") bounds = contentBounds(layer.surface);
2861
+ else if (this.moves.includes("pixels") && (layer?.type === "smart" || layer?.type === "shape")) bounds = contentBounds(layer.surface);
2986
2862
  else if (this.moves.includes("mask") && layer?.mask) bounds = toDoc(contentBounds(layer.mask.surface), layer.type === "pixel" ? layer.offset : { x: 0, y: 0 });
2987
2863
  else if (this.moves.includes("selection")) bounds = selection?.bounds ? { ...selection.bounds } : null;
2988
2864
  return {
@@ -3601,7 +3477,7 @@ var ImageEditorController = class {
3601
3477
  */
3602
3478
  getLayerPixels(id) {
3603
3479
  const layer = this.getLayer(id);
3604
- if (!layer || layer.type !== "pixel" && layer.type !== "smart") return null;
3480
+ if (!layer || !hasSurface(layer)) return null;
3605
3481
  const surface = this.surfaceOf(layer);
3606
3482
  const b = contentBounds(surface);
3607
3483
  if (!b) return null;
@@ -3614,7 +3490,7 @@ var ImageEditorController = class {
3614
3490
  */
3615
3491
  getLayerBounds(id) {
3616
3492
  const layer = this.getLayer(id);
3617
- if (!layer || layer.type !== "pixel" && layer.type !== "smart") return null;
3493
+ if (!layer || !hasSurface(layer)) return null;
3618
3494
  const surface = this.surfaceOf(layer);
3619
3495
  let b = this.boundsCache.get(surface);
3620
3496
  if (b === void 0) {
@@ -3629,7 +3505,7 @@ var ImageEditorController = class {
3629
3505
  */
3630
3506
  getLayerDataURL(id) {
3631
3507
  const layer = this.getLayer(id);
3632
- if (!layer || layer.type !== "pixel" && layer.type !== "smart") return null;
3508
+ if (!layer || !hasSurface(layer)) return null;
3633
3509
  const surface = this.surfaceOf(layer);
3634
3510
  const enc = encodeSurface(surface);
3635
3511
  return enc ? enc.url : null;
@@ -3674,9 +3550,9 @@ var ImageEditorController = class {
3674
3550
  }
3675
3551
  return String(id);
3676
3552
  }
3677
- /** The pixels a pixel layer or smart object composites (the decoded source for the Background). */
3553
+ /** The pixels a pixel layer, smart object or shape composites (the decoded source for the Background). */
3678
3554
  surfaceOf(layer) {
3679
- if (layer.type === "smart" || layer.source !== "document") return layer.surface;
3555
+ if (layer.type !== "pixel" || layer.source !== "document") return layer.surface;
3680
3556
  return this.sourceSurface() ?? layer.surface;
3681
3557
  }
3682
3558
  /**
@@ -3859,7 +3735,7 @@ var ImageEditorController = class {
3859
3735
  removeLayer(id) {
3860
3736
  const next = removeLayer(this.state.layers, id);
3861
3737
  this.commitLayers(next, () => {
3862
- if (this.state.selectedAnnotationId && !flattenLayers(next).some((l) => l.type === "annotation" && l.annotation.id === this.state.selectedAnnotationId)) {
3738
+ if (this.state.selectedAnnotationId && !flattenLayers(next).some((l) => annotationOfLayer(l)?.id === this.state.selectedAnnotationId)) {
3863
3739
  this.state.selectedAnnotationId = null;
3864
3740
  }
3865
3741
  });
@@ -3929,6 +3805,19 @@ var ImageEditorController = class {
3929
3805
  const moved = findLayer(this.state.layers, id).layer;
3930
3806
  return { offset: { x: Math.round(moved.placement.corners[0].x), y: Math.round(moved.placement.corners[0].y) }, bounds: this.getLayerBounds(id) };
3931
3807
  }
3808
+ if (layer.type === "shape") {
3809
+ const g = shapeGeometryBounds(layer) ?? { x: 0, y: 0 };
3810
+ const dx = move.x !== void 0 ? move.x - g.x : move.dx ?? 0;
3811
+ const dy = move.y !== void 0 ? move.y - g.y : move.dy ?? 0;
3812
+ if (!Number.isFinite(dx) || !Number.isFinite(dy)) throw new LayerOperationError("x and y must be numbers.");
3813
+ requireUnlocked(layer, "position", "move it");
3814
+ let moved = offsetShape(layer, dx, dy, this.shapeEnv());
3815
+ const mask = layer.mask;
3816
+ if (mask && mask.linked !== false && !mask.source && (Math.round(dx) || Math.round(dy))) moved = { ...moved, mask: { ...mask, surface: translateSurface(mask.surface, Math.round(dx), Math.round(dy)) } };
3817
+ this.commitLayers(mapLayer(this.state.layers, id, () => moved), void 0, commitToHistory);
3818
+ const b = shapeGeometryBounds(moved) ?? { x: 0, y: 0 };
3819
+ return { offset: { x: Math.round(b.x), y: Math.round(b.y) }, bounds: this.getLayerBounds(id) };
3820
+ }
3932
3821
  const current = layer.type === "pixel" ? layer.offset : { x: 0, y: 0 };
3933
3822
  const x = move.x ?? current.x + (move.dx ?? 0);
3934
3823
  const y = move.y ?? current.y + (move.dy ?? 0);
@@ -4076,6 +3965,7 @@ var ImageEditorController = class {
4076
3965
  selection: () => this.state.selection?.bounds ? this.state.selection : null,
4077
3966
  canvas: () => this.naturalSize,
4078
3967
  pixelsOf: (layer) => this.surfaceOf(layer),
3968
+ shapeEnv: () => this.shapeEnv(),
4079
3969
  capture: () => this.captureHistoryEntry(),
4080
3970
  show: (layers, selection, active) => {
4081
3971
  this.state.layers = layers;
@@ -4680,7 +4570,7 @@ var ImageEditorController = class {
4680
4570
  paintBounds(layer) {
4681
4571
  const { width, height } = this.naturalSize;
4682
4572
  const canvas = { x: 0, y: 0, width, height };
4683
- if (layer.type === "pixel") return this.getLayerBounds(layer.id) ?? { x: 0, y: 0, width: 0, height: 0 };
4573
+ if (hasSurface(layer)) return this.getLayerBounds(layer.id) ?? { x: 0, y: 0, width: 0, height: 0 };
4684
4574
  if (layer.type === "annotation") {
4685
4575
  const b = getAnnotationBounds(layer.annotation);
4686
4576
  const pad = 4 + ("strokeWidth" in layer.annotation ? layer.annotation.strokeWidth : 0);
@@ -4791,7 +4681,7 @@ var ImageEditorController = class {
4791
4681
  const k = Math.min(1, side / Math.max(width, height));
4792
4682
  const tw = Math.max(1, Math.round(width * k));
4793
4683
  const th = Math.max(1, Math.round(height * k));
4794
- const surface = options.mask ? layer.mask && !layer.mask.source ? layer.mask.surface : null : layer.type === "pixel" || layer.type === "smart" ? this.surfaceOf(layer) : null;
4684
+ const surface = options.mask ? layer.mask && !layer.mask.source ? layer.mask.surface : null : hasSurface(layer) ? this.surfaceOf(layer) : null;
4795
4685
  if (!surface) return null;
4796
4686
  const ox = layer.type === "pixel" ? layer.offset.x : 0;
4797
4687
  const oy = layer.type === "pixel" ? layer.offset.y : 0;
@@ -4895,6 +4785,7 @@ var ImageEditorController = class {
4895
4785
  requireUnlocked(layer, "all", action);
4896
4786
  } else {
4897
4787
  if (layer.type === "smart") throw new LayerOperationError(`Layer "${layer.name || id}" is a smart object; its pixels come from its contents. Rasterize it (rasterize_smart_object), edit its contents (edit_smart_object_contents), or ${action} on its mask or filter mask.`);
4788
+ if (layer.type === "shape") throw new LayerOperationError(`Layer "${layer.name || id}" is a shape layer; its pixels are drawn from its paths. Rasterize it first (rasterize_layer), or ${action} on its mask (channel mask).`);
4898
4789
  if (layer.type !== "pixel") throw new LayerOperationError(`Layer "${layer.name || id}" is a ${layer.type} layer; ${action} on a pixel layer, or on this layer's mask (channel mask).`);
4899
4790
  requireUnlocked(layer, "pixels", action);
4900
4791
  }
@@ -5598,17 +5489,21 @@ var ImageEditorController = class {
5598
5489
  this.notify();
5599
5490
  }
5600
5491
  // --- Annotations & Layers ---
5492
+ /** The layer showing annotation `id`: an annotation layer, or a shape layer migrated from it. */
5601
5493
  annotationLayer(id) {
5602
- return flattenLayers(this.state.layers).find(
5603
- (l) => l.type === "annotation" && l.annotation.id === id
5604
- );
5494
+ return flattenLayers(this.state.layers).find((l) => (l.type === "annotation" || l.type === "shape") && annotationOfLayer(l)?.id === id);
5605
5495
  }
5606
- /** Adds an annotation as its own layer (top of the stack unless `aboveId` is given). */
5496
+ /**
5497
+ * Adds an annotation as its own layer (top of the stack unless `aboveId` is given). Pen,
5498
+ * rectangle, ellipse, line and arrow annotations become shape layers that remember the
5499
+ * annotation (so `annotations` and `updateAnnotation` keep working); text and images stay
5500
+ * annotation layers.
5501
+ */
5607
5502
  addAnnotation(annotation, placement = {}) {
5608
5503
  this.recordHistory();
5609
5504
  const id = `ann_${Date.now()}_${Math.random().toString(36).slice(2, 7)}`;
5610
5505
  const fullAnnotation = { ...annotation, id };
5611
- const layer = annotationLayerFor(fullAnnotation);
5506
+ const layer = annotationLayerFor(fullAnnotation, this.shapeEnv());
5612
5507
  const loc = placement.aboveId ? findLayer(this.state.layers, placement.aboveId) : null;
5613
5508
  this.state.layers = loc ? insertLayer(this.state.layers, layer, loc.parent?.id ?? null, loc.index + 1) : insertLayer(this.state.layers, layer, null);
5614
5509
  this.state.selectedAnnotationId = id;
@@ -5620,13 +5515,33 @@ var ImageEditorController = class {
5620
5515
  this.recordHistory();
5621
5516
  }
5622
5517
  const layer = this.annotationLayer(id);
5623
- if (layer) {
5624
- const annotation = { ...layer.annotation, ...partial, id };
5625
- const name = layer.name === annotationLayerName(layer.annotation) ? annotationLayerName(annotation) : layer.name;
5626
- this.state.layers = mapLayer(this.state.layers, layer.id, () => ({ ...layer, annotation, name }));
5518
+ const current = layer ? annotationOfLayer(layer) : null;
5519
+ if (layer && current) {
5520
+ const annotation = { ...current, ...partial, id };
5521
+ const name = layer.name === annotationLayerName(current) ? annotationLayerName(annotation) : layer.name;
5522
+ this.state.layers = mapLayer(this.state.layers, layer.id, () => this.layerForAnnotation(layer, annotation, name));
5627
5523
  }
5628
5524
  this.notify();
5629
5525
  }
5526
+ /** `layer` (an annotation or migrated shape layer) showing `annotation` instead, keeping its other properties. */
5527
+ layerForAnnotation(layer, annotation, name) {
5528
+ const shape = shapeFromAnnotation(annotation);
5529
+ const { annotation: _old, ...props } = layer;
5530
+ const blend = annotation.blend && annotation.blend !== "normal" ? annotation.blend : "normal";
5531
+ if (shape) {
5532
+ const base = { ...props, name, blend: layer.type === "annotation" && layer.blend !== "normal" ? layer.blend : blend };
5533
+ if (layer.type === "shape") {
5534
+ const fresh = rerenderShape({ ...base, components: shape.components, fill: shape.fill, stroke: shape.stroke }, this.shapeEnv());
5535
+ delete fresh.fillRule;
5536
+ return { ...fresh, annotation };
5537
+ }
5538
+ const created = createShapeLayer({ ...shape, name, blend: base.blend }, this.shapeEnv(), layer.id);
5539
+ const { type: _t2, ...rest2 } = base;
5540
+ return { ...rest2, ...created, annotation };
5541
+ }
5542
+ const { type: _t, components: _c, fill: _f, stroke: _s, surface: _sf, offset: _o, fillRule: _r, fillOpacity: _fo, psd: _p, ...rest } = props;
5543
+ return { ...rest, type: "annotation", name, blend: layer.type === "shape" ? "normal" : layer.blend, annotation };
5544
+ }
5630
5545
  removeAnnotation(id) {
5631
5546
  this.recordHistory();
5632
5547
  const layer = this.annotationLayer(id);
@@ -5636,10 +5551,11 @@ var ImageEditorController = class {
5636
5551
  }
5637
5552
  this.notify();
5638
5553
  }
5554
+ /** Deletes every annotation layer and every shape layer that still is the annotation it was migrated from. */
5639
5555
  clearAnnotations() {
5640
5556
  this.recordHistory();
5641
5557
  let layers = this.state.layers;
5642
- for (const l of flattenLayers(layers)) if (l.type === "annotation") layers = mapLayer(layers, l.id, () => null);
5558
+ for (const l of flattenLayers(layers)) if (l.type === "annotation" || l.type === "shape" && l.annotation) layers = mapLayer(layers, l.id, () => null);
5643
5559
  this.state.layers = layers;
5644
5560
  this.state.selectedAnnotationId = null;
5645
5561
  this.notify();
@@ -5878,6 +5794,142 @@ var ImageEditorController = class {
5878
5794
  this.commitLayers(setLayerMask(this.state.layers, target.id, { surface, defaultValue: 0 }));
5879
5795
  return target.id;
5880
5796
  }
5797
+ // --- Shape layers ---
5798
+ /** What shape rendering needs: the canvas size and the document's patterns. */
5799
+ shapeEnv() {
5800
+ return { ...this.naturalSize, pattern: (id) => this.state.patterns?.find((p) => p.id === id) ?? null };
5801
+ }
5802
+ /** One component from tool or UI input (validated). */
5803
+ componentFrom(spec, path) {
5804
+ const op = requireOperation(spec.op, `${path}op`);
5805
+ if (spec.shape !== void 0 && spec.subpaths !== void 0) throw new LayerOperationError(`Give ${path}shape or ${path}subpaths, not both.`);
5806
+ if (spec.shape !== void 0) return liveComponent(normalizeLiveShape(spec.shape, `${path}shape`), op);
5807
+ if (spec.subpaths !== void 0) return { op, subpaths: normalizeSubpaths(spec.subpaths, `${path}subpaths`) };
5808
+ throw new LayerOperationError(`${path}shape (a live shape: { kind: rect | ellipse | polygon | star | line, \u2026 }) or ${path}subpaths (anchors or SVG path data) is required.`);
5809
+ }
5810
+ shapeInfo(id) {
5811
+ const layer = requireShapeLayer(this.state.layers, id);
5812
+ const r = (n) => Math.round(n * 1e3) / 1e3;
5813
+ const box = (b) => b ? { x: r(b.x), y: r(b.y), width: r(b.width), height: r(b.height) } : null;
5814
+ const info = {
5815
+ id: layer.id,
5816
+ name: layer.name,
5817
+ components: layer.components.map((c, index) => ({
5818
+ index,
5819
+ op: c.op,
5820
+ live: c.live ? JSON.parse(JSON.stringify(c.live)) : null,
5821
+ subpaths: roundSubpaths(c.subpaths),
5822
+ bounds: box(shapeGeometryBounds({ components: [c] }))
5823
+ })),
5824
+ fillRule: layer.fillRule ?? "nonzero",
5825
+ fill: layer.fill ? JSON.parse(JSON.stringify(layer.fill)) : null,
5826
+ stroke: layer.stroke ? JSON.parse(JSON.stringify(layer.stroke)) : null,
5827
+ geometryBounds: box(shapeGeometryBounds(layer)),
5828
+ bounds: this.getLayerBounds(id)
5829
+ };
5830
+ if (layer.annotation) info.annotationId = layer.annotation.id;
5831
+ return info;
5832
+ }
5833
+ /** A shape layer's components, fill, stroke and bounds. */
5834
+ getShapeLayer(id) {
5835
+ return this.shapeInfo(id);
5836
+ }
5837
+ /**
5838
+ * Adds a shape layer (one undo step) and makes it active: a live shape or subpaths (or several
5839
+ * `components`), filled with `fill` (default the foreground colour, except for a stroke-only
5840
+ * open path) and stroked with `stroke`. Goes above the active layer unless placed. Returns its id.
5841
+ */
5842
+ addShapeLayer(init) {
5843
+ const specs = init.components?.length ? init.components : [init];
5844
+ if (init.components?.length && (init.shape !== void 0 || init.subpaths !== void 0)) throw new LayerOperationError("Give components, or shape / subpaths, not both.");
5845
+ const components = specs.map((c, i) => this.componentFrom(c, init.components?.length ? `components[${i}].` : ""));
5846
+ const stroke = init.stroke ? normalizeShapeStroke(init.stroke, "stroke") : null;
5847
+ const openOnly = components.every((c) => !c.live && c.subpaths.every((sp) => !sp.closed));
5848
+ const fill = init.fill === void 0 ? stroke && openOnly ? null : normalizeShapePaint(this.state.paint.foreground, "fill") : init.fill === null ? null : normalizeShapePaint(init.fill, "fill");
5849
+ if (!fill && !stroke) throw new LayerOperationError("A shape with neither fill nor stroke draws nothing; give fill or stroke.");
5850
+ if (init.fillRule !== void 0 && init.fillRule !== "nonzero" && init.fillRule !== "evenodd") throw new LayerOperationError("fillRule must be nonzero or evenodd.");
5851
+ if (init.opacity !== void 0 && !(Number.isFinite(init.opacity) && init.opacity >= 0 && init.opacity <= 1)) throw new LayerOperationError("opacity must be between 0 and 1.");
5852
+ if (init.blend !== void 0 && !isBlendMode(init.blend)) throw new LayerOperationError(`Unknown blend mode "${String(init.blend)}".`);
5853
+ const layer = createShapeLayer({ components, fill, stroke, fillRule: init.fillRule, name: init.name, opacity: init.opacity, blend: init.blend }, this.shapeEnv());
5854
+ const next = placeLayer(this.state.layers, layer, this.newLayerPlacement(init));
5855
+ this.commitLayers(next, () => {
5856
+ this.state.activeLayerId = layer.id;
5857
+ this.state.activeTarget = "pixels";
5858
+ });
5859
+ return layer.id;
5860
+ }
5861
+ /**
5862
+ * Sets a shape's fill, stroke or fill rule (one undo step unless `commitToHistory` is false):
5863
+ * `fill` / `stroke` replace them (`null` removes); `stroke` given as a partial object changes
5864
+ * only those settings of the current stroke. Returns the shape.
5865
+ */
5866
+ setShapeStyle(id, changes, commitToHistory = true) {
5867
+ const layer = requireShapeLayer(this.state.layers, id);
5868
+ const fill = changes.fill === void 0 ? layer.fill : changes.fill === null ? null : normalizeShapePaint(changes.fill, "fill");
5869
+ const stroke = changes.stroke === void 0 ? layer.stroke : changes.stroke === null ? null : normalizeShapeStroke(changes.stroke, "stroke", layer.stroke);
5870
+ if (changes.fillRule !== void 0 && changes.fillRule !== "nonzero" && changes.fillRule !== "evenodd") throw new LayerOperationError("fillRule must be nonzero or evenodd.");
5871
+ const fillRule = changes.fillRule ?? layer.fillRule ?? "nonzero";
5872
+ const { layers } = updateShapeLayer(this.state.layers, id, () => ({ fill, stroke, fillRule: fillRule === "nonzero" ? void 0 : fillRule }), this.shapeEnv(), "restyle it");
5873
+ this.commitLayers(layers, void 0, commitToHistory);
5874
+ return this.shapeInfo(id);
5875
+ }
5876
+ componentIndex(layer, index) {
5877
+ if (!Number.isInteger(index) || index < 0 || index >= layer.components.length) {
5878
+ throw new LayerOperationError(`componentIndex must be 0..${layer.components.length - 1} (get_shape_layer lists the components), got ${String(index)}.`);
5879
+ }
5880
+ return index;
5881
+ }
5882
+ /**
5883
+ * Changes one component (one undo step unless `commitToHistory` is false): its live shape
5884
+ * (fields not given keep their values when the kind stays the same), its subpaths (which drops
5885
+ * the live shape) or its operation. Returns the shape.
5886
+ */
5887
+ setShapeComponent(id, index, change, commitToHistory = true) {
5888
+ const layer = requireShapeLayer(this.state.layers, id);
5889
+ const i = this.componentIndex(layer, index);
5890
+ const current = layer.components[i];
5891
+ let shape = change.shape;
5892
+ if (shape && current.live && shape.kind === void 0) shape = { ...current.live, ...shape };
5893
+ else if (shape && current.live && shape.kind === current.live.kind) shape = { ...current.live, ...shape };
5894
+ const next = shape !== void 0 || change.subpaths !== void 0 ? this.componentFrom({ ...change, shape, op: change.op ?? current.op }, "") : { ...current, op: requireOperation(change.op) };
5895
+ const components = layer.components.map((c, k) => k === i ? next : c);
5896
+ const { layers } = updateShapeLayer(this.state.layers, id, () => ({ components }), this.shapeEnv(), "edit its path");
5897
+ this.commitLayers(layers, void 0, commitToHistory);
5898
+ return this.shapeInfo(id);
5899
+ }
5900
+ /** Adds a component to a shape (path operations), on top unless `index` is given (one undo step). Returns the shape. */
5901
+ addShapeComponent(id, spec, index) {
5902
+ const layer = requireShapeLayer(this.state.layers, id);
5903
+ const c = this.componentFrom(spec, "");
5904
+ const at = index === void 0 ? layer.components.length : index;
5905
+ if (!Number.isInteger(at) || at < 0 || at > layer.components.length) throw new LayerOperationError(`index must be 0..${layer.components.length}, got ${String(index)}.`);
5906
+ const components = [...layer.components.slice(0, at), c, ...layer.components.slice(at)];
5907
+ const { layers } = updateShapeLayer(this.state.layers, id, () => ({ components }), this.shapeEnv(), "edit its path");
5908
+ this.commitLayers(layers);
5909
+ return this.shapeInfo(id);
5910
+ }
5911
+ /** Removes one component of a shape (one undo step); the last one cannot go (delete the layer instead). Returns the shape. */
5912
+ removeShapeComponent(id, index) {
5913
+ const layer = requireShapeLayer(this.state.layers, id);
5914
+ const i = this.componentIndex(layer, index);
5915
+ if (layer.components.length === 1) throw new LayerOperationError("This is the shape's only component; delete the layer instead (delete_layer).");
5916
+ const components = layer.components.filter((_, k) => k !== i);
5917
+ const { layers } = updateShapeLayer(this.state.layers, id, () => ({ components }), this.shapeEnv(), "edit its path");
5918
+ this.commitLayers(layers);
5919
+ return this.shapeInfo(id);
5920
+ }
5921
+ /**
5922
+ * Merge Shape Components: the area the components combine to becomes one plain component (live
5923
+ * shapes become paths), as one undo step. Returns the shape.
5924
+ */
5925
+ mergeShapeComponents(id) {
5926
+ const layer = requireShapeLayer(this.state.layers, id);
5927
+ if (layer.components.length < 2) throw new LayerOperationError("The shape has one component; there is nothing to merge.");
5928
+ const components = mergeShapeComponents(layer.components, layer.fillRule);
5929
+ const { layers } = updateShapeLayer(this.state.layers, id, () => ({ components, fillRule: void 0 }), this.shapeEnv(), "edit its path");
5930
+ this.commitLayers(layers);
5931
+ return this.shapeInfo(id);
5932
+ }
5881
5933
  /**
5882
5934
  * Rasterize Layer: an annotation layer becomes a pixel layer with the same id, name and
5883
5935
  * properties (needs a canvas), and a pixel layer's filters are baked into its pixels. One undo
@@ -5918,8 +5970,11 @@ var ImageEditorController = class {
5918
5970
  }
5919
5971
  } else if (layer.type === "smart") {
5920
5972
  return this.rasterizeSmartObject(id);
5973
+ } else if (layer.type === "shape") {
5974
+ next = shapeAsPixelLayer(layer);
5975
+ if (this.state.selectedAnnotationId && this.state.selectedAnnotationId === layer.annotation?.id) this.state.selectedAnnotationId = null;
5921
5976
  } else {
5922
- throw new LayerOperationError(`Only annotation layers, smart objects and pixel layers with filters rasterize; "${layer.name || id}" is a ${layer.type} layer. Merge it down or flatten instead.`);
5977
+ throw new LayerOperationError(`Only shape and annotation layers, smart objects and pixel layers with filters rasterize; "${layer.name || id}" is a ${layer.type} layer. Merge it down or flatten instead.`);
5923
5978
  }
5924
5979
  this.commitLayers(mapLayer(this.state.layers, id, () => next));
5925
5980
  return id;
@@ -6044,6 +6099,14 @@ var ImageEditorController = class {
6044
6099
  if (mask2) out2.mask = mask2;
6045
6100
  return out2;
6046
6101
  }
6102
+ if (layer.type === "shape") {
6103
+ add2("Shape layers were written as pixel layers", layer.name);
6104
+ const out2 = shapeAsPixelLayer(layer);
6105
+ const mask2 = bakeMask(layer);
6106
+ if (mask2) out2.mask = mask2;
6107
+ else delete out2.mask;
6108
+ return out2;
6109
+ }
6047
6110
  if (layer.type === "smart") {
6048
6111
  const out2 = { ...layer };
6049
6112
  const mask2 = bakeMask(layer);
@@ -6120,16 +6183,27 @@ var ImageEditorController = class {
6120
6183
  );
6121
6184
  }
6122
6185
  layers = resizeLegacyMasks(layers, prev, next.dimensions);
6186
+ const env = { ...next.dimensions, pattern: (pid) => this.state.patterns?.find((p) => p.id === pid) ?? null };
6123
6187
  for (const l of flattenLayers(layers)) {
6124
6188
  if (l.type === "annotation") {
6125
6189
  layers = mapLayer(layers, l.id, () => ({ ...l, annotation: scaleAnnotation(l.annotation, sx, sy) }));
6190
+ } else if (l.type === "shape") {
6191
+ const a = l.annotation;
6192
+ const scaled = a ? scaleAnnotation(a, sx, sy) : null;
6193
+ const fromAnnotation = scaled ? shapeFromAnnotation(scaled) : null;
6194
+ const m = [sx, 0, 0, sy, 0, 0];
6195
+ const components = fromAnnotation?.components ?? transformShapeComponents(l.components, (p) => ({ x: p.x * sx, y: p.y * sy }), m);
6196
+ const moved = { ...l, components, ...fromAnnotation ? { fill: fromAnnotation.fill, stroke: fromAnnotation.stroke } : {} };
6197
+ if (scaled && fromAnnotation) moved.annotation = scaled;
6198
+ else delete moved.annotation;
6199
+ layers = mapLayer(layers, l.id, () => rerenderShape(moved, env));
6126
6200
  }
6127
6201
  }
6128
6202
  }
6129
6203
  const bg = developTarget(layers, null);
6130
6204
  if (bg && bg.source !== "document") layers = mapLayer(layers, bg.id, () => ({ ...bg, source: "document" }));
6131
6205
  if (clearAnnotations) {
6132
- for (const l of flattenLayers(layers)) if (l.type === "annotation") layers = mapLayer(layers, l.id, () => null);
6206
+ for (const l of flattenLayers(layers)) if (l.type === "annotation" || l.type === "shape" && l.annotation) layers = mapLayer(layers, l.id, () => null);
6133
6207
  this.state.selectedAnnotationId = null;
6134
6208
  }
6135
6209
  this.state.layers = layers;
@@ -6199,7 +6273,14 @@ var ImageEditorController = class {
6199
6273
  if (isLayered(data)) {
6200
6274
  const problems = [];
6201
6275
  const canvas = typeof data.width === "number" && data.width > 0 && typeof data.height === "number" && data.height > 0 ? { width: data.width, height: data.height } : this.naturalSize;
6202
- s.layers = deserializeLayers(data.layers, { assets: data.assets ?? {}, problems, smartContents: Array.isArray(data.smartContents) ? data.smartContents : [], canvas });
6276
+ const storedPatterns = deserializePatterns(data.patterns, []);
6277
+ s.layers = deserializeLayers(data.layers, {
6278
+ assets: data.assets ?? {},
6279
+ problems,
6280
+ smartContents: Array.isArray(data.smartContents) ? data.smartContents : [],
6281
+ canvas,
6282
+ pattern: (pid) => storedPatterns.find((p) => p.id === pid) ?? null
6283
+ });
6203
6284
  this.lastLoadProblems = problems;
6204
6285
  s.crop = data.crop ? { ...data.crop } : null;
6205
6286
  s.transform = { ...data.transform };
@@ -6401,6 +6482,7 @@ function collectStateTiles(layers, into) {
6401
6482
  for (const l of flattenLayers(layers)) {
6402
6483
  if (l.type === "pixel" && l.source !== "document") collectTiles(l.surface, into);
6403
6484
  if (l.mask && !l.mask.source) collectTiles(l.mask.surface, into);
6485
+ if (l.type === "shape") collectTiles(l.surface, into);
6404
6486
  if (l.type === "smart") {
6405
6487
  collectTiles(l.surface, into);
6406
6488
  if (l.smartFilters?.mask) collectTiles(l.smartFilters.mask.surface, into);
@@ -6449,7 +6531,7 @@ function resizePixels(px2, width, height) {
6449
6531
  function withoutAnnotations(layers) {
6450
6532
  const out = [];
6451
6533
  for (const l of layers) {
6452
- if (l.type === "annotation") continue;
6534
+ if (l.type === "annotation" || l.type === "shape" && l.annotation) continue;
6453
6535
  out.push(l.type === "group" ? { ...l, children: withoutAnnotations(l.children) } : l);
6454
6536
  }
6455
6537
  return out;
@@ -6639,7 +6721,7 @@ function loadImageEditorDocument(input) {
6639
6721
  if (state.selection === void 0) delete state.selection;
6640
6722
  return { state, report: result.report, unknown: result.unknown };
6641
6723
  }
6642
- var LAYER_TYPES = /* @__PURE__ */ new Set(["pixel", "adjustment", "group", "annotation", "smart"]);
6724
+ var LAYER_TYPES = /* @__PURE__ */ new Set(["pixel", "adjustment", "group", "annotation", "smart", "shape"]);
6643
6725
  function cleanLayers(value, dropped) {
6644
6726
  const out = [];
6645
6727
  for (const raw of value) {
@@ -7431,7 +7513,7 @@ function createImageEditTools(controller) {
7431
7513
  return { layer: layers[index], index };
7432
7514
  };
7433
7515
  const layerJson = (id) => JSON.parse(JSON.stringify(controller.getAdjustmentLayer(id)));
7434
- const geometry = () => {
7516
+ const geometry2 = () => {
7435
7517
  const s = controller.getState();
7436
7518
  const region = s.crop ?? { x: 0, y: 0, ...s.imageDimensions };
7437
7519
  const out = getRotatedSize(region.width, region.height, s.transform);
@@ -7456,7 +7538,7 @@ function createImageEditTools(controller) {
7456
7538
  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.",
7457
7539
  inputSchema: { type: "object", properties: {}, additionalProperties: false },
7458
7540
  annotations: { title: "Read image geometry", ...READ },
7459
- handler: () => ({ ...geometry(), layers: controller.getAdjustmentLayers().length, ...history() })
7541
+ handler: () => ({ ...geometry2(), layers: controller.getAdjustmentLayers().length, ...history() })
7460
7542
  };
7461
7543
  const getHistogram = {
7462
7544
  name: "get_histogram",
@@ -7560,7 +7642,7 @@ function createImageEditTools(controller) {
7560
7642
  throw new Error(`The crop box (${x}, ${y}, ${width} \xD7 ${height}) goes outside the ${image.width} \xD7 ${image.height} image.`);
7561
7643
  }
7562
7644
  controller.setCrop({ x: Math.round(x), y: Math.round(y), width: Math.round(width), height: Math.round(height) });
7563
- return geometry();
7645
+ return geometry2();
7564
7646
  }
7565
7647
  };
7566
7648
  const resetCrop = {
@@ -7572,7 +7654,7 @@ function createImageEditTools(controller) {
7572
7654
  handler: () => {
7573
7655
  if (!controller.getState().crop) throw new Error("The image is not cropped.");
7574
7656
  controller.resetCrop();
7575
- return geometry();
7657
+ return geometry2();
7576
7658
  }
7577
7659
  };
7578
7660
  const setRotation = {
@@ -7589,7 +7671,7 @@ function createImageEditTools(controller) {
7589
7671
  handler: ({ degrees }) => {
7590
7672
  requireImage();
7591
7673
  controller.setRotation(degrees);
7592
- return geometry();
7674
+ return geometry2();
7593
7675
  }
7594
7676
  };
7595
7677
  const flipImage = {
@@ -7607,7 +7689,7 @@ function createImageEditTools(controller) {
7607
7689
  requireImage();
7608
7690
  if (axis === "horizontal") controller.flipHorizontal();
7609
7691
  else controller.flipVertical();
7610
- return geometry();
7692
+ return geometry2();
7611
7693
  }
7612
7694
  };
7613
7695
  const undo = {
@@ -7689,6 +7771,16 @@ function describeLayer(layer, activeId, boundsOf) {
7689
7771
  out.collapsed = Boolean(layer.collapsed);
7690
7772
  out.children = layer.children.map((c) => describeLayer(c, activeId, boundsOf));
7691
7773
  break;
7774
+ case "shape":
7775
+ out.bounds = boundsOf(layer);
7776
+ out.fillOpacity = round3(layer.fillOpacity ?? 1);
7777
+ out.shape = {
7778
+ components: layer.components.map((c) => ({ op: c.op, kind: c.live?.kind ?? "path" })),
7779
+ fill: layer.fill ? layer.fill.type : null,
7780
+ stroke: layer.stroke ? { width: layer.stroke.width, paint: layer.stroke.paint.type } : null
7781
+ };
7782
+ if (layer.annotation) out.annotation = { id: layer.annotation.id, type: layer.annotation.type };
7783
+ break;
7692
7784
  case "smart":
7693
7785
  out.bounds = boundsOf(layer);
7694
7786
  out.fillOpacity = round3(layer.fillOpacity ?? 1);
@@ -7734,7 +7826,7 @@ function createImageLayerTools(controller) {
7734
7826
  const listLayers = {
7735
7827
  name: "list_layers",
7736
7828
  title: "List image layers",
7737
- description: "Returns the layer stack bottom to top as a tree: id, type (pixel, adjustment, group, annotation), name, document bounds (pixel layers), visible, opacity, fillOpacity, blend, clipped, locks, a mask summary, an effects summary (master switch, scale, effect ids and kinds; details from list_layer_effects), filters (pixel layers), kind and params (adjustment layers), children (groups) and which layer is active. Also returns the active layer id and target (pixels or mask) and the canvas size.",
7829
+ description: "Returns the layer stack bottom to top as a tree: id, type (pixel, adjustment, group, shape, smart, annotation), name, document bounds (pixel, shape and smart layers), visible, opacity, fillOpacity, blend, clipped, locks, a mask summary, an effects summary (master switch, scale, effect ids and kinds; details from list_layer_effects), filters (pixel layers), kind and params (adjustment layers), a shape summary (component operations and kinds, fill and stroke paint types; details from get_shape_layer), children (groups) and which layer is active. Also returns the active layer id and target (pixels or mask) and the canvas size.",
7738
7830
  inputSchema: { type: "object", properties: {}, additionalProperties: false },
7739
7831
  annotations: { title: "List image layers", ...READ2 },
7740
7832
  handler: () => {
@@ -8078,7 +8170,7 @@ function createImageLayerTools(controller) {
8078
8170
  const rasterize = {
8079
8171
  name: "rasterize_layer",
8080
8172
  title: "Rasterize a layer",
8081
- description: "Rasterize Layer: turns an annotation layer into a pixel layer (same id, name, opacity, blend and mask; needs a browser) or bakes a pixel layer's filters (develop, blur) into its pixels. One undo step. Returns the layer.",
8173
+ description: "Rasterize Layer: turns a shape layer into a pixel layer holding what it shows now (same id, name, opacity, blend, fill, mask and effects; its paths are gone, so later transforms resample pixels), turns an annotation layer into a pixel layer (needs a browser), rasterizes a smart object, or bakes a pixel layer's filters (develop, blur) into its pixels. One undo step. Returns the layer.",
8082
8174
  inputSchema: { type: "object", properties: { layerId: LAYER_ID2 }, required: ["layerId"], additionalProperties: false },
8083
8175
  annotations: { title: "Rasterize a layer", ...DESTRUCTIVE },
8084
8176
  handler: async ({ layerId }) => ({ layer: describe(await controller.rasterizeLayer(exists(layerId))) })
@@ -8181,7 +8273,7 @@ var READ3 = { readOnlyHint: true, destructiveHint: false, idempotentHint: true,
8181
8273
  var WRITE3 = { readOnlyHint: false, destructiveHint: false, idempotentHint: false, openWorldHint: false };
8182
8274
  var WRITE_IDEMPOTENT3 = { readOnlyHint: false, destructiveHint: false, idempotentHint: true, openWorldHint: false };
8183
8275
  var DESTRUCTIVE2 = { readOnlyHint: false, destructiveHint: true, idempotentHint: false, openWorldHint: false };
8184
- var LAYER_ID3 = { type: "string", description: "Layer id from list_layers (a pixel, annotation or group layer)." };
8276
+ var LAYER_ID3 = { type: "string", description: "Layer id from list_layers (a pixel, shape, smart object, annotation or group layer)." };
8185
8277
  var EFFECT_ID = { type: "string", description: "Effect id from list_layer_effects or add_layer_effect." };
8186
8278
  var BLEND_IDS = BLEND_MODES.map((m) => m.id).filter((id) => id !== "pigment");
8187
8279
  var DEFS = {
@@ -8603,9 +8695,13 @@ var PaintGesture = class {
8603
8695
  if (tool === "move") {
8604
8696
  const layer = this.controller.getActiveLayer();
8605
8697
  if (!layer) throw new Error("Select a layer to move in the Layers panel.");
8606
- if (layer.type !== "pixel") throw new Error(`Only pixel layers move; "${layer.name}" is a ${layer.type} layer.`);
8698
+ if (layer.type !== "pixel" && layer.type !== "shape" && layer.type !== "smart") throw new Error(`Only pixel, shape and smart object layers move; "${layer.name}" is a ${layer.type} layer.`);
8607
8699
  if (layer.locks?.all || layer.locks?.position) throw new Error(`Layer "${layer.name}" has its position locked.`);
8608
- this.move = { id: layer.id, start: { x: sample.x, y: sample.y }, origin: { ...layer.offset }, moved: false };
8700
+ const origin = layer.type === "pixel" ? { ...layer.offset } : layer.type === "smart" ? { ...layer.placement.corners[0] } : (() => {
8701
+ const b = shapeGeometryBounds(layer);
8702
+ return b ? { x: b.x, y: b.y } : { x: 0, y: 0 };
8703
+ })();
8704
+ this.move = { id: layer.id, start: { x: sample.x, y: sample.y }, origin, moved: false };
8609
8705
  return null;
8610
8706
  }
8611
8707
  if (tool === "brush" || tool === "eraser" || tool === "mixer") {
@@ -9520,6 +9616,409 @@ function createImageSmartObjectTools(controller) {
9520
9616
  return [getSmart, convert, rasterize, setTransform, replace, exportContents, copy, editContents, closeContents, addFilter, updateFilter, removeFilter, moveFilter, setEnabled, setMask];
9521
9617
  }
9522
9618
 
9619
+ // src/image-editor/shape-tools.ts
9620
+ var READ8 = { readOnlyHint: true, destructiveHint: false, idempotentHint: true, openWorldHint: false };
9621
+ var WRITE7 = { readOnlyHint: false, destructiveHint: false, idempotentHint: false, openWorldHint: false };
9622
+ var WRITE_IDEMPOTENT5 = { readOnlyHint: false, destructiveHint: false, idempotentHint: true, openWorldHint: false };
9623
+ var DESTRUCTIVE6 = { readOnlyHint: false, destructiveHint: true, idempotentHint: false, openWorldHint: false };
9624
+ var LAYER_ID5 = { type: "string", description: "Shape layer id from list_layers (type shape) or add_shape_layer." };
9625
+ var COMPONENT_INDEX = { type: "integer", minimum: 0, description: "Component index from get_shape_layer (0 = bottom component)." };
9626
+ var OP = {
9627
+ type: "string",
9628
+ enum: ["combine", "subtract", "intersect", "exclude"],
9629
+ description: "How the component combines with the components below it (Photoshop path operations; default combine). The bottom component always combines."
9630
+ };
9631
+ var NUM = (description) => ({ type: "number", description });
9632
+ var MATRIX = {
9633
+ type: "array",
9634
+ items: { type: "number", description: "Matrix entry." },
9635
+ minItems: 6,
9636
+ maxItems: 6,
9637
+ description: "Optional affine map from the shape's own frame to the document [a, b, c, d, e, f] (x' = a\xB7x + c\xB7y + e, y' = b\xB7x + d\xB7y + f), e.g. a rotation. Default identity."
9638
+ };
9639
+ var BOX = {
9640
+ x: NUM("Left of the bounding box, document pixels."),
9641
+ y: NUM("Top of the bounding box, document pixels."),
9642
+ width: NUM("Width in pixels."),
9643
+ height: NUM("Height in pixels."),
9644
+ transform: { $ref: "#/$defs/matrix" }
9645
+ };
9646
+ var DEFS2 = {
9647
+ matrix: MATRIX,
9648
+ point: {
9649
+ type: "object",
9650
+ description: "A point in document pixels.",
9651
+ properties: { x: NUM("Pixels from the left."), y: NUM("Pixels from the top.") },
9652
+ required: ["x", "y"],
9653
+ additionalProperties: false
9654
+ },
9655
+ arrowhead: {
9656
+ type: "object",
9657
+ description: "An arrowhead whose tip is the line end: a triangle, notched by concavity.",
9658
+ properties: {
9659
+ width: NUM("Width across the base, pixels."),
9660
+ length: NUM("Length from base to tip, pixels."),
9661
+ concavity: { type: "number", minimum: -0.5, maximum: 0.5, description: "Notch of the base towards the tip as a fraction of length (default 0)." }
9662
+ },
9663
+ required: ["width", "length"],
9664
+ additionalProperties: false
9665
+ },
9666
+ shape: {
9667
+ description: "A live shape (stays editable as that shape): rect (with optional corner radii), ellipse, polygon, star, or line (a filled bar with optional arrowheads). Sizes are document pixels.",
9668
+ anyOf: [
9669
+ {
9670
+ type: "object",
9671
+ description: "Rectangle.",
9672
+ properties: {
9673
+ kind: { const: "rect", description: "Rectangle." },
9674
+ ...BOX,
9675
+ radius: { type: "number", minimum: 0, description: "One corner radius for all corners, pixels." },
9676
+ radii: { type: "array", items: { type: "number", minimum: 0, description: "Radius in pixels." }, minItems: 4, maxItems: 4, description: "Corner radii [top-left, top-right, bottom-right, bottom-left] in pixels." }
9677
+ },
9678
+ required: ["kind", "x", "y", "width", "height"],
9679
+ additionalProperties: false
9680
+ },
9681
+ { type: "object", description: "Ellipse inscribed in the box.", properties: { kind: { const: "ellipse", description: "Ellipse." }, ...BOX }, required: ["kind", "x", "y", "width", "height"], additionalProperties: false },
9682
+ {
9683
+ type: "object",
9684
+ description: "Regular polygon inscribed in the box, first vertex at the top.",
9685
+ properties: { kind: { const: "polygon", description: "Polygon." }, ...BOX, sides: { type: "integer", minimum: 3, maximum: 100, description: "Number of sides (default 6)." } },
9686
+ required: ["kind", "x", "y", "width", "height"],
9687
+ additionalProperties: false
9688
+ },
9689
+ {
9690
+ type: "object",
9691
+ description: "Star inscribed in the box, first point at the top.",
9692
+ properties: {
9693
+ kind: { const: "star", description: "Star." },
9694
+ ...BOX,
9695
+ points: { type: "integer", minimum: 3, maximum: 100, description: "Number of points (default 5)." },
9696
+ innerRatio: { type: "number", minimum: 0.01, maximum: 1, description: "Inner radius over outer radius (default 0.5)." }
9697
+ },
9698
+ required: ["kind", "x", "y", "width", "height"],
9699
+ additionalProperties: false
9700
+ },
9701
+ {
9702
+ type: "object",
9703
+ description: "Line drawn as a filled bar.",
9704
+ properties: {
9705
+ kind: { const: "line", description: "Line." },
9706
+ x1: NUM("Start x, document pixels."),
9707
+ y1: NUM("Start y, document pixels."),
9708
+ x2: NUM("End x, document pixels."),
9709
+ y2: NUM("End y, document pixels."),
9710
+ weight: { type: "number", minimum: 0, description: "Thickness in pixels (default 1)." },
9711
+ startArrow: { anyOf: [{ $ref: "#/$defs/arrowhead" }, { type: "null", description: "No arrowhead." }], description: "Arrowhead at the start, or null." },
9712
+ endArrow: { anyOf: [{ $ref: "#/$defs/arrowhead" }, { type: "null", description: "No arrowhead." }], description: "Arrowhead at the end, or null." },
9713
+ transform: { $ref: "#/$defs/matrix" }
9714
+ },
9715
+ required: ["kind", "x1", "y1", "x2", "y2"],
9716
+ additionalProperties: false
9717
+ }
9718
+ ]
9719
+ },
9720
+ subpaths: {
9721
+ description: "A free path: SVG path data (absolute or relative M, L, C, Q, A, Z) or subpaths with anchors. Handles are absolute document positions.",
9722
+ anyOf: [
9723
+ { type: "string", description: "SVG path data, document pixels." },
9724
+ {
9725
+ type: "array",
9726
+ minItems: 1,
9727
+ description: "Subpaths.",
9728
+ items: {
9729
+ type: "object",
9730
+ description: "One subpath.",
9731
+ properties: {
9732
+ anchors: {
9733
+ type: "array",
9734
+ minItems: 1,
9735
+ description: "Anchors in order.",
9736
+ items: {
9737
+ type: "object",
9738
+ description: "An anchor with optional handles.",
9739
+ properties: {
9740
+ x: NUM("Anchor x, document pixels."),
9741
+ y: NUM("Anchor y, document pixels."),
9742
+ in: { $ref: "#/$defs/point", description: "Handle of the segment arriving here (absolute)." },
9743
+ out: { $ref: "#/$defs/point", description: "Handle of the segment leaving here (absolute)." },
9744
+ corner: { type: "number", minimum: 0, description: "Live corner radius at this anchor, pixels." }
9745
+ },
9746
+ required: ["x", "y"],
9747
+ additionalProperties: false
9748
+ }
9749
+ },
9750
+ closed: { type: "boolean", description: "Joins the last anchor back to the first (default true)." }
9751
+ },
9752
+ required: ["anchors"],
9753
+ additionalProperties: false
9754
+ }
9755
+ }
9756
+ ]
9757
+ },
9758
+ gradient: {
9759
+ type: "object",
9760
+ description: "A gradient: colour stops, optional opacity stops and its layout (aligned with the shape bounds by default).",
9761
+ properties: {
9762
+ stops: {
9763
+ type: "array",
9764
+ minItems: 1,
9765
+ description: "Colour stops.",
9766
+ items: {
9767
+ type: "object",
9768
+ description: "A colour stop.",
9769
+ properties: { position: { type: "number", minimum: 0, maximum: 1, description: "0..1 along the gradient." }, color: { type: "string", description: "Colour (#rrggbb or a CSS colour)." } },
9770
+ required: ["position", "color"],
9771
+ additionalProperties: false
9772
+ }
9773
+ },
9774
+ opacityStops: {
9775
+ type: "array",
9776
+ description: "Opacity stops (default opaque).",
9777
+ items: {
9778
+ type: "object",
9779
+ description: "An opacity stop.",
9780
+ properties: { position: { type: "number", minimum: 0, maximum: 1, description: "0..1 along the gradient." }, opacity: { type: "number", minimum: 0, maximum: 1, description: "0..1." } },
9781
+ required: ["position", "opacity"],
9782
+ additionalProperties: false
9783
+ }
9784
+ },
9785
+ style: { type: "string", enum: ["linear", "radial", "angle", "reflected", "diamond"], description: "Geometry (default linear)." },
9786
+ angle: { type: "number", minimum: -360, maximum: 360, description: "Degrees, 90 = bottom to top (default 90)." },
9787
+ scale: { type: "number", minimum: 0.1, maximum: 1.5, description: "1 = 100 % (default 1)." },
9788
+ reverse: { type: "boolean", description: "Swap the ends (default false)." },
9789
+ align: { type: "boolean", description: "Lay out over the shape bounds (true, default) or the canvas." },
9790
+ offset: { $ref: "#/$defs/point", description: "Centre offset as fractions of the bounds (default 0, 0)." }
9791
+ },
9792
+ required: ["stops"],
9793
+ additionalProperties: false
9794
+ },
9795
+ paint: {
9796
+ description: "What a fill or stroke paints with: a colour string (#rrggbb, #rrggbbaa, rgb(), a CSS name; alpha becomes opacity), or an object of type color, gradient or pattern.",
9797
+ anyOf: [
9798
+ { type: "string", description: "A colour." },
9799
+ {
9800
+ type: "object",
9801
+ description: "Solid colour.",
9802
+ properties: { type: { const: "color", description: "Solid colour." }, color: { type: "string", description: "Colour." }, opacity: { type: "number", minimum: 0, maximum: 1, description: "0..1 (default 1)." } },
9803
+ required: ["type", "color"],
9804
+ additionalProperties: false
9805
+ },
9806
+ {
9807
+ type: "object",
9808
+ description: "Gradient.",
9809
+ properties: { type: { const: "gradient", description: "Gradient." }, gradient: { $ref: "#/$defs/gradient" }, opacity: { type: "number", minimum: 0, maximum: 1, description: "0..1 (default 1)." } },
9810
+ required: ["type", "gradient"],
9811
+ additionalProperties: false
9812
+ },
9813
+ {
9814
+ type: "object",
9815
+ description: "Pattern (list_patterns gives ids).",
9816
+ properties: {
9817
+ type: { const: "pattern", description: "Pattern." },
9818
+ patternId: { type: "string", description: "A document or built-in pattern id." },
9819
+ scale: { type: "number", minimum: 0.01, maximum: 10, description: "1 = 100 % (default 1)." },
9820
+ angle: { type: "number", minimum: -360, maximum: 360, description: "Degrees counter-clockwise (default 0)." },
9821
+ link: { type: "boolean", description: "Tile from the shape\u2019s top-left (default true) instead of the canvas origin." },
9822
+ phase: { $ref: "#/$defs/point", description: "Origin offset in pixels." },
9823
+ opacity: { type: "number", minimum: 0, maximum: 1, description: "0..1 (default 1)." }
9824
+ },
9825
+ required: ["type", "patternId"],
9826
+ additionalProperties: false
9827
+ }
9828
+ ]
9829
+ },
9830
+ stroke: {
9831
+ type: "object",
9832
+ description: "The shape stroke (not the layer-effect stroke). Unset settings keep their current values (or the defaults).",
9833
+ properties: {
9834
+ width: { type: "number", minimum: 0, maximum: 1e4, description: "Width in pixels (required for a new stroke)." },
9835
+ paint: { $ref: "#/$defs/paint" },
9836
+ color: { type: "string", description: "Shorthand for paint as a colour." },
9837
+ align: { type: "string", enum: ["inside", "center", "outside"], description: "Where it sits on the outline (default center; open paths always centre)." },
9838
+ cap: { type: "string", enum: ["butt", "round", "square"], description: "Line ends (default butt)." },
9839
+ join: { type: "string", enum: ["miter", "round", "bevel"], description: "Corners (default miter)." },
9840
+ miterLimit: { type: "number", minimum: 1, maximum: 500, description: "Miter length over width above which a corner is bevelled (default 100)." },
9841
+ dashes: { type: "array", maxItems: 32, items: { type: "number", minimum: 0, description: "Length in multiples of the width." }, description: "Dash and gap lengths in multiples of the stroke width ([] = solid)." },
9842
+ dashOffset: { type: "number", description: "Start offset into the dashes, multiples of the width (default 0)." }
9843
+ },
9844
+ additionalProperties: false
9845
+ }
9846
+ };
9847
+ var GEOMETRY2 = {
9848
+ shape: { $ref: "#/$defs/shape", description: "A live shape: { kind: rect | ellipse | polygon | star | line, \u2026 } in document pixels. Give shape or subpaths." },
9849
+ subpaths: { $ref: "#/$defs/subpaths", description: "A free path: SVG path data or subpaths with anchors, document pixels. Give shape or subpaths." }
9850
+ };
9851
+ function geometry(args) {
9852
+ const out = {};
9853
+ if (args.shape !== void 0) out.shape = args.shape;
9854
+ if (args.subpaths !== void 0) out.subpaths = args.subpaths;
9855
+ if (args.op !== void 0) out.op = args.op;
9856
+ return out;
9857
+ }
9858
+ function createImageShapeTools(controller) {
9859
+ const shapeId = (id) => {
9860
+ if (typeof id !== "string" || !id) throw new LayerOperationError("layerId is required (a shape layer id from list_layers).");
9861
+ requireShapeLayer(controller.getLayers(), id);
9862
+ return id;
9863
+ };
9864
+ const layerSummary = (id) => {
9865
+ const s = controller.getState();
9866
+ const loc = findLayer(s.layers, id);
9867
+ return loc ? { ...describeLayer(loc.layer, s.activeLayerId, (x) => controller.getLayerBounds(x.id)), parentId: loc.parent?.id ?? null, index: loc.index } : null;
9868
+ };
9869
+ const get = {
9870
+ name: "get_shape_layer",
9871
+ title: "Read a shape layer",
9872
+ 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.",
9873
+ inputSchema: { type: "object", properties: { layerId: LAYER_ID5 }, required: ["layerId"], additionalProperties: false },
9874
+ annotations: { title: "Read a shape layer", ...READ8 },
9875
+ handler: ({ layerId }) => controller.getShapeLayer(shapeId(layerId))
9876
+ };
9877
+ const add2 = {
9878
+ name: "add_shape_layer",
9879
+ title: "Add a shape layer",
9880
+ description: "Adds a vector shape layer and makes it active: a live shape (rect, ellipse, polygon, star, line), a free path (SVG path data or anchors), or several components combined with path operations. fill defaults to the foreground colour (none for an open path with a stroke); give stroke for an outline. It renders sharp at any size, can be moved and transformed without losing quality, and takes masks, effects, clipping and blend modes like any layer. Goes above the active layer unless placed. One undo step. Returns the layer and the shape.",
9881
+ inputSchema: {
9882
+ type: "object",
9883
+ $defs: DEFS2,
9884
+ properties: {
9885
+ ...GEOMETRY2,
9886
+ components: {
9887
+ type: "array",
9888
+ minItems: 1,
9889
+ description: "Several components instead of shape / subpaths, bottom first, each with its path operation.",
9890
+ items: { type: "object", description: "One component.", properties: { ...GEOMETRY2, op: OP }, additionalProperties: false }
9891
+ },
9892
+ fill: { anyOf: [{ $ref: "#/$defs/paint" }, { type: "null", description: "No fill." }], description: "Fill paint, or null for none (default the foreground colour)." },
9893
+ stroke: { anyOf: [{ $ref: "#/$defs/stroke" }, { type: "null", description: "No stroke." }], description: "Stroke { width, paint, \u2026 }, or null / omitted for none." },
9894
+ fillRule: { type: "string", enum: ["nonzero", "evenodd"], description: "Winding rule within a component (default nonzero)." },
9895
+ name: { type: "string", description: "Layer name (default by kind: Rectangle, Ellipse, \u2026)." },
9896
+ opacity: { type: "number", minimum: 0, maximum: 1, description: "Layer opacity 0..1 (default 1)." },
9897
+ blend: { type: "string", enum: BLEND_MODES.map((m) => m.id), description: "Blend mode (default normal)." },
9898
+ aboveId: { type: "string", description: "Put the layer directly above this layer (default above the active layer)." },
9899
+ belowId: { type: "string", description: "Put the layer directly below this layer." },
9900
+ parentId: { type: "string", description: "Put the layer inside this group." },
9901
+ index: { type: "integer", minimum: 0, description: "Position among the siblings, 0 = bottom." }
9902
+ },
9903
+ additionalProperties: false
9904
+ },
9905
+ annotations: { title: "Add a shape layer", ...WRITE7 },
9906
+ handler: (args) => {
9907
+ const init = { ...geometry(args) };
9908
+ if (args.components !== void 0) init.components = args.components.map(geometry);
9909
+ for (const k of ["fill", "stroke", "fillRule", "name", "opacity", "blend", "aboveId", "belowId", "parentId", "index"]) {
9910
+ if (args[k] !== void 0) init[k] = args[k];
9911
+ }
9912
+ if (init.blend !== void 0) init.blend = init.blend;
9913
+ const id = controller.addShapeLayer(init);
9914
+ return { layer: layerSummary(id), shape: controller.getShapeLayer(id) };
9915
+ }
9916
+ };
9917
+ const update = {
9918
+ name: "update_shape",
9919
+ title: "Restyle a shape",
9920
+ description: "Changes a shape layer\u2019s fill, stroke or fill rule and renders it again: fill replaces the fill (null removes it); stroke changes only the settings given (null removes the stroke; a new stroke needs width and paint). Geometry is untouched (set_shape_component edits paths). One undo step. Returns the shape.",
9921
+ inputSchema: {
9922
+ type: "object",
9923
+ $defs: DEFS2,
9924
+ properties: {
9925
+ layerId: LAYER_ID5,
9926
+ fill: { anyOf: [{ $ref: "#/$defs/paint" }, { type: "null", description: "No fill." }], description: "New fill paint, or null for none." },
9927
+ stroke: { anyOf: [{ $ref: "#/$defs/stroke" }, { type: "null", description: "No stroke." }], description: "Stroke settings to change, or null to remove the stroke." },
9928
+ fillRule: { type: "string", enum: ["nonzero", "evenodd"], description: "Winding rule within a component." }
9929
+ },
9930
+ required: ["layerId"],
9931
+ additionalProperties: false
9932
+ },
9933
+ annotations: { title: "Restyle a shape", ...WRITE_IDEMPOTENT5 },
9934
+ handler: ({ layerId, fill, stroke, fillRule }) => {
9935
+ const id = shapeId(layerId);
9936
+ if (fill === void 0 && stroke === void 0 && fillRule === void 0) throw new LayerOperationError("Give fill, stroke or fillRule to change.");
9937
+ return controller.setShapeStyle(id, { fill, stroke, fillRule });
9938
+ }
9939
+ };
9940
+ const setComponent = {
9941
+ name: "set_shape_component",
9942
+ title: "Edit a shape component",
9943
+ description: "Changes one component of a shape layer: shape sets its live shape (fields you leave out keep their values when the kind stays the same, e.g. only radii), subpaths replaces its path (which ends the live shape), op changes its path operation. Positions are document pixels. One undo step. Returns the shape.",
9944
+ inputSchema: {
9945
+ type: "object",
9946
+ $defs: DEFS2,
9947
+ properties: {
9948
+ layerId: LAYER_ID5,
9949
+ componentIndex: COMPONENT_INDEX,
9950
+ shape: {
9951
+ type: "object",
9952
+ description: "Live shape fields to set (see add_shape_layer\u2019s shape): kind plus its fields; without kind, the fields change the current live shape.",
9953
+ properties: {
9954
+ kind: { type: "string", enum: ["rect", "ellipse", "polygon", "star", "line"], description: "Shape kind (default the current one)." },
9955
+ x: NUM("Left of the box, pixels."),
9956
+ y: NUM("Top of the box, pixels."),
9957
+ width: NUM("Box width, pixels."),
9958
+ height: NUM("Box height, pixels."),
9959
+ radius: { type: "number", minimum: 0, description: "rect: one corner radius, pixels." },
9960
+ radii: { type: "array", items: { type: "number", minimum: 0, description: "Radius in pixels." }, minItems: 4, maxItems: 4, description: "rect: [top-left, top-right, bottom-right, bottom-left] radii, pixels." },
9961
+ sides: { type: "integer", minimum: 3, maximum: 100, description: "polygon: number of sides." },
9962
+ points: { type: "integer", minimum: 3, maximum: 100, description: "star: number of points." },
9963
+ innerRatio: { type: "number", minimum: 0.01, maximum: 1, description: "star: inner over outer radius." },
9964
+ x1: NUM("line: start x."),
9965
+ y1: NUM("line: start y."),
9966
+ x2: NUM("line: end x."),
9967
+ y2: NUM("line: end y."),
9968
+ weight: { type: "number", minimum: 0, description: "line: thickness in pixels." },
9969
+ startArrow: { anyOf: [{ $ref: "#/$defs/arrowhead" }, { type: "null", description: "No arrowhead." }], description: "line: start arrowhead or null." },
9970
+ endArrow: { anyOf: [{ $ref: "#/$defs/arrowhead" }, { type: "null", description: "No arrowhead." }], description: "line: end arrowhead or null." },
9971
+ transform: { $ref: "#/$defs/matrix" }
9972
+ },
9973
+ additionalProperties: false
9974
+ },
9975
+ subpaths: { $ref: "#/$defs/subpaths", description: "A new path for the component (SVG path data or anchors); ends its live shape." },
9976
+ op: OP
9977
+ },
9978
+ required: ["layerId", "componentIndex"],
9979
+ additionalProperties: false
9980
+ },
9981
+ annotations: { title: "Edit a shape component", ...WRITE_IDEMPOTENT5 },
9982
+ handler: ({ layerId, componentIndex, ...rest }) => {
9983
+ const id = shapeId(layerId);
9984
+ const change = geometry(rest);
9985
+ if (change.shape === void 0 && change.subpaths === void 0 && change.op === void 0) throw new LayerOperationError("Give shape, subpaths or op to change.");
9986
+ return controller.setShapeComponent(id, componentIndex, change);
9987
+ }
9988
+ };
9989
+ const addComponent = {
9990
+ name: "add_shape_component",
9991
+ title: "Add a shape component",
9992
+ description: "Adds a component to a shape layer and combines it with the components below it by op: combine (union), subtract, intersect or exclude, as Photoshop\u2019s path operations. Goes on top unless index is given. One undo step. Returns the shape.",
9993
+ inputSchema: {
9994
+ type: "object",
9995
+ $defs: DEFS2,
9996
+ properties: { layerId: LAYER_ID5, ...GEOMETRY2, op: OP, index: { type: "integer", minimum: 0, description: "Position among the components, 0 = bottom (default the top)." } },
9997
+ required: ["layerId"],
9998
+ additionalProperties: false
9999
+ },
10000
+ annotations: { title: "Add a shape component", ...WRITE7 },
10001
+ handler: ({ layerId, index, ...rest }) => controller.addShapeComponent(shapeId(layerId), geometry(rest), index)
10002
+ };
10003
+ const removeComponent = {
10004
+ name: "remove_shape_component",
10005
+ title: "Delete a shape component",
10006
+ description: "Deletes one component of a shape layer (the last one cannot go: delete the layer instead). One undo step. Returns the shape.",
10007
+ inputSchema: { type: "object", properties: { layerId: LAYER_ID5, componentIndex: COMPONENT_INDEX }, required: ["layerId", "componentIndex"], additionalProperties: false },
10008
+ annotations: { title: "Delete a shape component", ...DESTRUCTIVE6 },
10009
+ handler: ({ layerId, componentIndex }) => controller.removeShapeComponent(shapeId(layerId), componentIndex)
10010
+ };
10011
+ const merge = {
10012
+ name: "merge_shape_components",
10013
+ title: "Merge shape components",
10014
+ description: "Merge Shape Components: the area a shape\u2019s components combine to (after their path operations) becomes one plain component; live shapes become paths. The look is unchanged. One undo step. Returns the shape.",
10015
+ inputSchema: { type: "object", properties: { layerId: LAYER_ID5 }, required: ["layerId"], additionalProperties: false },
10016
+ annotations: { title: "Merge shape components", ...DESTRUCTIVE6 },
10017
+ handler: ({ layerId }) => controller.mergeShapeComponents(shapeId(layerId))
10018
+ };
10019
+ return [get, add2, update, setComponent, addComponent, removeComponent, merge];
10020
+ }
10021
+
9523
10022
  // src/image-editor/tools.ts
9524
10023
  var LAYER_PATH = /^layers\.([^.]+)\.(.+)$/;
9525
10024
  function adjustmentsSummary(adjustments) {
@@ -9759,6 +10258,7 @@ function createImageEditorToolProvider(controller, options = {}) {
9759
10258
  ...createImageLayerTools(controller),
9760
10259
  ...createImageTransformTools(controller),
9761
10260
  ...createImageSmartObjectTools(controller),
10261
+ ...createImageShapeTools(controller),
9762
10262
  ...createImageEffectTools(controller),
9763
10263
  ...createImagePaintTools(controller),
9764
10264
  ...createPigmentTools(),
@@ -10099,6 +10599,8 @@ function layerBadge(layer) {
10099
10599
  return layer.annotation.type === "text" ? "T" : "ann";
10100
10600
  case "smart":
10101
10601
  return "so";
10602
+ case "shape":
10603
+ return "shp";
10102
10604
  }
10103
10605
  }
10104
10606
  function dropZone(fraction, isGroup) {
@@ -10138,7 +10640,7 @@ function layerMenu(controller) {
10138
10640
  { action: "delete-mask", label: "Delete Layer Mask", enabled: Boolean(active?.mask) },
10139
10641
  { action: "toggle-mask", label: active?.mask?.enabled === false ? "Enable Layer Mask" : "Disable Layer Mask", enabled: Boolean(active?.mask) },
10140
10642
  { action: "toggle-mask-link", label: active?.mask?.linked === false ? "Link Layer Mask" : "Unlink Layer Mask", enabled: Boolean(active?.mask && !active.mask.source) },
10141
- { action: "rasterize", label: "Rasterize Layer", enabled: active?.type === "annotation" || active?.type === "smart" || isPixel && Boolean(active?.type === "pixel" && active.filters?.length) },
10643
+ { action: "rasterize", label: "Rasterize Layer", enabled: active?.type === "annotation" || active?.type === "smart" || active?.type === "shape" || isPixel && Boolean(active?.type === "pixel" && active.filters?.length) },
10142
10644
  { action: "convert-smart", label: "Convert to Smart Object", enabled: Boolean(active && active.type !== "annotation") },
10143
10645
  { action: "edit-contents", label: "Edit Contents", enabled: active?.type === "smart" },
10144
10646
  { action: "smart-via-copy", label: "New Smart Object via Copy", enabled: active?.type === "smart" },
@@ -11071,12 +11573,15 @@ export {
11071
11573
  D50,
11072
11574
  D65,
11073
11575
  DEFAULT_ADJUSTMENTS,
11576
+ DEFAULT_BLENDER,
11074
11577
  DEFAULT_BRUSH,
11075
11578
  DEFAULT_DEPTH_MASK,
11076
11579
  DEFAULT_GLOBAL_LIGHT,
11580
+ DEFAULT_MITER_LIMIT,
11077
11581
  DEFAULT_MIXER,
11078
11582
  DEFAULT_REFINE_EDGE_PARAMS,
11079
11583
  DEFAULT_SELECTION_TOOL_OPTIONS,
11584
+ DEFAULT_SMUDGE,
11080
11585
  DEFAULT_TRANSFORM,
11081
11586
  DEPTH_MASK_CONTROLS,
11082
11587
  DEVELOP_FILTER_ID,
@@ -11107,6 +11612,7 @@ export {
11107
11612
  LAYER_EFFECT_KINDS,
11108
11613
  LAYER_EFFECT_MENU,
11109
11614
  LEGACY_PROCESS_VERSION,
11615
+ LIVE_SHAPE_KINDS,
11110
11616
  LUMA_709,
11111
11617
  LayerCompositor,
11112
11618
  LayerEffectError,
@@ -11132,6 +11638,7 @@ export {
11132
11638
  PIGMENT_WAVELENGTHS,
11133
11639
  PSD_BLEND_KEYS,
11134
11640
  PSD_FILE_ACCEPT,
11641
+ PaintExchange,
11135
11642
  PaintGesture,
11136
11643
  PaintStrokeSession,
11137
11644
  PatternPlacement,
@@ -11143,9 +11650,12 @@ export {
11143
11650
  SELECTION_MODIFY_OPS,
11144
11651
  SELECTION_TOOLS,
11145
11652
  SHADOW_AMBIENT,
11653
+ SHAPE_OPERATIONS,
11654
+ SHAPE_RENDER_MARGIN,
11146
11655
  SMART_RENDER_MARGIN,
11147
11656
  SUPPORTED_ADJUSTMENT_KEYS,
11148
11657
  SelectionGesture,
11658
+ SmudgeTip,
11149
11659
  StrokeCoverage,
11150
11660
  SurfaceDraft,
11151
11661
  TILE,
@@ -11154,6 +11664,7 @@ export {
11154
11664
  TONE_LUT_SIZE,
11155
11665
  TONE_RANGE_PRESETS,
11156
11666
  TRANSFORM_ANCHORS,
11667
+ TileMedium,
11157
11668
  TiledSurface,
11158
11669
  TransformTooLargeError,
11159
11670
  VIBRANCE_GREY,
@@ -11175,8 +11686,11 @@ export {
11175
11686
  adjustmentNodeFromLegacy,
11176
11687
  adler32,
11177
11688
  anchorPoint,
11689
+ annotationBlend,
11178
11690
  annotationLayerFor,
11179
11691
  annotationLayerName,
11692
+ annotationOfLayer,
11693
+ annotationOfShape,
11180
11694
  applyAdjustmentLayers,
11181
11695
  applyAdjustments,
11182
11696
  applyBoxBlur,
@@ -11200,8 +11714,10 @@ export {
11200
11714
  blendImageData,
11201
11715
  blendLum,
11202
11716
  blendPixel,
11717
+ blendRadius,
11203
11718
  blendRgb,
11204
11719
  blendTile,
11720
+ blenderDab,
11205
11721
  blurFilter,
11206
11722
  borderCoverage,
11207
11723
  boxMean,
@@ -11226,6 +11742,7 @@ export {
11226
11742
  colorRange,
11227
11743
  combineSelections,
11228
11744
  compileAdjustmentLayer,
11745
+ componentPath,
11229
11746
  compositeSurfaceOver,
11230
11747
  computeHistogram,
11231
11748
  computeSelectionBounds,
@@ -11242,6 +11759,7 @@ export {
11242
11759
  coverageToByte,
11243
11760
  coveringRect,
11244
11761
  createAdjustmentLayer,
11762
+ createBlenderScratch,
11245
11763
  createImageCaptureSurface,
11246
11764
  createImageEditTools,
11247
11765
  createImageEditorToolProvider,
@@ -11250,11 +11768,13 @@ export {
11250
11768
  createImagePaintTools,
11251
11769
  createImagePsdTools,
11252
11770
  createImageSelectionTools,
11771
+ createImageShapeTools,
11253
11772
  createImageSmartObjectTools,
11254
11773
  createImageTransformTools,
11255
11774
  createPigment,
11256
11775
  createPigmentTools,
11257
11776
  createSelectionMask,
11777
+ createShapeLayer,
11258
11778
  curveLut,
11259
11779
  curveLutFromLevels,
11260
11780
  curvePointToBox,
@@ -11271,6 +11791,7 @@ export {
11271
11791
  defaultAdjustmentParams,
11272
11792
  defaultEffectGradient,
11273
11793
  defaultLayerEffect,
11794
+ defaultShapeName,
11274
11795
  deflate,
11275
11796
  deflateRaw,
11276
11797
  depthMaskAlpha,
@@ -11311,6 +11832,7 @@ export {
11311
11832
  encodeSurface,
11312
11833
  evalWarpMesh,
11313
11834
  evaluateAdjustmentLayers,
11835
+ exchangeRates,
11314
11836
  expandCoverage,
11315
11837
  exportPsd,
11316
11838
  extractNormalizedLayerItems,
@@ -11322,6 +11844,7 @@ export {
11322
11844
  findLayerControl,
11323
11845
  finishTile,
11324
11846
  flattenLayers,
11847
+ flattenPath,
11325
11848
  flipCorners,
11326
11849
  formatControlValue,
11327
11850
  fullSelection,
@@ -11387,6 +11910,7 @@ export {
11387
11910
  isPng,
11388
11911
  isQuarterTurn,
11389
11912
  isQuarterTurnSwapped,
11913
+ isRenderedLayer,
11390
11914
  isSelectionEmpty,
11391
11915
  isSelectionMode,
11392
11916
  isSeparableBlendMode,
@@ -11420,21 +11944,26 @@ export {
11420
11944
  levelsValue,
11421
11945
  linearSrgbToXy,
11422
11946
  linearToSrgb,
11947
+ liveComponent,
11948
+ liveShapeSubpaths,
11423
11949
  loadImageEditorDocument,
11424
11950
  loadMixer,
11425
11951
  magicWand,
11426
11952
  mapCropHandle,
11427
11953
  mapLayer,
11954
+ mapSubpaths,
11428
11955
  maskSummary,
11429
11956
  matrixFromCorners,
11430
11957
  matrixFromParams,
11431
11958
  mergeLayerEffect,
11959
+ mergeShapeComponents,
11432
11960
  migrateSerializedImageEditorState,
11433
11961
  migrateToLayeredState,
11434
11962
  mixPigment8,
11435
11963
  mixPigmentRgb,
11436
11964
  mixPigments,
11437
11965
  mixerLoadResult,
11966
+ mixerRates,
11438
11967
  modifySelection,
11439
11968
  moveCurvePoint,
11440
11969
  moveLayer,
@@ -11449,24 +11978,32 @@ export {
11449
11978
  normalizeAdjustmentLayers,
11450
11979
  normalizeAngle,
11451
11980
  normalizeBlendMode,
11981
+ normalizeBlender,
11452
11982
  normalizeBrush,
11453
11983
  normalizeFilter,
11454
11984
  normalizeLayerEffects,
11455
11985
  normalizeLayerMask,
11986
+ normalizeLiveShape,
11456
11987
  normalizeMixer,
11457
11988
  normalizePlacement,
11458
11989
  normalizeProcessVersion,
11459
11990
  normalizeRect,
11460
11991
  normalizeRefineEdgeParams,
11992
+ normalizeShapePaint,
11993
+ normalizeShapeStroke,
11461
11994
  normalizeSmartFilter,
11995
+ normalizeSmudge,
11462
11996
  normalizeStoredAdjustments,
11997
+ normalizeSubpaths,
11463
11998
  offsetLayer,
11999
+ offsetShape,
11464
12000
  offsetSmartObject,
11465
12001
  openPsdFile,
11466
12002
  packRgba,
11467
12003
  parseAdjustmentBlock,
11468
12004
  parseBlend,
11469
12005
  parseColor,
12006
+ parseCssColor,
11470
12007
  parseDataUrl,
11471
12008
  parseEffectColor,
11472
12009
  parseLfx2,
@@ -11475,6 +12012,7 @@ export {
11475
12012
  parseSelectionMode,
11476
12013
  parseVersionedDescriptor,
11477
12014
  photoFilterMatrix,
12015
+ pigment8,
11478
12016
  pixelLayerBounds,
11479
12017
  pixelLayers,
11480
12018
  placeContents,
@@ -11502,6 +12040,7 @@ export {
11502
12040
  rangeLightness,
11503
12041
  rasterContents,
11504
12042
  rasterMaskFromLegacy,
12043
+ rasterizeCoverage,
11505
12044
  rasterizeDab,
11506
12045
  rasterizeSmartObject,
11507
12046
  readAdjustmentControls,
@@ -11524,12 +12063,17 @@ export {
11524
12063
  removeSmartFilter,
11525
12064
  renderLayerEffects,
11526
12065
  renderLayeredBase,
12066
+ renderShape,
12067
+ renderShapeRect,
11527
12068
  renderSmartObject,
11528
12069
  replaceContents,
11529
12070
  requireLayer,
12071
+ requireOperation,
12072
+ requireShapeLayer,
11530
12073
  requireSmartObject,
11531
12074
  requireUnlocked,
11532
12075
  rerender,
12076
+ rerenderShape,
11533
12077
  resizePixels,
11534
12078
  resizeSelection,
11535
12079
  resizeWarpMesh,
@@ -11569,6 +12113,12 @@ export {
11569
12113
  setSmartFilterMask,
11570
12114
  setSmartFiltersEnabled,
11571
12115
  setSmartPlacement,
12116
+ shapeAsPixelLayer,
12117
+ shapeFromAnnotation,
12118
+ shapeGeometryBounds,
12119
+ shapeLayers,
12120
+ shapeOutline,
12121
+ shapeRenderClip,
11572
12122
  shiftCoverage,
11573
12123
  smartFilterRows,
11574
12124
  smartObjectLayers,
@@ -11598,6 +12148,7 @@ export {
11598
12148
  toneRange,
11599
12149
  transformHomography,
11600
12150
  transformMap,
12151
+ transformShapeComponents,
11601
12152
  transformSurface,
11602
12153
  transformedBounds,
11603
12154
  transformedBox,
@@ -11610,6 +12161,7 @@ export {
11610
12161
  updateLayerEffect,
11611
12162
  updateLayerFilter,
11612
12163
  updateLayerMaskProps,
12164
+ updateShapeLayer,
11613
12165
  updateSmartFilter,
11614
12166
  uvToXy,
11615
12167
  validateEffectGradient,