@pixerate/editor 0.107.0 → 0.109.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/THIRD_PARTY_NOTICES.md +4 -0
- 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/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-T3QZ6S47.cjs → chunk-AZTZRLDI.cjs} +40 -2
- package/dist/chunk-AZTZRLDI.cjs.map +1 -0
- package/dist/{chunk-63EFQCT5.js → chunk-BLWCAQCW.js} +2879 -3362
- package/dist/chunk-BLWCAQCW.js.map +1 -0
- package/dist/{chunk-G4BIYE6R.js → chunk-CFMWALBM.js} +39 -1
- package/dist/chunk-CFMWALBM.js.map +1 -0
- package/dist/{chunk-FRD3FUXI.cjs → chunk-D37BX2ZA.cjs} +2081 -460
- package/dist/chunk-D37BX2ZA.cjs.map +1 -0
- 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-SOROZ2BF.js → chunk-RE77QYUF.js} +1938 -317
- package/dist/chunk-RE77QYUF.js.map +1 -0
- package/dist/{chunk-KANRAHTL.cjs → chunk-W3GXSUSQ.cjs} +2830 -3313
- package/dist/chunk-W3GXSUSQ.cjs.map +1 -0
- package/dist/{chunk-JK6M6V7G.js → chunk-Z3YWVUWT.js} +4 -4
- package/dist/{export-a9bnF7Cl.d.ts → export-BLPLaE6Z.d.ts} +417 -16
- package/dist/{export-JGWQEPJn.d.cts → export-DSUc4fQD.d.cts} +417 -16
- package/dist/image-editor/index.cjs +1527 -1001
- package/dist/image-editor/index.cjs.map +1 -1
- package/dist/image-editor/index.d.cts +73 -9
- package/dist/image-editor/index.d.ts +73 -9
- package/dist/image-editor/index.js +814 -288
- package/dist/image-editor/index.js.map +1 -1
- package/dist/paint-editor/index.cjs +252 -64
- package/dist/paint-editor/index.cjs.map +1 -1
- package/dist/paint-editor/index.d.cts +101 -4
- package/dist/paint-editor/index.d.ts +101 -4
- package/dist/paint-editor/index.js +196 -8
- package/dist/paint-editor/index.js.map +1 -1
- package/dist/raster/index.cjs +4 -2
- 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 +3 -1
- package/dist/{compositor-D92URMlm.d.cts → render-C-fO_mes.d.cts} +135 -1
- package/dist/{compositor-Kt_M4ruo.d.ts → render-OLIMk_v0.d.ts} +135 -1
- package/dist/{svg-vme0PLNo.d.ts → svg-D_cfkhlB.d.cts} +6 -608
- package/dist/{svg-Bj1MQFh6.d.cts → svg-wIaKLrU0.d.ts} +6 -608
- package/dist/types-DtL_qLFz.d.ts +630 -0
- package/dist/types-DyQq-WPY.d.cts +630 -0
- package/dist/vector/index.cjs +1080 -1051
- package/dist/vector/index.cjs.map +1 -1
- package/dist/vector/index.d.cts +268 -7
- package/dist/vector/index.d.ts +268 -7
- package/dist/vector/index.js +495 -466
- package/dist/vector/index.js.map +1 -1
- package/dist/video-editor/index.cjs +3 -3
- package/dist/video-editor/index.js +2 -2
- package/package.json +1 -1
- package/skills/pixerate-image-editor/SKILL.md +50 -7
- package/skills/pixerate-vector/SKILL.md +35 -5
- package/dist/chunk-63EFQCT5.js.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-KANRAHTL.cjs.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-T3QZ6S47.cjs.map +0 -1
- /package/dist/{chunk-JK6M6V7G.js.map → chunk-Z3YWVUWT.js.map} +0 -0
|
@@ -10,12 +10,17 @@ import {
|
|
|
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-RE77QYUF.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-CFMWALBM.js";
|
|
347
402
|
import {
|
|
348
403
|
applyHomography,
|
|
349
404
|
boxQuad,
|
|
@@ -444,6 +499,7 @@ import {
|
|
|
444
499
|
multiplyMatrices,
|
|
445
500
|
pullWarpMesh,
|
|
446
501
|
resizeWarpMesh,
|
|
502
|
+
roundSubpaths,
|
|
447
503
|
translateMatrix,
|
|
448
504
|
warpMeshColumns,
|
|
449
505
|
warpMeshEditPoints,
|
|
@@ -451,7 +507,10 @@ import {
|
|
|
451
507
|
warpMeshParamAt,
|
|
452
508
|
warpMeshRows,
|
|
453
509
|
warpPresetMesh
|
|
454
|
-
} from "../chunk-
|
|
510
|
+
} from "../chunk-BLWCAQCW.js";
|
|
511
|
+
import "../chunk-YCSFS3GK.js";
|
|
512
|
+
import "../chunk-ZRJMJIQU.js";
|
|
513
|
+
import "../chunk-TGD52FEZ.js";
|
|
455
514
|
|
|
456
515
|
// src/image-editor/histogram.ts
|
|
457
516
|
var HISTOGRAM_BINS = 256;
|
|
@@ -554,249 +613,6 @@ function quickMaskPixels(mask, style = {}) {
|
|
|
554
613
|
return out;
|
|
555
614
|
}
|
|
556
615
|
|
|
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
616
|
// src/image-editor/layers/render.ts
|
|
801
617
|
function isQuarterTurn(rotate) {
|
|
802
618
|
return normalizeAngle(rotate) % 90 === 0;
|
|
@@ -871,17 +687,28 @@ function needsLegacyBase(input) {
|
|
|
871
687
|
if (hasResampledLegacyMask(stack.layers, canvas.width, canvas.height)) return true;
|
|
872
688
|
return false;
|
|
873
689
|
}
|
|
874
|
-
var isPlainAnnotation = (l) => l.type === "annotation" && l.opacity === 1 && l.blend === "normal" && !l.clipped && !l.mask;
|
|
690
|
+
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;
|
|
691
|
+
var overlayViews = /* @__PURE__ */ new WeakMap();
|
|
692
|
+
function overlayView(l) {
|
|
693
|
+
if (l.type === "annotation") return l;
|
|
694
|
+
let view = overlayViews.get(l);
|
|
695
|
+
if (!view) {
|
|
696
|
+
const shape = l;
|
|
697
|
+
view = { type: "annotation", id: shape.id, name: shape.name, visible: shape.visible, opacity: 1, blend: "normal", annotation: shape.annotation };
|
|
698
|
+
overlayViews.set(l, view);
|
|
699
|
+
}
|
|
700
|
+
return view;
|
|
701
|
+
}
|
|
875
702
|
function splitOverlayAnnotations(layers) {
|
|
876
703
|
let i = layers.length;
|
|
877
704
|
while (i > 0) {
|
|
878
705
|
const l = layers[i - 1];
|
|
879
|
-
if (l.type === "annotation" && (!l.visible || isPlainAnnotation(l))) i--;
|
|
706
|
+
if ((l.type === "annotation" || l.type === "shape" && l.annotation) && (!l.visible || isPlainAnnotation(l))) i--;
|
|
880
707
|
else break;
|
|
881
708
|
}
|
|
882
709
|
return {
|
|
883
710
|
composite: layers.slice(0, i),
|
|
884
|
-
overlay: layers.slice(i).filter((l) => l.visible)
|
|
711
|
+
overlay: layers.slice(i).filter((l) => l.visible).map(overlayView)
|
|
885
712
|
};
|
|
886
713
|
}
|
|
887
714
|
function coveringRect(region) {
|
|
@@ -2672,7 +2499,7 @@ var FreeTransformSession = class {
|
|
|
2672
2499
|
}
|
|
2673
2500
|
const targetId = init.layerId ?? host.activeLayerId();
|
|
2674
2501
|
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";
|
|
2502
|
+
const smartTarget = (target?.type === "smart" || target?.type === "shape") && (init.channel ?? (target.id === host.activeLayerId() ? host.activeTarget() : "pixels")) !== "mask";
|
|
2676
2503
|
if (content === "auto") content = selected && !smartTarget ? "selection" : "layer";
|
|
2677
2504
|
this.content = content;
|
|
2678
2505
|
if (content !== "layer" && !selected) fail(`Nothing is selected; ${content} needs a selection. Select first, or use content layer.`);
|
|
@@ -2699,7 +2526,7 @@ var FreeTransformSession = class {
|
|
|
2699
2526
|
const linked = mask?.linked !== false;
|
|
2700
2527
|
const maskAlone = channel === "mask" && (!linked || layer.type !== "pixel");
|
|
2701
2528
|
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
|
|
2529
|
+
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
2530
|
if (!layer.visible && content !== "selection-copy") fail(`Layer "${layer.name || id}" is hidden; show it before you transform it.`);
|
|
2704
2531
|
if (content !== "selection-copy") {
|
|
2705
2532
|
requireUnlocked(layer, "position", "transform it");
|
|
@@ -2721,7 +2548,21 @@ var FreeTransformSession = class {
|
|
|
2721
2548
|
this.shown = this.startLayers;
|
|
2722
2549
|
return;
|
|
2723
2550
|
}
|
|
2724
|
-
if (maskAlone) {
|
|
2551
|
+
if (layer.type === "shape" && !maskAlone) {
|
|
2552
|
+
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).`);
|
|
2553
|
+
const g = shapeGeometryBounds(layer);
|
|
2554
|
+
if (!g) fail(`Shape layer "${layer.name || id}" has no path to transform.`);
|
|
2555
|
+
const w = Math.max(g.width, 1);
|
|
2556
|
+
const h = Math.max(g.height, 1);
|
|
2557
|
+
box = { x: g.x + (g.width - w) / 2, y: g.y + (g.height - h) / 2, width: w, height: h };
|
|
2558
|
+
this.shape = layer;
|
|
2559
|
+
this.shapeLayer = true;
|
|
2560
|
+
moves.push("pixels");
|
|
2561
|
+
if (mask && linked) {
|
|
2562
|
+
this.parts.push({ what: "mask", offset: o, moving: mask.surface, base: null, reduced: /* @__PURE__ */ new Map() });
|
|
2563
|
+
moves.push("mask");
|
|
2564
|
+
}
|
|
2565
|
+
} else if (maskAlone) {
|
|
2725
2566
|
if (content === "selection-copy") fail("Layer via Copy copies pixels; transform a mask with content layer or selection.");
|
|
2726
2567
|
if (content === "selection") {
|
|
2727
2568
|
const { lifted, rest } = liftMask(mask.surface, sel, o);
|
|
@@ -2806,6 +2647,9 @@ var FreeTransformSession = class {
|
|
|
2806
2647
|
startGeometry;
|
|
2807
2648
|
/** True when the layer is a smart object (the transform sets its placement). */
|
|
2808
2649
|
smartObject = false;
|
|
2650
|
+
/** True when the layer is a shape layer (the transform maps its paths and renders them again). */
|
|
2651
|
+
shapeLayer = false;
|
|
2652
|
+
shape = null;
|
|
2809
2653
|
geometryValue;
|
|
2810
2654
|
smart = null;
|
|
2811
2655
|
smartReduced = /* @__PURE__ */ new Map();
|
|
@@ -2871,6 +2715,7 @@ var FreeTransformSession = class {
|
|
|
2871
2715
|
let layers = this.startLayers;
|
|
2872
2716
|
let selection = this.startSelection;
|
|
2873
2717
|
const targetId = this.copyId ?? this.layerId;
|
|
2718
|
+
if (this.shape) layers = this.resampleShape(final, preview, warp ? f : null, h);
|
|
2874
2719
|
for (const part of this.parts) {
|
|
2875
2720
|
const o = part.offset;
|
|
2876
2721
|
let moved;
|
|
@@ -2922,6 +2767,24 @@ var FreeTransformSession = class {
|
|
|
2922
2767
|
}
|
|
2923
2768
|
return { layers, selection };
|
|
2924
2769
|
}
|
|
2770
|
+
/**
|
|
2771
|
+
* A shape with its paths mapped through the current geometry and rendered again (live shapes
|
|
2772
|
+
* stay live under an affine map; perspective and warps turn them into plain paths). Previews
|
|
2773
|
+
* render only the visible part.
|
|
2774
|
+
*/
|
|
2775
|
+
resampleShape(final, preview, warpMap, h) {
|
|
2776
|
+
const layer = this.shape;
|
|
2777
|
+
let components = layer.components;
|
|
2778
|
+
if (!isIdentityTransform(this.geometryValue)) {
|
|
2779
|
+
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;
|
|
2780
|
+
const map = warpMap ?? ((p) => h ? applyHomography(h, p) : p);
|
|
2781
|
+
components = transformShapeComponents(layer.components, (p) => map(p) ?? p, affine);
|
|
2782
|
+
}
|
|
2783
|
+
const moved = { ...layer, components };
|
|
2784
|
+
delete moved.annotation;
|
|
2785
|
+
const surface = renderShape(moved, this.host.shapeEnv(), final ? {} : { clip: preview.clip });
|
|
2786
|
+
return mapLayer(this.startLayers, layer.id, () => ({ ...moved, surface }));
|
|
2787
|
+
}
|
|
2925
2788
|
/** A smart object re-rendered from its contents for the current geometry (previews leave the smart filters out). */
|
|
2926
2789
|
resampleSmart(final, preview) {
|
|
2927
2790
|
const layer = this.smart;
|
|
@@ -2982,7 +2845,7 @@ var FreeTransformSession = class {
|
|
|
2982
2845
|
const id = this.copyId ?? this.layerId;
|
|
2983
2846
|
const layer = id ? findLayer(layers, id)?.layer : void 0;
|
|
2984
2847
|
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);
|
|
2848
|
+
else if (this.moves.includes("pixels") && (layer?.type === "smart" || layer?.type === "shape")) bounds = contentBounds(layer.surface);
|
|
2986
2849
|
else if (this.moves.includes("mask") && layer?.mask) bounds = toDoc(contentBounds(layer.mask.surface), layer.type === "pixel" ? layer.offset : { x: 0, y: 0 });
|
|
2987
2850
|
else if (this.moves.includes("selection")) bounds = selection?.bounds ? { ...selection.bounds } : null;
|
|
2988
2851
|
return {
|
|
@@ -3601,7 +3464,7 @@ var ImageEditorController = class {
|
|
|
3601
3464
|
*/
|
|
3602
3465
|
getLayerPixels(id) {
|
|
3603
3466
|
const layer = this.getLayer(id);
|
|
3604
|
-
if (!layer || layer
|
|
3467
|
+
if (!layer || !hasSurface(layer)) return null;
|
|
3605
3468
|
const surface = this.surfaceOf(layer);
|
|
3606
3469
|
const b = contentBounds(surface);
|
|
3607
3470
|
if (!b) return null;
|
|
@@ -3614,7 +3477,7 @@ var ImageEditorController = class {
|
|
|
3614
3477
|
*/
|
|
3615
3478
|
getLayerBounds(id) {
|
|
3616
3479
|
const layer = this.getLayer(id);
|
|
3617
|
-
if (!layer || layer
|
|
3480
|
+
if (!layer || !hasSurface(layer)) return null;
|
|
3618
3481
|
const surface = this.surfaceOf(layer);
|
|
3619
3482
|
let b = this.boundsCache.get(surface);
|
|
3620
3483
|
if (b === void 0) {
|
|
@@ -3629,7 +3492,7 @@ var ImageEditorController = class {
|
|
|
3629
3492
|
*/
|
|
3630
3493
|
getLayerDataURL(id) {
|
|
3631
3494
|
const layer = this.getLayer(id);
|
|
3632
|
-
if (!layer || layer
|
|
3495
|
+
if (!layer || !hasSurface(layer)) return null;
|
|
3633
3496
|
const surface = this.surfaceOf(layer);
|
|
3634
3497
|
const enc = encodeSurface(surface);
|
|
3635
3498
|
return enc ? enc.url : null;
|
|
@@ -3674,9 +3537,9 @@ var ImageEditorController = class {
|
|
|
3674
3537
|
}
|
|
3675
3538
|
return String(id);
|
|
3676
3539
|
}
|
|
3677
|
-
/** The pixels a pixel layer
|
|
3540
|
+
/** The pixels a pixel layer, smart object or shape composites (the decoded source for the Background). */
|
|
3678
3541
|
surfaceOf(layer) {
|
|
3679
|
-
if (layer.type
|
|
3542
|
+
if (layer.type !== "pixel" || layer.source !== "document") return layer.surface;
|
|
3680
3543
|
return this.sourceSurface() ?? layer.surface;
|
|
3681
3544
|
}
|
|
3682
3545
|
/**
|
|
@@ -3859,7 +3722,7 @@ var ImageEditorController = class {
|
|
|
3859
3722
|
removeLayer(id) {
|
|
3860
3723
|
const next = removeLayer(this.state.layers, id);
|
|
3861
3724
|
this.commitLayers(next, () => {
|
|
3862
|
-
if (this.state.selectedAnnotationId && !flattenLayers(next).some((l) => l
|
|
3725
|
+
if (this.state.selectedAnnotationId && !flattenLayers(next).some((l) => annotationOfLayer(l)?.id === this.state.selectedAnnotationId)) {
|
|
3863
3726
|
this.state.selectedAnnotationId = null;
|
|
3864
3727
|
}
|
|
3865
3728
|
});
|
|
@@ -3929,6 +3792,19 @@ var ImageEditorController = class {
|
|
|
3929
3792
|
const moved = findLayer(this.state.layers, id).layer;
|
|
3930
3793
|
return { offset: { x: Math.round(moved.placement.corners[0].x), y: Math.round(moved.placement.corners[0].y) }, bounds: this.getLayerBounds(id) };
|
|
3931
3794
|
}
|
|
3795
|
+
if (layer.type === "shape") {
|
|
3796
|
+
const g = shapeGeometryBounds(layer) ?? { x: 0, y: 0 };
|
|
3797
|
+
const dx = move.x !== void 0 ? move.x - g.x : move.dx ?? 0;
|
|
3798
|
+
const dy = move.y !== void 0 ? move.y - g.y : move.dy ?? 0;
|
|
3799
|
+
if (!Number.isFinite(dx) || !Number.isFinite(dy)) throw new LayerOperationError("x and y must be numbers.");
|
|
3800
|
+
requireUnlocked(layer, "position", "move it");
|
|
3801
|
+
let moved = offsetShape(layer, dx, dy, this.shapeEnv());
|
|
3802
|
+
const mask = layer.mask;
|
|
3803
|
+
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)) } };
|
|
3804
|
+
this.commitLayers(mapLayer(this.state.layers, id, () => moved), void 0, commitToHistory);
|
|
3805
|
+
const b = shapeGeometryBounds(moved) ?? { x: 0, y: 0 };
|
|
3806
|
+
return { offset: { x: Math.round(b.x), y: Math.round(b.y) }, bounds: this.getLayerBounds(id) };
|
|
3807
|
+
}
|
|
3932
3808
|
const current = layer.type === "pixel" ? layer.offset : { x: 0, y: 0 };
|
|
3933
3809
|
const x = move.x ?? current.x + (move.dx ?? 0);
|
|
3934
3810
|
const y = move.y ?? current.y + (move.dy ?? 0);
|
|
@@ -4076,6 +3952,7 @@ var ImageEditorController = class {
|
|
|
4076
3952
|
selection: () => this.state.selection?.bounds ? this.state.selection : null,
|
|
4077
3953
|
canvas: () => this.naturalSize,
|
|
4078
3954
|
pixelsOf: (layer) => this.surfaceOf(layer),
|
|
3955
|
+
shapeEnv: () => this.shapeEnv(),
|
|
4079
3956
|
capture: () => this.captureHistoryEntry(),
|
|
4080
3957
|
show: (layers, selection, active) => {
|
|
4081
3958
|
this.state.layers = layers;
|
|
@@ -4680,7 +4557,7 @@ var ImageEditorController = class {
|
|
|
4680
4557
|
paintBounds(layer) {
|
|
4681
4558
|
const { width, height } = this.naturalSize;
|
|
4682
4559
|
const canvas = { x: 0, y: 0, width, height };
|
|
4683
|
-
if (layer
|
|
4560
|
+
if (hasSurface(layer)) return this.getLayerBounds(layer.id) ?? { x: 0, y: 0, width: 0, height: 0 };
|
|
4684
4561
|
if (layer.type === "annotation") {
|
|
4685
4562
|
const b = getAnnotationBounds(layer.annotation);
|
|
4686
4563
|
const pad = 4 + ("strokeWidth" in layer.annotation ? layer.annotation.strokeWidth : 0);
|
|
@@ -4791,7 +4668,7 @@ var ImageEditorController = class {
|
|
|
4791
4668
|
const k = Math.min(1, side / Math.max(width, height));
|
|
4792
4669
|
const tw = Math.max(1, Math.round(width * k));
|
|
4793
4670
|
const th = Math.max(1, Math.round(height * k));
|
|
4794
|
-
const surface = options.mask ? layer.mask && !layer.mask.source ? layer.mask.surface : null : layer
|
|
4671
|
+
const surface = options.mask ? layer.mask && !layer.mask.source ? layer.mask.surface : null : hasSurface(layer) ? this.surfaceOf(layer) : null;
|
|
4795
4672
|
if (!surface) return null;
|
|
4796
4673
|
const ox = layer.type === "pixel" ? layer.offset.x : 0;
|
|
4797
4674
|
const oy = layer.type === "pixel" ? layer.offset.y : 0;
|
|
@@ -4895,6 +4772,7 @@ var ImageEditorController = class {
|
|
|
4895
4772
|
requireUnlocked(layer, "all", action);
|
|
4896
4773
|
} else {
|
|
4897
4774
|
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.`);
|
|
4775
|
+
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
4776
|
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
4777
|
requireUnlocked(layer, "pixels", action);
|
|
4900
4778
|
}
|
|
@@ -5598,17 +5476,21 @@ var ImageEditorController = class {
|
|
|
5598
5476
|
this.notify();
|
|
5599
5477
|
}
|
|
5600
5478
|
// --- Annotations & Layers ---
|
|
5479
|
+
/** The layer showing annotation `id`: an annotation layer, or a shape layer migrated from it. */
|
|
5601
5480
|
annotationLayer(id) {
|
|
5602
|
-
return flattenLayers(this.state.layers).find(
|
|
5603
|
-
(l) => l.type === "annotation" && l.annotation.id === id
|
|
5604
|
-
);
|
|
5481
|
+
return flattenLayers(this.state.layers).find((l) => (l.type === "annotation" || l.type === "shape") && annotationOfLayer(l)?.id === id);
|
|
5605
5482
|
}
|
|
5606
|
-
/**
|
|
5483
|
+
/**
|
|
5484
|
+
* Adds an annotation as its own layer (top of the stack unless `aboveId` is given). Pen,
|
|
5485
|
+
* rectangle, ellipse, line and arrow annotations become shape layers that remember the
|
|
5486
|
+
* annotation (so `annotations` and `updateAnnotation` keep working); text and images stay
|
|
5487
|
+
* annotation layers.
|
|
5488
|
+
*/
|
|
5607
5489
|
addAnnotation(annotation, placement = {}) {
|
|
5608
5490
|
this.recordHistory();
|
|
5609
5491
|
const id = `ann_${Date.now()}_${Math.random().toString(36).slice(2, 7)}`;
|
|
5610
5492
|
const fullAnnotation = { ...annotation, id };
|
|
5611
|
-
const layer = annotationLayerFor(fullAnnotation);
|
|
5493
|
+
const layer = annotationLayerFor(fullAnnotation, this.shapeEnv());
|
|
5612
5494
|
const loc = placement.aboveId ? findLayer(this.state.layers, placement.aboveId) : null;
|
|
5613
5495
|
this.state.layers = loc ? insertLayer(this.state.layers, layer, loc.parent?.id ?? null, loc.index + 1) : insertLayer(this.state.layers, layer, null);
|
|
5614
5496
|
this.state.selectedAnnotationId = id;
|
|
@@ -5620,13 +5502,33 @@ var ImageEditorController = class {
|
|
|
5620
5502
|
this.recordHistory();
|
|
5621
5503
|
}
|
|
5622
5504
|
const layer = this.annotationLayer(id);
|
|
5623
|
-
|
|
5624
|
-
|
|
5625
|
-
const
|
|
5626
|
-
|
|
5505
|
+
const current = layer ? annotationOfLayer(layer) : null;
|
|
5506
|
+
if (layer && current) {
|
|
5507
|
+
const annotation = { ...current, ...partial, id };
|
|
5508
|
+
const name = layer.name === annotationLayerName(current) ? annotationLayerName(annotation) : layer.name;
|
|
5509
|
+
this.state.layers = mapLayer(this.state.layers, layer.id, () => this.layerForAnnotation(layer, annotation, name));
|
|
5627
5510
|
}
|
|
5628
5511
|
this.notify();
|
|
5629
5512
|
}
|
|
5513
|
+
/** `layer` (an annotation or migrated shape layer) showing `annotation` instead, keeping its other properties. */
|
|
5514
|
+
layerForAnnotation(layer, annotation, name) {
|
|
5515
|
+
const shape = shapeFromAnnotation(annotation);
|
|
5516
|
+
const { annotation: _old, ...props } = layer;
|
|
5517
|
+
const blend = annotation.blend && annotation.blend !== "normal" ? annotation.blend : "normal";
|
|
5518
|
+
if (shape) {
|
|
5519
|
+
const base = { ...props, name, blend: layer.type === "annotation" && layer.blend !== "normal" ? layer.blend : blend };
|
|
5520
|
+
if (layer.type === "shape") {
|
|
5521
|
+
const fresh = rerenderShape({ ...base, components: shape.components, fill: shape.fill, stroke: shape.stroke }, this.shapeEnv());
|
|
5522
|
+
delete fresh.fillRule;
|
|
5523
|
+
return { ...fresh, annotation };
|
|
5524
|
+
}
|
|
5525
|
+
const created = createShapeLayer({ ...shape, name, blend: base.blend }, this.shapeEnv(), layer.id);
|
|
5526
|
+
const { type: _t2, ...rest2 } = base;
|
|
5527
|
+
return { ...rest2, ...created, annotation };
|
|
5528
|
+
}
|
|
5529
|
+
const { type: _t, components: _c, fill: _f, stroke: _s, surface: _sf, offset: _o, fillRule: _r, fillOpacity: _fo, psd: _p, ...rest } = props;
|
|
5530
|
+
return { ...rest, type: "annotation", name, blend: layer.type === "shape" ? "normal" : layer.blend, annotation };
|
|
5531
|
+
}
|
|
5630
5532
|
removeAnnotation(id) {
|
|
5631
5533
|
this.recordHistory();
|
|
5632
5534
|
const layer = this.annotationLayer(id);
|
|
@@ -5636,10 +5538,11 @@ var ImageEditorController = class {
|
|
|
5636
5538
|
}
|
|
5637
5539
|
this.notify();
|
|
5638
5540
|
}
|
|
5541
|
+
/** Deletes every annotation layer and every shape layer that still is the annotation it was migrated from. */
|
|
5639
5542
|
clearAnnotations() {
|
|
5640
5543
|
this.recordHistory();
|
|
5641
5544
|
let layers = this.state.layers;
|
|
5642
|
-
for (const l of flattenLayers(layers)) if (l.type === "annotation") layers = mapLayer(layers, l.id, () => null);
|
|
5545
|
+
for (const l of flattenLayers(layers)) if (l.type === "annotation" || l.type === "shape" && l.annotation) layers = mapLayer(layers, l.id, () => null);
|
|
5643
5546
|
this.state.layers = layers;
|
|
5644
5547
|
this.state.selectedAnnotationId = null;
|
|
5645
5548
|
this.notify();
|
|
@@ -5878,6 +5781,142 @@ var ImageEditorController = class {
|
|
|
5878
5781
|
this.commitLayers(setLayerMask(this.state.layers, target.id, { surface, defaultValue: 0 }));
|
|
5879
5782
|
return target.id;
|
|
5880
5783
|
}
|
|
5784
|
+
// --- Shape layers ---
|
|
5785
|
+
/** What shape rendering needs: the canvas size and the document's patterns. */
|
|
5786
|
+
shapeEnv() {
|
|
5787
|
+
return { ...this.naturalSize, pattern: (id) => this.state.patterns?.find((p) => p.id === id) ?? null };
|
|
5788
|
+
}
|
|
5789
|
+
/** One component from tool or UI input (validated). */
|
|
5790
|
+
componentFrom(spec, path) {
|
|
5791
|
+
const op = requireOperation(spec.op, `${path}op`);
|
|
5792
|
+
if (spec.shape !== void 0 && spec.subpaths !== void 0) throw new LayerOperationError(`Give ${path}shape or ${path}subpaths, not both.`);
|
|
5793
|
+
if (spec.shape !== void 0) return liveComponent(normalizeLiveShape(spec.shape, `${path}shape`), op);
|
|
5794
|
+
if (spec.subpaths !== void 0) return { op, subpaths: normalizeSubpaths(spec.subpaths, `${path}subpaths`) };
|
|
5795
|
+
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.`);
|
|
5796
|
+
}
|
|
5797
|
+
shapeInfo(id) {
|
|
5798
|
+
const layer = requireShapeLayer(this.state.layers, id);
|
|
5799
|
+
const r = (n) => Math.round(n * 1e3) / 1e3;
|
|
5800
|
+
const box = (b) => b ? { x: r(b.x), y: r(b.y), width: r(b.width), height: r(b.height) } : null;
|
|
5801
|
+
const info = {
|
|
5802
|
+
id: layer.id,
|
|
5803
|
+
name: layer.name,
|
|
5804
|
+
components: layer.components.map((c, index) => ({
|
|
5805
|
+
index,
|
|
5806
|
+
op: c.op,
|
|
5807
|
+
live: c.live ? JSON.parse(JSON.stringify(c.live)) : null,
|
|
5808
|
+
subpaths: roundSubpaths(c.subpaths),
|
|
5809
|
+
bounds: box(shapeGeometryBounds({ components: [c] }))
|
|
5810
|
+
})),
|
|
5811
|
+
fillRule: layer.fillRule ?? "nonzero",
|
|
5812
|
+
fill: layer.fill ? JSON.parse(JSON.stringify(layer.fill)) : null,
|
|
5813
|
+
stroke: layer.stroke ? JSON.parse(JSON.stringify(layer.stroke)) : null,
|
|
5814
|
+
geometryBounds: box(shapeGeometryBounds(layer)),
|
|
5815
|
+
bounds: this.getLayerBounds(id)
|
|
5816
|
+
};
|
|
5817
|
+
if (layer.annotation) info.annotationId = layer.annotation.id;
|
|
5818
|
+
return info;
|
|
5819
|
+
}
|
|
5820
|
+
/** A shape layer's components, fill, stroke and bounds. */
|
|
5821
|
+
getShapeLayer(id) {
|
|
5822
|
+
return this.shapeInfo(id);
|
|
5823
|
+
}
|
|
5824
|
+
/**
|
|
5825
|
+
* Adds a shape layer (one undo step) and makes it active: a live shape or subpaths (or several
|
|
5826
|
+
* `components`), filled with `fill` (default the foreground colour, except for a stroke-only
|
|
5827
|
+
* open path) and stroked with `stroke`. Goes above the active layer unless placed. Returns its id.
|
|
5828
|
+
*/
|
|
5829
|
+
addShapeLayer(init) {
|
|
5830
|
+
const specs = init.components?.length ? init.components : [init];
|
|
5831
|
+
if (init.components?.length && (init.shape !== void 0 || init.subpaths !== void 0)) throw new LayerOperationError("Give components, or shape / subpaths, not both.");
|
|
5832
|
+
const components = specs.map((c, i) => this.componentFrom(c, init.components?.length ? `components[${i}].` : ""));
|
|
5833
|
+
const stroke = init.stroke ? normalizeShapeStroke(init.stroke, "stroke") : null;
|
|
5834
|
+
const openOnly = components.every((c) => !c.live && c.subpaths.every((sp) => !sp.closed));
|
|
5835
|
+
const fill = init.fill === void 0 ? stroke && openOnly ? null : normalizeShapePaint(this.state.paint.foreground, "fill") : init.fill === null ? null : normalizeShapePaint(init.fill, "fill");
|
|
5836
|
+
if (!fill && !stroke) throw new LayerOperationError("A shape with neither fill nor stroke draws nothing; give fill or stroke.");
|
|
5837
|
+
if (init.fillRule !== void 0 && init.fillRule !== "nonzero" && init.fillRule !== "evenodd") throw new LayerOperationError("fillRule must be nonzero or evenodd.");
|
|
5838
|
+
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.");
|
|
5839
|
+
if (init.blend !== void 0 && !isBlendMode(init.blend)) throw new LayerOperationError(`Unknown blend mode "${String(init.blend)}".`);
|
|
5840
|
+
const layer = createShapeLayer({ components, fill, stroke, fillRule: init.fillRule, name: init.name, opacity: init.opacity, blend: init.blend }, this.shapeEnv());
|
|
5841
|
+
const next = placeLayer(this.state.layers, layer, this.newLayerPlacement(init));
|
|
5842
|
+
this.commitLayers(next, () => {
|
|
5843
|
+
this.state.activeLayerId = layer.id;
|
|
5844
|
+
this.state.activeTarget = "pixels";
|
|
5845
|
+
});
|
|
5846
|
+
return layer.id;
|
|
5847
|
+
}
|
|
5848
|
+
/**
|
|
5849
|
+
* Sets a shape's fill, stroke or fill rule (one undo step unless `commitToHistory` is false):
|
|
5850
|
+
* `fill` / `stroke` replace them (`null` removes); `stroke` given as a partial object changes
|
|
5851
|
+
* only those settings of the current stroke. Returns the shape.
|
|
5852
|
+
*/
|
|
5853
|
+
setShapeStyle(id, changes, commitToHistory = true) {
|
|
5854
|
+
const layer = requireShapeLayer(this.state.layers, id);
|
|
5855
|
+
const fill = changes.fill === void 0 ? layer.fill : changes.fill === null ? null : normalizeShapePaint(changes.fill, "fill");
|
|
5856
|
+
const stroke = changes.stroke === void 0 ? layer.stroke : changes.stroke === null ? null : normalizeShapeStroke(changes.stroke, "stroke", layer.stroke);
|
|
5857
|
+
if (changes.fillRule !== void 0 && changes.fillRule !== "nonzero" && changes.fillRule !== "evenodd") throw new LayerOperationError("fillRule must be nonzero or evenodd.");
|
|
5858
|
+
const fillRule = changes.fillRule ?? layer.fillRule ?? "nonzero";
|
|
5859
|
+
const { layers } = updateShapeLayer(this.state.layers, id, () => ({ fill, stroke, fillRule: fillRule === "nonzero" ? void 0 : fillRule }), this.shapeEnv(), "restyle it");
|
|
5860
|
+
this.commitLayers(layers, void 0, commitToHistory);
|
|
5861
|
+
return this.shapeInfo(id);
|
|
5862
|
+
}
|
|
5863
|
+
componentIndex(layer, index) {
|
|
5864
|
+
if (!Number.isInteger(index) || index < 0 || index >= layer.components.length) {
|
|
5865
|
+
throw new LayerOperationError(`componentIndex must be 0..${layer.components.length - 1} (get_shape_layer lists the components), got ${String(index)}.`);
|
|
5866
|
+
}
|
|
5867
|
+
return index;
|
|
5868
|
+
}
|
|
5869
|
+
/**
|
|
5870
|
+
* Changes one component (one undo step unless `commitToHistory` is false): its live shape
|
|
5871
|
+
* (fields not given keep their values when the kind stays the same), its subpaths (which drops
|
|
5872
|
+
* the live shape) or its operation. Returns the shape.
|
|
5873
|
+
*/
|
|
5874
|
+
setShapeComponent(id, index, change, commitToHistory = true) {
|
|
5875
|
+
const layer = requireShapeLayer(this.state.layers, id);
|
|
5876
|
+
const i = this.componentIndex(layer, index);
|
|
5877
|
+
const current = layer.components[i];
|
|
5878
|
+
let shape = change.shape;
|
|
5879
|
+
if (shape && current.live && shape.kind === void 0) shape = { ...current.live, ...shape };
|
|
5880
|
+
else if (shape && current.live && shape.kind === current.live.kind) shape = { ...current.live, ...shape };
|
|
5881
|
+
const next = shape !== void 0 || change.subpaths !== void 0 ? this.componentFrom({ ...change, shape, op: change.op ?? current.op }, "") : { ...current, op: requireOperation(change.op) };
|
|
5882
|
+
const components = layer.components.map((c, k) => k === i ? next : c);
|
|
5883
|
+
const { layers } = updateShapeLayer(this.state.layers, id, () => ({ components }), this.shapeEnv(), "edit its path");
|
|
5884
|
+
this.commitLayers(layers, void 0, commitToHistory);
|
|
5885
|
+
return this.shapeInfo(id);
|
|
5886
|
+
}
|
|
5887
|
+
/** Adds a component to a shape (path operations), on top unless `index` is given (one undo step). Returns the shape. */
|
|
5888
|
+
addShapeComponent(id, spec, index) {
|
|
5889
|
+
const layer = requireShapeLayer(this.state.layers, id);
|
|
5890
|
+
const c = this.componentFrom(spec, "");
|
|
5891
|
+
const at = index === void 0 ? layer.components.length : index;
|
|
5892
|
+
if (!Number.isInteger(at) || at < 0 || at > layer.components.length) throw new LayerOperationError(`index must be 0..${layer.components.length}, got ${String(index)}.`);
|
|
5893
|
+
const components = [...layer.components.slice(0, at), c, ...layer.components.slice(at)];
|
|
5894
|
+
const { layers } = updateShapeLayer(this.state.layers, id, () => ({ components }), this.shapeEnv(), "edit its path");
|
|
5895
|
+
this.commitLayers(layers);
|
|
5896
|
+
return this.shapeInfo(id);
|
|
5897
|
+
}
|
|
5898
|
+
/** Removes one component of a shape (one undo step); the last one cannot go (delete the layer instead). Returns the shape. */
|
|
5899
|
+
removeShapeComponent(id, index) {
|
|
5900
|
+
const layer = requireShapeLayer(this.state.layers, id);
|
|
5901
|
+
const i = this.componentIndex(layer, index);
|
|
5902
|
+
if (layer.components.length === 1) throw new LayerOperationError("This is the shape's only component; delete the layer instead (delete_layer).");
|
|
5903
|
+
const components = layer.components.filter((_, k) => k !== i);
|
|
5904
|
+
const { layers } = updateShapeLayer(this.state.layers, id, () => ({ components }), this.shapeEnv(), "edit its path");
|
|
5905
|
+
this.commitLayers(layers);
|
|
5906
|
+
return this.shapeInfo(id);
|
|
5907
|
+
}
|
|
5908
|
+
/**
|
|
5909
|
+
* Merge Shape Components: the area the components combine to becomes one plain component (live
|
|
5910
|
+
* shapes become paths), as one undo step. Returns the shape.
|
|
5911
|
+
*/
|
|
5912
|
+
mergeShapeComponents(id) {
|
|
5913
|
+
const layer = requireShapeLayer(this.state.layers, id);
|
|
5914
|
+
if (layer.components.length < 2) throw new LayerOperationError("The shape has one component; there is nothing to merge.");
|
|
5915
|
+
const components = mergeShapeComponents(layer.components, layer.fillRule);
|
|
5916
|
+
const { layers } = updateShapeLayer(this.state.layers, id, () => ({ components, fillRule: void 0 }), this.shapeEnv(), "edit its path");
|
|
5917
|
+
this.commitLayers(layers);
|
|
5918
|
+
return this.shapeInfo(id);
|
|
5919
|
+
}
|
|
5881
5920
|
/**
|
|
5882
5921
|
* Rasterize Layer: an annotation layer becomes a pixel layer with the same id, name and
|
|
5883
5922
|
* properties (needs a canvas), and a pixel layer's filters are baked into its pixels. One undo
|
|
@@ -5918,8 +5957,11 @@ var ImageEditorController = class {
|
|
|
5918
5957
|
}
|
|
5919
5958
|
} else if (layer.type === "smart") {
|
|
5920
5959
|
return this.rasterizeSmartObject(id);
|
|
5960
|
+
} else if (layer.type === "shape") {
|
|
5961
|
+
next = shapeAsPixelLayer(layer);
|
|
5962
|
+
if (this.state.selectedAnnotationId && this.state.selectedAnnotationId === layer.annotation?.id) this.state.selectedAnnotationId = null;
|
|
5921
5963
|
} 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.`);
|
|
5964
|
+
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
5965
|
}
|
|
5924
5966
|
this.commitLayers(mapLayer(this.state.layers, id, () => next));
|
|
5925
5967
|
return id;
|
|
@@ -6044,6 +6086,14 @@ var ImageEditorController = class {
|
|
|
6044
6086
|
if (mask2) out2.mask = mask2;
|
|
6045
6087
|
return out2;
|
|
6046
6088
|
}
|
|
6089
|
+
if (layer.type === "shape") {
|
|
6090
|
+
add2("Shape layers were written as pixel layers", layer.name);
|
|
6091
|
+
const out2 = shapeAsPixelLayer(layer);
|
|
6092
|
+
const mask2 = bakeMask(layer);
|
|
6093
|
+
if (mask2) out2.mask = mask2;
|
|
6094
|
+
else delete out2.mask;
|
|
6095
|
+
return out2;
|
|
6096
|
+
}
|
|
6047
6097
|
if (layer.type === "smart") {
|
|
6048
6098
|
const out2 = { ...layer };
|
|
6049
6099
|
const mask2 = bakeMask(layer);
|
|
@@ -6120,16 +6170,27 @@ var ImageEditorController = class {
|
|
|
6120
6170
|
);
|
|
6121
6171
|
}
|
|
6122
6172
|
layers = resizeLegacyMasks(layers, prev, next.dimensions);
|
|
6173
|
+
const env = { ...next.dimensions, pattern: (pid) => this.state.patterns?.find((p) => p.id === pid) ?? null };
|
|
6123
6174
|
for (const l of flattenLayers(layers)) {
|
|
6124
6175
|
if (l.type === "annotation") {
|
|
6125
6176
|
layers = mapLayer(layers, l.id, () => ({ ...l, annotation: scaleAnnotation(l.annotation, sx, sy) }));
|
|
6177
|
+
} else if (l.type === "shape") {
|
|
6178
|
+
const a = l.annotation;
|
|
6179
|
+
const scaled = a ? scaleAnnotation(a, sx, sy) : null;
|
|
6180
|
+
const fromAnnotation = scaled ? shapeFromAnnotation(scaled) : null;
|
|
6181
|
+
const m = [sx, 0, 0, sy, 0, 0];
|
|
6182
|
+
const components = fromAnnotation?.components ?? transformShapeComponents(l.components, (p) => ({ x: p.x * sx, y: p.y * sy }), m);
|
|
6183
|
+
const moved = { ...l, components, ...fromAnnotation ? { fill: fromAnnotation.fill, stroke: fromAnnotation.stroke } : {} };
|
|
6184
|
+
if (scaled && fromAnnotation) moved.annotation = scaled;
|
|
6185
|
+
else delete moved.annotation;
|
|
6186
|
+
layers = mapLayer(layers, l.id, () => rerenderShape(moved, env));
|
|
6126
6187
|
}
|
|
6127
6188
|
}
|
|
6128
6189
|
}
|
|
6129
6190
|
const bg = developTarget(layers, null);
|
|
6130
6191
|
if (bg && bg.source !== "document") layers = mapLayer(layers, bg.id, () => ({ ...bg, source: "document" }));
|
|
6131
6192
|
if (clearAnnotations) {
|
|
6132
|
-
for (const l of flattenLayers(layers)) if (l.type === "annotation") layers = mapLayer(layers, l.id, () => null);
|
|
6193
|
+
for (const l of flattenLayers(layers)) if (l.type === "annotation" || l.type === "shape" && l.annotation) layers = mapLayer(layers, l.id, () => null);
|
|
6133
6194
|
this.state.selectedAnnotationId = null;
|
|
6134
6195
|
}
|
|
6135
6196
|
this.state.layers = layers;
|
|
@@ -6199,7 +6260,14 @@ var ImageEditorController = class {
|
|
|
6199
6260
|
if (isLayered(data)) {
|
|
6200
6261
|
const problems = [];
|
|
6201
6262
|
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
|
-
|
|
6263
|
+
const storedPatterns = deserializePatterns(data.patterns, []);
|
|
6264
|
+
s.layers = deserializeLayers(data.layers, {
|
|
6265
|
+
assets: data.assets ?? {},
|
|
6266
|
+
problems,
|
|
6267
|
+
smartContents: Array.isArray(data.smartContents) ? data.smartContents : [],
|
|
6268
|
+
canvas,
|
|
6269
|
+
pattern: (pid) => storedPatterns.find((p) => p.id === pid) ?? null
|
|
6270
|
+
});
|
|
6203
6271
|
this.lastLoadProblems = problems;
|
|
6204
6272
|
s.crop = data.crop ? { ...data.crop } : null;
|
|
6205
6273
|
s.transform = { ...data.transform };
|
|
@@ -6401,6 +6469,7 @@ function collectStateTiles(layers, into) {
|
|
|
6401
6469
|
for (const l of flattenLayers(layers)) {
|
|
6402
6470
|
if (l.type === "pixel" && l.source !== "document") collectTiles(l.surface, into);
|
|
6403
6471
|
if (l.mask && !l.mask.source) collectTiles(l.mask.surface, into);
|
|
6472
|
+
if (l.type === "shape") collectTiles(l.surface, into);
|
|
6404
6473
|
if (l.type === "smart") {
|
|
6405
6474
|
collectTiles(l.surface, into);
|
|
6406
6475
|
if (l.smartFilters?.mask) collectTiles(l.smartFilters.mask.surface, into);
|
|
@@ -6449,7 +6518,7 @@ function resizePixels(px2, width, height) {
|
|
|
6449
6518
|
function withoutAnnotations(layers) {
|
|
6450
6519
|
const out = [];
|
|
6451
6520
|
for (const l of layers) {
|
|
6452
|
-
if (l.type === "annotation") continue;
|
|
6521
|
+
if (l.type === "annotation" || l.type === "shape" && l.annotation) continue;
|
|
6453
6522
|
out.push(l.type === "group" ? { ...l, children: withoutAnnotations(l.children) } : l);
|
|
6454
6523
|
}
|
|
6455
6524
|
return out;
|
|
@@ -6639,7 +6708,7 @@ function loadImageEditorDocument(input) {
|
|
|
6639
6708
|
if (state.selection === void 0) delete state.selection;
|
|
6640
6709
|
return { state, report: result.report, unknown: result.unknown };
|
|
6641
6710
|
}
|
|
6642
|
-
var LAYER_TYPES = /* @__PURE__ */ new Set(["pixel", "adjustment", "group", "annotation", "smart"]);
|
|
6711
|
+
var LAYER_TYPES = /* @__PURE__ */ new Set(["pixel", "adjustment", "group", "annotation", "smart", "shape"]);
|
|
6643
6712
|
function cleanLayers(value, dropped) {
|
|
6644
6713
|
const out = [];
|
|
6645
6714
|
for (const raw of value) {
|
|
@@ -7431,7 +7500,7 @@ function createImageEditTools(controller) {
|
|
|
7431
7500
|
return { layer: layers[index], index };
|
|
7432
7501
|
};
|
|
7433
7502
|
const layerJson = (id) => JSON.parse(JSON.stringify(controller.getAdjustmentLayer(id)));
|
|
7434
|
-
const
|
|
7503
|
+
const geometry2 = () => {
|
|
7435
7504
|
const s = controller.getState();
|
|
7436
7505
|
const region = s.crop ?? { x: 0, y: 0, ...s.imageDimensions };
|
|
7437
7506
|
const out = getRotatedSize(region.width, region.height, s.transform);
|
|
@@ -7456,7 +7525,7 @@ function createImageEditTools(controller) {
|
|
|
7456
7525
|
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
7526
|
inputSchema: { type: "object", properties: {}, additionalProperties: false },
|
|
7458
7527
|
annotations: { title: "Read image geometry", ...READ },
|
|
7459
|
-
handler: () => ({ ...
|
|
7528
|
+
handler: () => ({ ...geometry2(), layers: controller.getAdjustmentLayers().length, ...history() })
|
|
7460
7529
|
};
|
|
7461
7530
|
const getHistogram = {
|
|
7462
7531
|
name: "get_histogram",
|
|
@@ -7560,7 +7629,7 @@ function createImageEditTools(controller) {
|
|
|
7560
7629
|
throw new Error(`The crop box (${x}, ${y}, ${width} \xD7 ${height}) goes outside the ${image.width} \xD7 ${image.height} image.`);
|
|
7561
7630
|
}
|
|
7562
7631
|
controller.setCrop({ x: Math.round(x), y: Math.round(y), width: Math.round(width), height: Math.round(height) });
|
|
7563
|
-
return
|
|
7632
|
+
return geometry2();
|
|
7564
7633
|
}
|
|
7565
7634
|
};
|
|
7566
7635
|
const resetCrop = {
|
|
@@ -7572,7 +7641,7 @@ function createImageEditTools(controller) {
|
|
|
7572
7641
|
handler: () => {
|
|
7573
7642
|
if (!controller.getState().crop) throw new Error("The image is not cropped.");
|
|
7574
7643
|
controller.resetCrop();
|
|
7575
|
-
return
|
|
7644
|
+
return geometry2();
|
|
7576
7645
|
}
|
|
7577
7646
|
};
|
|
7578
7647
|
const setRotation = {
|
|
@@ -7589,7 +7658,7 @@ function createImageEditTools(controller) {
|
|
|
7589
7658
|
handler: ({ degrees }) => {
|
|
7590
7659
|
requireImage();
|
|
7591
7660
|
controller.setRotation(degrees);
|
|
7592
|
-
return
|
|
7661
|
+
return geometry2();
|
|
7593
7662
|
}
|
|
7594
7663
|
};
|
|
7595
7664
|
const flipImage = {
|
|
@@ -7607,7 +7676,7 @@ function createImageEditTools(controller) {
|
|
|
7607
7676
|
requireImage();
|
|
7608
7677
|
if (axis === "horizontal") controller.flipHorizontal();
|
|
7609
7678
|
else controller.flipVertical();
|
|
7610
|
-
return
|
|
7679
|
+
return geometry2();
|
|
7611
7680
|
}
|
|
7612
7681
|
};
|
|
7613
7682
|
const undo = {
|
|
@@ -7689,6 +7758,16 @@ function describeLayer(layer, activeId, boundsOf) {
|
|
|
7689
7758
|
out.collapsed = Boolean(layer.collapsed);
|
|
7690
7759
|
out.children = layer.children.map((c) => describeLayer(c, activeId, boundsOf));
|
|
7691
7760
|
break;
|
|
7761
|
+
case "shape":
|
|
7762
|
+
out.bounds = boundsOf(layer);
|
|
7763
|
+
out.fillOpacity = round3(layer.fillOpacity ?? 1);
|
|
7764
|
+
out.shape = {
|
|
7765
|
+
components: layer.components.map((c) => ({ op: c.op, kind: c.live?.kind ?? "path" })),
|
|
7766
|
+
fill: layer.fill ? layer.fill.type : null,
|
|
7767
|
+
stroke: layer.stroke ? { width: layer.stroke.width, paint: layer.stroke.paint.type } : null
|
|
7768
|
+
};
|
|
7769
|
+
if (layer.annotation) out.annotation = { id: layer.annotation.id, type: layer.annotation.type };
|
|
7770
|
+
break;
|
|
7692
7771
|
case "smart":
|
|
7693
7772
|
out.bounds = boundsOf(layer);
|
|
7694
7773
|
out.fillOpacity = round3(layer.fillOpacity ?? 1);
|
|
@@ -7734,7 +7813,7 @@ function createImageLayerTools(controller) {
|
|
|
7734
7813
|
const listLayers = {
|
|
7735
7814
|
name: "list_layers",
|
|
7736
7815
|
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.",
|
|
7816
|
+
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
7817
|
inputSchema: { type: "object", properties: {}, additionalProperties: false },
|
|
7739
7818
|
annotations: { title: "List image layers", ...READ2 },
|
|
7740
7819
|
handler: () => {
|
|
@@ -8078,7 +8157,7 @@ function createImageLayerTools(controller) {
|
|
|
8078
8157
|
const rasterize = {
|
|
8079
8158
|
name: "rasterize_layer",
|
|
8080
8159
|
title: "Rasterize a layer",
|
|
8081
|
-
description: "Rasterize Layer: turns
|
|
8160
|
+
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
8161
|
inputSchema: { type: "object", properties: { layerId: LAYER_ID2 }, required: ["layerId"], additionalProperties: false },
|
|
8083
8162
|
annotations: { title: "Rasterize a layer", ...DESTRUCTIVE },
|
|
8084
8163
|
handler: async ({ layerId }) => ({ layer: describe(await controller.rasterizeLayer(exists(layerId))) })
|
|
@@ -8181,7 +8260,7 @@ var READ3 = { readOnlyHint: true, destructiveHint: false, idempotentHint: true,
|
|
|
8181
8260
|
var WRITE3 = { readOnlyHint: false, destructiveHint: false, idempotentHint: false, openWorldHint: false };
|
|
8182
8261
|
var WRITE_IDEMPOTENT3 = { readOnlyHint: false, destructiveHint: false, idempotentHint: true, openWorldHint: false };
|
|
8183
8262
|
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)." };
|
|
8263
|
+
var LAYER_ID3 = { type: "string", description: "Layer id from list_layers (a pixel, shape, smart object, annotation or group layer)." };
|
|
8185
8264
|
var EFFECT_ID = { type: "string", description: "Effect id from list_layer_effects or add_layer_effect." };
|
|
8186
8265
|
var BLEND_IDS = BLEND_MODES.map((m) => m.id).filter((id) => id !== "pigment");
|
|
8187
8266
|
var DEFS = {
|
|
@@ -8603,9 +8682,13 @@ var PaintGesture = class {
|
|
|
8603
8682
|
if (tool === "move") {
|
|
8604
8683
|
const layer = this.controller.getActiveLayer();
|
|
8605
8684
|
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.`);
|
|
8685
|
+
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
8686
|
if (layer.locks?.all || layer.locks?.position) throw new Error(`Layer "${layer.name}" has its position locked.`);
|
|
8608
|
-
|
|
8687
|
+
const origin = layer.type === "pixel" ? { ...layer.offset } : layer.type === "smart" ? { ...layer.placement.corners[0] } : (() => {
|
|
8688
|
+
const b = shapeGeometryBounds(layer);
|
|
8689
|
+
return b ? { x: b.x, y: b.y } : { x: 0, y: 0 };
|
|
8690
|
+
})();
|
|
8691
|
+
this.move = { id: layer.id, start: { x: sample.x, y: sample.y }, origin, moved: false };
|
|
8609
8692
|
return null;
|
|
8610
8693
|
}
|
|
8611
8694
|
if (tool === "brush" || tool === "eraser" || tool === "mixer") {
|
|
@@ -9520,6 +9603,409 @@ function createImageSmartObjectTools(controller) {
|
|
|
9520
9603
|
return [getSmart, convert, rasterize, setTransform, replace, exportContents, copy, editContents, closeContents, addFilter, updateFilter, removeFilter, moveFilter, setEnabled, setMask];
|
|
9521
9604
|
}
|
|
9522
9605
|
|
|
9606
|
+
// src/image-editor/shape-tools.ts
|
|
9607
|
+
var READ8 = { readOnlyHint: true, destructiveHint: false, idempotentHint: true, openWorldHint: false };
|
|
9608
|
+
var WRITE7 = { readOnlyHint: false, destructiveHint: false, idempotentHint: false, openWorldHint: false };
|
|
9609
|
+
var WRITE_IDEMPOTENT5 = { readOnlyHint: false, destructiveHint: false, idempotentHint: true, openWorldHint: false };
|
|
9610
|
+
var DESTRUCTIVE6 = { readOnlyHint: false, destructiveHint: true, idempotentHint: false, openWorldHint: false };
|
|
9611
|
+
var LAYER_ID5 = { type: "string", description: "Shape layer id from list_layers (type shape) or add_shape_layer." };
|
|
9612
|
+
var COMPONENT_INDEX = { type: "integer", minimum: 0, description: "Component index from get_shape_layer (0 = bottom component)." };
|
|
9613
|
+
var OP = {
|
|
9614
|
+
type: "string",
|
|
9615
|
+
enum: ["combine", "subtract", "intersect", "exclude"],
|
|
9616
|
+
description: "How the component combines with the components below it (Photoshop path operations; default combine). The bottom component always combines."
|
|
9617
|
+
};
|
|
9618
|
+
var NUM = (description) => ({ type: "number", description });
|
|
9619
|
+
var MATRIX = {
|
|
9620
|
+
type: "array",
|
|
9621
|
+
items: { type: "number", description: "Matrix entry." },
|
|
9622
|
+
minItems: 6,
|
|
9623
|
+
maxItems: 6,
|
|
9624
|
+
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."
|
|
9625
|
+
};
|
|
9626
|
+
var BOX = {
|
|
9627
|
+
x: NUM("Left of the bounding box, document pixels."),
|
|
9628
|
+
y: NUM("Top of the bounding box, document pixels."),
|
|
9629
|
+
width: NUM("Width in pixels."),
|
|
9630
|
+
height: NUM("Height in pixels."),
|
|
9631
|
+
transform: { $ref: "#/$defs/matrix" }
|
|
9632
|
+
};
|
|
9633
|
+
var DEFS2 = {
|
|
9634
|
+
matrix: MATRIX,
|
|
9635
|
+
point: {
|
|
9636
|
+
type: "object",
|
|
9637
|
+
description: "A point in document pixels.",
|
|
9638
|
+
properties: { x: NUM("Pixels from the left."), y: NUM("Pixels from the top.") },
|
|
9639
|
+
required: ["x", "y"],
|
|
9640
|
+
additionalProperties: false
|
|
9641
|
+
},
|
|
9642
|
+
arrowhead: {
|
|
9643
|
+
type: "object",
|
|
9644
|
+
description: "An arrowhead whose tip is the line end: a triangle, notched by concavity.",
|
|
9645
|
+
properties: {
|
|
9646
|
+
width: NUM("Width across the base, pixels."),
|
|
9647
|
+
length: NUM("Length from base to tip, pixels."),
|
|
9648
|
+
concavity: { type: "number", minimum: -0.5, maximum: 0.5, description: "Notch of the base towards the tip as a fraction of length (default 0)." }
|
|
9649
|
+
},
|
|
9650
|
+
required: ["width", "length"],
|
|
9651
|
+
additionalProperties: false
|
|
9652
|
+
},
|
|
9653
|
+
shape: {
|
|
9654
|
+
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.",
|
|
9655
|
+
anyOf: [
|
|
9656
|
+
{
|
|
9657
|
+
type: "object",
|
|
9658
|
+
description: "Rectangle.",
|
|
9659
|
+
properties: {
|
|
9660
|
+
kind: { const: "rect", description: "Rectangle." },
|
|
9661
|
+
...BOX,
|
|
9662
|
+
radius: { type: "number", minimum: 0, description: "One corner radius for all corners, pixels." },
|
|
9663
|
+
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." }
|
|
9664
|
+
},
|
|
9665
|
+
required: ["kind", "x", "y", "width", "height"],
|
|
9666
|
+
additionalProperties: false
|
|
9667
|
+
},
|
|
9668
|
+
{ type: "object", description: "Ellipse inscribed in the box.", properties: { kind: { const: "ellipse", description: "Ellipse." }, ...BOX }, required: ["kind", "x", "y", "width", "height"], additionalProperties: false },
|
|
9669
|
+
{
|
|
9670
|
+
type: "object",
|
|
9671
|
+
description: "Regular polygon inscribed in the box, first vertex at the top.",
|
|
9672
|
+
properties: { kind: { const: "polygon", description: "Polygon." }, ...BOX, sides: { type: "integer", minimum: 3, maximum: 100, description: "Number of sides (default 6)." } },
|
|
9673
|
+
required: ["kind", "x", "y", "width", "height"],
|
|
9674
|
+
additionalProperties: false
|
|
9675
|
+
},
|
|
9676
|
+
{
|
|
9677
|
+
type: "object",
|
|
9678
|
+
description: "Star inscribed in the box, first point at the top.",
|
|
9679
|
+
properties: {
|
|
9680
|
+
kind: { const: "star", description: "Star." },
|
|
9681
|
+
...BOX,
|
|
9682
|
+
points: { type: "integer", minimum: 3, maximum: 100, description: "Number of points (default 5)." },
|
|
9683
|
+
innerRatio: { type: "number", minimum: 0.01, maximum: 1, description: "Inner radius over outer radius (default 0.5)." }
|
|
9684
|
+
},
|
|
9685
|
+
required: ["kind", "x", "y", "width", "height"],
|
|
9686
|
+
additionalProperties: false
|
|
9687
|
+
},
|
|
9688
|
+
{
|
|
9689
|
+
type: "object",
|
|
9690
|
+
description: "Line drawn as a filled bar.",
|
|
9691
|
+
properties: {
|
|
9692
|
+
kind: { const: "line", description: "Line." },
|
|
9693
|
+
x1: NUM("Start x, document pixels."),
|
|
9694
|
+
y1: NUM("Start y, document pixels."),
|
|
9695
|
+
x2: NUM("End x, document pixels."),
|
|
9696
|
+
y2: NUM("End y, document pixels."),
|
|
9697
|
+
weight: { type: "number", minimum: 0, description: "Thickness in pixels (default 1)." },
|
|
9698
|
+
startArrow: { anyOf: [{ $ref: "#/$defs/arrowhead" }, { type: "null", description: "No arrowhead." }], description: "Arrowhead at the start, or null." },
|
|
9699
|
+
endArrow: { anyOf: [{ $ref: "#/$defs/arrowhead" }, { type: "null", description: "No arrowhead." }], description: "Arrowhead at the end, or null." },
|
|
9700
|
+
transform: { $ref: "#/$defs/matrix" }
|
|
9701
|
+
},
|
|
9702
|
+
required: ["kind", "x1", "y1", "x2", "y2"],
|
|
9703
|
+
additionalProperties: false
|
|
9704
|
+
}
|
|
9705
|
+
]
|
|
9706
|
+
},
|
|
9707
|
+
subpaths: {
|
|
9708
|
+
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.",
|
|
9709
|
+
anyOf: [
|
|
9710
|
+
{ type: "string", description: "SVG path data, document pixels." },
|
|
9711
|
+
{
|
|
9712
|
+
type: "array",
|
|
9713
|
+
minItems: 1,
|
|
9714
|
+
description: "Subpaths.",
|
|
9715
|
+
items: {
|
|
9716
|
+
type: "object",
|
|
9717
|
+
description: "One subpath.",
|
|
9718
|
+
properties: {
|
|
9719
|
+
anchors: {
|
|
9720
|
+
type: "array",
|
|
9721
|
+
minItems: 1,
|
|
9722
|
+
description: "Anchors in order.",
|
|
9723
|
+
items: {
|
|
9724
|
+
type: "object",
|
|
9725
|
+
description: "An anchor with optional handles.",
|
|
9726
|
+
properties: {
|
|
9727
|
+
x: NUM("Anchor x, document pixels."),
|
|
9728
|
+
y: NUM("Anchor y, document pixels."),
|
|
9729
|
+
in: { $ref: "#/$defs/point", description: "Handle of the segment arriving here (absolute)." },
|
|
9730
|
+
out: { $ref: "#/$defs/point", description: "Handle of the segment leaving here (absolute)." },
|
|
9731
|
+
corner: { type: "number", minimum: 0, description: "Live corner radius at this anchor, pixels." }
|
|
9732
|
+
},
|
|
9733
|
+
required: ["x", "y"],
|
|
9734
|
+
additionalProperties: false
|
|
9735
|
+
}
|
|
9736
|
+
},
|
|
9737
|
+
closed: { type: "boolean", description: "Joins the last anchor back to the first (default true)." }
|
|
9738
|
+
},
|
|
9739
|
+
required: ["anchors"],
|
|
9740
|
+
additionalProperties: false
|
|
9741
|
+
}
|
|
9742
|
+
}
|
|
9743
|
+
]
|
|
9744
|
+
},
|
|
9745
|
+
gradient: {
|
|
9746
|
+
type: "object",
|
|
9747
|
+
description: "A gradient: colour stops, optional opacity stops and its layout (aligned with the shape bounds by default).",
|
|
9748
|
+
properties: {
|
|
9749
|
+
stops: {
|
|
9750
|
+
type: "array",
|
|
9751
|
+
minItems: 1,
|
|
9752
|
+
description: "Colour stops.",
|
|
9753
|
+
items: {
|
|
9754
|
+
type: "object",
|
|
9755
|
+
description: "A colour stop.",
|
|
9756
|
+
properties: { position: { type: "number", minimum: 0, maximum: 1, description: "0..1 along the gradient." }, color: { type: "string", description: "Colour (#rrggbb or a CSS colour)." } },
|
|
9757
|
+
required: ["position", "color"],
|
|
9758
|
+
additionalProperties: false
|
|
9759
|
+
}
|
|
9760
|
+
},
|
|
9761
|
+
opacityStops: {
|
|
9762
|
+
type: "array",
|
|
9763
|
+
description: "Opacity stops (default opaque).",
|
|
9764
|
+
items: {
|
|
9765
|
+
type: "object",
|
|
9766
|
+
description: "An opacity stop.",
|
|
9767
|
+
properties: { position: { type: "number", minimum: 0, maximum: 1, description: "0..1 along the gradient." }, opacity: { type: "number", minimum: 0, maximum: 1, description: "0..1." } },
|
|
9768
|
+
required: ["position", "opacity"],
|
|
9769
|
+
additionalProperties: false
|
|
9770
|
+
}
|
|
9771
|
+
},
|
|
9772
|
+
style: { type: "string", enum: ["linear", "radial", "angle", "reflected", "diamond"], description: "Geometry (default linear)." },
|
|
9773
|
+
angle: { type: "number", minimum: -360, maximum: 360, description: "Degrees, 90 = bottom to top (default 90)." },
|
|
9774
|
+
scale: { type: "number", minimum: 0.1, maximum: 1.5, description: "1 = 100 % (default 1)." },
|
|
9775
|
+
reverse: { type: "boolean", description: "Swap the ends (default false)." },
|
|
9776
|
+
align: { type: "boolean", description: "Lay out over the shape bounds (true, default) or the canvas." },
|
|
9777
|
+
offset: { $ref: "#/$defs/point", description: "Centre offset as fractions of the bounds (default 0, 0)." }
|
|
9778
|
+
},
|
|
9779
|
+
required: ["stops"],
|
|
9780
|
+
additionalProperties: false
|
|
9781
|
+
},
|
|
9782
|
+
paint: {
|
|
9783
|
+
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.",
|
|
9784
|
+
anyOf: [
|
|
9785
|
+
{ type: "string", description: "A colour." },
|
|
9786
|
+
{
|
|
9787
|
+
type: "object",
|
|
9788
|
+
description: "Solid colour.",
|
|
9789
|
+
properties: { type: { const: "color", description: "Solid colour." }, color: { type: "string", description: "Colour." }, opacity: { type: "number", minimum: 0, maximum: 1, description: "0..1 (default 1)." } },
|
|
9790
|
+
required: ["type", "color"],
|
|
9791
|
+
additionalProperties: false
|
|
9792
|
+
},
|
|
9793
|
+
{
|
|
9794
|
+
type: "object",
|
|
9795
|
+
description: "Gradient.",
|
|
9796
|
+
properties: { type: { const: "gradient", description: "Gradient." }, gradient: { $ref: "#/$defs/gradient" }, opacity: { type: "number", minimum: 0, maximum: 1, description: "0..1 (default 1)." } },
|
|
9797
|
+
required: ["type", "gradient"],
|
|
9798
|
+
additionalProperties: false
|
|
9799
|
+
},
|
|
9800
|
+
{
|
|
9801
|
+
type: "object",
|
|
9802
|
+
description: "Pattern (list_patterns gives ids).",
|
|
9803
|
+
properties: {
|
|
9804
|
+
type: { const: "pattern", description: "Pattern." },
|
|
9805
|
+
patternId: { type: "string", description: "A document or built-in pattern id." },
|
|
9806
|
+
scale: { type: "number", minimum: 0.01, maximum: 10, description: "1 = 100 % (default 1)." },
|
|
9807
|
+
angle: { type: "number", minimum: -360, maximum: 360, description: "Degrees counter-clockwise (default 0)." },
|
|
9808
|
+
link: { type: "boolean", description: "Tile from the shape\u2019s top-left (default true) instead of the canvas origin." },
|
|
9809
|
+
phase: { $ref: "#/$defs/point", description: "Origin offset in pixels." },
|
|
9810
|
+
opacity: { type: "number", minimum: 0, maximum: 1, description: "0..1 (default 1)." }
|
|
9811
|
+
},
|
|
9812
|
+
required: ["type", "patternId"],
|
|
9813
|
+
additionalProperties: false
|
|
9814
|
+
}
|
|
9815
|
+
]
|
|
9816
|
+
},
|
|
9817
|
+
stroke: {
|
|
9818
|
+
type: "object",
|
|
9819
|
+
description: "The shape stroke (not the layer-effect stroke). Unset settings keep their current values (or the defaults).",
|
|
9820
|
+
properties: {
|
|
9821
|
+
width: { type: "number", minimum: 0, maximum: 1e4, description: "Width in pixels (required for a new stroke)." },
|
|
9822
|
+
paint: { $ref: "#/$defs/paint" },
|
|
9823
|
+
color: { type: "string", description: "Shorthand for paint as a colour." },
|
|
9824
|
+
align: { type: "string", enum: ["inside", "center", "outside"], description: "Where it sits on the outline (default center; open paths always centre)." },
|
|
9825
|
+
cap: { type: "string", enum: ["butt", "round", "square"], description: "Line ends (default butt)." },
|
|
9826
|
+
join: { type: "string", enum: ["miter", "round", "bevel"], description: "Corners (default miter)." },
|
|
9827
|
+
miterLimit: { type: "number", minimum: 1, maximum: 500, description: "Miter length over width above which a corner is bevelled (default 100)." },
|
|
9828
|
+
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)." },
|
|
9829
|
+
dashOffset: { type: "number", description: "Start offset into the dashes, multiples of the width (default 0)." }
|
|
9830
|
+
},
|
|
9831
|
+
additionalProperties: false
|
|
9832
|
+
}
|
|
9833
|
+
};
|
|
9834
|
+
var GEOMETRY2 = {
|
|
9835
|
+
shape: { $ref: "#/$defs/shape", description: "A live shape: { kind: rect | ellipse | polygon | star | line, \u2026 } in document pixels. Give shape or subpaths." },
|
|
9836
|
+
subpaths: { $ref: "#/$defs/subpaths", description: "A free path: SVG path data or subpaths with anchors, document pixels. Give shape or subpaths." }
|
|
9837
|
+
};
|
|
9838
|
+
function geometry(args) {
|
|
9839
|
+
const out = {};
|
|
9840
|
+
if (args.shape !== void 0) out.shape = args.shape;
|
|
9841
|
+
if (args.subpaths !== void 0) out.subpaths = args.subpaths;
|
|
9842
|
+
if (args.op !== void 0) out.op = args.op;
|
|
9843
|
+
return out;
|
|
9844
|
+
}
|
|
9845
|
+
function createImageShapeTools(controller) {
|
|
9846
|
+
const shapeId = (id) => {
|
|
9847
|
+
if (typeof id !== "string" || !id) throw new LayerOperationError("layerId is required (a shape layer id from list_layers).");
|
|
9848
|
+
requireShapeLayer(controller.getLayers(), id);
|
|
9849
|
+
return id;
|
|
9850
|
+
};
|
|
9851
|
+
const layerSummary = (id) => {
|
|
9852
|
+
const s = controller.getState();
|
|
9853
|
+
const loc = findLayer(s.layers, id);
|
|
9854
|
+
return loc ? { ...describeLayer(loc.layer, s.activeLayerId, (x) => controller.getLayerBounds(x.id)), parentId: loc.parent?.id ?? null, index: loc.index } : null;
|
|
9855
|
+
};
|
|
9856
|
+
const get = {
|
|
9857
|
+
name: "get_shape_layer",
|
|
9858
|
+
title: "Read a shape layer",
|
|
9859
|
+
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.",
|
|
9860
|
+
inputSchema: { type: "object", properties: { layerId: LAYER_ID5 }, required: ["layerId"], additionalProperties: false },
|
|
9861
|
+
annotations: { title: "Read a shape layer", ...READ8 },
|
|
9862
|
+
handler: ({ layerId }) => controller.getShapeLayer(shapeId(layerId))
|
|
9863
|
+
};
|
|
9864
|
+
const add2 = {
|
|
9865
|
+
name: "add_shape_layer",
|
|
9866
|
+
title: "Add a shape layer",
|
|
9867
|
+
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.",
|
|
9868
|
+
inputSchema: {
|
|
9869
|
+
type: "object",
|
|
9870
|
+
$defs: DEFS2,
|
|
9871
|
+
properties: {
|
|
9872
|
+
...GEOMETRY2,
|
|
9873
|
+
components: {
|
|
9874
|
+
type: "array",
|
|
9875
|
+
minItems: 1,
|
|
9876
|
+
description: "Several components instead of shape / subpaths, bottom first, each with its path operation.",
|
|
9877
|
+
items: { type: "object", description: "One component.", properties: { ...GEOMETRY2, op: OP }, additionalProperties: false }
|
|
9878
|
+
},
|
|
9879
|
+
fill: { anyOf: [{ $ref: "#/$defs/paint" }, { type: "null", description: "No fill." }], description: "Fill paint, or null for none (default the foreground colour)." },
|
|
9880
|
+
stroke: { anyOf: [{ $ref: "#/$defs/stroke" }, { type: "null", description: "No stroke." }], description: "Stroke { width, paint, \u2026 }, or null / omitted for none." },
|
|
9881
|
+
fillRule: { type: "string", enum: ["nonzero", "evenodd"], description: "Winding rule within a component (default nonzero)." },
|
|
9882
|
+
name: { type: "string", description: "Layer name (default by kind: Rectangle, Ellipse, \u2026)." },
|
|
9883
|
+
opacity: { type: "number", minimum: 0, maximum: 1, description: "Layer opacity 0..1 (default 1)." },
|
|
9884
|
+
blend: { type: "string", enum: BLEND_MODES.map((m) => m.id), description: "Blend mode (default normal)." },
|
|
9885
|
+
aboveId: { type: "string", description: "Put the layer directly above this layer (default above the active layer)." },
|
|
9886
|
+
belowId: { type: "string", description: "Put the layer directly below this layer." },
|
|
9887
|
+
parentId: { type: "string", description: "Put the layer inside this group." },
|
|
9888
|
+
index: { type: "integer", minimum: 0, description: "Position among the siblings, 0 = bottom." }
|
|
9889
|
+
},
|
|
9890
|
+
additionalProperties: false
|
|
9891
|
+
},
|
|
9892
|
+
annotations: { title: "Add a shape layer", ...WRITE7 },
|
|
9893
|
+
handler: (args) => {
|
|
9894
|
+
const init = { ...geometry(args) };
|
|
9895
|
+
if (args.components !== void 0) init.components = args.components.map(geometry);
|
|
9896
|
+
for (const k of ["fill", "stroke", "fillRule", "name", "opacity", "blend", "aboveId", "belowId", "parentId", "index"]) {
|
|
9897
|
+
if (args[k] !== void 0) init[k] = args[k];
|
|
9898
|
+
}
|
|
9899
|
+
if (init.blend !== void 0) init.blend = init.blend;
|
|
9900
|
+
const id = controller.addShapeLayer(init);
|
|
9901
|
+
return { layer: layerSummary(id), shape: controller.getShapeLayer(id) };
|
|
9902
|
+
}
|
|
9903
|
+
};
|
|
9904
|
+
const update = {
|
|
9905
|
+
name: "update_shape",
|
|
9906
|
+
title: "Restyle a shape",
|
|
9907
|
+
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.",
|
|
9908
|
+
inputSchema: {
|
|
9909
|
+
type: "object",
|
|
9910
|
+
$defs: DEFS2,
|
|
9911
|
+
properties: {
|
|
9912
|
+
layerId: LAYER_ID5,
|
|
9913
|
+
fill: { anyOf: [{ $ref: "#/$defs/paint" }, { type: "null", description: "No fill." }], description: "New fill paint, or null for none." },
|
|
9914
|
+
stroke: { anyOf: [{ $ref: "#/$defs/stroke" }, { type: "null", description: "No stroke." }], description: "Stroke settings to change, or null to remove the stroke." },
|
|
9915
|
+
fillRule: { type: "string", enum: ["nonzero", "evenodd"], description: "Winding rule within a component." }
|
|
9916
|
+
},
|
|
9917
|
+
required: ["layerId"],
|
|
9918
|
+
additionalProperties: false
|
|
9919
|
+
},
|
|
9920
|
+
annotations: { title: "Restyle a shape", ...WRITE_IDEMPOTENT5 },
|
|
9921
|
+
handler: ({ layerId, fill, stroke, fillRule }) => {
|
|
9922
|
+
const id = shapeId(layerId);
|
|
9923
|
+
if (fill === void 0 && stroke === void 0 && fillRule === void 0) throw new LayerOperationError("Give fill, stroke or fillRule to change.");
|
|
9924
|
+
return controller.setShapeStyle(id, { fill, stroke, fillRule });
|
|
9925
|
+
}
|
|
9926
|
+
};
|
|
9927
|
+
const setComponent = {
|
|
9928
|
+
name: "set_shape_component",
|
|
9929
|
+
title: "Edit a shape component",
|
|
9930
|
+
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.",
|
|
9931
|
+
inputSchema: {
|
|
9932
|
+
type: "object",
|
|
9933
|
+
$defs: DEFS2,
|
|
9934
|
+
properties: {
|
|
9935
|
+
layerId: LAYER_ID5,
|
|
9936
|
+
componentIndex: COMPONENT_INDEX,
|
|
9937
|
+
shape: {
|
|
9938
|
+
type: "object",
|
|
9939
|
+
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.",
|
|
9940
|
+
properties: {
|
|
9941
|
+
kind: { type: "string", enum: ["rect", "ellipse", "polygon", "star", "line"], description: "Shape kind (default the current one)." },
|
|
9942
|
+
x: NUM("Left of the box, pixels."),
|
|
9943
|
+
y: NUM("Top of the box, pixels."),
|
|
9944
|
+
width: NUM("Box width, pixels."),
|
|
9945
|
+
height: NUM("Box height, pixels."),
|
|
9946
|
+
radius: { type: "number", minimum: 0, description: "rect: one corner radius, pixels." },
|
|
9947
|
+
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." },
|
|
9948
|
+
sides: { type: "integer", minimum: 3, maximum: 100, description: "polygon: number of sides." },
|
|
9949
|
+
points: { type: "integer", minimum: 3, maximum: 100, description: "star: number of points." },
|
|
9950
|
+
innerRatio: { type: "number", minimum: 0.01, maximum: 1, description: "star: inner over outer radius." },
|
|
9951
|
+
x1: NUM("line: start x."),
|
|
9952
|
+
y1: NUM("line: start y."),
|
|
9953
|
+
x2: NUM("line: end x."),
|
|
9954
|
+
y2: NUM("line: end y."),
|
|
9955
|
+
weight: { type: "number", minimum: 0, description: "line: thickness in pixels." },
|
|
9956
|
+
startArrow: { anyOf: [{ $ref: "#/$defs/arrowhead" }, { type: "null", description: "No arrowhead." }], description: "line: start arrowhead or null." },
|
|
9957
|
+
endArrow: { anyOf: [{ $ref: "#/$defs/arrowhead" }, { type: "null", description: "No arrowhead." }], description: "line: end arrowhead or null." },
|
|
9958
|
+
transform: { $ref: "#/$defs/matrix" }
|
|
9959
|
+
},
|
|
9960
|
+
additionalProperties: false
|
|
9961
|
+
},
|
|
9962
|
+
subpaths: { $ref: "#/$defs/subpaths", description: "A new path for the component (SVG path data or anchors); ends its live shape." },
|
|
9963
|
+
op: OP
|
|
9964
|
+
},
|
|
9965
|
+
required: ["layerId", "componentIndex"],
|
|
9966
|
+
additionalProperties: false
|
|
9967
|
+
},
|
|
9968
|
+
annotations: { title: "Edit a shape component", ...WRITE_IDEMPOTENT5 },
|
|
9969
|
+
handler: ({ layerId, componentIndex, ...rest }) => {
|
|
9970
|
+
const id = shapeId(layerId);
|
|
9971
|
+
const change = geometry(rest);
|
|
9972
|
+
if (change.shape === void 0 && change.subpaths === void 0 && change.op === void 0) throw new LayerOperationError("Give shape, subpaths or op to change.");
|
|
9973
|
+
return controller.setShapeComponent(id, componentIndex, change);
|
|
9974
|
+
}
|
|
9975
|
+
};
|
|
9976
|
+
const addComponent = {
|
|
9977
|
+
name: "add_shape_component",
|
|
9978
|
+
title: "Add a shape component",
|
|
9979
|
+
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.",
|
|
9980
|
+
inputSchema: {
|
|
9981
|
+
type: "object",
|
|
9982
|
+
$defs: DEFS2,
|
|
9983
|
+
properties: { layerId: LAYER_ID5, ...GEOMETRY2, op: OP, index: { type: "integer", minimum: 0, description: "Position among the components, 0 = bottom (default the top)." } },
|
|
9984
|
+
required: ["layerId"],
|
|
9985
|
+
additionalProperties: false
|
|
9986
|
+
},
|
|
9987
|
+
annotations: { title: "Add a shape component", ...WRITE7 },
|
|
9988
|
+
handler: ({ layerId, index, ...rest }) => controller.addShapeComponent(shapeId(layerId), geometry(rest), index)
|
|
9989
|
+
};
|
|
9990
|
+
const removeComponent = {
|
|
9991
|
+
name: "remove_shape_component",
|
|
9992
|
+
title: "Delete a shape component",
|
|
9993
|
+
description: "Deletes one component of a shape layer (the last one cannot go: delete the layer instead). One undo step. Returns the shape.",
|
|
9994
|
+
inputSchema: { type: "object", properties: { layerId: LAYER_ID5, componentIndex: COMPONENT_INDEX }, required: ["layerId", "componentIndex"], additionalProperties: false },
|
|
9995
|
+
annotations: { title: "Delete a shape component", ...DESTRUCTIVE6 },
|
|
9996
|
+
handler: ({ layerId, componentIndex }) => controller.removeShapeComponent(shapeId(layerId), componentIndex)
|
|
9997
|
+
};
|
|
9998
|
+
const merge = {
|
|
9999
|
+
name: "merge_shape_components",
|
|
10000
|
+
title: "Merge shape components",
|
|
10001
|
+
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.",
|
|
10002
|
+
inputSchema: { type: "object", properties: { layerId: LAYER_ID5 }, required: ["layerId"], additionalProperties: false },
|
|
10003
|
+
annotations: { title: "Merge shape components", ...DESTRUCTIVE6 },
|
|
10004
|
+
handler: ({ layerId }) => controller.mergeShapeComponents(shapeId(layerId))
|
|
10005
|
+
};
|
|
10006
|
+
return [get, add2, update, setComponent, addComponent, removeComponent, merge];
|
|
10007
|
+
}
|
|
10008
|
+
|
|
9523
10009
|
// src/image-editor/tools.ts
|
|
9524
10010
|
var LAYER_PATH = /^layers\.([^.]+)\.(.+)$/;
|
|
9525
10011
|
function adjustmentsSummary(adjustments) {
|
|
@@ -9759,6 +10245,7 @@ function createImageEditorToolProvider(controller, options = {}) {
|
|
|
9759
10245
|
...createImageLayerTools(controller),
|
|
9760
10246
|
...createImageTransformTools(controller),
|
|
9761
10247
|
...createImageSmartObjectTools(controller),
|
|
10248
|
+
...createImageShapeTools(controller),
|
|
9762
10249
|
...createImageEffectTools(controller),
|
|
9763
10250
|
...createImagePaintTools(controller),
|
|
9764
10251
|
...createPigmentTools(),
|
|
@@ -10099,6 +10586,8 @@ function layerBadge(layer) {
|
|
|
10099
10586
|
return layer.annotation.type === "text" ? "T" : "ann";
|
|
10100
10587
|
case "smart":
|
|
10101
10588
|
return "so";
|
|
10589
|
+
case "shape":
|
|
10590
|
+
return "shp";
|
|
10102
10591
|
}
|
|
10103
10592
|
}
|
|
10104
10593
|
function dropZone(fraction, isGroup) {
|
|
@@ -10138,7 +10627,7 @@ function layerMenu(controller) {
|
|
|
10138
10627
|
{ action: "delete-mask", label: "Delete Layer Mask", enabled: Boolean(active?.mask) },
|
|
10139
10628
|
{ action: "toggle-mask", label: active?.mask?.enabled === false ? "Enable Layer Mask" : "Disable Layer Mask", enabled: Boolean(active?.mask) },
|
|
10140
10629
|
{ 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) },
|
|
10630
|
+
{ action: "rasterize", label: "Rasterize Layer", enabled: active?.type === "annotation" || active?.type === "smart" || active?.type === "shape" || isPixel && Boolean(active?.type === "pixel" && active.filters?.length) },
|
|
10142
10631
|
{ action: "convert-smart", label: "Convert to Smart Object", enabled: Boolean(active && active.type !== "annotation") },
|
|
10143
10632
|
{ action: "edit-contents", label: "Edit Contents", enabled: active?.type === "smart" },
|
|
10144
10633
|
{ action: "smart-via-copy", label: "New Smart Object via Copy", enabled: active?.type === "smart" },
|
|
@@ -11074,6 +11563,7 @@ export {
|
|
|
11074
11563
|
DEFAULT_BRUSH,
|
|
11075
11564
|
DEFAULT_DEPTH_MASK,
|
|
11076
11565
|
DEFAULT_GLOBAL_LIGHT,
|
|
11566
|
+
DEFAULT_MITER_LIMIT,
|
|
11077
11567
|
DEFAULT_MIXER,
|
|
11078
11568
|
DEFAULT_REFINE_EDGE_PARAMS,
|
|
11079
11569
|
DEFAULT_SELECTION_TOOL_OPTIONS,
|
|
@@ -11107,6 +11597,7 @@ export {
|
|
|
11107
11597
|
LAYER_EFFECT_KINDS,
|
|
11108
11598
|
LAYER_EFFECT_MENU,
|
|
11109
11599
|
LEGACY_PROCESS_VERSION,
|
|
11600
|
+
LIVE_SHAPE_KINDS,
|
|
11110
11601
|
LUMA_709,
|
|
11111
11602
|
LayerCompositor,
|
|
11112
11603
|
LayerEffectError,
|
|
@@ -11143,6 +11634,8 @@ export {
|
|
|
11143
11634
|
SELECTION_MODIFY_OPS,
|
|
11144
11635
|
SELECTION_TOOLS,
|
|
11145
11636
|
SHADOW_AMBIENT,
|
|
11637
|
+
SHAPE_OPERATIONS,
|
|
11638
|
+
SHAPE_RENDER_MARGIN,
|
|
11146
11639
|
SMART_RENDER_MARGIN,
|
|
11147
11640
|
SUPPORTED_ADJUSTMENT_KEYS,
|
|
11148
11641
|
SelectionGesture,
|
|
@@ -11175,8 +11668,11 @@ export {
|
|
|
11175
11668
|
adjustmentNodeFromLegacy,
|
|
11176
11669
|
adler32,
|
|
11177
11670
|
anchorPoint,
|
|
11671
|
+
annotationBlend,
|
|
11178
11672
|
annotationLayerFor,
|
|
11179
11673
|
annotationLayerName,
|
|
11674
|
+
annotationOfLayer,
|
|
11675
|
+
annotationOfShape,
|
|
11180
11676
|
applyAdjustmentLayers,
|
|
11181
11677
|
applyAdjustments,
|
|
11182
11678
|
applyBoxBlur,
|
|
@@ -11226,6 +11722,7 @@ export {
|
|
|
11226
11722
|
colorRange,
|
|
11227
11723
|
combineSelections,
|
|
11228
11724
|
compileAdjustmentLayer,
|
|
11725
|
+
componentPath,
|
|
11229
11726
|
compositeSurfaceOver,
|
|
11230
11727
|
computeHistogram,
|
|
11231
11728
|
computeSelectionBounds,
|
|
@@ -11250,11 +11747,13 @@ export {
|
|
|
11250
11747
|
createImagePaintTools,
|
|
11251
11748
|
createImagePsdTools,
|
|
11252
11749
|
createImageSelectionTools,
|
|
11750
|
+
createImageShapeTools,
|
|
11253
11751
|
createImageSmartObjectTools,
|
|
11254
11752
|
createImageTransformTools,
|
|
11255
11753
|
createPigment,
|
|
11256
11754
|
createPigmentTools,
|
|
11257
11755
|
createSelectionMask,
|
|
11756
|
+
createShapeLayer,
|
|
11258
11757
|
curveLut,
|
|
11259
11758
|
curveLutFromLevels,
|
|
11260
11759
|
curvePointToBox,
|
|
@@ -11271,6 +11770,7 @@ export {
|
|
|
11271
11770
|
defaultAdjustmentParams,
|
|
11272
11771
|
defaultEffectGradient,
|
|
11273
11772
|
defaultLayerEffect,
|
|
11773
|
+
defaultShapeName,
|
|
11274
11774
|
deflate,
|
|
11275
11775
|
deflateRaw,
|
|
11276
11776
|
depthMaskAlpha,
|
|
@@ -11322,6 +11822,7 @@ export {
|
|
|
11322
11822
|
findLayerControl,
|
|
11323
11823
|
finishTile,
|
|
11324
11824
|
flattenLayers,
|
|
11825
|
+
flattenPath,
|
|
11325
11826
|
flipCorners,
|
|
11326
11827
|
formatControlValue,
|
|
11327
11828
|
fullSelection,
|
|
@@ -11387,6 +11888,7 @@ export {
|
|
|
11387
11888
|
isPng,
|
|
11388
11889
|
isQuarterTurn,
|
|
11389
11890
|
isQuarterTurnSwapped,
|
|
11891
|
+
isRenderedLayer,
|
|
11390
11892
|
isSelectionEmpty,
|
|
11391
11893
|
isSelectionMode,
|
|
11392
11894
|
isSeparableBlendMode,
|
|
@@ -11420,15 +11922,19 @@ export {
|
|
|
11420
11922
|
levelsValue,
|
|
11421
11923
|
linearSrgbToXy,
|
|
11422
11924
|
linearToSrgb,
|
|
11925
|
+
liveComponent,
|
|
11926
|
+
liveShapeSubpaths,
|
|
11423
11927
|
loadImageEditorDocument,
|
|
11424
11928
|
loadMixer,
|
|
11425
11929
|
magicWand,
|
|
11426
11930
|
mapCropHandle,
|
|
11427
11931
|
mapLayer,
|
|
11932
|
+
mapSubpaths,
|
|
11428
11933
|
maskSummary,
|
|
11429
11934
|
matrixFromCorners,
|
|
11430
11935
|
matrixFromParams,
|
|
11431
11936
|
mergeLayerEffect,
|
|
11937
|
+
mergeShapeComponents,
|
|
11432
11938
|
migrateSerializedImageEditorState,
|
|
11433
11939
|
migrateToLayeredState,
|
|
11434
11940
|
mixPigment8,
|
|
@@ -11453,20 +11959,26 @@ export {
|
|
|
11453
11959
|
normalizeFilter,
|
|
11454
11960
|
normalizeLayerEffects,
|
|
11455
11961
|
normalizeLayerMask,
|
|
11962
|
+
normalizeLiveShape,
|
|
11456
11963
|
normalizeMixer,
|
|
11457
11964
|
normalizePlacement,
|
|
11458
11965
|
normalizeProcessVersion,
|
|
11459
11966
|
normalizeRect,
|
|
11460
11967
|
normalizeRefineEdgeParams,
|
|
11968
|
+
normalizeShapePaint,
|
|
11969
|
+
normalizeShapeStroke,
|
|
11461
11970
|
normalizeSmartFilter,
|
|
11462
11971
|
normalizeStoredAdjustments,
|
|
11972
|
+
normalizeSubpaths,
|
|
11463
11973
|
offsetLayer,
|
|
11974
|
+
offsetShape,
|
|
11464
11975
|
offsetSmartObject,
|
|
11465
11976
|
openPsdFile,
|
|
11466
11977
|
packRgba,
|
|
11467
11978
|
parseAdjustmentBlock,
|
|
11468
11979
|
parseBlend,
|
|
11469
11980
|
parseColor,
|
|
11981
|
+
parseCssColor,
|
|
11470
11982
|
parseDataUrl,
|
|
11471
11983
|
parseEffectColor,
|
|
11472
11984
|
parseLfx2,
|
|
@@ -11502,6 +12014,7 @@ export {
|
|
|
11502
12014
|
rangeLightness,
|
|
11503
12015
|
rasterContents,
|
|
11504
12016
|
rasterMaskFromLegacy,
|
|
12017
|
+
rasterizeCoverage,
|
|
11505
12018
|
rasterizeDab,
|
|
11506
12019
|
rasterizeSmartObject,
|
|
11507
12020
|
readAdjustmentControls,
|
|
@@ -11524,12 +12037,17 @@ export {
|
|
|
11524
12037
|
removeSmartFilter,
|
|
11525
12038
|
renderLayerEffects,
|
|
11526
12039
|
renderLayeredBase,
|
|
12040
|
+
renderShape,
|
|
12041
|
+
renderShapeRect,
|
|
11527
12042
|
renderSmartObject,
|
|
11528
12043
|
replaceContents,
|
|
11529
12044
|
requireLayer,
|
|
12045
|
+
requireOperation,
|
|
12046
|
+
requireShapeLayer,
|
|
11530
12047
|
requireSmartObject,
|
|
11531
12048
|
requireUnlocked,
|
|
11532
12049
|
rerender,
|
|
12050
|
+
rerenderShape,
|
|
11533
12051
|
resizePixels,
|
|
11534
12052
|
resizeSelection,
|
|
11535
12053
|
resizeWarpMesh,
|
|
@@ -11569,6 +12087,12 @@ export {
|
|
|
11569
12087
|
setSmartFilterMask,
|
|
11570
12088
|
setSmartFiltersEnabled,
|
|
11571
12089
|
setSmartPlacement,
|
|
12090
|
+
shapeAsPixelLayer,
|
|
12091
|
+
shapeFromAnnotation,
|
|
12092
|
+
shapeGeometryBounds,
|
|
12093
|
+
shapeLayers,
|
|
12094
|
+
shapeOutline,
|
|
12095
|
+
shapeRenderClip,
|
|
11572
12096
|
shiftCoverage,
|
|
11573
12097
|
smartFilterRows,
|
|
11574
12098
|
smartObjectLayers,
|
|
@@ -11598,6 +12122,7 @@ export {
|
|
|
11598
12122
|
toneRange,
|
|
11599
12123
|
transformHomography,
|
|
11600
12124
|
transformMap,
|
|
12125
|
+
transformShapeComponents,
|
|
11601
12126
|
transformSurface,
|
|
11602
12127
|
transformedBounds,
|
|
11603
12128
|
transformedBox,
|
|
@@ -11610,6 +12135,7 @@ export {
|
|
|
11610
12135
|
updateLayerEffect,
|
|
11611
12136
|
updateLayerFilter,
|
|
11612
12137
|
updateLayerMaskProps,
|
|
12138
|
+
updateShapeLayer,
|
|
11613
12139
|
updateSmartFilter,
|
|
11614
12140
|
uvToXy,
|
|
11615
12141
|
validateEffectGradient,
|