@pixerate/editor 0.190.0 → 0.192.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-T4U4O6DS.cjs → chunk-2UR4OX3B.cjs} +6 -9
- package/dist/{chunk-M7XIC2FC.js → chunk-RQ2EM5TY.js} +333 -0
- package/dist/{chunk-7ZWLDVFH.js → chunk-TXYXF32N.js} +6 -9
- package/dist/{chunk-5UEFNX24.cjs → chunk-ZQGZWFPU.cjs} +344 -11
- package/dist/image-editor/controller.d.cts +42 -1
- package/dist/image-editor/controller.d.ts +42 -1
- package/dist/image-editor/index.cjs +1171 -906
- package/dist/image-editor/index.js +292 -27
- package/dist/image-editor/layers/panel.d.cts +1 -1
- package/dist/image-editor/layers/panel.d.ts +1 -1
- package/dist/image-editor/layers/vector-mask.d.cts +2 -2
- package/dist/image-editor/layers/vector-mask.d.ts +2 -2
- 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/image-editor/selection/tool-options.d.cts +7 -1
- package/dist/image-editor/selection/tool-options.d.ts +7 -1
- 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 +4 -1
- package/skills/pixerate-image-editor/references/shapes-and-type.md +2 -0
- package/skills/pixerate-image-selection-masking/SKILL.md +8 -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,
|
|
@@ -383,7 +384,7 @@ import {
|
|
|
383
384
|
writePsd,
|
|
384
385
|
xyToTempTint,
|
|
385
386
|
xyToUv
|
|
386
|
-
} from "../chunk-
|
|
387
|
+
} from "../chunk-TXYXF32N.js";
|
|
387
388
|
import {
|
|
388
389
|
parseCssColor
|
|
389
390
|
} from "../chunk-3CI7FRMK.js";
|
|
@@ -630,6 +631,7 @@ import {
|
|
|
630
631
|
roundSubpaths,
|
|
631
632
|
spliceRuns,
|
|
632
633
|
styleAt,
|
|
634
|
+
subpathsToBezier,
|
|
633
635
|
textIndexAt,
|
|
634
636
|
translateMatrix,
|
|
635
637
|
warpMeshColumns,
|
|
@@ -641,7 +643,9 @@ import {
|
|
|
641
643
|
} from "../chunk-LPIKXPBM.js";
|
|
642
644
|
import "../chunk-YXKLVD56.js";
|
|
643
645
|
import "../chunk-M6DNZNVY.js";
|
|
644
|
-
import
|
|
646
|
+
import {
|
|
647
|
+
bezierPathToSvg
|
|
648
|
+
} from "../chunk-WEIK3UYB.js";
|
|
645
649
|
import "../chunk-PWBZH3FK.js";
|
|
646
650
|
import {
|
|
647
651
|
EffectUnsupportedError,
|
|
@@ -2426,6 +2430,67 @@ function hexRgb(hex) {
|
|
|
2426
2430
|
return [v >> 16 & 255, v >> 8 & 255, v & 255];
|
|
2427
2431
|
}
|
|
2428
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
|
+
|
|
2429
2494
|
// src/image-editor/layers/shape-type-ui.ts
|
|
2430
2495
|
var DEFAULT_SHAPE_TOOL_OPTIONS = {
|
|
2431
2496
|
kind: "rect",
|
|
@@ -6273,21 +6338,61 @@ var ImageEditorController = class {
|
|
|
6273
6338
|
* hiding everything) or `path` (reveals inside the path given). Replaces an existing vector mask;
|
|
6274
6339
|
* the raster mask is kept and both apply. One undo step. Returns the vector mask.
|
|
6275
6340
|
*/
|
|
6276
|
-
addVectorMask(id, from = "reveal-all", path = {}) {
|
|
6341
|
+
addVectorMask(id, from = "reveal-all", path = {}, trace2 = {}) {
|
|
6277
6342
|
this.vectorMaskLayer(id);
|
|
6278
6343
|
let vm;
|
|
6279
|
-
if (from === "
|
|
6344
|
+
if (from === "selection") {
|
|
6345
|
+
const subpaths = this.traceSelection(trace2);
|
|
6346
|
+
vm = { components: [{ op: "combine", subpaths }], fillRule: "evenodd" };
|
|
6347
|
+
} else if (from === "path") {
|
|
6280
6348
|
vm = { components: this.vectorMaskComponents(path) };
|
|
6281
6349
|
if (path.fillRule === "evenodd") vm.fillRule = "evenodd";
|
|
6282
6350
|
} else if (from === "reveal-all" || from === "hide-all") {
|
|
6283
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.`);
|
|
6284
6352
|
vm = { components: [], ...from === "reveal-all" ? { inverted: true } : {} };
|
|
6285
6353
|
} else {
|
|
6286
|
-
throw new LayerOperationError(`Unknown vector mask source "${String(from)}". Use reveal-all, hide-all or
|
|
6354
|
+
throw new LayerOperationError(`Unknown vector mask source "${String(from)}". Use reveal-all, hide-all, path or selection.`);
|
|
6287
6355
|
}
|
|
6288
6356
|
this.commitLayers(mapLayer(this.state.layers, id, (l) => ({ ...l, vectorMask: vm })));
|
|
6289
6357
|
return vm;
|
|
6290
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
|
+
}
|
|
6291
6396
|
/** Replaces a vector mask's path (its properties are kept). One undo step. Returns the vector mask. */
|
|
6292
6397
|
setVectorMaskPath(id, path) {
|
|
6293
6398
|
const { vm } = this.requireVectorMask(id);
|
|
@@ -9241,6 +9346,62 @@ var ImageEditorController = class {
|
|
|
9241
9346
|
* text wrapped in `box`. Refused without a text shaper. Returns its id.
|
|
9242
9347
|
*/
|
|
9243
9348
|
addTextLayer(init) {
|
|
9349
|
+
const layer = this.layoutTypeLayer(init);
|
|
9350
|
+
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.");
|
|
9351
|
+
if (init.blend !== void 0 && !isBlendMode(init.blend)) throw new LayerOperationError(`Unknown blend mode "${String(init.blend)}".`);
|
|
9352
|
+
if (init.opacity !== void 0) layer.opacity = init.opacity;
|
|
9353
|
+
if (init.blend !== void 0) layer.blend = init.blend;
|
|
9354
|
+
const next = placeLayer(this.state.layers, layer, this.newLayerPlacement(init));
|
|
9355
|
+
this.commitLayers(next, () => {
|
|
9356
|
+
this.state.activeLayerId = layer.id;
|
|
9357
|
+
this.state.activeTarget = "pixels";
|
|
9358
|
+
});
|
|
9359
|
+
return layer.id;
|
|
9360
|
+
}
|
|
9361
|
+
/**
|
|
9362
|
+
* Type Mask (horizontal or vertical): a selection in the shape of `text`, laid out and drawn as
|
|
9363
|
+
* `addTextLayer` would place it (font, size, tracking, box, rotation), combined with the current
|
|
9364
|
+
* selection by `mode`. Vertical type stacks the characters top to bottom, centred on `x`. No
|
|
9365
|
+
* layer is added. One undo step. Returns whether anything is selected.
|
|
9366
|
+
*/
|
|
9367
|
+
selectTextShape(init) {
|
|
9368
|
+
const vertical = init.orientation === "vertical";
|
|
9369
|
+
if (init.orientation !== void 0 && init.orientation !== "horizontal" && !vertical) throw new LayerOperationError("orientation must be horizontal or vertical.");
|
|
9370
|
+
if (init.feather !== void 0 && !(init.feather >= 0 && init.feather <= 1e3)) throw new LayerOperationError("feather must be from 0 to 1000 px.");
|
|
9371
|
+
let text = normalizeText(init.text);
|
|
9372
|
+
if (!text.trim()) throw new LayerOperationError("The text is empty, so there is nothing to select; give some characters.");
|
|
9373
|
+
let paragraph = init.paragraph;
|
|
9374
|
+
if (vertical) {
|
|
9375
|
+
if (/\n/.test(text)) throw new LayerOperationError("Vertical type masks take one column; select each column separately (mode add) instead of line breaks.");
|
|
9376
|
+
if (init.box) throw new LayerOperationError("Vertical type masks are point text; leave out box.");
|
|
9377
|
+
text = Array.from(text).join("\n");
|
|
9378
|
+
paragraph = { align: "center", ...paragraph ?? {} };
|
|
9379
|
+
}
|
|
9380
|
+
const layer = this.layoutTypeLayer({ ...init, text, paragraph });
|
|
9381
|
+
const { width, height } = this.naturalSize;
|
|
9382
|
+
const b = contentBounds(layer.surface);
|
|
9383
|
+
const x0 = Math.max(0, b?.x ?? 0);
|
|
9384
|
+
const y0 = Math.max(0, b?.y ?? 0);
|
|
9385
|
+
const x1 = Math.min(width, b ? b.x + b.width : 0);
|
|
9386
|
+
const y1 = Math.min(height, b ? b.y + b.height : 0);
|
|
9387
|
+
let mask;
|
|
9388
|
+
if (!b || x1 <= x0 || y1 <= y0) mask = createSelectionMask(width, height);
|
|
9389
|
+
else {
|
|
9390
|
+
const rect = { x: x0, y: y0, width: x1 - x0, height: y1 - y0 };
|
|
9391
|
+
const rgba = readRegion(layer.surface, rect);
|
|
9392
|
+
const bytes = new Uint8Array(rect.width * rect.height);
|
|
9393
|
+
const hard = init.antiAlias === false;
|
|
9394
|
+
for (let i = 0; i < bytes.length; i++) {
|
|
9395
|
+
const a = rgba[i * 4 + 3];
|
|
9396
|
+
bytes[i] = hard ? a >= 128 ? 255 : 0 : a;
|
|
9397
|
+
}
|
|
9398
|
+
mask = selectionFromRegion(width, height, rect, bytes);
|
|
9399
|
+
}
|
|
9400
|
+
if ((init.feather ?? 0) > 0 && mask.bounds) mask = modifySelection(mask, "feather", init.feather);
|
|
9401
|
+
return this.setSelection(mask.bounds ? mask : null, init.mode ?? "replace");
|
|
9402
|
+
}
|
|
9403
|
+
/** A type layer laid out and drawn from `init` (not added): the shared part of `addTextLayer` and the type masks. */
|
|
9404
|
+
layoutTypeLayer(init) {
|
|
9244
9405
|
const text = normalizeText(init.text);
|
|
9245
9406
|
if (!Number.isFinite(init.x) || !Number.isFinite(init.y)) throw new LayerOperationError("x and y must be numbers (document pixels).");
|
|
9246
9407
|
const style = patchTextStyle({ ...DEFAULT_TEXT_STYLE, color: this.state.paint.foreground }, init.style ?? {});
|
|
@@ -9252,19 +9413,9 @@ var ImageEditorController = class {
|
|
|
9252
9413
|
frame = { kind: "box", width, height };
|
|
9253
9414
|
}
|
|
9254
9415
|
if (init.rotation !== void 0 && !Number.isFinite(init.rotation)) throw new LayerOperationError("rotation must be a number of degrees.");
|
|
9255
|
-
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.");
|
|
9256
|
-
if (init.blend !== void 0 && !isBlendMode(init.blend)) throw new LayerOperationError(`Unknown blend mode "${String(init.blend)}".`);
|
|
9257
9416
|
const t = [1, 0, 0, 1, init.x, init.y];
|
|
9258
9417
|
const transform = init.rotation ? multiplyMatrices(t, rotateMatrix(init.rotation, { x: init.x, y: init.y })) : t;
|
|
9259
|
-
|
|
9260
|
-
if (init.opacity !== void 0) layer.opacity = init.opacity;
|
|
9261
|
-
if (init.blend !== void 0) layer.blend = init.blend;
|
|
9262
|
-
const next = placeLayer(this.state.layers, layer, this.newLayerPlacement(init));
|
|
9263
|
-
this.commitLayers(next, () => {
|
|
9264
|
-
this.state.activeLayerId = layer.id;
|
|
9265
|
-
this.state.activeTarget = "pixels";
|
|
9266
|
-
});
|
|
9267
|
-
return layer.id;
|
|
9418
|
+
return createTypeLayer({ text, runs: [{ length: text.length, style }], paragraphs: [{ length: text.length, style: paragraph }], frame, transform, name: init.name ?? typeLayerName(text) }, this.textEnv());
|
|
9268
9419
|
}
|
|
9269
9420
|
/**
|
|
9270
9421
|
* Replaces text [start, end) (default all of it) with `text`; inserted text takes the style at
|
|
@@ -11150,7 +11301,7 @@ var DEPTH_MASK_CONTROLS = [
|
|
|
11150
11301
|
// src/image-editor/playback.ts
|
|
11151
11302
|
var IMAGE_PLAYBACK_KIND = "layer";
|
|
11152
11303
|
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)$/;
|
|
11153
|
-
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)$/;
|
|
11304
|
+
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|select_text_shape)$/;
|
|
11154
11305
|
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)$/;
|
|
11155
11306
|
var CROP = /^(set_crop|crop_to_selection|reset_crop|set_rotation|flip_image|transform_layer|warp_layer|flip_layer|offset_layer)$/;
|
|
11156
11307
|
var ADJUST = /^(set_adjustments|add_adjustment_layer|update_adjustment_layer|remove_adjustment_layer|move_adjustment_layer)$/;
|
|
@@ -11271,7 +11422,9 @@ var SELECTION_TOOLS = [
|
|
|
11271
11422
|
{ id: "column", label: "Single Column" },
|
|
11272
11423
|
{ id: "lasso", label: "Lasso" },
|
|
11273
11424
|
{ id: "polygon", label: "Polygonal Lasso" },
|
|
11274
|
-
{ id: "range", label: "Color Range" }
|
|
11425
|
+
{ id: "range", label: "Color Range" },
|
|
11426
|
+
{ id: "type", label: "Type Mask" },
|
|
11427
|
+
{ id: "type-vertical", label: "Vertical Type Mask" }
|
|
11275
11428
|
];
|
|
11276
11429
|
var COLOR_RANGE_PRESET_LABELS = {
|
|
11277
11430
|
reds: "Reds",
|
|
@@ -11302,7 +11455,10 @@ var DEFAULT_SELECTION_TOOL_OPTIONS = {
|
|
|
11302
11455
|
feather: 0,
|
|
11303
11456
|
fuzziness: 40,
|
|
11304
11457
|
localized: 0,
|
|
11305
|
-
rangeInvert: false
|
|
11458
|
+
rangeInvert: false,
|
|
11459
|
+
typeText: "Type",
|
|
11460
|
+
typeSize: 72,
|
|
11461
|
+
typeFamily: "sans-serif"
|
|
11306
11462
|
};
|
|
11307
11463
|
function selectionModeFromModifiers(modifiers, fallback) {
|
|
11308
11464
|
if (modifiers.shiftKey && modifiers.altKey) return "intersect";
|
|
@@ -13684,6 +13840,24 @@ var SelectionGesture = class {
|
|
|
13684
13840
|
}
|
|
13685
13841
|
return;
|
|
13686
13842
|
}
|
|
13843
|
+
if (options.tool === "type" || options.tool === "type-vertical") {
|
|
13844
|
+
this.start = null;
|
|
13845
|
+
try {
|
|
13846
|
+
this.controller.selectTextShape({
|
|
13847
|
+
text: options.typeText || "Type",
|
|
13848
|
+
x: point.x,
|
|
13849
|
+
y: point.y,
|
|
13850
|
+
orientation: options.tool === "type-vertical" ? "vertical" : "horizontal",
|
|
13851
|
+
style: { fontSize: options.typeSize ?? 72, fontFamily: options.typeFamily || "sans-serif" },
|
|
13852
|
+
mode: selectionModeFromModifiers(modifiers, options.mode),
|
|
13853
|
+
feather: options.feather ?? 0,
|
|
13854
|
+
antiAlias: options.antiAlias
|
|
13855
|
+
});
|
|
13856
|
+
} catch (err) {
|
|
13857
|
+
this.error = err instanceof Error ? err.message : String(err);
|
|
13858
|
+
}
|
|
13859
|
+
return;
|
|
13860
|
+
}
|
|
13687
13861
|
if (options.tool === "wand" || options.tool === "range") {
|
|
13688
13862
|
this.start = null;
|
|
13689
13863
|
const common = {
|
|
@@ -15917,6 +16091,52 @@ function createImageShapeTools(controller) {
|
|
|
15917
16091
|
fillRule: { type: "string", enum: ["nonzero", "evenodd"], description: "Winding rule within a component (default nonzero)." }
|
|
15918
16092
|
};
|
|
15919
16093
|
const LAYER = { type: "string", minLength: 1, description: "The layer (see list_layers). Not a shape layer: its path already is its vector mask." };
|
|
16094
|
+
const TRACE_PROPS = {
|
|
16095
|
+
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)." },
|
|
16096
|
+
cornerRoundness: { type: "number", minimum: 0, maximum: 1, description: "0: straight sides and sharp corners; 1: as smooth as the outline allows (default 0.5)." },
|
|
16097
|
+
minArea: { type: "number", minimum: 0, description: "Islands and holes smaller than this many square px are left out (default 4)." }
|
|
16098
|
+
};
|
|
16099
|
+
const TRACE_KEYS = ["tolerance", "cornerRoundness", "minArea"];
|
|
16100
|
+
const traceOptions = (args) => {
|
|
16101
|
+
const o = {};
|
|
16102
|
+
for (const k of TRACE_KEYS) if (typeof args[k] === "number") o[k] = args[k];
|
|
16103
|
+
return o;
|
|
16104
|
+
};
|
|
16105
|
+
const selectionToPath = {
|
|
16106
|
+
name: "selection_to_path",
|
|
16107
|
+
title: "Make a path from the selection",
|
|
16108
|
+
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.",
|
|
16109
|
+
inputSchema: { type: "object", properties: { ...TRACE_PROPS, anchors: { type: "boolean", description: "Also return the subpaths as anchors with handles (default false: svg only)." } }, additionalProperties: false },
|
|
16110
|
+
annotations: { title: "Make a path from the selection", ...READ10 },
|
|
16111
|
+
handler: (args) => {
|
|
16112
|
+
const out = controller.selectionToPath(traceOptions(args));
|
|
16113
|
+
const { subpaths, ...rest } = out;
|
|
16114
|
+
return args.anchors === true ? out : rest;
|
|
16115
|
+
}
|
|
16116
|
+
};
|
|
16117
|
+
const pathToSelection2 = {
|
|
16118
|
+
name: "path_to_selection",
|
|
16119
|
+
title: "Make a selection from a path",
|
|
16120
|
+
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.",
|
|
16121
|
+
inputSchema: {
|
|
16122
|
+
type: "object",
|
|
16123
|
+
$defs: DEFS2,
|
|
16124
|
+
properties: {
|
|
16125
|
+
...PATH_PROPS,
|
|
16126
|
+
feather: { type: "number", minimum: 0, maximum: 1e3, description: "Feather radius in px (default 0)." },
|
|
16127
|
+
antiAlias: { type: "boolean", description: "Soft edges (default true); false makes every pixel fully in or out." },
|
|
16128
|
+
mode: { type: "string", enum: ["replace", "add", "subtract", "intersect"], description: "How it combines with the current selection (default replace)." }
|
|
16129
|
+
},
|
|
16130
|
+
additionalProperties: false
|
|
16131
|
+
},
|
|
16132
|
+
annotations: { title: "Make a selection from a path", ...WRITE8 },
|
|
16133
|
+
handler: (args) => {
|
|
16134
|
+
const mode = args.mode;
|
|
16135
|
+
controller.pathToSelection(pathSpec(args), { feather: args.feather, antiAlias: args.antiAlias, mode });
|
|
16136
|
+
const sel = controller.getSelection();
|
|
16137
|
+
return { empty: !sel, bounds: sel?.bounds ?? null, area: Math.round(sel ? selectionArea(sel) : 0) };
|
|
16138
|
+
}
|
|
16139
|
+
};
|
|
15920
16140
|
const getVectorMask = {
|
|
15921
16141
|
name: "get_vector_mask",
|
|
15922
16142
|
title: "Read a vector mask",
|
|
@@ -15928,14 +16148,15 @@ function createImageShapeTools(controller) {
|
|
|
15928
16148
|
const addVectorMask = {
|
|
15929
16149
|
name: "add_vector_mask",
|
|
15930
16150
|
title: "Add a vector mask",
|
|
15931
|
-
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; 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.",
|
|
16151
|
+
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.",
|
|
15932
16152
|
inputSchema: {
|
|
15933
16153
|
type: "object",
|
|
15934
16154
|
$defs: DEFS2,
|
|
15935
16155
|
properties: {
|
|
15936
16156
|
layerId: LAYER,
|
|
15937
|
-
from: { type: "string", enum: ["path", "reveal-all", "hide-all"], description: "path (default when a shape, subpaths or components are given), reveal-all or hide-all (an empty path)." },
|
|
15938
|
-
...PATH_PROPS
|
|
16157
|
+
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)." },
|
|
16158
|
+
...PATH_PROPS,
|
|
16159
|
+
...TRACE_PROPS
|
|
15939
16160
|
},
|
|
15940
16161
|
required: ["layerId"],
|
|
15941
16162
|
additionalProperties: false
|
|
@@ -15946,7 +16167,8 @@ function createImageShapeTools(controller) {
|
|
|
15946
16167
|
const spec = pathSpec(args);
|
|
15947
16168
|
const hasPath = spec.components?.length || spec.shape !== void 0 || spec.subpaths !== void 0;
|
|
15948
16169
|
const from = args.from ?? (hasPath ? "path" : "reveal-all");
|
|
15949
|
-
|
|
16170
|
+
if (from !== "selection" && TRACE_KEYS.some((k) => args[k] !== void 0)) throw new Error("tolerance, cornerRoundness and minArea apply only to from selection.");
|
|
16171
|
+
controller.addVectorMask(id, from, spec, traceOptions(args));
|
|
15950
16172
|
return { vectorMask: controller.getVectorMask(id) };
|
|
15951
16173
|
}
|
|
15952
16174
|
};
|
|
@@ -16010,7 +16232,7 @@ function createImageShapeTools(controller) {
|
|
|
16010
16232
|
return { layerId: id, vectorMask: null };
|
|
16011
16233
|
}
|
|
16012
16234
|
};
|
|
16013
|
-
return [get, add2, update, setComponent, addComponent, removeComponent, merge, getVectorMask, addVectorMask, setVectorMaskPath, setVectorMaskProps, rasterizeVectorMask, removeVectorMask];
|
|
16235
|
+
return [get, add2, update, setComponent, addComponent, removeComponent, merge, getVectorMask, addVectorMask, setVectorMaskPath, setVectorMaskProps, rasterizeVectorMask, removeVectorMask, selectionToPath, pathToSelection2];
|
|
16014
16236
|
}
|
|
16015
16237
|
|
|
16016
16238
|
// src/image-editor/type-tools.ts
|
|
@@ -16180,7 +16402,43 @@ function createImageTypeTools(controller) {
|
|
|
16180
16402
|
return { layer: layerSummary(id), shape: controller.getShapeLayer(id), warnings };
|
|
16181
16403
|
}
|
|
16182
16404
|
};
|
|
16183
|
-
|
|
16405
|
+
const typeMask = {
|
|
16406
|
+
name: "select_text_shape",
|
|
16407
|
+
title: "Select the shape of text",
|
|
16408
|
+
description: "Horizontal / Vertical Type Mask: selects the shape of text laid out as add_text_layer would place it (font family, size, weight, tracking, box, rotation; colour is ignored), combined with the current selection by mode. No layer is added. Vertical stacks the characters top to bottom, centred on x (one column; add more with mode add). Use it to cut text out of an image (layer_via_copy), fill letters with a photo (add_layer_mask), or mask an adjustment. Check fonts with list_fonts first. One undo step. Returns the selection.",
|
|
16409
|
+
inputSchema: {
|
|
16410
|
+
type: "object",
|
|
16411
|
+
properties: {
|
|
16412
|
+
text: { type: "string", minLength: 1, description: "The text; \\n starts a new line (horizontal only)." },
|
|
16413
|
+
x: { type: "number", description: "Document x of the origin, pixels (vertical: the column centre)." },
|
|
16414
|
+
y: { type: "number", description: "Document y of the origin (the first baseline), pixels." },
|
|
16415
|
+
orientation: { type: "string", enum: ["horizontal", "vertical"], description: "horizontal (default) or vertical (characters stacked top to bottom)." },
|
|
16416
|
+
box: {
|
|
16417
|
+
type: "object",
|
|
16418
|
+
description: "Horizontal only: wrap in this box, pixels.",
|
|
16419
|
+
properties: { width: { type: "number", minimum: 1, description: "Box width, pixels." }, height: { type: "number", minimum: 1, description: "Box height, pixels." } },
|
|
16420
|
+
required: ["width", "height"],
|
|
16421
|
+
additionalProperties: false
|
|
16422
|
+
},
|
|
16423
|
+
style: { type: "object", description: "Character style (size in px, family, weight, tracking\u2026; colour is ignored).", properties: STYLE, additionalProperties: false },
|
|
16424
|
+
paragraph: { type: "object", description: "Paragraph style (alignment, leading\u2026).", properties: PARAGRAPH, additionalProperties: false },
|
|
16425
|
+
rotation: { type: "number", description: "Degrees clockwise about the origin (default 0)." },
|
|
16426
|
+
mode: { type: "string", enum: ["replace", "add", "subtract", "intersect"], description: "How it combines with the current selection (default replace)." },
|
|
16427
|
+
feather: { type: "number", minimum: 0, maximum: 1e3, description: "Feather radius in px (default 0)." },
|
|
16428
|
+
antiAlias: { type: "boolean", description: "Soft glyph edges (default true)." }
|
|
16429
|
+
},
|
|
16430
|
+
required: ["text", "x", "y"],
|
|
16431
|
+
additionalProperties: false
|
|
16432
|
+
},
|
|
16433
|
+
annotations: { title: "Select the shape of text", ...WRITE9 },
|
|
16434
|
+
handler: (args) => {
|
|
16435
|
+
const init = pick2(args, ["text", "x", "y", "orientation", "box", "style", "paragraph", "rotation", "mode", "feather", "antiAlias"]);
|
|
16436
|
+
controller.selectTextShape(init);
|
|
16437
|
+
const sel = controller.getSelection();
|
|
16438
|
+
return { empty: !sel, bounds: sel?.bounds ?? null, area: Math.round(sel ? selectionArea(sel) : 0) };
|
|
16439
|
+
}
|
|
16440
|
+
};
|
|
16441
|
+
return [listFonts, get, add2, setText, setStyle, setParagraph, setFrame, toShape, typeMask];
|
|
16184
16442
|
}
|
|
16185
16443
|
|
|
16186
16444
|
// src/image-editor/tools.ts
|
|
@@ -16782,6 +17040,7 @@ function layerMenu(controller) {
|
|
|
16782
17040
|
{ action: "toggle-mask-link", label: active?.mask?.linked === false ? "Link Layer Mask" : "Unlink Layer Mask", enabled: Boolean(active?.mask && !active.mask.source) },
|
|
16783
17041
|
{ action: "add-vector-mask", label: "Add Vector Mask (Reveal All)", enabled: Boolean(active && active.type !== "shape" && !active.mask?.source) },
|
|
16784
17042
|
{ action: "add-vector-mask-hide", label: "Add Vector Mask (Hide All)", enabled: Boolean(active && active.type !== "shape" && !active.mask?.source) },
|
|
17043
|
+
{ action: "add-vector-mask-selection", label: "Add Vector Mask from Selection", enabled: Boolean(active && active.type !== "shape" && !active.mask?.source) && hasSel },
|
|
16785
17044
|
{ action: "delete-vector-mask", label: "Delete Vector Mask", enabled: Boolean(active?.vectorMask) },
|
|
16786
17045
|
{ action: "toggle-vector-mask", label: active?.vectorMask?.enabled === false ? "Enable Vector Mask" : "Disable Vector Mask", enabled: Boolean(active?.vectorMask) },
|
|
16787
17046
|
{ action: "rasterize-vector-mask", label: "Rasterize Vector Mask", enabled: Boolean(active?.vectorMask) },
|
|
@@ -16860,6 +17119,9 @@ async function runLayerMenuAction(controller, action) {
|
|
|
16860
17119
|
case "add-vector-mask-hide":
|
|
16861
17120
|
if (id) controller.addVectorMask(id, action === "add-vector-mask" ? "reveal-all" : "hide-all");
|
|
16862
17121
|
break;
|
|
17122
|
+
case "add-vector-mask-selection":
|
|
17123
|
+
if (id) controller.addVectorMask(id, "selection");
|
|
17124
|
+
break;
|
|
16863
17125
|
case "delete-vector-mask":
|
|
16864
17126
|
if (id) controller.removeVectorMask(id);
|
|
16865
17127
|
break;
|
|
@@ -18850,6 +19112,7 @@ export {
|
|
|
18850
19112
|
patchTextStyle,
|
|
18851
19113
|
pathStrokeCoverage,
|
|
18852
19114
|
pathStrokeDabs,
|
|
19115
|
+
pathToSelection,
|
|
18853
19116
|
perDabStrength,
|
|
18854
19117
|
photoFilterMatrix,
|
|
18855
19118
|
pigment8,
|
|
@@ -18978,6 +19241,7 @@ export {
|
|
|
18978
19241
|
selectionRing,
|
|
18979
19242
|
selectionToCoverage,
|
|
18980
19243
|
selectionToPixels,
|
|
19244
|
+
selectionToSubpaths,
|
|
18981
19245
|
selectionValueAt,
|
|
18982
19246
|
serializeChannels,
|
|
18983
19247
|
serializeColorMode,
|
|
@@ -19035,6 +19299,7 @@ export {
|
|
|
19035
19299
|
stepSlotTs,
|
|
19036
19300
|
strokeGrainCatch,
|
|
19037
19301
|
styleAt,
|
|
19302
|
+
subpathsToSvgPath,
|
|
19038
19303
|
sumHistograms,
|
|
19039
19304
|
surfaceBytes,
|
|
19040
19305
|
surfaceDepth,
|
|
@@ -67,7 +67,7 @@ export type DropZone = 'above' | 'below' | 'inside';
|
|
|
67
67
|
export declare function dropZone(fraction: number, isGroup: boolean): DropZone;
|
|
68
68
|
/** The placement for `move_layer` when `dragId` is dropped on row `targetId` in `zone`, or `null` for a no-op. */
|
|
69
69
|
export declare function dropPlacement(layers: readonly ImageLayer[], dragId: string, targetId: string, zone: DropZone): LayerPlacement | null;
|
|
70
|
-
export type LayerMenuAction = 'new-layer' | 'new-group' | 'duplicate' | 'delete' | 'group-selected' | 'ungroup' | 'merge-down' | 'merge-visible' | 'flatten' | 'via-copy' | 'via-cut' | 'add-mask' | 'add-vector-mask' | 'add-vector-mask-hide' | 'delete-vector-mask' | 'toggle-vector-mask' | 'rasterize-vector-mask' | 'add-mask-selection' | 'add-mask-hide-selection' | 'add-mask-transparency' | 'select-transparency' | 'apply-mask' | 'delete-mask' | 'toggle-mask' | 'toggle-mask-link' | 'toggle-clip' | 'rasterize' | 'convert-smart' | 'edit-contents' | 'smart-via-copy' | 'add-filter-mask';
|
|
70
|
+
export type LayerMenuAction = 'new-layer' | 'new-group' | 'duplicate' | 'delete' | 'group-selected' | 'ungroup' | 'merge-down' | 'merge-visible' | 'flatten' | 'via-copy' | 'via-cut' | 'add-mask' | 'add-vector-mask' | 'add-vector-mask-hide' | 'add-vector-mask-selection' | 'delete-vector-mask' | 'toggle-vector-mask' | 'rasterize-vector-mask' | 'add-mask-selection' | 'add-mask-hide-selection' | 'add-mask-transparency' | 'select-transparency' | 'apply-mask' | 'delete-mask' | 'toggle-mask' | 'toggle-mask-link' | 'toggle-clip' | 'rasterize' | 'convert-smart' | 'edit-contents' | 'smart-via-copy' | 'add-filter-mask';
|
|
71
71
|
export interface LayerMenuItem {
|
|
72
72
|
action: LayerMenuAction;
|
|
73
73
|
label: string;
|
|
@@ -67,7 +67,7 @@ export type DropZone = 'above' | 'below' | 'inside';
|
|
|
67
67
|
export declare function dropZone(fraction: number, isGroup: boolean): DropZone;
|
|
68
68
|
/** The placement for `move_layer` when `dragId` is dropped on row `targetId` in `zone`, or `null` for a no-op. */
|
|
69
69
|
export declare function dropPlacement(layers: readonly ImageLayer[], dragId: string, targetId: string, zone: DropZone): LayerPlacement | null;
|
|
70
|
-
export type LayerMenuAction = 'new-layer' | 'new-group' | 'duplicate' | 'delete' | 'group-selected' | 'ungroup' | 'merge-down' | 'merge-visible' | 'flatten' | 'via-copy' | 'via-cut' | 'add-mask' | 'add-vector-mask' | 'add-vector-mask-hide' | 'delete-vector-mask' | 'toggle-vector-mask' | 'rasterize-vector-mask' | 'add-mask-selection' | 'add-mask-hide-selection' | 'add-mask-transparency' | 'select-transparency' | 'apply-mask' | 'delete-mask' | 'toggle-mask' | 'toggle-mask-link' | 'toggle-clip' | 'rasterize' | 'convert-smart' | 'edit-contents' | 'smart-via-copy' | 'add-filter-mask';
|
|
70
|
+
export type LayerMenuAction = 'new-layer' | 'new-group' | 'duplicate' | 'delete' | 'group-selected' | 'ungroup' | 'merge-down' | 'merge-visible' | 'flatten' | 'via-copy' | 'via-cut' | 'add-mask' | 'add-vector-mask' | 'add-vector-mask-hide' | 'add-vector-mask-selection' | 'delete-vector-mask' | 'toggle-vector-mask' | 'rasterize-vector-mask' | 'add-mask-selection' | 'add-mask-hide-selection' | 'add-mask-transparency' | 'select-transparency' | 'apply-mask' | 'delete-mask' | 'toggle-mask' | 'toggle-mask-link' | 'toggle-clip' | 'rasterize' | 'convert-smart' | 'edit-contents' | 'smart-via-copy' | 'add-filter-mask';
|
|
71
71
|
export interface LayerMenuItem {
|
|
72
72
|
action: LayerMenuAction;
|
|
73
73
|
label: string;
|
|
@@ -38,8 +38,8 @@ export declare function transformVectorMask(vm: VectorMask, f: (p: {
|
|
|
38
38
|
}, affine?: AffineMatrix): VectorMask;
|
|
39
39
|
/** A linked vector mask moved with its layer by (dx, dy); unlinked ones stay. */
|
|
40
40
|
export declare function followLayerMove<T extends ImageLayer>(layer: T, dx: number, dy: number): T;
|
|
41
|
-
/** Bounds of a vector mask's paths (document pixels), or `null` when it has none. */
|
|
42
|
-
export declare function vectorMaskBounds(vm: VectorMask): CropBox | null;
|
|
41
|
+
/** Bounds of the area a vector mask's paths enclose (document pixels, the curves themselves, not their handles), or `null` when it has none. */
|
|
42
|
+
export declare function vectorMaskBounds(vm: Pick<VectorMask, 'components'>): CropBox | null;
|
|
43
43
|
/**
|
|
44
44
|
* A vector mask's thumbnail over the `width × height` canvas, at most `maxSide` px: grey RGBA,
|
|
45
45
|
* white where the path reveals (cached per mask object).
|
|
@@ -38,8 +38,8 @@ export declare function transformVectorMask(vm: VectorMask, f: (p: {
|
|
|
38
38
|
}, affine?: AffineMatrix): VectorMask;
|
|
39
39
|
/** A linked vector mask moved with its layer by (dx, dy); unlinked ones stay. */
|
|
40
40
|
export declare function followLayerMove<T extends ImageLayer>(layer: T, dx: number, dy: number): T;
|
|
41
|
-
/** Bounds of a vector mask's paths (document pixels), or `null` when it has none. */
|
|
42
|
-
export declare function vectorMaskBounds(vm: VectorMask): CropBox | null;
|
|
41
|
+
/** Bounds of the area a vector mask's paths enclose (document pixels, the curves themselves, not their handles), or `null` when it has none. */
|
|
42
|
+
export declare function vectorMaskBounds(vm: Pick<VectorMask, 'components'>): CropBox | null;
|
|
43
43
|
/**
|
|
44
44
|
* A vector mask's thumbnail over the `width × height` canvas, at most `maxSide` px: grey RGBA,
|
|
45
45
|
* white where the path reveals (cached per mask object).
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Selections ⇄ paths (Photoshop's Make Work Path and Make Selection): a selection's outline traced
|
|
3
|
+
* into Bézier subpaths, and a path filled into a selection.
|
|
4
|
+
*/
|
|
5
|
+
import type { VectorSubpath } from '../../vector/types.cjs';
|
|
6
|
+
import type { ShapeComponent, ShapeFillRule } from '../layers/model.cjs';
|
|
7
|
+
import { type SelectionMask } from './mask.cjs';
|
|
8
|
+
export interface SelectionToPathOptions {
|
|
9
|
+
/** How far (px) the path may stray from the outline (default 0.5; Photoshop's Make Work Path tolerance). */
|
|
10
|
+
tolerance?: number;
|
|
11
|
+
/** 0: straight sides and sharp corners; 1: as smooth as the outline allows (default 0.5). */
|
|
12
|
+
cornerRoundness?: number;
|
|
13
|
+
/** Islands and holes smaller than this many square pixels are left out (default 4). */
|
|
14
|
+
minArea?: number;
|
|
15
|
+
/** Coverage (0..255) where the outline runs (default 128, half selected). */
|
|
16
|
+
threshold?: number;
|
|
17
|
+
}
|
|
18
|
+
/**
|
|
19
|
+
* The outline of a selection as closed subpaths in document pixels (holes included, so fill them
|
|
20
|
+
* even-odd). Traced in the selection's bounds only; an empty selection gives none.
|
|
21
|
+
*/
|
|
22
|
+
export declare function selectionToSubpaths(mask: SelectionMask, options?: SelectionToPathOptions): VectorSubpath[];
|
|
23
|
+
/** Subpaths as compact SVG path data (absolute, `precision` decimals). */
|
|
24
|
+
export declare function subpathsToSvgPath(subpaths: readonly VectorSubpath[], precision?: number): string;
|
|
25
|
+
export interface PathToSelectionOptions {
|
|
26
|
+
/** Feather radius in px applied after filling (default 0). */
|
|
27
|
+
feather?: number;
|
|
28
|
+
/** Soft edges (default true); false makes every pixel fully in or out. */
|
|
29
|
+
antiAlias?: boolean;
|
|
30
|
+
/** How overlapping subpaths fill (default nonzero). */
|
|
31
|
+
fillRule?: ShapeFillRule;
|
|
32
|
+
}
|
|
33
|
+
/**
|
|
34
|
+
* Make Selection: the area of `components` (document pixels, combined by their ops) over a
|
|
35
|
+
* `width × height` image, computed in the path's bounds only.
|
|
36
|
+
*/
|
|
37
|
+
export declare function pathToSelection(components: readonly ShapeComponent[], width: number, height: number, options?: PathToSelectionOptions): SelectionMask;
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Selections ⇄ paths (Photoshop's Make Work Path and Make Selection): a selection's outline traced
|
|
3
|
+
* into Bézier subpaths, and a path filled into a selection.
|
|
4
|
+
*/
|
|
5
|
+
import type { VectorSubpath } from '../../vector/types.js';
|
|
6
|
+
import type { ShapeComponent, ShapeFillRule } from '../layers/model.js';
|
|
7
|
+
import { type SelectionMask } from './mask.js';
|
|
8
|
+
export interface SelectionToPathOptions {
|
|
9
|
+
/** How far (px) the path may stray from the outline (default 0.5; Photoshop's Make Work Path tolerance). */
|
|
10
|
+
tolerance?: number;
|
|
11
|
+
/** 0: straight sides and sharp corners; 1: as smooth as the outline allows (default 0.5). */
|
|
12
|
+
cornerRoundness?: number;
|
|
13
|
+
/** Islands and holes smaller than this many square pixels are left out (default 4). */
|
|
14
|
+
minArea?: number;
|
|
15
|
+
/** Coverage (0..255) where the outline runs (default 128, half selected). */
|
|
16
|
+
threshold?: number;
|
|
17
|
+
}
|
|
18
|
+
/**
|
|
19
|
+
* The outline of a selection as closed subpaths in document pixels (holes included, so fill them
|
|
20
|
+
* even-odd). Traced in the selection's bounds only; an empty selection gives none.
|
|
21
|
+
*/
|
|
22
|
+
export declare function selectionToSubpaths(mask: SelectionMask, options?: SelectionToPathOptions): VectorSubpath[];
|
|
23
|
+
/** Subpaths as compact SVG path data (absolute, `precision` decimals). */
|
|
24
|
+
export declare function subpathsToSvgPath(subpaths: readonly VectorSubpath[], precision?: number): string;
|
|
25
|
+
export interface PathToSelectionOptions {
|
|
26
|
+
/** Feather radius in px applied after filling (default 0). */
|
|
27
|
+
feather?: number;
|
|
28
|
+
/** Soft edges (default true); false makes every pixel fully in or out. */
|
|
29
|
+
antiAlias?: boolean;
|
|
30
|
+
/** How overlapping subpaths fill (default nonzero). */
|
|
31
|
+
fillRule?: ShapeFillRule;
|
|
32
|
+
}
|
|
33
|
+
/**
|
|
34
|
+
* Make Selection: the area of `components` (document pixels, combined by their ops) over a
|
|
35
|
+
* `width × height` image, computed in the path's bounds only.
|
|
36
|
+
*/
|
|
37
|
+
export declare function pathToSelection(components: readonly ShapeComponent[], width: number, height: number, options?: PathToSelectionOptions): SelectionMask;
|
|
@@ -2,7 +2,7 @@ import { type SelectionMask, type SelectionMode } from './mask.cjs';
|
|
|
2
2
|
import type { ColorDistance, ColorRangePreset } from './wand.cjs';
|
|
3
3
|
import type { RefineEdgeParams } from './refine.cjs';
|
|
4
4
|
/** Selection tools of the image editor UI. */
|
|
5
|
-
export type SelectionToolKind = 'wand' | 'rect' | 'ellipse' | 'row' | 'column' | 'lasso' | 'polygon' | 'range';
|
|
5
|
+
export type SelectionToolKind = 'wand' | 'rect' | 'ellipse' | 'row' | 'column' | 'lasso' | 'polygon' | 'range' | 'type' | 'type-vertical';
|
|
6
6
|
export declare const SELECTION_TOOLS: readonly {
|
|
7
7
|
id: SelectionToolKind;
|
|
8
8
|
label: string;
|
|
@@ -31,6 +31,12 @@ export interface SelectionToolOptions {
|
|
|
31
31
|
localized?: number;
|
|
32
32
|
/** Color Range: select everything except the sampled colours. */
|
|
33
33
|
rangeInvert?: boolean;
|
|
34
|
+
/** Type Mask: the text to select (default "Type"). A click places its first baseline. */
|
|
35
|
+
typeText?: string;
|
|
36
|
+
/** Type Mask: font size in px (default 72). */
|
|
37
|
+
typeSize?: number;
|
|
38
|
+
/** Type Mask: font family (default sans-serif). */
|
|
39
|
+
typeFamily?: string;
|
|
34
40
|
}
|
|
35
41
|
export declare const DEFAULT_SELECTION_TOOL_OPTIONS: SelectionToolOptions;
|
|
36
42
|
/**
|