@pixerate/editor 0.190.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-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 +23 -1
- package/dist/image-editor/controller.d.ts +23 -1
- package/dist/image-editor/index.cjs +1056 -896
- package/dist/image-editor/index.js +173 -13
- 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/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 +3 -1
- package/skills/pixerate-image-selection-masking/SKILL.md +6 -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);
|
|
@@ -11150,7 +11255,7 @@ var DEPTH_MASK_CONTROLS = [
|
|
|
11150
11255
|
// src/image-editor/playback.ts
|
|
11151
11256
|
var IMAGE_PLAYBACK_KIND = "layer";
|
|
11152
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)$/;
|
|
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)$/;
|
|
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)$/;
|
|
11154
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)$/;
|
|
11155
11260
|
var CROP = /^(set_crop|crop_to_selection|reset_crop|set_rotation|flip_image|transform_layer|warp_layer|flip_layer|offset_layer)$/;
|
|
11156
11261
|
var ADJUST = /^(set_adjustments|add_adjustment_layer|update_adjustment_layer|remove_adjustment_layer|move_adjustment_layer)$/;
|
|
@@ -15917,6 +16022,52 @@ function createImageShapeTools(controller) {
|
|
|
15917
16022
|
fillRule: { type: "string", enum: ["nonzero", "evenodd"], description: "Winding rule within a component (default nonzero)." }
|
|
15918
16023
|
};
|
|
15919
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
|
+
};
|
|
15920
16071
|
const getVectorMask = {
|
|
15921
16072
|
name: "get_vector_mask",
|
|
15922
16073
|
title: "Read a vector mask",
|
|
@@ -15928,14 +16079,15 @@ function createImageShapeTools(controller) {
|
|
|
15928
16079
|
const addVectorMask = {
|
|
15929
16080
|
name: "add_vector_mask",
|
|
15930
16081
|
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.",
|
|
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.",
|
|
15932
16083
|
inputSchema: {
|
|
15933
16084
|
type: "object",
|
|
15934
16085
|
$defs: DEFS2,
|
|
15935
16086
|
properties: {
|
|
15936
16087
|
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
|
|
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
|
|
15939
16091
|
},
|
|
15940
16092
|
required: ["layerId"],
|
|
15941
16093
|
additionalProperties: false
|
|
@@ -15946,7 +16098,8 @@ function createImageShapeTools(controller) {
|
|
|
15946
16098
|
const spec = pathSpec(args);
|
|
15947
16099
|
const hasPath = spec.components?.length || spec.shape !== void 0 || spec.subpaths !== void 0;
|
|
15948
16100
|
const from = args.from ?? (hasPath ? "path" : "reveal-all");
|
|
15949
|
-
|
|
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));
|
|
15950
16103
|
return { vectorMask: controller.getVectorMask(id) };
|
|
15951
16104
|
}
|
|
15952
16105
|
};
|
|
@@ -16010,7 +16163,7 @@ function createImageShapeTools(controller) {
|
|
|
16010
16163
|
return { layerId: id, vectorMask: null };
|
|
16011
16164
|
}
|
|
16012
16165
|
};
|
|
16013
|
-
return [get, add2, update, setComponent, addComponent, removeComponent, merge, getVectorMask, addVectorMask, setVectorMaskPath, setVectorMaskProps, rasterizeVectorMask, removeVectorMask];
|
|
16166
|
+
return [get, add2, update, setComponent, addComponent, removeComponent, merge, getVectorMask, addVectorMask, setVectorMaskPath, setVectorMaskProps, rasterizeVectorMask, removeVectorMask, selectionToPath, pathToSelection2];
|
|
16014
16167
|
}
|
|
16015
16168
|
|
|
16016
16169
|
// src/image-editor/type-tools.ts
|
|
@@ -16782,6 +16935,7 @@ function layerMenu(controller) {
|
|
|
16782
16935
|
{ action: "toggle-mask-link", label: active?.mask?.linked === false ? "Link Layer Mask" : "Unlink Layer Mask", enabled: Boolean(active?.mask && !active.mask.source) },
|
|
16783
16936
|
{ action: "add-vector-mask", label: "Add Vector Mask (Reveal All)", enabled: Boolean(active && active.type !== "shape" && !active.mask?.source) },
|
|
16784
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 },
|
|
16785
16939
|
{ action: "delete-vector-mask", label: "Delete Vector Mask", enabled: Boolean(active?.vectorMask) },
|
|
16786
16940
|
{ action: "toggle-vector-mask", label: active?.vectorMask?.enabled === false ? "Enable Vector Mask" : "Disable Vector Mask", enabled: Boolean(active?.vectorMask) },
|
|
16787
16941
|
{ action: "rasterize-vector-mask", label: "Rasterize Vector Mask", enabled: Boolean(active?.vectorMask) },
|
|
@@ -16860,6 +17014,9 @@ async function runLayerMenuAction(controller, action) {
|
|
|
16860
17014
|
case "add-vector-mask-hide":
|
|
16861
17015
|
if (id) controller.addVectorMask(id, action === "add-vector-mask" ? "reveal-all" : "hide-all");
|
|
16862
17016
|
break;
|
|
17017
|
+
case "add-vector-mask-selection":
|
|
17018
|
+
if (id) controller.addVectorMask(id, "selection");
|
|
17019
|
+
break;
|
|
16863
17020
|
case "delete-vector-mask":
|
|
16864
17021
|
if (id) controller.removeVectorMask(id);
|
|
16865
17022
|
break;
|
|
@@ -18850,6 +19007,7 @@ export {
|
|
|
18850
19007
|
patchTextStyle,
|
|
18851
19008
|
pathStrokeCoverage,
|
|
18852
19009
|
pathStrokeDabs,
|
|
19010
|
+
pathToSelection,
|
|
18853
19011
|
perDabStrength,
|
|
18854
19012
|
photoFilterMatrix,
|
|
18855
19013
|
pigment8,
|
|
@@ -18978,6 +19136,7 @@ export {
|
|
|
18978
19136
|
selectionRing,
|
|
18979
19137
|
selectionToCoverage,
|
|
18980
19138
|
selectionToPixels,
|
|
19139
|
+
selectionToSubpaths,
|
|
18981
19140
|
selectionValueAt,
|
|
18982
19141
|
serializeChannels,
|
|
18983
19142
|
serializeColorMode,
|
|
@@ -19035,6 +19194,7 @@ export {
|
|
|
19035
19194
|
stepSlotTs,
|
|
19036
19195
|
strokeGrainCatch,
|
|
19037
19196
|
styleAt,
|
|
19197
|
+
subpathsToSvgPath,
|
|
19038
19198
|
sumHistograms,
|
|
19039
19199
|
surfaceBytes,
|
|
19040
19200
|
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;
|
|
@@ -7,7 +7,7 @@ var _chunkZ64NRDEHcjs = require('../chunk-Z64NRDEH.cjs');
|
|
|
7
7
|
|
|
8
8
|
|
|
9
9
|
|
|
10
|
-
var
|
|
10
|
+
var _chunk2UR4OX3Bcjs = require('../chunk-2UR4OX3B.cjs');
|
|
11
11
|
require('../chunk-A2IS7EHE.cjs');
|
|
12
12
|
|
|
13
13
|
|
|
@@ -1653,11 +1653,11 @@ function header(state, options) {
|
|
|
1653
1653
|
return out;
|
|
1654
1654
|
}
|
|
1655
1655
|
function serializePaintDocument(state, options = {}) {
|
|
1656
|
-
const { layers, assets } =
|
|
1656
|
+
const { layers, assets } = _chunk2UR4OX3Bcjs.serializeLayers.call(void 0, state.layers);
|
|
1657
1657
|
return { ...header(state, options), layers, assets };
|
|
1658
1658
|
}
|
|
1659
1659
|
async function serializePaintDocumentExternal(state, store, options = {}) {
|
|
1660
|
-
const { layers, assets } = await
|
|
1660
|
+
const { layers, assets } = await _chunk2UR4OX3Bcjs.serializeLayersExternal.call(void 0, state.layers, store);
|
|
1661
1661
|
return { ...header(state, options), layers, assets };
|
|
1662
1662
|
}
|
|
1663
1663
|
function paintLayersOnly(layers, problems) {
|
|
@@ -1694,7 +1694,7 @@ function deserializePaintDocument(raw) {
|
|
|
1694
1694
|
const canvas = normalizeCanvas({ width: r.width, height: r.height, dpi: _nullishCoalesce(r.dpi, () => ( void 0)) });
|
|
1695
1695
|
const paper = _chunkYQ5C5BVKcjs.normalizePaper.call(void 0, _nullishCoalesce(r.paper, () => ( {})));
|
|
1696
1696
|
const assets = r.assets && typeof r.assets === "object" ? r.assets : {};
|
|
1697
|
-
const layers = paintLayersOnly(
|
|
1697
|
+
const layers = paintLayersOnly(_chunk2UR4OX3Bcjs.deserializeLayers.call(void 0, r.layers, { assets, problems }), problems);
|
|
1698
1698
|
const wanted = typeof r.activeLayerId === "string" ? r.activeLayerId : null;
|
|
1699
1699
|
const activeLayerId = wanted && _chunkHGTDMKNEcjs.findLayer.call(void 0, layers, wanted) ? wanted : _nullishCoalesce(_optionalChain([_chunkHGTDMKNEcjs.flattenLayers.call(void 0, layers), 'access', _70 => _70.filter, 'call', _71 => _71((l) => l.type === "pixel"), 'access', _72 => _72.pop, 'call', _73 => _73(), 'optionalAccess', _74 => _74.id]), () => ( null));
|
|
1700
1700
|
const wetEvents = Array.isArray(r.wetEvents) ? r.wetEvents.filter((e) => e && typeof e === "object" && Number.isInteger(e.clock) && typeof e.kind === "string") : [];
|
|
@@ -1710,7 +1710,7 @@ async function deserializePaintDocumentAsync(raw, store) {
|
|
|
1710
1710
|
if (!raw || typeof raw !== "object") throw new Error("Not a paint document: expected a JSON object.");
|
|
1711
1711
|
const r = raw;
|
|
1712
1712
|
const problems = [];
|
|
1713
|
-
const assets = await
|
|
1713
|
+
const assets = await _chunk2UR4OX3Bcjs.resolveExternalAssets.call(void 0, _nullishCoalesce(r.assets, () => ( {})), store, problems);
|
|
1714
1714
|
const loaded = deserializePaintDocument({ ...r, assets });
|
|
1715
1715
|
return { ...loaded, problems: [...problems, ...loaded.problems] };
|
|
1716
1716
|
}
|
|
@@ -5409,7 +5409,7 @@ var PaintEditorController = (_class10 = class _PaintEditorController {
|
|
|
5409
5409
|
return next.type === "group" ? { ...next, children: toStandard(next.children) } : next;
|
|
5410
5410
|
});
|
|
5411
5411
|
const layers = toStandard(dried);
|
|
5412
|
-
const result =
|
|
5412
|
+
const result = _chunk2UR4OX3Bcjs.exportPsd.call(void 0, { width, height, layers: [paperLayer, ...layers], merged, resolution: dpi });
|
|
5413
5413
|
for (const [mode, names] of changed) {
|
|
5414
5414
|
const label = _nullishCoalesce(_optionalChain([_chunkCWM3CUUXcjs.BLEND_MODES, 'access', _271 => _271.find, 'call', _272 => _272((m) => m.id === mode), 'optionalAccess', _273 => _273.label]), () => ( mode));
|
|
5415
5415
|
const std = _nullishCoalesce(_optionalChain([_chunkCWM3CUUXcjs.BLEND_MODES, 'access', _274 => _274.find, 'call', _275 => _275((m) => m.id === _chunkCWM3CUUXcjs.standardBlendMode.call(void 0, mode)), 'optionalAccess', _276 => _276.label]), () => ( _chunkCWM3CUUXcjs.standardBlendMode.call(void 0, mode)));
|