@pixerate/editor 0.189.0 → 0.191.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/{chunk-ED4L4UD6.cjs → chunk-2UR4OX3B.cjs} +1002 -787
- package/dist/{chunk-M7XIC2FC.js → chunk-RQ2EM5TY.js} +333 -0
- package/dist/{chunk-ZEHTUR7Z.js → chunk-TXYXF32N.js} +968 -753
- package/dist/{chunk-5UEFNX24.cjs → chunk-ZQGZWFPU.cjs} +344 -11
- package/dist/image-editor/controller.d.cts +73 -1
- package/dist/image-editor/controller.d.ts +73 -1
- package/dist/image-editor/free-transform.d.cts +3 -6
- package/dist/image-editor/free-transform.d.ts +3 -6
- package/dist/image-editor/index.cjs +1382 -904
- package/dist/image-editor/index.d.cts +1 -0
- package/dist/image-editor/index.d.ts +1 -0
- package/dist/image-editor/index.js +485 -7
- package/dist/image-editor/layer-tools.d.cts +1 -1
- package/dist/image-editor/layer-tools.d.ts +1 -1
- package/dist/image-editor/layers/compositor.d.cts +3 -0
- package/dist/image-editor/layers/compositor.d.ts +3 -0
- package/dist/image-editor/layers/model.d.cts +23 -0
- package/dist/image-editor/layers/model.d.ts +23 -0
- package/dist/image-editor/layers/panel.d.cts +11 -1
- package/dist/image-editor/layers/panel.d.ts +11 -1
- package/dist/image-editor/layers/serialize.d.cts +11 -0
- package/dist/image-editor/layers/serialize.d.ts +11 -0
- package/dist/image-editor/layers/shape.d.cts +5 -0
- package/dist/image-editor/layers/shape.d.ts +5 -0
- package/dist/image-editor/layers/vector-mask.d.cts +51 -0
- package/dist/image-editor/layers/vector-mask.d.ts +51 -0
- package/dist/image-editor/psd/import.d.cts +0 -7
- package/dist/image-editor/psd/import.d.ts +0 -7
- package/dist/image-editor/selection/index.d.cts +1 -0
- package/dist/image-editor/selection/index.d.ts +1 -0
- package/dist/image-editor/selection/path.d.cts +37 -0
- package/dist/image-editor/selection/path.d.ts +37 -0
- package/dist/paint-editor/index.cjs +6 -6
- package/dist/paint-editor/index.js +1 -1
- package/dist/vector/index.cjs +21 -303
- package/dist/vector/index.js +15 -297
- package/dist/vector/trace.d.cts +8 -1
- package/dist/vector/trace.d.ts +8 -1
- package/package.json +1 -1
- package/skills/pixerate-image-editor/SKILL.md +8 -0
- package/skills/pixerate-image-selection-masking/SKILL.md +10 -2
|
@@ -1,7 +1,8 @@
|
|
|
1
1
|
import {
|
|
2
2
|
loadOpenTypeFont,
|
|
3
|
-
parseOpenTypeFont
|
|
4
|
-
|
|
3
|
+
parseOpenTypeFont,
|
|
4
|
+
traceField
|
|
5
|
+
} from "../chunk-RQ2EM5TY.js";
|
|
5
6
|
import {
|
|
6
7
|
ADJUSTMENT_KEYS,
|
|
7
8
|
ADJUSTMENT_KINDS,
|
|
@@ -99,6 +100,7 @@ import {
|
|
|
99
100
|
combineSelections,
|
|
100
101
|
compileAdjustmentLayer,
|
|
101
102
|
componentPath,
|
|
103
|
+
componentsCoverage,
|
|
102
104
|
compositeDeep,
|
|
103
105
|
computeSelectionBounds,
|
|
104
106
|
contentsBox,
|
|
@@ -137,6 +139,7 @@ import {
|
|
|
137
139
|
distanceTransform,
|
|
138
140
|
documentContents,
|
|
139
141
|
duplicateLayer,
|
|
142
|
+
effectiveLayerMask,
|
|
140
143
|
encodeDeepSurface,
|
|
141
144
|
encodeRle,
|
|
142
145
|
encodeSelectionRle,
|
|
@@ -148,6 +151,7 @@ import {
|
|
|
148
151
|
filterMaskCoverage,
|
|
149
152
|
flattenPath,
|
|
150
153
|
flipCorners,
|
|
154
|
+
followLayerMove,
|
|
151
155
|
fullSelection,
|
|
152
156
|
geometryHomography,
|
|
153
157
|
getAnnotationBounds,
|
|
@@ -177,6 +181,7 @@ import {
|
|
|
177
181
|
invertMatrix3,
|
|
178
182
|
invertSelection,
|
|
179
183
|
isActiveFilter,
|
|
184
|
+
isActiveVectorMask,
|
|
180
185
|
isAdjustmentKind,
|
|
181
186
|
isContentsDecoded,
|
|
182
187
|
isDefaultDepthMask,
|
|
@@ -237,6 +242,7 @@ import {
|
|
|
237
242
|
offsetLayer,
|
|
238
243
|
offsetShape,
|
|
239
244
|
offsetSmartObject,
|
|
245
|
+
offsetVectorMask,
|
|
240
246
|
parseAdjustmentBlock,
|
|
241
247
|
parseBlend,
|
|
242
248
|
parseLfx2,
|
|
@@ -290,6 +296,7 @@ import {
|
|
|
290
296
|
rerenderText,
|
|
291
297
|
resizeSelection,
|
|
292
298
|
resolveExternalAssets,
|
|
299
|
+
resolveVectorMasks,
|
|
293
300
|
rgbToGray,
|
|
294
301
|
rgbToHsl,
|
|
295
302
|
runLayerFilters,
|
|
@@ -341,6 +348,7 @@ import {
|
|
|
341
348
|
transformMap,
|
|
342
349
|
transformShapeComponents,
|
|
343
350
|
transformTypeLayer,
|
|
351
|
+
transformVectorMask,
|
|
344
352
|
transformedBox,
|
|
345
353
|
transformedPoint,
|
|
346
354
|
translateLayer,
|
|
@@ -359,6 +367,9 @@ import {
|
|
|
359
367
|
updateTypeLayer,
|
|
360
368
|
userErrorMessage,
|
|
361
369
|
uvToXy,
|
|
370
|
+
vectorMaskBounds,
|
|
371
|
+
vectorMaskCoverage,
|
|
372
|
+
vectorMaskThumbnail,
|
|
362
373
|
versionedDescriptorBytes,
|
|
363
374
|
vibranceLinear,
|
|
364
375
|
vibrancePixel,
|
|
@@ -373,7 +384,7 @@ import {
|
|
|
373
384
|
writePsd,
|
|
374
385
|
xyToTempTint,
|
|
375
386
|
xyToUv
|
|
376
|
-
} from "../chunk-
|
|
387
|
+
} from "../chunk-TXYXF32N.js";
|
|
377
388
|
import {
|
|
378
389
|
parseCssColor
|
|
379
390
|
} from "../chunk-3CI7FRMK.js";
|
|
@@ -620,6 +631,7 @@ import {
|
|
|
620
631
|
roundSubpaths,
|
|
621
632
|
spliceRuns,
|
|
622
633
|
styleAt,
|
|
634
|
+
subpathsToBezier,
|
|
623
635
|
textIndexAt,
|
|
624
636
|
translateMatrix,
|
|
625
637
|
warpMeshColumns,
|
|
@@ -631,7 +643,9 @@ import {
|
|
|
631
643
|
} from "../chunk-LPIKXPBM.js";
|
|
632
644
|
import "../chunk-YXKLVD56.js";
|
|
633
645
|
import "../chunk-M6DNZNVY.js";
|
|
634
|
-
import
|
|
646
|
+
import {
|
|
647
|
+
bezierPathToSvg
|
|
648
|
+
} from "../chunk-WEIK3UYB.js";
|
|
635
649
|
import "../chunk-PWBZH3FK.js";
|
|
636
650
|
import {
|
|
637
651
|
EffectUnsupportedError,
|
|
@@ -2416,6 +2430,67 @@ function hexRgb(hex) {
|
|
|
2416
2430
|
return [v >> 16 & 255, v >> 8 & 255, v & 255];
|
|
2417
2431
|
}
|
|
2418
2432
|
|
|
2433
|
+
// src/image-editor/selection/path.ts
|
|
2434
|
+
function selectionToSubpaths(mask, options = {}) {
|
|
2435
|
+
const b = mask.bounds;
|
|
2436
|
+
if (!b) return [];
|
|
2437
|
+
const x0 = Math.max(0, b.x - 1);
|
|
2438
|
+
const y0 = Math.max(0, b.y - 1);
|
|
2439
|
+
const x1 = Math.min(mask.width, b.x + b.width + 1);
|
|
2440
|
+
const y1 = Math.min(mask.height, b.y + b.height + 1);
|
|
2441
|
+
const w = x1 - x0;
|
|
2442
|
+
const h = y1 - y0;
|
|
2443
|
+
const data = mask.data;
|
|
2444
|
+
const field = new Float32Array(w * h);
|
|
2445
|
+
for (let y = 0; y < h; y++) {
|
|
2446
|
+
const row = (y0 + y) * mask.width + x0;
|
|
2447
|
+
for (let x = 0; x < w; x++) field[y * w + x] = data[row + x] / 255;
|
|
2448
|
+
}
|
|
2449
|
+
const threshold = Math.min(254, Math.max(1, options.threshold ?? 128)) / 255;
|
|
2450
|
+
const subpaths = traceField(field, w, h, threshold, {
|
|
2451
|
+
tolerance: Math.max(0.1, options.tolerance ?? 0.5),
|
|
2452
|
+
cornerRoundness: options.cornerRoundness ?? 0.5,
|
|
2453
|
+
minArea: options.minArea ?? 4,
|
|
2454
|
+
squareCorners: true
|
|
2455
|
+
});
|
|
2456
|
+
return subpaths.map((sp) => mapSubpaths([sp], (p) => ({ x: p.x + x0, y: p.y + y0 }))[0]);
|
|
2457
|
+
}
|
|
2458
|
+
function subpathsToSvgPath(subpaths, precision = 2) {
|
|
2459
|
+
return bezierPathToSvg(subpathsToBezier(subpaths), { precision });
|
|
2460
|
+
}
|
|
2461
|
+
function pathToSelection(components, width, height, options = {}) {
|
|
2462
|
+
let x0 = Infinity;
|
|
2463
|
+
let y0 = Infinity;
|
|
2464
|
+
let x1 = -Infinity;
|
|
2465
|
+
let y1 = -Infinity;
|
|
2466
|
+
for (const c of components) {
|
|
2467
|
+
for (const sp of c.subpaths) {
|
|
2468
|
+
for (const a of sp.anchors) {
|
|
2469
|
+
for (const p of [a, a.in, a.out]) {
|
|
2470
|
+
if (!p) continue;
|
|
2471
|
+
x0 = Math.min(x0, p.x);
|
|
2472
|
+
y0 = Math.min(y0, p.y);
|
|
2473
|
+
x1 = Math.max(x1, p.x);
|
|
2474
|
+
y1 = Math.max(y1, p.y);
|
|
2475
|
+
}
|
|
2476
|
+
}
|
|
2477
|
+
}
|
|
2478
|
+
}
|
|
2479
|
+
const rx0 = Math.max(0, Math.floor(x0));
|
|
2480
|
+
const ry0 = Math.max(0, Math.floor(y0));
|
|
2481
|
+
const rx1 = Math.min(width, Math.ceil(x1));
|
|
2482
|
+
const ry1 = Math.min(height, Math.ceil(y1));
|
|
2483
|
+
if (!(rx1 > rx0 && ry1 > ry0)) return createSelectionMask(width, height);
|
|
2484
|
+
const rect = { x: rx0, y: ry0, width: rx1 - rx0, height: ry1 - ry0 };
|
|
2485
|
+
const cov = componentsCoverage(components, rect, options.fillRule ?? "nonzero");
|
|
2486
|
+
const hard = options.antiAlias === false;
|
|
2487
|
+
const bytes = new Uint8Array(cov.length);
|
|
2488
|
+
for (let i = 0; i < cov.length; i++) bytes[i] = hard ? cov[i] >= 0.5 ? 255 : 0 : coverageToByte(cov[i]);
|
|
2489
|
+
const mask = selectionFromRegion(width, height, rect, bytes);
|
|
2490
|
+
const feather = options.feather ?? 0;
|
|
2491
|
+
return feather > 0 && mask.bounds ? modifySelection(mask, "feather", feather) : mask;
|
|
2492
|
+
}
|
|
2493
|
+
|
|
2419
2494
|
// src/image-editor/layers/shape-type-ui.ts
|
|
2420
2495
|
var DEFAULT_SHAPE_TOOL_OPTIONS = {
|
|
2421
2496
|
kind: "rect",
|
|
@@ -4164,6 +4239,24 @@ var FreeTransformSession = class {
|
|
|
4164
4239
|
}
|
|
4165
4240
|
/** Resamples every part for the current geometry and shows it (or returns it, for commit). */
|
|
4166
4241
|
resample(final, preview = {}) {
|
|
4242
|
+
const out = this.resampleContent(final, preview);
|
|
4243
|
+
return { ...out, layers: this.moveVectorMask(out.layers) };
|
|
4244
|
+
}
|
|
4245
|
+
/** A linked vector mask follows the layer's content through the same document-space map. */
|
|
4246
|
+
moveVectorMask(layers) {
|
|
4247
|
+
const g = this.geometryValue;
|
|
4248
|
+
if (!this.layerId || this.copyId || !this.moves.includes("pixels") || isIdentityTransform(g)) return layers;
|
|
4249
|
+
const vm = findLayer(layers, this.layerId)?.layer.vectorMask;
|
|
4250
|
+
if (!vm || vm.linked === false) return layers;
|
|
4251
|
+
const warp = hasWarp(g);
|
|
4252
|
+
const f = warp ? transformMap(g) : null;
|
|
4253
|
+
const h = warp ? null : geometryHomography(g);
|
|
4254
|
+
const affine = !warp && 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;
|
|
4255
|
+
const map = (p) => (f ? f(p) : h ? applyHomography(h, p) : p) ?? p;
|
|
4256
|
+
const moved = transformVectorMask(vm, map, affine);
|
|
4257
|
+
return mapLayer(layers, this.layerId, (l) => ({ ...l, vectorMask: moved }));
|
|
4258
|
+
}
|
|
4259
|
+
resampleContent(final, preview = {}) {
|
|
4167
4260
|
if (this.smart) return { layers: this.resampleSmart(final, preview), selection: this.startSelection };
|
|
4168
4261
|
if (this.typeTarget) return { layers: this.resampleType(), selection: this.startSelection };
|
|
4169
4262
|
const g = this.geometryValue;
|
|
@@ -6167,6 +6260,7 @@ var ImageEditorController = class {
|
|
|
6167
6260
|
let moved = { ...transformTypeLayer(layer, [1, 0, 0, 1, mx, my]), surface: translateSurface(layer.surface, mx, my) };
|
|
6168
6261
|
const mask = layer.mask;
|
|
6169
6262
|
if (mask && mask.linked !== false && !mask.source && (mx || my)) moved = { ...moved, mask: { ...mask, surface: translateSurface(mask.surface, mx, my) } };
|
|
6263
|
+
moved = followLayerMove(moved, mx, my);
|
|
6170
6264
|
this.commitLayers(mapLayer(this.state.layers, id, () => moved), void 0, commitToHistory);
|
|
6171
6265
|
const t = moved.transform ?? [1, 0, 0, 1, 0, 0];
|
|
6172
6266
|
return { offset: { x: Math.round(t[4]), y: Math.round(t[5]) }, bounds: this.getLayerBounds(id) };
|
|
@@ -6218,6 +6312,179 @@ var ImageEditorController = class {
|
|
|
6218
6312
|
if (this.state.activeLayerId === id) this.state.activeTarget = "pixels";
|
|
6219
6313
|
});
|
|
6220
6314
|
}
|
|
6315
|
+
// --- Vector masks ---
|
|
6316
|
+
/** The layer for a vector mask command: not a shape layer (its path is its vector mask), not a depth-masked one. */
|
|
6317
|
+
vectorMaskLayer(id) {
|
|
6318
|
+
const layer = requireLayer(this.state.layers, id).layer;
|
|
6319
|
+
const name = layer.name || id;
|
|
6320
|
+
if (layer.type === "shape") throw new LayerOperationError(`Layer "${name}" is a shape layer, whose path already is its vector mask; change it with set_shape_component, or rasterize the layer first.`);
|
|
6321
|
+
if (layer.mask?.source) throw new LayerOperationError(`Layer "${name}" has a depth mask, which cannot be combined with a vector mask; remove the depth mask first.`);
|
|
6322
|
+
return layer;
|
|
6323
|
+
}
|
|
6324
|
+
requireVectorMask(id) {
|
|
6325
|
+
const layer = this.vectorMaskLayer(id);
|
|
6326
|
+
if (!layer.vectorMask) throw new LayerOperationError(`Layer "${layer.name || id}" has no vector mask; add one with add_vector_mask.`);
|
|
6327
|
+
return { layer, vm: layer.vectorMask };
|
|
6328
|
+
}
|
|
6329
|
+
/** Components from a path spec: `components`, or one `shape` / `subpaths`. */
|
|
6330
|
+
vectorMaskComponents(path) {
|
|
6331
|
+
if (path.components?.length && (path.shape !== void 0 || path.subpaths !== void 0)) throw new LayerOperationError("Give components, or shape / subpaths, not both.");
|
|
6332
|
+
if (path.fillRule !== void 0 && path.fillRule !== "nonzero" && path.fillRule !== "evenodd") throw new LayerOperationError("fillRule must be nonzero or evenodd.");
|
|
6333
|
+
const specs = path.components?.length ? path.components : [path];
|
|
6334
|
+
return specs.map((c, i) => this.componentFrom(c, path.components?.length ? `components[${i}].` : ""));
|
|
6335
|
+
}
|
|
6336
|
+
/**
|
|
6337
|
+
* Layer › Vector Mask: `reveal-all` (an empty path showing everything), `hide-all` (an empty path
|
|
6338
|
+
* hiding everything) or `path` (reveals inside the path given). Replaces an existing vector mask;
|
|
6339
|
+
* the raster mask is kept and both apply. One undo step. Returns the vector mask.
|
|
6340
|
+
*/
|
|
6341
|
+
addVectorMask(id, from = "reveal-all", path = {}, trace2 = {}) {
|
|
6342
|
+
this.vectorMaskLayer(id);
|
|
6343
|
+
let vm;
|
|
6344
|
+
if (from === "selection") {
|
|
6345
|
+
const subpaths = this.traceSelection(trace2);
|
|
6346
|
+
vm = { components: [{ op: "combine", subpaths }], fillRule: "evenodd" };
|
|
6347
|
+
} else if (from === "path") {
|
|
6348
|
+
vm = { components: this.vectorMaskComponents(path) };
|
|
6349
|
+
if (path.fillRule === "evenodd") vm.fillRule = "evenodd";
|
|
6350
|
+
} else if (from === "reveal-all" || from === "hide-all") {
|
|
6351
|
+
if (path.components?.length || path.shape !== void 0 || path.subpaths !== void 0) throw new LayerOperationError(`A ${from} vector mask starts empty; use from path to give a path.`);
|
|
6352
|
+
vm = { components: [], ...from === "reveal-all" ? { inverted: true } : {} };
|
|
6353
|
+
} else {
|
|
6354
|
+
throw new LayerOperationError(`Unknown vector mask source "${String(from)}". Use reveal-all, hide-all, path or selection.`);
|
|
6355
|
+
}
|
|
6356
|
+
this.commitLayers(mapLayer(this.state.layers, id, (l) => ({ ...l, vectorMask: vm })));
|
|
6357
|
+
return vm;
|
|
6358
|
+
}
|
|
6359
|
+
/** The selection's outline as subpaths; throws when nothing is selected or nothing is left to trace. */
|
|
6360
|
+
traceSelection(options) {
|
|
6361
|
+
const sel = this.state.selection;
|
|
6362
|
+
if (!sel?.bounds) throw new LayerOperationError("Nothing is selected; select first, then make a path from the selection.");
|
|
6363
|
+
if (options.tolerance !== void 0 && !(options.tolerance >= 0.1 && options.tolerance <= 10)) throw new LayerOperationError("tolerance must be from 0.1 to 10 px.");
|
|
6364
|
+
const subpaths = selectionToSubpaths(sel, options);
|
|
6365
|
+
if (!subpaths.length) throw new LayerOperationError("The selection is too small or too faint to trace (below minArea, or under half selected); lower minArea or threshold.");
|
|
6366
|
+
return subpaths;
|
|
6367
|
+
}
|
|
6368
|
+
/**
|
|
6369
|
+
* Make Work Path: the selection's outline as closed Bézier subpaths in document pixels, holes
|
|
6370
|
+
* included (fill them even-odd), within `tolerance` px. Changes nothing. Throws without a selection.
|
|
6371
|
+
*/
|
|
6372
|
+
selectionToPath(options = {}) {
|
|
6373
|
+
const subpaths = this.traceSelection(options);
|
|
6374
|
+
const b = vectorMaskBounds({ components: [{ op: "combine", subpaths }] });
|
|
6375
|
+
const r = (n) => Math.round(n * 100) / 100;
|
|
6376
|
+
return {
|
|
6377
|
+
subpaths: roundSubpaths(subpaths),
|
|
6378
|
+
svg: subpathsToSvgPath(subpaths),
|
|
6379
|
+
fillRule: "evenodd",
|
|
6380
|
+
bounds: b ? { x: r(b.x), y: r(b.y), width: r(b.width), height: r(b.height) } : null,
|
|
6381
|
+
anchors: subpaths.reduce((n, sp) => n + sp.anchors.length, 0)
|
|
6382
|
+
};
|
|
6383
|
+
}
|
|
6384
|
+
/**
|
|
6385
|
+
* Make Selection: fills a path (`shape`, `subpaths` or `components`, document pixels) into a
|
|
6386
|
+
* selection combined with the current one by `mode`, with `feather` px and `antiAlias`. One
|
|
6387
|
+
* undo step. Returns whether anything is selected.
|
|
6388
|
+
*/
|
|
6389
|
+
pathToSelection(path, options = {}) {
|
|
6390
|
+
if (options.feather !== void 0 && !(options.feather >= 0 && options.feather <= 1e3)) throw new LayerOperationError("feather must be from 0 to 1000 px.");
|
|
6391
|
+
const components = this.vectorMaskComponents(path);
|
|
6392
|
+
const { width, height } = this.naturalSize;
|
|
6393
|
+
const mask = pathToSelection(components, width, height, { ...options, fillRule: path.fillRule ?? options.fillRule });
|
|
6394
|
+
return this.setSelection(mask, options.mode ?? "replace");
|
|
6395
|
+
}
|
|
6396
|
+
/** Replaces a vector mask's path (its properties are kept). One undo step. Returns the vector mask. */
|
|
6397
|
+
setVectorMaskPath(id, path) {
|
|
6398
|
+
const { vm } = this.requireVectorMask(id);
|
|
6399
|
+
const next = { ...vm, components: this.vectorMaskComponents(path) };
|
|
6400
|
+
if (path.fillRule === "evenodd") next.fillRule = "evenodd";
|
|
6401
|
+
else if (path.fillRule === "nonzero") delete next.fillRule;
|
|
6402
|
+
this.commitLayers(mapLayer(this.state.layers, id, (l) => ({ ...l, vectorMask: next })));
|
|
6403
|
+
return next;
|
|
6404
|
+
}
|
|
6405
|
+
/** Changes a vector mask's enabled, linked, inverted, density or feather (the path is kept). One undo step. */
|
|
6406
|
+
setVectorMaskProps(id, props) {
|
|
6407
|
+
const { vm } = this.requireVectorMask(id);
|
|
6408
|
+
if (props.density !== void 0 && !(props.density >= 0 && props.density <= 1)) throw new LayerOperationError("density must be from 0 to 1.");
|
|
6409
|
+
if (props.feather !== void 0 && !(props.feather >= 0 && props.feather <= 1e3)) throw new LayerOperationError("feather must be from 0 to 1000 px.");
|
|
6410
|
+
const next = { ...vm };
|
|
6411
|
+
for (const k of ["enabled", "linked", "inverted"]) {
|
|
6412
|
+
if (props[k] === void 0) continue;
|
|
6413
|
+
if (typeof props[k] !== "boolean") throw new LayerOperationError(`${k} must be true or false.`);
|
|
6414
|
+
if (k === "inverted" ? props[k] : !props[k]) next[k] = k === "inverted";
|
|
6415
|
+
else delete next[k];
|
|
6416
|
+
}
|
|
6417
|
+
if (props.density !== void 0) {
|
|
6418
|
+
if (props.density === 1) delete next.density;
|
|
6419
|
+
else next.density = props.density;
|
|
6420
|
+
}
|
|
6421
|
+
if (props.feather !== void 0) {
|
|
6422
|
+
if (props.feather === 0) delete next.feather;
|
|
6423
|
+
else next.feather = props.feather;
|
|
6424
|
+
}
|
|
6425
|
+
this.commitLayers(mapLayer(this.state.layers, id, (l) => ({ ...l, vectorMask: next })));
|
|
6426
|
+
return next;
|
|
6427
|
+
}
|
|
6428
|
+
/** Deletes a layer's vector mask (one undo step). */
|
|
6429
|
+
removeVectorMask(id) {
|
|
6430
|
+
this.requireVectorMask(id);
|
|
6431
|
+
this.commitLayers(
|
|
6432
|
+
mapLayer(this.state.layers, id, (l) => {
|
|
6433
|
+
const next = { ...l };
|
|
6434
|
+
delete next.vectorMask;
|
|
6435
|
+
return next;
|
|
6436
|
+
})
|
|
6437
|
+
);
|
|
6438
|
+
}
|
|
6439
|
+
/**
|
|
6440
|
+
* Layer › Rasterize › Vector Mask: multiplies the path (with its density and feather) into the
|
|
6441
|
+
* layer's raster mask, adding one when there is none, and removes the vector mask. The raster
|
|
6442
|
+
* mask keeps its own properties. One undo step. Returns the raster mask.
|
|
6443
|
+
*/
|
|
6444
|
+
rasterizeVectorMask(id) {
|
|
6445
|
+
const { layer } = this.requireVectorMask(id);
|
|
6446
|
+
const { width, height } = this.canvasSize;
|
|
6447
|
+
const raster = layer.mask;
|
|
6448
|
+
const plain3 = { ...layer, vectorMask: { ...layer.vectorMask, enabled: true }, mask: raster ? { surface: raster.surface, defaultValue: raster.defaultValue } : void 0 };
|
|
6449
|
+
const baked = effectiveLayerMask(plain3, width, height);
|
|
6450
|
+
const mask = raster ? { ...raster, surface: baked.surface, defaultValue: baked.defaultValue } : baked;
|
|
6451
|
+
this.commitLayers(
|
|
6452
|
+
mapLayer(this.state.layers, id, (l) => {
|
|
6453
|
+
const next = { ...l, mask };
|
|
6454
|
+
delete next.vectorMask;
|
|
6455
|
+
return next;
|
|
6456
|
+
})
|
|
6457
|
+
);
|
|
6458
|
+
return mask;
|
|
6459
|
+
}
|
|
6460
|
+
/** Load Selection from a vector mask's path (density and feather are not applied), inverted with `invert`. */
|
|
6461
|
+
selectionFromVectorMask(id, mode = "replace", invert2 = false) {
|
|
6462
|
+
const { vm } = this.requireVectorMask(id);
|
|
6463
|
+
const { width, height } = this.naturalSize;
|
|
6464
|
+
const cov = componentsCoverage(vm.components, { x: 0, y: 0, width, height }, vm.fillRule ?? "nonzero", vm.inverted === true !== invert2);
|
|
6465
|
+
const bytes = new Uint8Array(cov.length);
|
|
6466
|
+
for (let i = 0; i < cov.length; i++) bytes[i] = Math.round(Math.min(1, Math.max(0, cov[i])) * 255);
|
|
6467
|
+
return this.setSelection(createSelectionMask(width, height, bytes), mode);
|
|
6468
|
+
}
|
|
6469
|
+
/** A layer's vector mask with its paths (rounded), or `null` without one. */
|
|
6470
|
+
getVectorMask(id) {
|
|
6471
|
+
const layer = requireLayer(this.state.layers, id).layer;
|
|
6472
|
+
const vm = layer.vectorMask;
|
|
6473
|
+
if (!vm) return null;
|
|
6474
|
+
const b = vectorMaskBounds(vm);
|
|
6475
|
+
const r = (n) => Math.round(n * 1e3) / 1e3;
|
|
6476
|
+
return {
|
|
6477
|
+
layerId: id,
|
|
6478
|
+
enabled: vm.enabled !== false,
|
|
6479
|
+
linked: vm.linked !== false,
|
|
6480
|
+
inverted: vm.inverted === true,
|
|
6481
|
+
fillRule: vm.fillRule ?? "nonzero",
|
|
6482
|
+
density: vm.density ?? 1,
|
|
6483
|
+
feather: vm.feather ?? 0,
|
|
6484
|
+
bounds: b ? { x: r(b.x), y: r(b.y), width: r(b.width), height: r(b.height) } : null,
|
|
6485
|
+
components: vm.components.map((c, index) => ({ index, op: c.op, live: c.live ? JSON.parse(JSON.stringify(c.live)) : null, subpaths: roundSubpaths(c.subpaths) }))
|
|
6486
|
+
};
|
|
6487
|
+
}
|
|
6221
6488
|
/** Changes any layer mask's density, feather, enabled or linked flag (pixels kept). Returns the mask. */
|
|
6222
6489
|
setLayerMaskProps(id, props, commitToHistory = true) {
|
|
6223
6490
|
const layer = requireLayer(this.state.layers, id).layer;
|
|
@@ -10988,7 +11255,7 @@ var DEPTH_MASK_CONTROLS = [
|
|
|
10988
11255
|
// src/image-editor/playback.ts
|
|
10989
11256
|
var IMAGE_PLAYBACK_KIND = "layer";
|
|
10990
11257
|
var PAINT = /^(paint_stroke|clone_stamp|heal_brush|spot_heal|dodge|burn|sponge|smudge|blur_stroke|sharpen_stroke|history_brush|set_clone_source|set_clone_transform|fill_pixels|clear_pixels|set_paint|set_retouch_options|load_mixer_brush|clean_mixer_brush)$/;
|
|
10991
|
-
var SELECT = /^(select_region|select_subject|select_by_color|select_all|deselect|invert_selection|select_rect|select_ellipse|select_polygon|select_line|magic_wand|grow_selection|select_similar|reselect|select_layer_transparency|select_color_range|load_selection_from_image|load_selection_from_mask|set_selection_display|modify_selection|refine_selection|transform_selection|apply_selection_as_mask|layer_via_copy|stroke_selection|content_aware_fill|copy_pixels|cut_pixels|paste_pixels|save_selection|load_selection_from_channel|set_channel_properties|duplicate_channel|delete_channel|load_channel_from_image|set_quick_mask)$/;
|
|
11258
|
+
var SELECT = /^(select_region|select_subject|select_by_color|select_all|deselect|invert_selection|select_rect|select_ellipse|select_polygon|select_line|magic_wand|grow_selection|select_similar|reselect|select_layer_transparency|select_color_range|load_selection_from_image|load_selection_from_mask|set_selection_display|modify_selection|refine_selection|transform_selection|apply_selection_as_mask|layer_via_copy|stroke_selection|content_aware_fill|copy_pixels|cut_pixels|paste_pixels|save_selection|load_selection_from_channel|set_channel_properties|duplicate_channel|delete_channel|load_channel_from_image|set_quick_mask|path_to_selection)$/;
|
|
10992
11259
|
var ANNOTATE = /^(add_shape_layer|update_shape|add_shape_component|set_shape_component|remove_shape_component|merge_shape_components|add_text_layer|set_text|set_text_style|set_paragraph_style|set_text_frame|convert_text_to_shape)$/;
|
|
10993
11260
|
var CROP = /^(set_crop|crop_to_selection|reset_crop|set_rotation|flip_image|transform_layer|warp_layer|flip_layer|offset_layer)$/;
|
|
10994
11261
|
var ADJUST = /^(set_adjustments|add_adjustment_layer|update_adjustment_layer|remove_adjustment_layer|move_adjustment_layer)$/;
|
|
@@ -11658,11 +11925,12 @@ function createImageSelectionTools(controller) {
|
|
|
11658
11925
|
const loadFromMask = {
|
|
11659
11926
|
name: "load_selection_from_mask",
|
|
11660
11927
|
title: "Load a selection from a layer mask",
|
|
11661
|
-
description: "Load Selection from a layer's mask (Ctrl/\u2318-click its mask thumbnail): the mask's pixels become the selection, combined by mode (density and feather are not applied). Use it to reuse a mask on another layer or to edit it as a selection. Fails when the layer has no
|
|
11928
|
+
description: "Load Selection from a layer's mask (Ctrl/\u2318-click its mask thumbnail): the raster mask's pixels, or with source vector the vector mask's path, become the selection, combined by mode (density and feather are not applied). Use it to reuse a mask on another layer or to edit it as a selection. Fails when the layer has no such mask. One undoable step. Returns the selection.",
|
|
11662
11929
|
inputSchema: {
|
|
11663
11930
|
type: "object",
|
|
11664
11931
|
properties: {
|
|
11665
11932
|
layerId: { type: "string", minLength: 1, description: "The layer whose mask to load (see list_layers)." },
|
|
11933
|
+
source: { type: "string", enum: ["pixels", "vector"], description: "pixels: the raster (layer) mask (default); vector: the vector mask's path." },
|
|
11666
11934
|
invert: { type: "boolean", description: "Select what the mask hides instead (default false)." },
|
|
11667
11935
|
mode: modeSchema
|
|
11668
11936
|
},
|
|
@@ -11674,6 +11942,11 @@ function createImageSelectionTools(controller) {
|
|
|
11674
11942
|
const id = String(args.layerId ?? "");
|
|
11675
11943
|
const layer = controller.getLayer(id);
|
|
11676
11944
|
if (!layer) throw new Error(`No layer "${id}". Call list_layers for ids.`);
|
|
11945
|
+
if (args.source === "vector") {
|
|
11946
|
+
if (!layer.vectorMask) throw new Error(`Layer "${id}" has no vector mask. Add one with add_vector_mask, or load its pixels mask (source pixels).`);
|
|
11947
|
+
controller.selectionFromVectorMask(id, parseSelectionMode(args.mode), args.invert === true);
|
|
11948
|
+
return summary(controller);
|
|
11949
|
+
}
|
|
11677
11950
|
if (!layer.mask) throw new Error(`Layer "${id}" has no mask. Add one with add_layer_mask, or pick a layer with a mask.`);
|
|
11678
11951
|
if (layer.mask.source) throw new Error(`Layer "${id}" has a computed (depth) mask, not a painted one, so it cannot be loaded as a selection.`);
|
|
11679
11952
|
controller.selectionFromLayerMask(id, parseSelectionMode(args.mode), args.invert === true);
|
|
@@ -12540,6 +12813,7 @@ function describeLayer(layer, activeId, boundsOf) {
|
|
|
12540
12813
|
clipped: Boolean(layer.clipped),
|
|
12541
12814
|
locks: layer.locks ? { ...layer.locks } : {},
|
|
12542
12815
|
mask: maskSummary(layer.mask),
|
|
12816
|
+
vectorMask: layer.vectorMask ? { enabled: layer.vectorMask.enabled !== false, linked: layer.vectorMask.linked !== false, inverted: layer.vectorMask.inverted === true, components: layer.vectorMask.components.length, bounds: vectorMaskBounds(layer.vectorMask) } : null,
|
|
12543
12817
|
effects: layer.effects ? { enabled: layer.effects.enabled, scale: layer.effects.scale ?? 1, items: layer.effects.items.map((e) => ({ id: e.id, kind: e.kind, enabled: e.enabled })) } : null,
|
|
12544
12818
|
active: layer.id === activeId
|
|
12545
12819
|
};
|
|
@@ -15726,7 +16000,170 @@ function createImageShapeTools(controller) {
|
|
|
15726
16000
|
annotations: { title: "Merge shape components", ...DESTRUCTIVE6 },
|
|
15727
16001
|
handler: ({ layerId }) => controller.mergeShapeComponents(shapeId(layerId))
|
|
15728
16002
|
};
|
|
15729
|
-
|
|
16003
|
+
const layerArg = (v) => {
|
|
16004
|
+
const id = String(v ?? "");
|
|
16005
|
+
if (!controller.getLayer(id)) throw new Error(`No layer "${id}". Call list_layers for ids.`);
|
|
16006
|
+
return id;
|
|
16007
|
+
};
|
|
16008
|
+
const pathSpec = (args) => {
|
|
16009
|
+
const spec = { ...geometry(args) };
|
|
16010
|
+
if (args.components !== void 0) spec.components = args.components.map(geometry);
|
|
16011
|
+
if (args.fillRule !== void 0) spec.fillRule = args.fillRule;
|
|
16012
|
+
return spec;
|
|
16013
|
+
};
|
|
16014
|
+
const PATH_PROPS = {
|
|
16015
|
+
...GEOMETRY2,
|
|
16016
|
+
components: {
|
|
16017
|
+
type: "array",
|
|
16018
|
+
minItems: 1,
|
|
16019
|
+
description: "Several components instead of shape / subpaths, bottom first, each with its path operation (combine, subtract, intersect, exclude).",
|
|
16020
|
+
items: { type: "object", description: "One component.", properties: { ...GEOMETRY2, op: OP }, additionalProperties: false }
|
|
16021
|
+
},
|
|
16022
|
+
fillRule: { type: "string", enum: ["nonzero", "evenodd"], description: "Winding rule within a component (default nonzero)." }
|
|
16023
|
+
};
|
|
16024
|
+
const LAYER = { type: "string", minLength: 1, description: "The layer (see list_layers). Not a shape layer: its path already is its vector mask." };
|
|
16025
|
+
const TRACE_PROPS = {
|
|
16026
|
+
tolerance: { type: "number", minimum: 0.1, maximum: 10, description: "How far (px) the path may stray from the selection edge (default 0.5; lower is tighter, more anchors)." },
|
|
16027
|
+
cornerRoundness: { type: "number", minimum: 0, maximum: 1, description: "0: straight sides and sharp corners; 1: as smooth as the outline allows (default 0.5)." },
|
|
16028
|
+
minArea: { type: "number", minimum: 0, description: "Islands and holes smaller than this many square px are left out (default 4)." }
|
|
16029
|
+
};
|
|
16030
|
+
const TRACE_KEYS = ["tolerance", "cornerRoundness", "minArea"];
|
|
16031
|
+
const traceOptions = (args) => {
|
|
16032
|
+
const o = {};
|
|
16033
|
+
for (const k of TRACE_KEYS) if (typeof args[k] === "number") o[k] = args[k];
|
|
16034
|
+
return o;
|
|
16035
|
+
};
|
|
16036
|
+
const selectionToPath = {
|
|
16037
|
+
name: "selection_to_path",
|
|
16038
|
+
title: "Make a path from the selection",
|
|
16039
|
+
description: "Make Work Path: traces the selection's outline (where it is at least half selected) into closed B\xE9zier subpaths in document pixels, holes included, within tolerance px. Changes nothing. Returns svg (compact SVG path data) and fillRule evenodd, ready for add_vector_mask, set_vector_mask_path or add_shape_layer (pass the svg as subpaths and fillRule evenodd), plus bounds and the anchor count. Use add_vector_mask from selection to do it in one step.",
|
|
16040
|
+
inputSchema: { type: "object", properties: { ...TRACE_PROPS, anchors: { type: "boolean", description: "Also return the subpaths as anchors with handles (default false: svg only)." } }, additionalProperties: false },
|
|
16041
|
+
annotations: { title: "Make a path from the selection", ...READ10 },
|
|
16042
|
+
handler: (args) => {
|
|
16043
|
+
const out = controller.selectionToPath(traceOptions(args));
|
|
16044
|
+
const { subpaths, ...rest } = out;
|
|
16045
|
+
return args.anchors === true ? out : rest;
|
|
16046
|
+
}
|
|
16047
|
+
};
|
|
16048
|
+
const pathToSelection2 = {
|
|
16049
|
+
name: "path_to_selection",
|
|
16050
|
+
title: "Make a selection from a path",
|
|
16051
|
+
description: "Make Selection: fills a path (a live shape, free subpaths or SVG path data, or components, in document pixels, as add_shape_layer takes) into a selection, combined with the current one by mode, with feather px and antiAlias. Use it to select along B\xE9zier curves, or to turn a path from selection_to_path or get_vector_mask back into a selection. One undo step. Returns the selection.",
|
|
16052
|
+
inputSchema: {
|
|
16053
|
+
type: "object",
|
|
16054
|
+
$defs: DEFS2,
|
|
16055
|
+
properties: {
|
|
16056
|
+
...PATH_PROPS,
|
|
16057
|
+
feather: { type: "number", minimum: 0, maximum: 1e3, description: "Feather radius in px (default 0)." },
|
|
16058
|
+
antiAlias: { type: "boolean", description: "Soft edges (default true); false makes every pixel fully in or out." },
|
|
16059
|
+
mode: { type: "string", enum: ["replace", "add", "subtract", "intersect"], description: "How it combines with the current selection (default replace)." }
|
|
16060
|
+
},
|
|
16061
|
+
additionalProperties: false
|
|
16062
|
+
},
|
|
16063
|
+
annotations: { title: "Make a selection from a path", ...WRITE8 },
|
|
16064
|
+
handler: (args) => {
|
|
16065
|
+
const mode = args.mode;
|
|
16066
|
+
controller.pathToSelection(pathSpec(args), { feather: args.feather, antiAlias: args.antiAlias, mode });
|
|
16067
|
+
const sel = controller.getSelection();
|
|
16068
|
+
return { empty: !sel, bounds: sel?.bounds ?? null, area: Math.round(sel ? selectionArea(sel) : 0) };
|
|
16069
|
+
}
|
|
16070
|
+
};
|
|
16071
|
+
const getVectorMask = {
|
|
16072
|
+
name: "get_vector_mask",
|
|
16073
|
+
title: "Read a vector mask",
|
|
16074
|
+
description: "Reads a layer's vector mask: enabled, linked, inverted, fill rule, density, feather, the bounds of its path and its components (op, live shape, subpaths in document pixels). Use it before set_vector_mask_path to edit the path. Returns null when the layer has none.",
|
|
16075
|
+
inputSchema: { type: "object", properties: { layerId: LAYER }, required: ["layerId"], additionalProperties: false },
|
|
16076
|
+
annotations: { title: "Read a vector mask", ...READ10 },
|
|
16077
|
+
handler: ({ layerId }) => ({ vectorMask: controller.getVectorMask(layerArg(layerId)) })
|
|
16078
|
+
};
|
|
16079
|
+
const addVectorMask = {
|
|
16080
|
+
name: "add_vector_mask",
|
|
16081
|
+
title: "Add a vector mask",
|
|
16082
|
+
description: "Layer \u203A Vector Mask: a path that reveals the layer inside it and stays sharp at any size, on top of any raster mask (both apply). from path takes a live shape, free subpaths or components like add_shape_layer; from selection traces the current selection (within tolerance px); reveal-all and hide-all start with an empty path. Use it for hard-edged geometric or traced cutouts that must stay editable; use add_layer_mask for soft or painted ones. A linked vector mask moves and transforms with the layer. Replaces an existing vector mask. One undo step. Returns the vector mask.",
|
|
16083
|
+
inputSchema: {
|
|
16084
|
+
type: "object",
|
|
16085
|
+
$defs: DEFS2,
|
|
16086
|
+
properties: {
|
|
16087
|
+
layerId: LAYER,
|
|
16088
|
+
from: { type: "string", enum: ["path", "selection", "reveal-all", "hide-all"], description: "path (default when a shape, subpaths or components are given), selection (trace the current selection), reveal-all or hide-all (an empty path)." },
|
|
16089
|
+
...PATH_PROPS,
|
|
16090
|
+
...TRACE_PROPS
|
|
16091
|
+
},
|
|
16092
|
+
required: ["layerId"],
|
|
16093
|
+
additionalProperties: false
|
|
16094
|
+
},
|
|
16095
|
+
annotations: { title: "Add a vector mask", ...WRITE8 },
|
|
16096
|
+
handler: (args) => {
|
|
16097
|
+
const id = layerArg(args.layerId);
|
|
16098
|
+
const spec = pathSpec(args);
|
|
16099
|
+
const hasPath = spec.components?.length || spec.shape !== void 0 || spec.subpaths !== void 0;
|
|
16100
|
+
const from = args.from ?? (hasPath ? "path" : "reveal-all");
|
|
16101
|
+
if (from !== "selection" && TRACE_KEYS.some((k) => args[k] !== void 0)) throw new Error("tolerance, cornerRoundness and minArea apply only to from selection.");
|
|
16102
|
+
controller.addVectorMask(id, from, spec, traceOptions(args));
|
|
16103
|
+
return { vectorMask: controller.getVectorMask(id) };
|
|
16104
|
+
}
|
|
16105
|
+
};
|
|
16106
|
+
const setVectorMaskPath = {
|
|
16107
|
+
name: "set_vector_mask_path",
|
|
16108
|
+
title: "Replace a vector mask path",
|
|
16109
|
+
description: "Replaces the path of a layer's vector mask (shape, subpaths or components, document pixels); enabled, linked, inverted, density and feather are kept. Read the current path with get_vector_mask first to change part of it. One undo step. Returns the vector mask.",
|
|
16110
|
+
inputSchema: { type: "object", $defs: DEFS2, properties: { layerId: LAYER, ...PATH_PROPS }, required: ["layerId"], additionalProperties: false },
|
|
16111
|
+
annotations: { title: "Replace a vector mask path", ...WRITE_IDEMPOTENT5 },
|
|
16112
|
+
handler: (args) => {
|
|
16113
|
+
const id = layerArg(args.layerId);
|
|
16114
|
+
controller.setVectorMaskPath(id, pathSpec(args));
|
|
16115
|
+
return { vectorMask: controller.getVectorMask(id) };
|
|
16116
|
+
}
|
|
16117
|
+
};
|
|
16118
|
+
const setVectorMaskProps = {
|
|
16119
|
+
name: "set_vector_mask_properties",
|
|
16120
|
+
title: "Set vector mask properties",
|
|
16121
|
+
description: "Changes a layer's vector mask without touching its path: enabled (false ignores it), linked (false: it stays put when the layer moves or transforms), inverted (reveal outside the path), density 0..1 and feather in px (soften the edge without losing the path). One undo step. Returns the vector mask.",
|
|
16122
|
+
inputSchema: {
|
|
16123
|
+
type: "object",
|
|
16124
|
+
properties: {
|
|
16125
|
+
layerId: LAYER,
|
|
16126
|
+
enabled: { type: "boolean", description: "false ignores the vector mask but keeps it." },
|
|
16127
|
+
linked: { type: "boolean", description: "false keeps the path in place when the layer moves or transforms." },
|
|
16128
|
+
inverted: { type: "boolean", description: "true reveals outside the path instead." },
|
|
16129
|
+
density: { type: "number", minimum: 0, maximum: 1, description: "Density 0..1 (default 1): at 0.5 the hidden area still shows half." },
|
|
16130
|
+
feather: { type: "number", minimum: 0, maximum: 1e3, description: "Feather \u03C3 in px (default 0)." }
|
|
16131
|
+
},
|
|
16132
|
+
required: ["layerId"],
|
|
16133
|
+
additionalProperties: false
|
|
16134
|
+
},
|
|
16135
|
+
annotations: { title: "Set vector mask properties", ...WRITE_IDEMPOTENT5 },
|
|
16136
|
+
handler: ({ layerId, ...props }) => {
|
|
16137
|
+
const id = layerArg(layerId);
|
|
16138
|
+
controller.setVectorMaskProps(id, props);
|
|
16139
|
+
return { vectorMask: controller.getVectorMask(id) };
|
|
16140
|
+
}
|
|
16141
|
+
};
|
|
16142
|
+
const rasterizeVectorMask = {
|
|
16143
|
+
name: "rasterize_vector_mask",
|
|
16144
|
+
title: "Rasterize a vector mask",
|
|
16145
|
+
description: "Layer \u203A Rasterize \u203A Vector Mask: multiplies the path (with its density and feather) into the layer's raster mask, adding one if needed, and removes the vector mask, so the mask can be painted. The path can no longer be edited (undo_image brings it back). One undo step. Returns the layer's raster mask summary.",
|
|
16146
|
+
inputSchema: { type: "object", properties: { layerId: LAYER }, required: ["layerId"], additionalProperties: false },
|
|
16147
|
+
annotations: { title: "Rasterize a vector mask", ...DESTRUCTIVE6 },
|
|
16148
|
+
handler: ({ layerId }) => {
|
|
16149
|
+
const id = layerArg(layerId);
|
|
16150
|
+
const mask = controller.rasterizeVectorMask(id);
|
|
16151
|
+
return { layerId: id, mask: { defaultValue: mask.defaultValue, density: mask.density ?? 1, feather: mask.feather ?? 0, enabled: mask.enabled !== false } };
|
|
16152
|
+
}
|
|
16153
|
+
};
|
|
16154
|
+
const removeVectorMask = {
|
|
16155
|
+
name: "remove_vector_mask",
|
|
16156
|
+
title: "Delete a vector mask",
|
|
16157
|
+
description: "Deletes a layer's vector mask without applying it (the raster mask, if any, stays). Disable it instead with set_vector_mask_properties enabled false to keep it. One undo step. Returns the layer id.",
|
|
16158
|
+
inputSchema: { type: "object", properties: { layerId: LAYER }, required: ["layerId"], additionalProperties: false },
|
|
16159
|
+
annotations: { title: "Delete a vector mask", ...DESTRUCTIVE6 },
|
|
16160
|
+
handler: ({ layerId }) => {
|
|
16161
|
+
const id = layerArg(layerId);
|
|
16162
|
+
controller.removeVectorMask(id);
|
|
16163
|
+
return { layerId: id, vectorMask: null };
|
|
16164
|
+
}
|
|
16165
|
+
};
|
|
16166
|
+
return [get, add2, update, setComponent, addComponent, removeComponent, merge, getVectorMask, addVectorMask, setVectorMaskPath, setVectorMaskProps, rasterizeVectorMask, removeVectorMask, selectionToPath, pathToSelection2];
|
|
15730
16167
|
}
|
|
15731
16168
|
|
|
15732
16169
|
// src/image-editor/type-tools.ts
|
|
@@ -16393,6 +16830,11 @@ function maskThumbnailClick(controller, layer, e) {
|
|
|
16393
16830
|
controller.setActiveLayer(layer.id, "mask");
|
|
16394
16831
|
}
|
|
16395
16832
|
}
|
|
16833
|
+
function vectorMaskThumbnailClick(controller, layer, e) {
|
|
16834
|
+
if (!layer.vectorMask) return;
|
|
16835
|
+
if (e.ctrlKey || e.metaKey) controller.selectionFromVectorMask(layer.id, selectionModeFromModifiers(e, "replace"));
|
|
16836
|
+
else if (e.shiftKey) controller.setVectorMaskProps(layer.id, { enabled: layer.vectorMask.enabled === false });
|
|
16837
|
+
}
|
|
16396
16838
|
function layerThumbnailClick(controller, layer, e) {
|
|
16397
16839
|
if (!(e.ctrlKey || e.metaKey)) return false;
|
|
16398
16840
|
controller.selectLayerTransparency(layer.id, { mode: selectionModeFromModifiers(e, "replace") });
|
|
@@ -16491,6 +16933,12 @@ function layerMenu(controller) {
|
|
|
16491
16933
|
{ action: "delete-mask", label: "Delete Layer Mask", enabled: Boolean(active?.mask) },
|
|
16492
16934
|
{ action: "toggle-mask", label: active?.mask?.enabled === false ? "Enable Layer Mask" : "Disable Layer Mask", enabled: Boolean(active?.mask) },
|
|
16493
16935
|
{ action: "toggle-mask-link", label: active?.mask?.linked === false ? "Link Layer Mask" : "Unlink Layer Mask", enabled: Boolean(active?.mask && !active.mask.source) },
|
|
16936
|
+
{ action: "add-vector-mask", label: "Add Vector Mask (Reveal All)", enabled: Boolean(active && active.type !== "shape" && !active.mask?.source) },
|
|
16937
|
+
{ action: "add-vector-mask-hide", label: "Add Vector Mask (Hide All)", enabled: Boolean(active && active.type !== "shape" && !active.mask?.source) },
|
|
16938
|
+
{ action: "add-vector-mask-selection", label: "Add Vector Mask from Selection", enabled: Boolean(active && active.type !== "shape" && !active.mask?.source) && hasSel },
|
|
16939
|
+
{ action: "delete-vector-mask", label: "Delete Vector Mask", enabled: Boolean(active?.vectorMask) },
|
|
16940
|
+
{ action: "toggle-vector-mask", label: active?.vectorMask?.enabled === false ? "Enable Vector Mask" : "Disable Vector Mask", enabled: Boolean(active?.vectorMask) },
|
|
16941
|
+
{ action: "rasterize-vector-mask", label: "Rasterize Vector Mask", enabled: Boolean(active?.vectorMask) },
|
|
16494
16942
|
{ action: "rasterize", label: "Rasterize Layer", enabled: active?.type === "annotation" || active?.type === "smart" || active?.type === "shape" || active?.type === "type" || isPixel && Boolean(active?.type === "pixel" && active.filters?.length) },
|
|
16495
16943
|
{ action: "convert-smart", label: "Convert to Smart Object", enabled: Boolean(active && active.type !== "annotation") },
|
|
16496
16944
|
{ action: "edit-contents", label: "Edit Contents", enabled: active?.type === "smart" },
|
|
@@ -16562,6 +17010,22 @@ async function runLayerMenuAction(controller, action) {
|
|
|
16562
17010
|
case "toggle-mask-link":
|
|
16563
17011
|
if (id && active?.mask) controller.setLayerMaskProps(id, { linked: active.mask.linked === false });
|
|
16564
17012
|
break;
|
|
17013
|
+
case "add-vector-mask":
|
|
17014
|
+
case "add-vector-mask-hide":
|
|
17015
|
+
if (id) controller.addVectorMask(id, action === "add-vector-mask" ? "reveal-all" : "hide-all");
|
|
17016
|
+
break;
|
|
17017
|
+
case "add-vector-mask-selection":
|
|
17018
|
+
if (id) controller.addVectorMask(id, "selection");
|
|
17019
|
+
break;
|
|
17020
|
+
case "delete-vector-mask":
|
|
17021
|
+
if (id) controller.removeVectorMask(id);
|
|
17022
|
+
break;
|
|
17023
|
+
case "toggle-vector-mask":
|
|
17024
|
+
if (id && active?.vectorMask) controller.setVectorMaskProps(id, { enabled: active.vectorMask.enabled === false });
|
|
17025
|
+
break;
|
|
17026
|
+
case "rasterize-vector-mask":
|
|
17027
|
+
if (id) controller.rasterizeVectorMask(id);
|
|
17028
|
+
break;
|
|
16565
17029
|
case "rasterize":
|
|
16566
17030
|
if (id) await controller.rasterizeLayer(id);
|
|
16567
17031
|
break;
|
|
@@ -18154,6 +18618,7 @@ export {
|
|
|
18154
18618
|
combineSelections,
|
|
18155
18619
|
compileAdjustmentLayer,
|
|
18156
18620
|
componentPath,
|
|
18621
|
+
componentsCoverage,
|
|
18157
18622
|
compositeDeep,
|
|
18158
18623
|
compositeSurfaceOver,
|
|
18159
18624
|
computeHistogram,
|
|
@@ -18273,6 +18738,7 @@ export {
|
|
|
18273
18738
|
effectLayerTiming,
|
|
18274
18739
|
effectPixelsToSurface,
|
|
18275
18740
|
effectReach,
|
|
18741
|
+
effectiveLayerMask,
|
|
18276
18742
|
effectiveProof,
|
|
18277
18743
|
effectsFingerprint,
|
|
18278
18744
|
effectsMargin,
|
|
@@ -18309,6 +18775,7 @@ export {
|
|
|
18309
18775
|
flattenPath,
|
|
18310
18776
|
flipCorners,
|
|
18311
18777
|
flowField,
|
|
18778
|
+
followLayerMove,
|
|
18312
18779
|
fontOf,
|
|
18313
18780
|
formatControlValue,
|
|
18314
18781
|
fullSelection,
|
|
@@ -18373,6 +18840,7 @@ export {
|
|
|
18373
18840
|
invertMatrix3,
|
|
18374
18841
|
invertSelection,
|
|
18375
18842
|
isActiveFilter,
|
|
18843
|
+
isActiveVectorMask,
|
|
18376
18844
|
isAdjustmentKind,
|
|
18377
18845
|
isBitDepth,
|
|
18378
18846
|
isBlendMode,
|
|
@@ -18512,6 +18980,7 @@ export {
|
|
|
18512
18980
|
offsetLayer,
|
|
18513
18981
|
offsetShape,
|
|
18514
18982
|
offsetSmartObject,
|
|
18983
|
+
offsetVectorMask,
|
|
18515
18984
|
openMaskFile,
|
|
18516
18985
|
openPsdFile,
|
|
18517
18986
|
packRgba,
|
|
@@ -18538,6 +19007,7 @@ export {
|
|
|
18538
19007
|
patchTextStyle,
|
|
18539
19008
|
pathStrokeCoverage,
|
|
18540
19009
|
pathStrokeDabs,
|
|
19010
|
+
pathToSelection,
|
|
18541
19011
|
perDabStrength,
|
|
18542
19012
|
photoFilterMatrix,
|
|
18543
19013
|
pigment8,
|
|
@@ -18632,6 +19102,7 @@ export {
|
|
|
18632
19102
|
resolveMosaicRegionBounds,
|
|
18633
19103
|
resolvePaintGradient,
|
|
18634
19104
|
resolveProfile,
|
|
19105
|
+
resolveVectorMasks,
|
|
18635
19106
|
restyleRange,
|
|
18636
19107
|
retouchFilter,
|
|
18637
19108
|
reversePaintGradient,
|
|
@@ -18665,6 +19136,7 @@ export {
|
|
|
18665
19136
|
selectionRing,
|
|
18666
19137
|
selectionToCoverage,
|
|
18667
19138
|
selectionToPixels,
|
|
19139
|
+
selectionToSubpaths,
|
|
18668
19140
|
selectionValueAt,
|
|
18669
19141
|
serializeChannels,
|
|
18670
19142
|
serializeColorMode,
|
|
@@ -18722,6 +19194,7 @@ export {
|
|
|
18722
19194
|
stepSlotTs,
|
|
18723
19195
|
strokeGrainCatch,
|
|
18724
19196
|
styleAt,
|
|
19197
|
+
subpathsToSvgPath,
|
|
18725
19198
|
sumHistograms,
|
|
18726
19199
|
surfaceBytes,
|
|
18727
19200
|
surfaceDepth,
|
|
@@ -18761,6 +19234,7 @@ export {
|
|
|
18761
19234
|
transformShapeComponents,
|
|
18762
19235
|
transformSurface,
|
|
18763
19236
|
transformTypeLayer,
|
|
19237
|
+
transformVectorMask,
|
|
18764
19238
|
transformedBounds,
|
|
18765
19239
|
transformedBox,
|
|
18766
19240
|
transformedPoint,
|
|
@@ -18787,6 +19261,10 @@ export {
|
|
|
18787
19261
|
validateEffectGradient,
|
|
18788
19262
|
validateLayerEffect,
|
|
18789
19263
|
validateLayerEffects,
|
|
19264
|
+
vectorMaskBounds,
|
|
19265
|
+
vectorMaskCoverage,
|
|
19266
|
+
vectorMaskThumbnail,
|
|
19267
|
+
vectorMaskThumbnailClick,
|
|
18790
19268
|
versionedDescriptorBytes,
|
|
18791
19269
|
vibranceLinear,
|
|
18792
19270
|
vibrancePixel,
|