@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.
- package/dist/animation/index.cjs +5 -5
- package/dist/animation/index.cjs.map +1 -1
- package/dist/animation/index.js +9 -9
- package/dist/animation-editor/index.cjs +59 -59
- package/dist/animation-editor/index.cjs.map +1 -1
- package/dist/animation-editor/index.d.cts +2 -1
- package/dist/animation-editor/index.d.ts +2 -1
- package/dist/animation-editor/index.js +29 -29
- package/dist/blending-DXl06NSx.d.ts +486 -0
- package/dist/blending-aOE6Y3UE.d.cts +486 -0
- package/dist/canvas/index.cjs +27 -27
- package/dist/canvas/index.cjs.map +1 -1
- package/dist/canvas/index.js +11 -11
- package/dist/{chunk-2FGRTYJN.cjs → chunk-477UVMNR.cjs} +3 -3
- package/dist/{chunk-2FGRTYJN.cjs.map → chunk-477UVMNR.cjs.map} +1 -1
- package/dist/chunk-7VOHOPLW.cjs +1136 -0
- package/dist/chunk-7VOHOPLW.cjs.map +1 -0
- package/dist/{chunk-HUQ7EHVI.js → chunk-BLWCAQCW.js} +723 -1975
- package/dist/chunk-BLWCAQCW.js.map +1 -0
- package/dist/{chunk-SOROZ2BF.js → chunk-DMRSJBJF.js} +1939 -318
- package/dist/chunk-DMRSJBJF.js.map +1 -0
- package/dist/chunk-FTBZPI77.cjs +1 -0
- package/dist/chunk-FTBZPI77.cjs.map +1 -0
- package/dist/{chunk-WMBIJCHA.js → chunk-HNS7UHCM.js} +5 -1
- package/dist/{chunk-WMBIJCHA.js.map → chunk-HNS7UHCM.js.map} +1 -1
- package/dist/chunk-JLYX4V57.cjs +2001 -0
- package/dist/chunk-JLYX4V57.cjs.map +1 -0
- package/dist/chunk-KB2YBCVS.js +2001 -0
- package/dist/chunk-KB2YBCVS.js.map +1 -0
- package/dist/{chunk-G4BIYE6R.js → chunk-LQEEDHOP.js} +40 -2
- package/dist/chunk-LQEEDHOP.js.map +1 -0
- package/dist/{chunk-FRD3FUXI.cjs → chunk-MGL4U5WA.cjs} +2144 -523
- package/dist/chunk-MGL4U5WA.cjs.map +1 -0
- package/dist/{chunk-T3QZ6S47.cjs → chunk-POGBUYWE.cjs} +131 -93
- package/dist/chunk-POGBUYWE.cjs.map +1 -0
- package/dist/chunk-VKK5VS2T.js +1 -0
- package/dist/{chunk-UTEVPQTS.cjs → chunk-W3GXSUSQ.cjs} +746 -1998
- package/dist/chunk-W3GXSUSQ.cjs.map +1 -0
- package/dist/{chunk-SZ6KMZNM.cjs → chunk-XIOMMGPD.cjs} +6 -2
- package/dist/chunk-XIOMMGPD.cjs.map +1 -0
- package/dist/{chunk-JK6M6V7G.js → chunk-Z3YWVUWT.js} +4 -4
- package/dist/{chunk-3PHA3UKI.js → chunk-Z4BC6M74.js} +523 -121
- package/dist/chunk-Z4BC6M74.js.map +1 -0
- package/dist/{export-a9bnF7Cl.d.ts → export-CO2ryu3I.d.ts} +419 -18
- package/dist/{export-JGWQEPJn.d.cts → export-DRiLzP_j.d.cts} +419 -18
- package/dist/image-editor/index.cjs +1665 -1113
- package/dist/image-editor/index.cjs.map +1 -1
- package/dist/image-editor/index.d.cts +78 -14
- package/dist/image-editor/index.d.ts +78 -14
- package/dist/image-editor/index.js +843 -291
- package/dist/image-editor/index.js.map +1 -1
- package/dist/paint/index.cjs +28 -4
- package/dist/paint/index.cjs.map +1 -1
- package/dist/paint/index.d.cts +3 -89
- package/dist/paint/index.d.ts +3 -89
- package/dist/paint/index.js +27 -3
- package/dist/paint-editor/index.cjs +1056 -287
- package/dist/paint-editor/index.cjs.map +1 -1
- package/dist/paint-editor/index.d.cts +304 -22
- package/dist/paint-editor/index.d.ts +304 -22
- package/dist/paint-editor/index.js +852 -83
- package/dist/paint-editor/index.js.map +1 -1
- package/dist/raster/index.cjs +7 -3
- package/dist/raster/index.cjs.map +1 -1
- package/dist/raster/index.d.cts +3 -129
- package/dist/raster/index.d.ts +3 -129
- package/dist/raster/index.js +6 -2
- package/dist/{compositor-D92URMlm.d.cts → render-BEriFuqK.d.cts} +136 -2
- package/dist/{compositor-Kt_M4ruo.d.ts → render-tjzKaik-.d.ts} +136 -2
- package/dist/{surface-CkKOHGPm.d.cts → surface-C8RRg_Bh.d.cts} +109 -1
- package/dist/{surface-CkKOHGPm.d.ts → surface-C8RRg_Bh.d.ts} +109 -1
- package/dist/{svg-SseJTIrA.d.ts → svg-D_cfkhlB.d.cts} +3 -629
- package/dist/{svg-ClVIG4T7.d.cts → svg-wIaKLrU0.d.ts} +3 -629
- package/dist/types-DtL_qLFz.d.ts +630 -0
- package/dist/types-DyQq-WPY.d.cts +630 -0
- package/dist/vector/index.cjs +563 -563
- package/dist/vector/index.cjs.map +1 -1
- package/dist/vector/index.d.cts +4 -2
- package/dist/vector/index.d.ts +4 -2
- package/dist/vector/index.js +69 -69
- package/dist/video-editor/index.cjs +3 -3
- package/dist/video-editor/index.js +2 -2
- package/package.json +1 -1
- package/skills/pixerate-paint-editor/SKILL.md +49 -13
- package/dist/chunk-3PHA3UKI.js.map +0 -1
- package/dist/chunk-6BU5WU4R.cjs +0 -1
- package/dist/chunk-6BU5WU4R.cjs.map +0 -1
- package/dist/chunk-DWGZUSES.cjs +0 -694
- package/dist/chunk-DWGZUSES.cjs.map +0 -1
- package/dist/chunk-FRD3FUXI.cjs.map +0 -1
- package/dist/chunk-G4BIYE6R.js.map +0 -1
- package/dist/chunk-HUFQD4CB.cjs +0 -734
- package/dist/chunk-HUFQD4CB.cjs.map +0 -1
- package/dist/chunk-HUQ7EHVI.js.map +0 -1
- package/dist/chunk-KKREERP2.js +0 -694
- package/dist/chunk-KKREERP2.js.map +0 -1
- package/dist/chunk-SOROZ2BF.js.map +0 -1
- package/dist/chunk-SZ6KMZNM.cjs.map +0 -1
- package/dist/chunk-T3QZ6S47.cjs.map +0 -1
- package/dist/chunk-UTEVPQTS.cjs.map +0 -1
- package/dist/chunk-XYBJKUAU.js +0 -1
- package/dist/mixer-5ziHU8OS.d.cts +0 -282
- package/dist/mixer-DNqiYXYQ.d.ts +0 -282
- /package/dist/{chunk-XYBJKUAU.js.map → chunk-VKK5VS2T.js.map} +0 -0
- /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-
|
|
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-
|
|
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-
|
|
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-
|
|
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-
|
|
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-
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
-
/**
|
|
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
|
-
|
|
5624
|
-
|
|
5625
|
-
const
|
|
5626
|
-
|
|
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
|
-
|
|
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
|
|
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: () => ({ ...
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
-
|
|
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,
|