@pixerate/editor 0.218.1 → 0.219.1

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.
@@ -5,7 +5,7 @@
5
5
 
6
6
 
7
7
 
8
- var _chunkMQDIP4CFcjs = require('./chunk-MQDIP4CF.cjs');
8
+ var _chunkBB6TR422cjs = require('./chunk-BB6TR422.cjs');
9
9
 
10
10
 
11
11
 
@@ -465,7 +465,7 @@ function toneOf(tool, settings) {
465
465
  }
466
466
  function healPixels(w, h, src, dst, mask) {
467
467
  const out = new Float32Array(dst);
468
- const bb = _chunkMQDIP4CFcjs.maskBounds.call(void 0, w, h, mask);
468
+ const bb = _chunkBB6TR422cjs.maskBounds.call(void 0, w, h, mask);
469
469
  if (!bb) return out;
470
470
  const x0 = Math.max(0, bb[0] - 1);
471
471
  const y0 = Math.max(0, bb[1] - 1);
@@ -487,7 +487,7 @@ function healPixels(w, h, src, dst, mask) {
487
487
  }
488
488
  }
489
489
  }
490
- const healed = _chunkMQDIP4CFcjs.seamlessClone.call(void 0, rw, rh, 4, s, d, m);
490
+ const healed = _chunkBB6TR422cjs.seamlessClone.call(void 0, rw, rh, 4, s, d, m);
491
491
  for (let y = 0; y < rh; y++) {
492
492
  for (let x = 0; x < rw; x++) {
493
493
  const gi = ((y + y0) * w + x + x0) * 4;
@@ -498,13 +498,13 @@ function healPixels(w, h, src, dst, mask) {
498
498
  return out;
499
499
  }
500
500
  function spotHealPixels(w, h, img, hole, size, type, onOffset) {
501
- const domain = _chunkMQDIP4CFcjs.dilateMask.call(void 0, w, h, hole, 2);
502
- const healMask = _chunkMQDIP4CFcjs.dilateMask.call(void 0, w, h, hole, 1);
501
+ const domain = _chunkBB6TR422cjs.dilateMask.call(void 0, w, h, hole, 2);
502
+ const healMask = _chunkBB6TR422cjs.dilateMask.call(void 0, w, h, hole, 1);
503
503
  let filled = null;
504
504
  let bestFound = null;
505
505
  if (type === "proximity-match") {
506
506
  const ring = Math.min(16, Math.max(3, Math.floor(size / 8)));
507
- const off = _chunkMQDIP4CFcjs.bestOffset.call(void 0, w, h, 4, img, domain, ring, Math.floor(Math.max(w, h) / 2));
507
+ const off = _chunkBB6TR422cjs.bestOffset.call(void 0, w, h, 4, img, domain, ring, Math.floor(Math.max(w, h) / 2));
508
508
  if (off) {
509
509
  bestFound = off;
510
510
  filled = new Float32Array(img);
@@ -519,8 +519,8 @@ function spotHealPixels(w, h, img, hole, size, type, onOffset) {
519
519
  }
520
520
  }
521
521
  _optionalChain([onOffset, 'optionalCall', _ => _(filled ? bestFound : null)]);
522
- if (!filled && type === "content-aware") filled = _chunkMQDIP4CFcjs.contentAwareFill.call(void 0, w, h, 4, img, domain);
523
- if (!filled) filled = _chunkMQDIP4CFcjs.membraneFill.call(void 0, w, h, 4, img, domain);
522
+ if (!filled && type === "content-aware") filled = _chunkBB6TR422cjs.contentAwareFill.call(void 0, w, h, 4, img, domain);
523
+ if (!filled) filled = _chunkBB6TR422cjs.membraneFill.call(void 0, w, h, 4, img, domain);
524
524
  return healPixels(w, h, filled, img, healMask);
525
525
  }
526
526
  function spotHealWindow(bounds, canvas) {
@@ -10,7 +10,7 @@ import {
10
10
  } from "./chunk-ZNA3IQZF.js";
11
11
  import {
12
12
  contentAwareFill
13
- } from "./chunk-XI2NV5U7.js";
13
+ } from "./chunk-VWEWY4FY.js";
14
14
 
15
15
  // src/image-editor/selection/shapes.ts
16
16
  function polygonCoverage(points, width, height, antiAlias = true) {
@@ -808,12 +808,15 @@ function selectObjectIn(pixels, target, options = {}) {
808
808
  const px = Math.min(w - 1, Math.max(0, Math.floor(target.point.x * scale)));
809
809
  const py = Math.min(h - 1, Math.max(0, Math.floor(target.point.y * scale)));
810
810
  let half = Math.max(24, Math.round(0.3 * Math.max(w, h)));
811
+ let seed = null;
812
+ let reseeds = 0;
811
813
  for (; ; ) {
812
814
  const rx = Math.max(0, px - half);
813
815
  const ry = Math.max(0, py - half);
814
816
  const roi = { x: rx, y: ry, width: Math.min(w, px + half + 1) - rx, height: Math.min(h, py + half + 1) - ry };
815
817
  const whole = roi.width === w && roi.height === h;
816
818
  const band = Math.max(4, Math.round(0.15 * half));
819
+ const radius = seed ?? 0.3 * half;
817
820
  const n = roi.width * roi.height;
818
821
  const initial = new Uint8Array(n);
819
822
  const hardBg = new Uint8Array(n);
@@ -822,9 +825,10 @@ function selectObjectIn(pixels, target, options = {}) {
822
825
  for (let y = 0; y < roi.height; y++) {
823
826
  for (let x = 0; x < roi.width; x++) {
824
827
  const p = y * roi.width + x;
828
+ const d = Math.hypot(rx + x - px, ry + y - py);
825
829
  if (inBand(x, y)) hardBg[p] = 1;
826
- else initial[p] = 1;
827
- if (Math.hypot(rx + x - px, ry + y - py) <= 1.5) hardFg[p] = 1;
830
+ else if (d <= radius) initial[p] = 1;
831
+ if (d <= 1.5) hardFg[p] = 1;
828
832
  }
829
833
  }
830
834
  const label = grabCutLabels(work, { roi, initial, hardBg, hardFg }, iterations, round);
@@ -838,6 +842,19 @@ function selectObjectIn(pixels, target, options = {}) {
838
842
  }
839
843
  }
840
844
  }
845
+ if (!touches && reseeds < 2) {
846
+ let found = 0;
847
+ let seeded = 0;
848
+ for (let p = 0; p < n; p++) {
849
+ found += label[p];
850
+ seeded += initial[p];
851
+ }
852
+ if (found * 2 < seeded) {
853
+ reseeds++;
854
+ seed = Math.max(2, 0.7 * Math.sqrt(found / Math.PI));
855
+ continue;
856
+ }
857
+ }
841
858
  if (!touches || whole) {
842
859
  report?.(0.9, "Scaling up");
843
860
  const out = upscaleRegion(work, pixels, roi, label, enhance);
@@ -845,6 +862,7 @@ function selectObjectIn(pixels, target, options = {}) {
845
862
  return out;
846
863
  }
847
864
  half = Math.round(half * 1.6);
865
+ seed = null;
848
866
  }
849
867
  }
850
868
 
@@ -1574,7 +1592,11 @@ function runSelectionJob(job, pixels, onProgress) {
1574
1592
  };
1575
1593
  case "fill": {
1576
1594
  onProgress?.(0, "Filling");
1577
- const filled = contentAwareFill(pixels.width, pixels.height, 4, pixels.data, job.hole, { seed: job.seed, exclude: job.exclude });
1595
+ const filled = contentAwareFill(pixels.width, pixels.height, 4, pixels.data, job.hole, {
1596
+ seed: job.seed,
1597
+ exclude: job.exclude,
1598
+ onProgress: (f) => onProgress?.(f * 0.99, "Filling")
1599
+ });
1578
1600
  onProgress?.(1, "Done");
1579
1601
  return { mask: createSelectionMask(1, 1), pixels: Uint8ClampedArray.from(filled) };
1580
1602
  }
@@ -5,7 +5,7 @@ import {
5
5
  maskBounds,
6
6
  membraneFill,
7
7
  seamlessClone
8
- } from "./chunk-XI2NV5U7.js";
8
+ } from "./chunk-VWEWY4FY.js";
9
9
  import {
10
10
  DabWalker,
11
11
  SmudgeTip,
@@ -1,4 +1,4 @@
1
- "use strict";Object.defineProperty(exports, "__esModule", {value: true}); function _nullishCoalesce(lhs, rhsFn) { if (lhs != null) { return lhs; } else { return rhsFn(); } }// src/paint/heal.ts
1
+ "use strict";Object.defineProperty(exports, "__esModule", {value: true}); function _nullishCoalesce(lhs, rhsFn) { if (lhs != null) { return lhs; } else { return rhsFn(); } } function _optionalChain(ops) { let lastAccessLHS = undefined; let value = ops[0]; let i = 1; while (i < ops.length) { const op = ops[i]; const fn = ops[i + 1]; i += 2; if ((op === 'optionalAccess' || op === 'optionalCall') && value == null) { return undefined; } if (op === 'access' || op === 'optionalAccess') { lastAccessLHS = value; value = fn(value); } else if (op === 'call' || op === 'optionalCall') { value = fn((...args) => value.call(lastAccessLHS, ...args)); lastAccessLHS = undefined; } } return value; }// src/paint/heal.ts
2
2
  var TOL = 2e-3;
3
3
  var FINE_ITERS = 30;
4
4
  var FINE_OMEGA = 1.7;
@@ -311,6 +311,8 @@ function contentAwareFill(w, h, ch, img, hole, options = {}) {
311
311
  levels.push(downsample(top, ch));
312
312
  }
313
313
  const random = rng(_nullishCoalesce(options.seed, () => ( w * 73856093 + h * 19349663)));
314
+ const totalWork = levels.reduce((n, l) => n + l.w * l.h, 0) * iters || 1;
315
+ let doneWork = 0;
314
316
  let prevFill = null;
315
317
  let prevNnf = null;
316
318
  let prevW = 0;
@@ -441,6 +443,8 @@ function contentAwareFill(w, h, ch, img, hole, options = {}) {
441
443
  }
442
444
  }
443
445
  for (let q = 0; q < W * H; q++) if (holeL[q] && wsum[q] > 0) for (let c = 0; c < ch; c++) cur[q * ch + c] = acc[q * ch + c] / wsum[q];
446
+ doneWork += W * H;
447
+ _optionalChain([options, 'access', _ => _.onProgress, 'optionalCall', _2 => _2(Math.min(1, doneWork / totalWork))]);
444
448
  }
445
449
  prevFill = cur;
446
450
  prevNnf = nnf;
@@ -10,7 +10,7 @@
10
10
  var _chunkSS53NNZOcjs = require('./chunk-SS53NNZO.cjs');
11
11
 
12
12
 
13
- var _chunkMQDIP4CFcjs = require('./chunk-MQDIP4CF.cjs');
13
+ var _chunkBB6TR422cjs = require('./chunk-BB6TR422.cjs');
14
14
 
15
15
  // src/image-editor/selection/shapes.ts
16
16
  function polygonCoverage(points, width, height, antiAlias = true) {
@@ -808,12 +808,15 @@ function selectObjectIn(pixels, target, options = {}) {
808
808
  const px = Math.min(w - 1, Math.max(0, Math.floor(target.point.x * scale)));
809
809
  const py = Math.min(h - 1, Math.max(0, Math.floor(target.point.y * scale)));
810
810
  let half = Math.max(24, Math.round(0.3 * Math.max(w, h)));
811
+ let seed = null;
812
+ let reseeds = 0;
811
813
  for (; ; ) {
812
814
  const rx = Math.max(0, px - half);
813
815
  const ry = Math.max(0, py - half);
814
816
  const roi = { x: rx, y: ry, width: Math.min(w, px + half + 1) - rx, height: Math.min(h, py + half + 1) - ry };
815
817
  const whole = roi.width === w && roi.height === h;
816
818
  const band = Math.max(4, Math.round(0.15 * half));
819
+ const radius = _nullishCoalesce(seed, () => ( 0.3 * half));
817
820
  const n = roi.width * roi.height;
818
821
  const initial = new Uint8Array(n);
819
822
  const hardBg = new Uint8Array(n);
@@ -822,9 +825,10 @@ function selectObjectIn(pixels, target, options = {}) {
822
825
  for (let y = 0; y < roi.height; y++) {
823
826
  for (let x = 0; x < roi.width; x++) {
824
827
  const p = y * roi.width + x;
828
+ const d = Math.hypot(rx + x - px, ry + y - py);
825
829
  if (inBand(x, y)) hardBg[p] = 1;
826
- else initial[p] = 1;
827
- if (Math.hypot(rx + x - px, ry + y - py) <= 1.5) hardFg[p] = 1;
830
+ else if (d <= radius) initial[p] = 1;
831
+ if (d <= 1.5) hardFg[p] = 1;
828
832
  }
829
833
  }
830
834
  const label = grabCutLabels(work, { roi, initial, hardBg, hardFg }, iterations, round);
@@ -838,6 +842,19 @@ function selectObjectIn(pixels, target, options = {}) {
838
842
  }
839
843
  }
840
844
  }
845
+ if (!touches && reseeds < 2) {
846
+ let found = 0;
847
+ let seeded = 0;
848
+ for (let p = 0; p < n; p++) {
849
+ found += label[p];
850
+ seeded += initial[p];
851
+ }
852
+ if (found * 2 < seeded) {
853
+ reseeds++;
854
+ seed = Math.max(2, 0.7 * Math.sqrt(found / Math.PI));
855
+ continue;
856
+ }
857
+ }
841
858
  if (!touches || whole) {
842
859
  _optionalChain([report, 'optionalCall', _10 => _10(0.9, "Scaling up")]);
843
860
  const out = upscaleRegion(work, pixels, roi, label, enhance);
@@ -845,6 +862,7 @@ function selectObjectIn(pixels, target, options = {}) {
845
862
  return out;
846
863
  }
847
864
  half = Math.round(half * 1.6);
865
+ seed = null;
848
866
  }
849
867
  }
850
868
 
@@ -1574,16 +1592,20 @@ function runSelectionJob(job, pixels, onProgress) {
1574
1592
  };
1575
1593
  case "fill": {
1576
1594
  _optionalChain([onProgress, 'optionalCall', _32 => _32(0, "Filling")]);
1577
- const filled = _chunkMQDIP4CFcjs.contentAwareFill.call(void 0, pixels.width, pixels.height, 4, pixels.data, job.hole, { seed: job.seed, exclude: job.exclude });
1578
- _optionalChain([onProgress, 'optionalCall', _33 => _33(1, "Done")]);
1595
+ const filled = _chunkBB6TR422cjs.contentAwareFill.call(void 0, pixels.width, pixels.height, 4, pixels.data, job.hole, {
1596
+ seed: job.seed,
1597
+ exclude: job.exclude,
1598
+ onProgress: (f) => _optionalChain([onProgress, 'optionalCall', _33 => _33(f * 0.99, "Filling")])
1599
+ });
1600
+ _optionalChain([onProgress, 'optionalCall', _34 => _34(1, "Done")]);
1579
1601
  return { mask: _chunkSS53NNZOcjs.createSelectionMask.call(void 0, 1, 1), pixels: Uint8ClampedArray.from(filled) };
1580
1602
  }
1581
1603
  case "refine-preview": {
1582
- _optionalChain([onProgress, 'optionalCall', _34 => _34(0, "Refining")]);
1604
+ _optionalChain([onProgress, 'optionalCall', _35 => _35(0, "Refining")]);
1583
1605
  const base = job.base ? deserializeMask(job.base) : null;
1584
1606
  const { selection, refineBrush } = applyRefineEdits(pixels, base, job.edits, pixels.width, pixels.height);
1585
- const mask = _optionalChain([selection, 'optionalAccess', _35 => _35.bounds]) ? _chunkSS53NNZOcjs.refineSelection.call(void 0, pixels, selection, job.params, refineBrush) : _chunkSS53NNZOcjs.createSelectionMask.call(void 0, pixels.width, pixels.height);
1586
- _optionalChain([onProgress, 'optionalCall', _36 => _36(1, "Done")]);
1607
+ const mask = _optionalChain([selection, 'optionalAccess', _36 => _36.bounds]) ? _chunkSS53NNZOcjs.refineSelection.call(void 0, pixels, selection, job.params, refineBrush) : _chunkSS53NNZOcjs.createSelectionMask.call(void 0, pixels.width, pixels.height);
1608
+ _optionalChain([onProgress, 'optionalCall', _37 => _37(1, "Done")]);
1587
1609
  return { mask };
1588
1610
  }
1589
1611
  }
@@ -1624,7 +1646,7 @@ var SelectionWorkerClient = (_class2 = class {
1624
1646
  __init9() {this.pending = /* @__PURE__ */ new Map()}
1625
1647
  /** Runs `job` on `pixels` in the worker. Aborting `signal` terminates the worker and rejects with an `AbortError`. */
1626
1648
  run(job, pixels, options = {}) {
1627
- if (_optionalChain([options, 'access', _37 => _37.signal, 'optionalAccess', _38 => _38.aborted])) return Promise.reject(abortError());
1649
+ if (_optionalChain([options, 'access', _38 => _38.signal, 'optionalAccess', _39 => _39.aborted])) return Promise.reject(abortError());
1628
1650
  const worker = this.ensure();
1629
1651
  let key = this.sent.get(pixels.data);
1630
1652
  if (key === void 0) {
@@ -1635,7 +1657,7 @@ var SelectionWorkerClient = (_class2 = class {
1635
1657
  const id = this.nextId++;
1636
1658
  return new Promise((resolve, reject) => {
1637
1659
  this.pending.set(id, { resolve, reject, onProgress: options.onProgress });
1638
- _optionalChain([options, 'access', _39 => _39.signal, 'optionalAccess', _40 => _40.addEventListener, 'call', _41 => _41(
1660
+ _optionalChain([options, 'access', _40 => _40.signal, 'optionalAccess', _41 => _41.addEventListener, 'call', _42 => _42(
1639
1661
  "abort",
1640
1662
  () => {
1641
1663
  if (this.pending.has(id)) this.terminate(abortError());
@@ -1647,7 +1669,7 @@ var SelectionWorkerClient = (_class2 = class {
1647
1669
  }
1648
1670
  /** Stops the worker; jobs still running reject with `reason` (default an `AbortError`). */
1649
1671
  terminate(reason = abortError()) {
1650
- _optionalChain([this, 'access', _42 => _42.worker, 'optionalAccess', _43 => _43.terminate, 'optionalCall', _44 => _44()]);
1672
+ _optionalChain([this, 'access', _43 => _43.worker, 'optionalAccess', _44 => _44.terminate, 'optionalCall', _45 => _45()]);
1651
1673
  this.worker = null;
1652
1674
  this.sent = /* @__PURE__ */ new WeakMap();
1653
1675
  const pending = [...this.pending.values()];
@@ -1661,14 +1683,14 @@ var SelectionWorkerClient = (_class2 = class {
1661
1683
  const msg = event.data;
1662
1684
  const p = this.pending.get(msg.id);
1663
1685
  if (!p) return;
1664
- if (msg.type === "progress") _optionalChain([p, 'access', _45 => _45.onProgress, 'optionalCall', _46 => _46(msg.fraction, msg.stage)]);
1686
+ if (msg.type === "progress") _optionalChain([p, 'access', _46 => _46.onProgress, 'optionalCall', _47 => _47(msg.fraction, msg.stage)]);
1665
1687
  else {
1666
1688
  this.pending.delete(msg.id);
1667
1689
  if (msg.type === "error") p.reject(new Error(msg.message));
1668
1690
  else p.resolve({ mask: deserializeMask(msg.mask), reason: msg.reason, noiseLevel: msg.noiseLevel, pixels: msg.pixels });
1669
1691
  }
1670
1692
  };
1671
- worker.onerror = (event) => this.terminate(new Error(_optionalChain([event, 'optionalAccess', _47 => _47.message]) || "The selection worker failed."));
1693
+ worker.onerror = (event) => this.terminate(new Error(_optionalChain([event, 'optionalAccess', _48 => _48.message]) || "The selection worker failed."));
1672
1694
  this.worker = worker;
1673
1695
  return worker;
1674
1696
  }
@@ -311,6 +311,8 @@ function contentAwareFill(w, h, ch, img, hole, options = {}) {
311
311
  levels.push(downsample(top, ch));
312
312
  }
313
313
  const random = rng(options.seed ?? w * 73856093 + h * 19349663);
314
+ const totalWork = levels.reduce((n, l) => n + l.w * l.h, 0) * iters || 1;
315
+ let doneWork = 0;
314
316
  let prevFill = null;
315
317
  let prevNnf = null;
316
318
  let prevW = 0;
@@ -441,6 +443,8 @@ function contentAwareFill(w, h, ch, img, hole, options = {}) {
441
443
  }
442
444
  }
443
445
  for (let q = 0; q < W * H; q++) if (holeL[q] && wsum[q] > 0) for (let c = 0; c < ch; c++) cur[q * ch + c] = acc[q * ch + c] / wsum[q];
446
+ doneWork += W * H;
447
+ options.onProgress?.(Math.min(1, doneWork / totalWork));
444
448
  }
445
449
  prevFill = cur;
446
450
  prevNnf = nnf;
@@ -285,6 +285,20 @@ export interface CloneSource {
285
285
  /** A vector mask's path: several `components`, or one `shape` / `subpaths` (with `op`). */
286
286
  /** What `inspectMaskEdge` reads: the selection, a layer's painted mask or its vector mask. */
287
287
  export type MaskEdgeTarget = 'selection' | 'layer-mask' | 'vector-mask';
288
+ /** What `separateSubject` made. */
289
+ export interface SeparateSubjectResult {
290
+ /** The subject: a copy of the layer masked by the selection, on top. */
291
+ foregroundLayerId: string;
292
+ /** The background plate: a copy with the subject's area filled. */
293
+ plateLayerId: string;
294
+ /** The layer it came from (now hidden). */
295
+ sourceLayerId: string;
296
+ /** An alpha channel of the filled area, to select and fill again where the fill is wrong. */
297
+ fillChannelId: string;
298
+ filledBounds: CropBox;
299
+ dilate: number;
300
+ fill: 'content-aware' | 'none';
301
+ }
288
302
  /** A selection `compareSelections` reads (see `selectionFrom`). */
289
303
  export type SelectionSource = {
290
304
  kind: 'selection';
@@ -1617,6 +1631,23 @@ export declare class ImageEditorController {
1617
1631
  bounds: CropBox;
1618
1632
  output: ContentAwareFillOutput;
1619
1633
  }>;
1634
+ /**
1635
+ * Separate Subject: splits the selected subject from its background in one undo step. The
1636
+ * subject goes to a copy of the layer masked by the selection (on top, made active); a second
1637
+ * copy becomes the background plate with the subject's area, grown by `dilate` px (default 8),
1638
+ * Content-Aware Filled (`fill: 'none'` leaves it transparent); an alpha channel marks the filled
1639
+ * area so a bad patch can be selected and filled again; the source layer is hidden. The fill is
1640
+ * computed first, so `signal` cancels it with nothing changed. 8-bit pixel layers only. Returns
1641
+ * the new layers, the channel and the filled bounds.
1642
+ */
1643
+ separateSubject(options?: {
1644
+ layerId?: string;
1645
+ dilate?: number;
1646
+ fill?: 'content-aware' | 'none';
1647
+ seed?: number;
1648
+ signal?: AbortSignal;
1649
+ onProgress?: SelectionProgress;
1650
+ }): Promise<SeparateSubjectResult>;
1620
1651
  /**
1621
1652
  * The fill without changing anything: the window, its pixels before and after (RGBA, the fill
1622
1653
  * blended by the selection's coverage) and the sampling area used, for a preview.
@@ -285,6 +285,20 @@ export interface CloneSource {
285
285
  /** A vector mask's path: several `components`, or one `shape` / `subpaths` (with `op`). */
286
286
  /** What `inspectMaskEdge` reads: the selection, a layer's painted mask or its vector mask. */
287
287
  export type MaskEdgeTarget = 'selection' | 'layer-mask' | 'vector-mask';
288
+ /** What `separateSubject` made. */
289
+ export interface SeparateSubjectResult {
290
+ /** The subject: a copy of the layer masked by the selection, on top. */
291
+ foregroundLayerId: string;
292
+ /** The background plate: a copy with the subject's area filled. */
293
+ plateLayerId: string;
294
+ /** The layer it came from (now hidden). */
295
+ sourceLayerId: string;
296
+ /** An alpha channel of the filled area, to select and fill again where the fill is wrong. */
297
+ fillChannelId: string;
298
+ filledBounds: CropBox;
299
+ dilate: number;
300
+ fill: 'content-aware' | 'none';
301
+ }
288
302
  /** A selection `compareSelections` reads (see `selectionFrom`). */
289
303
  export type SelectionSource = {
290
304
  kind: 'selection';
@@ -1617,6 +1631,23 @@ export declare class ImageEditorController {
1617
1631
  bounds: CropBox;
1618
1632
  output: ContentAwareFillOutput;
1619
1633
  }>;
1634
+ /**
1635
+ * Separate Subject: splits the selected subject from its background in one undo step. The
1636
+ * subject goes to a copy of the layer masked by the selection (on top, made active); a second
1637
+ * copy becomes the background plate with the subject's area, grown by `dilate` px (default 8),
1638
+ * Content-Aware Filled (`fill: 'none'` leaves it transparent); an alpha channel marks the filled
1639
+ * area so a bad patch can be selected and filled again; the source layer is hidden. The fill is
1640
+ * computed first, so `signal` cancels it with nothing changed. 8-bit pixel layers only. Returns
1641
+ * the new layers, the channel and the filled bounds.
1642
+ */
1643
+ separateSubject(options?: {
1644
+ layerId?: string;
1645
+ dilate?: number;
1646
+ fill?: 'content-aware' | 'none';
1647
+ seed?: number;
1648
+ signal?: AbortSignal;
1649
+ onProgress?: SelectionProgress;
1650
+ }): Promise<SeparateSubjectResult>;
1620
1651
  /**
1621
1652
  * The fill without changing anything: the window, its pixels before and after (RGBA, the fill
1622
1653
  * blended by the selection's coverage) and the sampling area used, for a preview.