@pixerate/editor 0.205.0 → 0.207.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/image-editor/controller.d.cts +11 -0
- package/dist/image-editor/controller.d.ts +11 -0
- package/dist/image-editor/index.cjs +853 -320
- package/dist/image-editor/index.js +557 -24
- package/dist/image-editor/selection/index.d.cts +2 -0
- package/dist/image-editor/selection/index.d.ts +2 -0
- package/dist/image-editor/selection/refine-edits.d.cts +50 -0
- package/dist/image-editor/selection/refine-edits.d.ts +50 -0
- package/dist/image-editor/selection/refine.d.cts +6 -4
- package/dist/image-editor/selection/refine.d.ts +6 -4
- package/dist/image-editor/selection/select-and-mask.d.cts +126 -0
- package/dist/image-editor/selection/select-and-mask.d.ts +126 -0
- package/package.json +1 -1
- package/skills/pixerate-image-editor/SKILL.md +1 -1
- package/skills/pixerate-image-selection-masking/SKILL.md +6 -1
|
@@ -4015,10 +4015,10 @@ function edgeWidth(guide, w, h, r) {
|
|
|
4015
4015
|
for (let i = 0; i < n; i++) out[i] = (hi[i] - lo[i]) / Math.max(gm[i], 1e-4);
|
|
4016
4016
|
return out;
|
|
4017
4017
|
}
|
|
4018
|
-
function refineCoverage(guide, m, w, h, params = {}) {
|
|
4018
|
+
function refineCoverage(guide, m, w, h, params = {}, band = null) {
|
|
4019
4019
|
const p = normalizeRefineEdgeParams(params);
|
|
4020
4020
|
let a = Float32Array.from(m);
|
|
4021
|
-
if (p.radius > 0 && guide) {
|
|
4021
|
+
if ((p.radius > 0 || band) && guide) {
|
|
4022
4022
|
const n = w * h;
|
|
4023
4023
|
const boundary = new Uint8Array(n);
|
|
4024
4024
|
let any = false;
|
|
@@ -4033,14 +4033,15 @@ function refineCoverage(guide, m, w, h, params = {}) {
|
|
|
4033
4033
|
}
|
|
4034
4034
|
}
|
|
4035
4035
|
}
|
|
4036
|
-
if (any) {
|
|
4036
|
+
if (any || band) {
|
|
4037
4037
|
const dist = distanceTransform(boundary, w, h);
|
|
4038
|
-
const r = gfRadius(p);
|
|
4038
|
+
const r = band ? Math.max(4, gfRadius(p)) : gfRadius(p);
|
|
4039
4039
|
const q = guidedFilterColor(guide, m, w, h, r);
|
|
4040
4040
|
const widths = p.smartRadius ? edgeWidth(guide, w, h, r) : null;
|
|
4041
4041
|
for (let i = 0; i < n; i++) {
|
|
4042
4042
|
const reff = widths ? Math.min(Math.max(p.radius, 1.5), Math.max(1.5, widths[i])) : p.radius;
|
|
4043
|
-
const
|
|
4043
|
+
const edge = p.radius > 0 ? Math.min(1, Math.max(0, reff + 1 - dist[i])) : 0;
|
|
4044
|
+
const wgt = band ? Math.max(edge, Math.min(1, Math.max(0, band[i]))) : edge;
|
|
4044
4045
|
if (wgt > 0) {
|
|
4045
4046
|
const qi = Math.min(1, Math.max(0, q[i]));
|
|
4046
4047
|
a[i] = m[i] + (qi - m[i]) * wgt;
|
|
@@ -4081,17 +4082,24 @@ function guidePlanes(pixels, win) {
|
|
|
4081
4082
|
}
|
|
4082
4083
|
return [r, g, b];
|
|
4083
4084
|
}
|
|
4084
|
-
function refineSelection(pixels, mask, params = {}) {
|
|
4085
|
+
function refineSelection(pixels, mask, params = {}, refineBrush = null) {
|
|
4085
4086
|
const p = normalizeRefineEdgeParams(params);
|
|
4086
4087
|
if (pixels && (pixels.width !== mask.width || pixels.height !== mask.height)) {
|
|
4087
4088
|
throw new Error("Refine Edge needs pixels the size of the selection");
|
|
4088
4089
|
}
|
|
4089
4090
|
if (!mask.bounds) return mask;
|
|
4090
|
-
const
|
|
4091
|
-
const
|
|
4092
|
-
const
|
|
4093
|
-
|
|
4094
|
-
|
|
4091
|
+
const brush = refineBrush?.bounds ? refineBrush : null;
|
|
4092
|
+
const halo = refineEdgeHalo(p) + (brush ? 2 * Math.max(4, gfRadius(p)) : 0);
|
|
4093
|
+
const b = brush ? {
|
|
4094
|
+
x: Math.min(mask.bounds.x, brush.bounds.x),
|
|
4095
|
+
y: Math.min(mask.bounds.y, brush.bounds.y),
|
|
4096
|
+
r: Math.max(mask.bounds.x + mask.bounds.width, brush.bounds.x + brush.bounds.width),
|
|
4097
|
+
btm: Math.max(mask.bounds.y + mask.bounds.height, brush.bounds.y + brush.bounds.height)
|
|
4098
|
+
} : { x: mask.bounds.x, y: mask.bounds.y, r: mask.bounds.x + mask.bounds.width, btm: mask.bounds.y + mask.bounds.height };
|
|
4099
|
+
const x0 = Math.max(0, b.x - halo);
|
|
4100
|
+
const y0 = Math.max(0, b.y - halo);
|
|
4101
|
+
const x1 = Math.min(mask.width, b.r + halo);
|
|
4102
|
+
const y1 = Math.min(mask.height, b.btm + halo);
|
|
4095
4103
|
const ww = x1 - x0;
|
|
4096
4104
|
const wh = y1 - y0;
|
|
4097
4105
|
const m = new Float32Array(ww * wh);
|
|
@@ -4099,8 +4107,17 @@ function refineSelection(pixels, mask, params = {}) {
|
|
|
4099
4107
|
const src = (y0 + y) * mask.width + x0;
|
|
4100
4108
|
for (let x = 0; x < ww; x++) m[y * ww + x] = mask.data[src + x] / 255;
|
|
4101
4109
|
}
|
|
4102
|
-
const guide = pixels && p.radius > 0 ? guidePlanes(pixels, { x: x0, y: y0, width: ww, height: wh }) : null;
|
|
4103
|
-
|
|
4110
|
+
const guide = pixels && (p.radius > 0 || brush) ? guidePlanes(pixels, { x: x0, y: y0, width: ww, height: wh }) : null;
|
|
4111
|
+
let band = null;
|
|
4112
|
+
if (brush) {
|
|
4113
|
+
band = new Float32Array(ww * wh);
|
|
4114
|
+
const bd = brush.data;
|
|
4115
|
+
for (let y = 0; y < wh; y++) {
|
|
4116
|
+
const src = (y0 + y) * mask.width + x0;
|
|
4117
|
+
for (let x = 0; x < ww; x++) band[y * ww + x] = bd[src + x] / 255;
|
|
4118
|
+
}
|
|
4119
|
+
}
|
|
4120
|
+
const refined = refineCoverage(guide, m, ww, wh, p, band);
|
|
4104
4121
|
const out = new Uint8Array(mask.width * mask.height);
|
|
4105
4122
|
for (let y = 0; y < wh; y++) {
|
|
4106
4123
|
const dst = (y0 + y) * mask.width + x0;
|
|
@@ -5302,6 +5319,78 @@ function segmentsToSelection(width, height, results) {
|
|
|
5302
5319
|
return out;
|
|
5303
5320
|
}
|
|
5304
5321
|
|
|
5322
|
+
// src/image-editor/selection/refine-edits.ts
|
|
5323
|
+
var REFINE_EDIT_KINDS = ["quick-select", "brush", "refine-brush", "lasso", "object"];
|
|
5324
|
+
function brushStrokeMask(width, height, points, size, hardness = 1) {
|
|
5325
|
+
const r = Math.max(0.5, size / 2);
|
|
5326
|
+
const solid = r * Math.min(1, Math.max(0, hardness));
|
|
5327
|
+
let x0 = Infinity, y0 = Infinity, x1 = -Infinity, y1 = -Infinity;
|
|
5328
|
+
for (const p of points) {
|
|
5329
|
+
x0 = Math.min(x0, p.x);
|
|
5330
|
+
y0 = Math.min(y0, p.y);
|
|
5331
|
+
x1 = Math.max(x1, p.x);
|
|
5332
|
+
y1 = Math.max(y1, p.y);
|
|
5333
|
+
}
|
|
5334
|
+
const bx = Math.max(0, Math.floor(x0 - r - 1));
|
|
5335
|
+
const by = Math.max(0, Math.floor(y0 - r - 1));
|
|
5336
|
+
const bw = Math.min(width, Math.ceil(x1 + r + 1)) - bx;
|
|
5337
|
+
const bh = Math.min(height, Math.ceil(y1 + r + 1)) - by;
|
|
5338
|
+
if (!(bw > 0 && bh > 0)) return createSelectionMask(width, height);
|
|
5339
|
+
const out = new Uint8Array(bw * bh);
|
|
5340
|
+
const segs = points.length === 1 ? [[points[0], points[0]]] : points.slice(1).map((p, i) => [points[i], p]);
|
|
5341
|
+
for (let y = 0; y < bh; y++) {
|
|
5342
|
+
for (let x = 0; x < bw; x++) {
|
|
5343
|
+
const px2 = bx + x + 0.5;
|
|
5344
|
+
const py = by + y + 0.5;
|
|
5345
|
+
let d = Infinity;
|
|
5346
|
+
for (const [a, b] of segs) {
|
|
5347
|
+
const dx = b.x - a.x;
|
|
5348
|
+
const dy = b.y - a.y;
|
|
5349
|
+
const len2 = dx * dx + dy * dy;
|
|
5350
|
+
const t = len2 > 0 ? Math.min(1, Math.max(0, ((px2 - a.x) * dx + (py - a.y) * dy) / len2)) : 0;
|
|
5351
|
+
d = Math.min(d, Math.hypot(px2 - a.x - t * dx, py - a.y - t * dy));
|
|
5352
|
+
}
|
|
5353
|
+
let v = d <= solid ? 1 : solid >= r ? 0 : 1 - (d - solid) / (r - solid);
|
|
5354
|
+
v = Math.min(v, Math.min(1, Math.max(0, r + 0.5 - d)));
|
|
5355
|
+
out[y * bw + x] = Math.round(Math.max(0, v) * 255);
|
|
5356
|
+
}
|
|
5357
|
+
}
|
|
5358
|
+
return selectionFromRegion(width, height, { x: bx, y: by, width: bw, height: bh }, out);
|
|
5359
|
+
}
|
|
5360
|
+
function applyRefineEdits(pixels, selection, edits, width, height) {
|
|
5361
|
+
let sel = selection?.bounds ? selection : null;
|
|
5362
|
+
let band = null;
|
|
5363
|
+
for (const e of edits) {
|
|
5364
|
+
if (e.kind === "object") {
|
|
5365
|
+
if (!pixels) throw new Error("Object Selection needs the image pixels.");
|
|
5366
|
+
if (!e.rect === !e.point) throw new Error("An object edit takes rect (a box around the object) or point (on it), not both.");
|
|
5367
|
+
const region2 = selectObjectIn(pixels, e.rect ? { rect: e.rect } : { point: e.point });
|
|
5368
|
+
sel = combineSelections(sel, region2, e.mode ?? "add");
|
|
5369
|
+
continue;
|
|
5370
|
+
}
|
|
5371
|
+
if (!e.points.length) throw new Error(`A ${e.kind} stroke needs at least one point.`);
|
|
5372
|
+
if (e.kind === "lasso") {
|
|
5373
|
+
if (e.points.length < 3) throw new Error("A lasso edit needs at least 3 points.");
|
|
5374
|
+
sel = combineSelections(sel, polygonSelection(width, height, e.points, {}), e.mode ?? "add");
|
|
5375
|
+
continue;
|
|
5376
|
+
}
|
|
5377
|
+
if (e.kind === "refine-brush") {
|
|
5378
|
+
band = combineSelections(band, brushStrokeMask(width, height, e.points, e.size ?? 40), "add");
|
|
5379
|
+
continue;
|
|
5380
|
+
}
|
|
5381
|
+
const mode = e.mode ?? "add";
|
|
5382
|
+
let region;
|
|
5383
|
+
if (e.kind === "quick-select") {
|
|
5384
|
+
if (!pixels) throw new Error("Quick Selection strokes need the image pixels.");
|
|
5385
|
+
region = quickSelectStroke(pixels, e.points, { size: e.size, selection: sel, grow: mode === "add" });
|
|
5386
|
+
} else {
|
|
5387
|
+
region = brushStrokeMask(width, height, e.points, e.size ?? 20, e.hardness ?? 1);
|
|
5388
|
+
}
|
|
5389
|
+
sel = combineSelections(sel, region, mode);
|
|
5390
|
+
}
|
|
5391
|
+
return { selection: sel, refineBrush: band };
|
|
5392
|
+
}
|
|
5393
|
+
|
|
5305
5394
|
// src/image-editor/selection/stroke.ts
|
|
5306
5395
|
function selectionRing(sel, width, location) {
|
|
5307
5396
|
const half = location === "center" ? width / 2 : width;
|
|
@@ -10332,10 +10421,19 @@ var ImageEditorController = class {
|
|
|
10332
10421
|
* - `new-layer-with-mask`: a copy of the layer masked by the selection, the source hidden.
|
|
10333
10422
|
* `decontaminate` (0..100, new-layer outputs only) replaces the old background's colour in the
|
|
10334
10423
|
* soft edge with the subject's own (see `decontaminateColors`).
|
|
10424
|
+
*
|
|
10425
|
+
* `edits` are the workspace's strokes, applied first in order (Quick Selection, Brush and
|
|
10426
|
+
* Refine Edge Brush; see `RefineEdit`); with them, an empty selection is a valid start.
|
|
10427
|
+
* `refineMode: 'object'` asks the segmentation provider for the object in the selection's
|
|
10428
|
+
* bounds and refines from its outline, kept to near the selection's edge.
|
|
10335
10429
|
*/
|
|
10336
10430
|
async refineSelectionTo(params, output = "selection", options = {}) {
|
|
10337
|
-
const
|
|
10338
|
-
|
|
10431
|
+
const edits = options.edits ?? [];
|
|
10432
|
+
const refineMode = options.refineMode ?? "color";
|
|
10433
|
+
if (!this.state.selection?.bounds && !edits.length) throw new Error("Nothing is selected; select first, or pass edits that paint a selection.");
|
|
10434
|
+
if (refineMode === "object" && !this.segmentation) {
|
|
10435
|
+
throw new Error("Object aware refinement needs a segmentation model (setSegmentationProvider); use refineMode color.");
|
|
10436
|
+
}
|
|
10339
10437
|
const amount = Math.min(100, Math.max(0, options.decontaminate ?? 0)) / 100;
|
|
10340
10438
|
const toLayer = output === "new-layer" || output === "new-layer-with-mask";
|
|
10341
10439
|
if (amount > 0 && !toLayer) throw new Error("Decontaminate colors writes the cleaned colours to a new layer: use output new-layer or new-layer-with-mask.");
|
|
@@ -10346,16 +10444,30 @@ var ImageEditorController = class {
|
|
|
10346
10444
|
const layer = requireLayer(this.state.layers, layerId).layer;
|
|
10347
10445
|
if (toLayer && layer.type !== "pixel") throw new LayerOperationError(`Output ${output} needs a pixel layer; "${layerId}" is a ${layer.type} layer.`);
|
|
10348
10446
|
}
|
|
10349
|
-
const
|
|
10350
|
-
const
|
|
10447
|
+
const needsPixels = (params.radius ?? 0) > 0 || edits.some((e) => e.kind !== "brush");
|
|
10448
|
+
const pixels = needsPixels ? await this.requirePixels(options) : null;
|
|
10449
|
+
const { width, height } = this.naturalSize;
|
|
10450
|
+
const edited = applyRefineEdits(pixels, this.state.selection ?? null, edits, width, height);
|
|
10451
|
+
let sel = edited.selection;
|
|
10452
|
+
if (!sel?.bounds) throw new Error("The edits leave nothing selected; paint with mode add first.");
|
|
10453
|
+
if (refineMode === "object") {
|
|
10454
|
+
const b = sel.bounds;
|
|
10455
|
+
const found = await this.modelObject({ rect: b }, options.signal);
|
|
10456
|
+
if (!found) throw new Error("The segmentation model found no object in the selection; use refineMode color.");
|
|
10457
|
+
const near = modifySelection(sel, "expand", Math.max(4, params.radius ?? 8));
|
|
10458
|
+
const clipped = combineSelections(found, near, "intersect");
|
|
10459
|
+
if (!clipped?.bounds) throw new Error("The segmentation model found no object in the selection; use refineMode color.");
|
|
10460
|
+
sel = clipped;
|
|
10461
|
+
}
|
|
10462
|
+
const refined = refineSelection(pixels, sel, params, edited.refineBrush);
|
|
10351
10463
|
if (!refined.bounds) throw new Error("Refining left nothing selected; use a smaller shiftEdge or contrast.");
|
|
10352
10464
|
return this.asOneStep(() => {
|
|
10353
10465
|
this.setSelection(refined);
|
|
10354
|
-
if (output === "selection") return { output };
|
|
10466
|
+
if (output === "selection") return { output, refineMode };
|
|
10355
10467
|
if (output === "layer-mask") {
|
|
10356
10468
|
this.addLayerMask(layerId, "selection");
|
|
10357
10469
|
this.setSelection(null);
|
|
10358
|
-
return { output, layerId };
|
|
10470
|
+
return { output, layerId, refineMode };
|
|
10359
10471
|
}
|
|
10360
10472
|
let newLayerId2;
|
|
10361
10473
|
if (output === "new-layer") newLayerId2 = this.layerViaCopy({ layerId });
|
|
@@ -10367,7 +10479,7 @@ var ImageEditorController = class {
|
|
|
10367
10479
|
if (amount > 0) this.decontaminateLayer(newLayerId2, layerId, refined, amount, Math.max(4, params.radius ?? 0));
|
|
10368
10480
|
this.setLayerProperties(layerId, { visible: false });
|
|
10369
10481
|
this.setSelection(null);
|
|
10370
|
-
return { output, layerId, newLayerId: newLayerId2 };
|
|
10482
|
+
return { output, layerId, newLayerId: newLayerId2, refineMode };
|
|
10371
10483
|
});
|
|
10372
10484
|
}
|
|
10373
10485
|
/**
|
|
@@ -13586,7 +13698,7 @@ function createImageSelectionTools(controller) {
|
|
|
13586
13698
|
const refine = {
|
|
13587
13699
|
name: "refine_selection",
|
|
13588
13700
|
title: "Refine the selection edge",
|
|
13589
|
-
description: "Select and Mask / Refine Edge: snaps the selection edge to the image within radius pixels (edge-aware guided filter), then smooth (0-100), feather (px), contrast (0-100) and shiftEdge (-100..100 %), and puts the result where output says: the selection (default), a layer-mask on layerId (default the active layer; then deselects), a new-layer of its selected pixels, or a new-layer-with-mask (a masked copy); the new-layer outputs hide the source layer. decontaminate (0-100, new-layer outputs only) replaces the old background colour in soft edges (hair, fur) with the subject's own. Use it after loading a coarse AI mask, or to cut a subject out cleanly. One undoable step. Returns the selection and, for layer outputs, layerId and newLayerId.",
|
|
13701
|
+
description: "Select and Mask / Refine Edge: snaps the selection edge to the image within radius pixels (edge-aware guided filter), then smooth (0-100), feather (px), contrast (0-100) and shiftEdge (-100..100 %), and puts the result where output says: the selection (default), a layer-mask on layerId (default the active layer; then deselects), a new-layer of its selected pixels, or a new-layer-with-mask (a masked copy); the new-layer outputs hide the source layer. decontaminate (0-100, new-layer outputs only) replaces the old background colour in soft edges (hair, fur) with the subject's own. Use it after loading a coarse AI mask, or to cut a subject out cleanly. edits are the Select and Mask workspace's strokes, applied first in order: quick-select (grows to the region's edges), brush (paints the selection in or out) and refine-brush (marks hair or fur for the edge to be re-estimated there); with edits, an empty selection is a valid start. refineMode object (needs a host segmentation model) refines from the model's outline of the object. One undoable step. Returns the selection, refineMode and, for layer outputs, layerId and newLayerId.",
|
|
13590
13702
|
inputSchema: {
|
|
13591
13703
|
type: "object",
|
|
13592
13704
|
properties: {
|
|
@@ -13602,13 +13714,52 @@ function createImageSelectionTools(controller) {
|
|
|
13602
13714
|
description: "Where the result goes (default selection). layer-mask keeps every pixel and is easiest to revise; new-layer-with-mask too, on a copy."
|
|
13603
13715
|
},
|
|
13604
13716
|
layerId: { type: "string", description: "The layer for layer outputs (default the active layer; see list_layers). New-layer outputs need a pixel layer." },
|
|
13605
|
-
decontaminate: { type: "number", minimum: 0, maximum: 100, description: "Decontaminate Colors amount 0-100 (default 0), only with output new-layer or new-layer-with-mask." }
|
|
13717
|
+
decontaminate: { type: "number", minimum: 0, maximum: 100, description: "Decontaminate Colors amount 0-100 (default 0), only with output new-layer or new-layer-with-mask." },
|
|
13718
|
+
edits: {
|
|
13719
|
+
type: "array",
|
|
13720
|
+
description: "Strokes applied before refining, in order.",
|
|
13721
|
+
items: {
|
|
13722
|
+
type: "object",
|
|
13723
|
+
properties: {
|
|
13724
|
+
kind: {
|
|
13725
|
+
type: "string",
|
|
13726
|
+
enum: ["quick-select", "brush", "refine-brush", "lasso", "object"],
|
|
13727
|
+
description: "quick-select grows to edges; brush paints directly; refine-brush marks soft detail (hair) for the edge to be re-estimated; lasso adds or removes a polygon; object adds or removes the object in rect or under point."
|
|
13728
|
+
},
|
|
13729
|
+
points: { type: "array", minItems: 1, items: pointSchema, description: "The stroke path (or lasso outline, 3+ points) in natural image pixels; one point is a dab. Not for object." },
|
|
13730
|
+
rect: {
|
|
13731
|
+
type: "object",
|
|
13732
|
+
properties: rectProperties,
|
|
13733
|
+
required: ["x", "y", "width", "height"],
|
|
13734
|
+
additionalProperties: false,
|
|
13735
|
+
description: "object: a box around the object, in natural image pixels."
|
|
13736
|
+
},
|
|
13737
|
+
point: { ...pointSchema, description: "object: a point on the object, in natural image pixels." },
|
|
13738
|
+
size: { type: "number", minimum: 1, maximum: 2500, description: "Brush diameter in natural image pixels (default 30 quick-select, 20 brush, 40 refine-brush)." },
|
|
13739
|
+
mode: { type: "string", enum: ["add", "subtract"], description: "quick-select, brush, lasso and object: add (default) or subtract." },
|
|
13740
|
+
hardness: { type: "number", minimum: 0, maximum: 1, description: "brush: the solid share of the radius, 0..1 (default 1)." }
|
|
13741
|
+
},
|
|
13742
|
+
required: ["kind"],
|
|
13743
|
+
additionalProperties: false
|
|
13744
|
+
}
|
|
13745
|
+
},
|
|
13746
|
+
refineMode: { type: "string", enum: ["color", "object"], description: "color (default): refine from the image colours; object: from a host segmentation model's outline of the object (needs a model)." }
|
|
13606
13747
|
},
|
|
13607
13748
|
additionalProperties: false
|
|
13608
13749
|
},
|
|
13609
13750
|
annotations: writeAnnotations("Refine the selection edge"),
|
|
13610
13751
|
handler: async (params) => {
|
|
13611
|
-
|
|
13752
|
+
const edits = Array.isArray(params.edits) ? params.edits.map((e) => e.kind === "object" ? { ...e } : { ...e, points: (e.points ?? []).map((p) => ({ x: num2(p?.x, NaN), y: num2(p?.y, NaN) })) }) : [];
|
|
13753
|
+
for (const e of edits) {
|
|
13754
|
+
if (e.kind === "object") {
|
|
13755
|
+
if (!e.rect === !e.point) throw new Error("An object edit takes rect (a box around the object) or point (on it).");
|
|
13756
|
+
continue;
|
|
13757
|
+
}
|
|
13758
|
+
if (!e.points.length) throw new Error(`A ${e.kind} edit needs points.`);
|
|
13759
|
+
if (!e.points.every((p) => Number.isFinite(p.x) && Number.isFinite(p.y))) throw new Error("Each edit needs points of { x, y } in natural image pixels.");
|
|
13760
|
+
if (e.kind === "refine-brush" && e.mode !== void 0) throw new Error("refine-brush strokes have no mode; they mark where the edge is re-estimated.");
|
|
13761
|
+
}
|
|
13762
|
+
if (!controller.getSelection() && !edits.length) throw new Error("Nothing is selected.");
|
|
13612
13763
|
const result = await controller.refineSelectionTo(
|
|
13613
13764
|
{
|
|
13614
13765
|
radius: num2(params.radius, 8),
|
|
@@ -13619,7 +13770,12 @@ function createImageSelectionTools(controller) {
|
|
|
13619
13770
|
shiftEdge: num2(params.shiftEdge, 0)
|
|
13620
13771
|
},
|
|
13621
13772
|
params.output ?? "selection",
|
|
13622
|
-
{
|
|
13773
|
+
{
|
|
13774
|
+
layerId: typeof params.layerId === "string" ? params.layerId : void 0,
|
|
13775
|
+
decontaminate: num2(params.decontaminate, 0),
|
|
13776
|
+
edits,
|
|
13777
|
+
refineMode: params.refineMode === "object" ? "object" : "color"
|
|
13778
|
+
}
|
|
13623
13779
|
);
|
|
13624
13780
|
return { ...summary(controller), ...result };
|
|
13625
13781
|
}
|
|
@@ -19000,6 +19156,374 @@ function downloadMaskImage(controller, source, name = "mask") {
|
|
|
19000
19156
|
return { fileName, width, height };
|
|
19001
19157
|
}
|
|
19002
19158
|
|
|
19159
|
+
// src/image-editor/selection/select-and-mask.ts
|
|
19160
|
+
var SELECT_AND_MASK_VIEWS = [
|
|
19161
|
+
{ id: "onion-skin", label: "Onion Skin", key: "o", opacity: 0.5 },
|
|
19162
|
+
{ id: "marching-ants", label: "Marching Ants", key: "m", opacity: 1 },
|
|
19163
|
+
{ id: "overlay", label: "Overlay", key: "v", opacity: 0.5 },
|
|
19164
|
+
{ id: "on-black", label: "On Black", key: "a", opacity: 1 },
|
|
19165
|
+
{ id: "on-white", label: "On White", key: "t", opacity: 1 },
|
|
19166
|
+
{ id: "black-white", label: "Black & White", key: "k", opacity: 1 },
|
|
19167
|
+
{ id: "on-layers", label: "On Layers", key: "y", opacity: 1 }
|
|
19168
|
+
];
|
|
19169
|
+
var SELECT_AND_MASK_TOOLS = [
|
|
19170
|
+
{ id: "quick-select", label: "Quick Selection", key: "w" },
|
|
19171
|
+
{ id: "refine-brush", label: "Refine Edge Brush", key: "r" },
|
|
19172
|
+
{ id: "brush", label: "Brush", key: "b" },
|
|
19173
|
+
{ id: "object", label: "Object Selection", key: "q" },
|
|
19174
|
+
{ id: "lasso", label: "Lasso", key: "l" },
|
|
19175
|
+
{ id: "hand", label: "Hand", key: "h" },
|
|
19176
|
+
{ id: "zoom", label: "Zoom", key: "z" }
|
|
19177
|
+
];
|
|
19178
|
+
var DEFAULT_SELECT_AND_MASK_SETTINGS = {
|
|
19179
|
+
params: { ...DEFAULT_REFINE_EDGE_PARAMS },
|
|
19180
|
+
output: "selection",
|
|
19181
|
+
decontaminate: 0,
|
|
19182
|
+
refineMode: "color",
|
|
19183
|
+
view: "onion-skin",
|
|
19184
|
+
opacity: 0.5,
|
|
19185
|
+
showEdge: false,
|
|
19186
|
+
showOriginal: false,
|
|
19187
|
+
realTime: true,
|
|
19188
|
+
highQuality: false,
|
|
19189
|
+
remember: false,
|
|
19190
|
+
tool: "quick-select",
|
|
19191
|
+
brushSize: 30,
|
|
19192
|
+
brushMode: "add"
|
|
19193
|
+
};
|
|
19194
|
+
var PREVIEW_SIZE = 1024;
|
|
19195
|
+
var remembered = null;
|
|
19196
|
+
function scalePixels(pixels, w, h) {
|
|
19197
|
+
if (w === pixels.width && h === pixels.height) return pixels;
|
|
19198
|
+
const { width: W, height: H, data } = pixels;
|
|
19199
|
+
const sum = new Float64Array(w * h * 4);
|
|
19200
|
+
const count = new Float64Array(w * h);
|
|
19201
|
+
for (let y = 0; y < H; y++) {
|
|
19202
|
+
const row = Math.min(h - 1, Math.floor(y * h / H)) * w;
|
|
19203
|
+
for (let x = 0; x < W; x++) {
|
|
19204
|
+
const o = row + Math.min(w - 1, Math.floor(x * w / W));
|
|
19205
|
+
const p = (y * W + x) * 4;
|
|
19206
|
+
for (let c = 0; c < 4; c++) sum[o * 4 + c] += data[p + c];
|
|
19207
|
+
count[o]++;
|
|
19208
|
+
}
|
|
19209
|
+
}
|
|
19210
|
+
const out = new Uint8ClampedArray(w * h * 4);
|
|
19211
|
+
for (let i = 0; i < w * h; i++) for (let c = 0; c < 4; c++) out[i * 4 + c] = sum[i * 4 + c] / (count[i] || 1);
|
|
19212
|
+
return { width: w, height: h, data: out };
|
|
19213
|
+
}
|
|
19214
|
+
function scaleEdit(edit, s) {
|
|
19215
|
+
const pt = (p) => ({ x: p.x * s, y: p.y * s });
|
|
19216
|
+
if (edit.kind === "object") {
|
|
19217
|
+
return { ...edit, rect: edit.rect && { x: edit.rect.x * s, y: edit.rect.y * s, width: edit.rect.width * s, height: edit.rect.height * s }, point: edit.point && pt(edit.point) };
|
|
19218
|
+
}
|
|
19219
|
+
const scaled = { ...edit, points: edit.points.map(pt) };
|
|
19220
|
+
if ("size" in edit && edit.size !== void 0) scaled.size = Math.max(1, edit.size * s);
|
|
19221
|
+
return scaled;
|
|
19222
|
+
}
|
|
19223
|
+
function scaleParams(p, s) {
|
|
19224
|
+
return { ...p, radius: p.radius * s, feather: p.feather * s };
|
|
19225
|
+
}
|
|
19226
|
+
var SelectAndMaskSession = class {
|
|
19227
|
+
constructor(controller) {
|
|
19228
|
+
this.controller = controller;
|
|
19229
|
+
this.base = controller.getSelection();
|
|
19230
|
+
this.settingsValue = remembered ? { ...remembered, params: { ...remembered.params }, showOriginal: false } : { ...DEFAULT_SELECT_AND_MASK_SETTINGS, params: { ...DEFAULT_SELECT_AND_MASK_SETTINGS.params } };
|
|
19231
|
+
}
|
|
19232
|
+
controller;
|
|
19233
|
+
settingsValue;
|
|
19234
|
+
editsList = [];
|
|
19235
|
+
/** The edit being painted, shown in the preview but not yet in `edits`. */
|
|
19236
|
+
live = null;
|
|
19237
|
+
dragStart = null;
|
|
19238
|
+
pixels = null;
|
|
19239
|
+
previewPixels = null;
|
|
19240
|
+
base;
|
|
19241
|
+
previewMask = null;
|
|
19242
|
+
baseMask = null;
|
|
19243
|
+
listeners = /* @__PURE__ */ new Set();
|
|
19244
|
+
/** Why the preview could not be made (no pixels), for the UI. */
|
|
19245
|
+
error = null;
|
|
19246
|
+
/** A note for the UI, such as object aware refinement applying only on OK. */
|
|
19247
|
+
note = null;
|
|
19248
|
+
/** Bumped each time the preview changes, so a UI can redraw. */
|
|
19249
|
+
revision = 0;
|
|
19250
|
+
get settings() {
|
|
19251
|
+
return this.settingsValue;
|
|
19252
|
+
}
|
|
19253
|
+
get edits() {
|
|
19254
|
+
return this.editsList;
|
|
19255
|
+
}
|
|
19256
|
+
/** Reads the image and makes the first preview. Resolves to false when the pixels are unavailable. */
|
|
19257
|
+
async open() {
|
|
19258
|
+
try {
|
|
19259
|
+
this.pixels = await this.controller.getSamplePixels({});
|
|
19260
|
+
} catch (err) {
|
|
19261
|
+
this.error = err instanceof Error ? err.message : String(err);
|
|
19262
|
+
}
|
|
19263
|
+
if (!this.pixels) {
|
|
19264
|
+
this.error ??= "The image pixels are not available, so Select and Mask cannot preview.";
|
|
19265
|
+
this.emit();
|
|
19266
|
+
return false;
|
|
19267
|
+
}
|
|
19268
|
+
this.rescale();
|
|
19269
|
+
this.recompute();
|
|
19270
|
+
return true;
|
|
19271
|
+
}
|
|
19272
|
+
subscribe(listener) {
|
|
19273
|
+
this.listeners.add(listener);
|
|
19274
|
+
return () => this.listeners.delete(listener);
|
|
19275
|
+
}
|
|
19276
|
+
emit() {
|
|
19277
|
+
this.revision++;
|
|
19278
|
+
for (const l of this.listeners) l();
|
|
19279
|
+
}
|
|
19280
|
+
/** Preview size and the scale from natural pixels to it. */
|
|
19281
|
+
get previewSize() {
|
|
19282
|
+
const p = this.previewPixels;
|
|
19283
|
+
if (!p || !this.pixels) return { width: 0, height: 0, scale: 1 };
|
|
19284
|
+
return { width: p.width, height: p.height, scale: p.width / this.pixels.width };
|
|
19285
|
+
}
|
|
19286
|
+
/** Changes settings; the preview follows. Choosing a view also sets its usual opacity. */
|
|
19287
|
+
update(changes) {
|
|
19288
|
+
const next = { ...this.settingsValue, ...changes, params: { ...this.settingsValue.params, ...changes.params ?? {} } };
|
|
19289
|
+
if (changes.view && changes.view !== this.settingsValue.view && changes.opacity === void 0) {
|
|
19290
|
+
next.opacity = SELECT_AND_MASK_VIEWS.find((v) => v.id === changes.view).opacity;
|
|
19291
|
+
}
|
|
19292
|
+
const rescale = next.highQuality !== this.settingsValue.highQuality;
|
|
19293
|
+
this.settingsValue = next;
|
|
19294
|
+
if (rescale) this.rescale();
|
|
19295
|
+
this.recompute();
|
|
19296
|
+
}
|
|
19297
|
+
rescale() {
|
|
19298
|
+
const px2 = this.pixels;
|
|
19299
|
+
if (!px2) return;
|
|
19300
|
+
const s = this.settingsValue.highQuality ? 1 : Math.min(1, PREVIEW_SIZE / Math.max(px2.width, px2.height));
|
|
19301
|
+
const w = Math.max(1, Math.round(px2.width * s));
|
|
19302
|
+
const h = Math.max(1, Math.round(px2.height * s));
|
|
19303
|
+
this.previewPixels = scalePixels(px2, w, h);
|
|
19304
|
+
this.baseMask = this.base?.bounds ? s === 1 ? this.base : resizeSelection(this.base, w, h) : null;
|
|
19305
|
+
}
|
|
19306
|
+
/** Recomputes the preview mask from the base selection, the strokes and the settings. */
|
|
19307
|
+
recompute() {
|
|
19308
|
+
const px2 = this.previewPixels;
|
|
19309
|
+
if (!px2) return;
|
|
19310
|
+
const { scale } = this.previewSize;
|
|
19311
|
+
const edits = [...this.editsList, ...this.live ? [this.live] : []].map((e) => scaleEdit(e, scale));
|
|
19312
|
+
this.note = this.settingsValue.refineMode === "object" ? "Object aware refinement asks the segmentation model on OK; the preview refines by colour." : null;
|
|
19313
|
+
try {
|
|
19314
|
+
const { selection, refineBrush } = applyRefineEdits(px2, this.baseMask, edits, px2.width, px2.height);
|
|
19315
|
+
this.previewMask = selection?.bounds ? refineSelection(px2, selection, scaleParams(this.settingsValue.params, scale), refineBrush) : null;
|
|
19316
|
+
this.error = null;
|
|
19317
|
+
} catch (err) {
|
|
19318
|
+
this.previewMask = null;
|
|
19319
|
+
this.error = err instanceof Error ? err.message : String(err);
|
|
19320
|
+
}
|
|
19321
|
+
this.emit();
|
|
19322
|
+
}
|
|
19323
|
+
/** Coverage 0..1 of the preview at (x, y) in preview pixels (for tests and hover readouts). */
|
|
19324
|
+
coverageAt(x, y) {
|
|
19325
|
+
const m = this.settingsValue.showOriginal ? this.baseMask : this.previewMask;
|
|
19326
|
+
if (!m) return 0;
|
|
19327
|
+
const xi = Math.floor(x);
|
|
19328
|
+
const yi = Math.floor(y);
|
|
19329
|
+
if (xi < 0 || yi < 0 || xi >= m.width || yi >= m.height) return 0;
|
|
19330
|
+
return m.data[yi * m.width + xi] / 255;
|
|
19331
|
+
}
|
|
19332
|
+
/** The preview drawn in the current view mode: RGBA at {@link previewSize}. */
|
|
19333
|
+
render() {
|
|
19334
|
+
const px2 = this.previewPixels;
|
|
19335
|
+
if (!px2) return new Uint8ClampedArray(0);
|
|
19336
|
+
const { width: w, height: h } = px2;
|
|
19337
|
+
const n = w * h;
|
|
19338
|
+
const s = this.settingsValue;
|
|
19339
|
+
const mask = (s.showOriginal ? this.baseMask : this.previewMask) ?? createSelectionMask(w, h);
|
|
19340
|
+
const m = mask.data;
|
|
19341
|
+
const out = new Uint8ClampedArray(n * 4);
|
|
19342
|
+
const k = Math.min(1, Math.max(0, s.opacity));
|
|
19343
|
+
let edgeBand = null;
|
|
19344
|
+
if (s.showEdge) {
|
|
19345
|
+
edgeBand = new Uint8Array(n);
|
|
19346
|
+
const r = Math.max(1, Math.round(Math.max(s.params.radius, 2) * this.previewSize.scale));
|
|
19347
|
+
for (let y = 0; y < h; y++) {
|
|
19348
|
+
for (let x = 0; x < w; x++) {
|
|
19349
|
+
const v = m[y * w + x];
|
|
19350
|
+
if (v <= 3 || v >= 252) continue;
|
|
19351
|
+
for (let j = Math.max(0, y - r); j <= Math.min(h - 1, y + r); j++) edgeBand.fill(1, j * w + Math.max(0, x - r), j * w + Math.min(w, x + r + 1));
|
|
19352
|
+
}
|
|
19353
|
+
}
|
|
19354
|
+
}
|
|
19355
|
+
for (let i = 0; i < n; i++) {
|
|
19356
|
+
const a = m[i] / 255;
|
|
19357
|
+
const p = i * 4;
|
|
19358
|
+
const r = px2.data[p];
|
|
19359
|
+
const g = px2.data[p + 1];
|
|
19360
|
+
const b = px2.data[p + 2];
|
|
19361
|
+
let o;
|
|
19362
|
+
const x = i % w;
|
|
19363
|
+
const y = (i - x) / w;
|
|
19364
|
+
const checker = ((x >> 3) + (y >> 3)) % 2 ? 204 : 255;
|
|
19365
|
+
const hide = (1 - a) * k;
|
|
19366
|
+
switch (s.view) {
|
|
19367
|
+
case "black-white":
|
|
19368
|
+
o = [m[i], m[i], m[i]];
|
|
19369
|
+
break;
|
|
19370
|
+
case "overlay":
|
|
19371
|
+
o = [r + (255 - r) * hide, g * (1 - hide), b * (1 - hide)];
|
|
19372
|
+
break;
|
|
19373
|
+
case "on-black":
|
|
19374
|
+
o = [r * (1 - hide), g * (1 - hide), b * (1 - hide)];
|
|
19375
|
+
break;
|
|
19376
|
+
case "on-white":
|
|
19377
|
+
o = [r + (255 - r) * hide, g + (255 - g) * hide, b + (255 - b) * hide];
|
|
19378
|
+
break;
|
|
19379
|
+
case "marching-ants": {
|
|
19380
|
+
o = [r, g, b];
|
|
19381
|
+
const inside = m[i] >= 128;
|
|
19382
|
+
const edge = x > 0 && m[i - 1] >= 128 !== inside || x + 1 < w && m[i + 1] >= 128 !== inside || y > 0 && m[i - w] >= 128 !== inside || y + 1 < h && m[i + w] >= 128 !== inside;
|
|
19383
|
+
if (edge) {
|
|
19384
|
+
const v = (x + y >> 2) % 2 ? 0 : 255;
|
|
19385
|
+
o = [v, v, v];
|
|
19386
|
+
}
|
|
19387
|
+
break;
|
|
19388
|
+
}
|
|
19389
|
+
default:
|
|
19390
|
+
o = [r + (checker - r) * hide, g + (checker - g) * hide, b + (checker - b) * hide];
|
|
19391
|
+
}
|
|
19392
|
+
if (edgeBand && !edgeBand[i]) o = [o[0] * 0.15, o[1] * 0.15, o[2] * 0.15];
|
|
19393
|
+
out[p] = o[0];
|
|
19394
|
+
out[p + 1] = o[1];
|
|
19395
|
+
out[p + 2] = o[2];
|
|
19396
|
+
out[p + 3] = 255;
|
|
19397
|
+
}
|
|
19398
|
+
return out;
|
|
19399
|
+
}
|
|
19400
|
+
/**
|
|
19401
|
+
* Pointer down at `point` (natural pixels) with the current tool: starts a stroke (Quick
|
|
19402
|
+
* Selection, brushes, lasso) or a box (Object Selection). Alt (or the subtract mode) removes.
|
|
19403
|
+
* Hand and Zoom are the UI's own; they return false here.
|
|
19404
|
+
*/
|
|
19405
|
+
begin(point, modifiers = {}) {
|
|
19406
|
+
const s = this.settingsValue;
|
|
19407
|
+
const mode = modifiers.altKey ? s.brushMode === "add" ? "subtract" : "add" : s.brushMode;
|
|
19408
|
+
this.dragStart = point;
|
|
19409
|
+
switch (s.tool) {
|
|
19410
|
+
case "quick-select":
|
|
19411
|
+
this.live = { kind: "quick-select", points: [point], size: s.brushSize, mode };
|
|
19412
|
+
break;
|
|
19413
|
+
case "brush":
|
|
19414
|
+
this.live = { kind: "brush", points: [point], size: s.brushSize, mode };
|
|
19415
|
+
break;
|
|
19416
|
+
case "refine-brush":
|
|
19417
|
+
this.live = { kind: "refine-brush", points: [point], size: s.brushSize };
|
|
19418
|
+
break;
|
|
19419
|
+
case "lasso":
|
|
19420
|
+
this.live = { kind: "lasso", points: [point], mode };
|
|
19421
|
+
break;
|
|
19422
|
+
case "object":
|
|
19423
|
+
this.live = { kind: "object", point, mode };
|
|
19424
|
+
break;
|
|
19425
|
+
default:
|
|
19426
|
+
this.dragStart = null;
|
|
19427
|
+
return false;
|
|
19428
|
+
}
|
|
19429
|
+
if (s.realTime && s.tool !== "lasso" && s.tool !== "object") this.recompute();
|
|
19430
|
+
return true;
|
|
19431
|
+
}
|
|
19432
|
+
/** Pointer move while a stroke is down. */
|
|
19433
|
+
move(point) {
|
|
19434
|
+
const live = this.live;
|
|
19435
|
+
if (!live || !this.dragStart) return;
|
|
19436
|
+
if (live.kind === "object") {
|
|
19437
|
+
const a = this.dragStart;
|
|
19438
|
+
const rect = { x: Math.min(a.x, point.x), y: Math.min(a.y, point.y), width: Math.abs(point.x - a.x), height: Math.abs(point.y - a.y) };
|
|
19439
|
+
this.live = rect.width >= 4 && rect.height >= 4 ? { kind: "object", rect, mode: live.mode } : { kind: "object", point: a, mode: live.mode };
|
|
19440
|
+
return;
|
|
19441
|
+
}
|
|
19442
|
+
const last = live.points[live.points.length - 1];
|
|
19443
|
+
if (Math.hypot(point.x - last.x, point.y - last.y) < 1) return;
|
|
19444
|
+
live.points.push(point);
|
|
19445
|
+
if (this.settingsValue.realTime && live.kind !== "lasso") this.recompute();
|
|
19446
|
+
}
|
|
19447
|
+
/** Pointer up: the stroke becomes an edit (one step of the workspace's own undo). */
|
|
19448
|
+
end() {
|
|
19449
|
+
const live = this.live;
|
|
19450
|
+
this.live = null;
|
|
19451
|
+
this.dragStart = null;
|
|
19452
|
+
if (!live) return;
|
|
19453
|
+
if (live.kind === "lasso" && live.points.length < 3) return;
|
|
19454
|
+
this.editsList.push(live);
|
|
19455
|
+
this.recompute();
|
|
19456
|
+
}
|
|
19457
|
+
/** Removes the last stroke (Ctrl/⌘+Z in the workspace). Returns false when there is none. */
|
|
19458
|
+
undoEdit() {
|
|
19459
|
+
if (!this.editsList.length) return false;
|
|
19460
|
+
this.editsList.pop();
|
|
19461
|
+
this.recompute();
|
|
19462
|
+
return true;
|
|
19463
|
+
}
|
|
19464
|
+
/** Clears the strokes and goes back to the default settings (Reset). */
|
|
19465
|
+
reset() {
|
|
19466
|
+
this.editsList.length = 0;
|
|
19467
|
+
this.settingsValue = { ...DEFAULT_SELECT_AND_MASK_SETTINGS, params: { ...DEFAULT_SELECT_AND_MASK_SETTINGS.params }, tool: this.settingsValue.tool };
|
|
19468
|
+
this.rescale();
|
|
19469
|
+
this.recompute();
|
|
19470
|
+
}
|
|
19471
|
+
/**
|
|
19472
|
+
* Photoshop's keys in the workspace: a view letter, F (next view), X (all views off and on:
|
|
19473
|
+
* Show Original), J (Show Edge), P (Show Original), a tool letter, [ and ] (brush size).
|
|
19474
|
+
* Returns true when the key was used.
|
|
19475
|
+
*/
|
|
19476
|
+
handleKey(key) {
|
|
19477
|
+
const k = key.toLowerCase();
|
|
19478
|
+
const view = SELECT_AND_MASK_VIEWS.find((v) => v.key === k);
|
|
19479
|
+
if (view) {
|
|
19480
|
+
this.update({ view: view.id });
|
|
19481
|
+
return true;
|
|
19482
|
+
}
|
|
19483
|
+
const tool = SELECT_AND_MASK_TOOLS.find((t) => t.key === k);
|
|
19484
|
+
if (tool) {
|
|
19485
|
+
this.update({ tool: tool.id });
|
|
19486
|
+
return true;
|
|
19487
|
+
}
|
|
19488
|
+
if (k === "f") {
|
|
19489
|
+
const i = SELECT_AND_MASK_VIEWS.findIndex((v) => v.id === this.settingsValue.view);
|
|
19490
|
+
this.update({ view: SELECT_AND_MASK_VIEWS[(i + 1) % SELECT_AND_MASK_VIEWS.length].id });
|
|
19491
|
+
return true;
|
|
19492
|
+
}
|
|
19493
|
+
if (k === "p" || k === "x") {
|
|
19494
|
+
this.update({ showOriginal: !this.settingsValue.showOriginal });
|
|
19495
|
+
return true;
|
|
19496
|
+
}
|
|
19497
|
+
if (k === "j") {
|
|
19498
|
+
this.update({ showEdge: !this.settingsValue.showEdge });
|
|
19499
|
+
return true;
|
|
19500
|
+
}
|
|
19501
|
+
if (k === "[" || k === "]") {
|
|
19502
|
+
const size = this.settingsValue.brushSize;
|
|
19503
|
+
this.settingsValue = { ...this.settingsValue, brushSize: Math.max(1, Math.round(k === "]" ? size * 1.25 : size / 1.25)) };
|
|
19504
|
+
this.emit();
|
|
19505
|
+
return true;
|
|
19506
|
+
}
|
|
19507
|
+
return false;
|
|
19508
|
+
}
|
|
19509
|
+
/**
|
|
19510
|
+
* OK: applies the strokes and settings to the image as one undo step (the same call an agent
|
|
19511
|
+
* makes) and remembers the settings when asked to.
|
|
19512
|
+
*/
|
|
19513
|
+
async commit() {
|
|
19514
|
+
const s = this.settingsValue;
|
|
19515
|
+
remembered = s.remember ? { ...s, params: { ...s.params } } : null;
|
|
19516
|
+
return this.controller.refineSelectionTo(s.params, s.output, {
|
|
19517
|
+
edits: [...this.editsList],
|
|
19518
|
+
decontaminate: s.output === "new-layer" || s.output === "new-layer-with-mask" ? s.decontaminate : 0,
|
|
19519
|
+
refineMode: s.refineMode
|
|
19520
|
+
});
|
|
19521
|
+
}
|
|
19522
|
+
};
|
|
19523
|
+
function forgetSelectAndMaskSettings() {
|
|
19524
|
+
remembered = null;
|
|
19525
|
+
}
|
|
19526
|
+
|
|
19003
19527
|
// src/image-editor/mosaic-filter.ts
|
|
19004
19528
|
function resolveMosaicRegionBounds(region, imgWidth, imgHeight) {
|
|
19005
19529
|
if (imgWidth <= 0 || imgHeight <= 0 || region.width <= 0 || region.height <= 0) {
|
|
@@ -20699,6 +21223,7 @@ export {
|
|
|
20699
21223
|
DEFAULT_REFINE_EDGE_PARAMS,
|
|
20700
21224
|
DEFAULT_RETOUCH,
|
|
20701
21225
|
DEFAULT_SELECTION_TOOL_OPTIONS,
|
|
21226
|
+
DEFAULT_SELECT_AND_MASK_SETTINGS,
|
|
20702
21227
|
DEFAULT_SHAPE_TOOL_OPTIONS,
|
|
20703
21228
|
DEFAULT_SMUDGE,
|
|
20704
21229
|
DEFAULT_TEXT_STYLE,
|
|
@@ -20816,6 +21341,7 @@ export {
|
|
|
20816
21341
|
PIGMENT_WAVELENGTHS,
|
|
20817
21342
|
PLANE,
|
|
20818
21343
|
PLANE_BYTES,
|
|
21344
|
+
PREVIEW_SIZE,
|
|
20819
21345
|
PROOF_PROFILE_OPTIONS,
|
|
20820
21346
|
PSD_BLEND_KEYS,
|
|
20821
21347
|
PSD_FILE_ACCEPT,
|
|
@@ -20840,6 +21366,7 @@ export {
|
|
|
20840
21366
|
PsdError,
|
|
20841
21367
|
QUICK_MASK_TARGET,
|
|
20842
21368
|
REFINE_EDGE_CONTROLS,
|
|
21369
|
+
REFINE_EDIT_KINDS,
|
|
20843
21370
|
REFINE_OUTPUTS,
|
|
20844
21371
|
RETOUCH_CHOICES,
|
|
20845
21372
|
RETOUCH_OPTION_CONTROLS,
|
|
@@ -20852,6 +21379,8 @@ export {
|
|
|
20852
21379
|
SELECTION_MODE_LABELS,
|
|
20853
21380
|
SELECTION_MODIFY_OPS,
|
|
20854
21381
|
SELECTION_TOOLS,
|
|
21382
|
+
SELECT_AND_MASK_TOOLS,
|
|
21383
|
+
SELECT_AND_MASK_VIEWS,
|
|
20855
21384
|
SET_PLANES,
|
|
20856
21385
|
SHADOW_AMBIENT,
|
|
20857
21386
|
SHAPE_KIND_LABELS,
|
|
@@ -20865,6 +21394,7 @@ export {
|
|
|
20865
21394
|
SRGB_TO_LINEAR,
|
|
20866
21395
|
STRIP,
|
|
20867
21396
|
SUPPORTED_ADJUSTMENT_KEYS,
|
|
21397
|
+
SelectAndMaskSession,
|
|
20868
21398
|
SelectionGesture,
|
|
20869
21399
|
ShapeDrawGesture,
|
|
20870
21400
|
SmudgeTip,
|
|
@@ -20952,6 +21482,7 @@ export {
|
|
|
20952
21482
|
applyParams,
|
|
20953
21483
|
applyProcess2Adjustments,
|
|
20954
21484
|
applyProcess2Float,
|
|
21485
|
+
applyRefineEdits,
|
|
20955
21486
|
applyRegionBlur,
|
|
20956
21487
|
applyRegionMosaic,
|
|
20957
21488
|
applyRetouchToTile,
|
|
@@ -20984,6 +21515,7 @@ export {
|
|
|
20984
21515
|
boxQuad,
|
|
20985
21516
|
boxToCurvePoint,
|
|
20986
21517
|
bradfordAdaptation,
|
|
21518
|
+
brushStrokeMask,
|
|
20987
21519
|
brushstrokeDataUrl,
|
|
20988
21520
|
brushstrokeKey,
|
|
20989
21521
|
brushstrokeSamples,
|
|
@@ -21184,6 +21716,7 @@ export {
|
|
|
21184
21716
|
focusArea,
|
|
21185
21717
|
followLayerMove,
|
|
21186
21718
|
fontOf,
|
|
21719
|
+
forgetSelectAndMaskSettings,
|
|
21187
21720
|
formatControlValue,
|
|
21188
21721
|
fullSelection,
|
|
21189
21722
|
gaussianBlur,
|