@pixerate/editor 0.218.1 → 0.219.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.
@@ -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) {
@@ -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) {
@@ -1574,16 +1574,20 @@ function runSelectionJob(job, pixels, onProgress) {
1574
1574
  };
1575
1575
  case "fill": {
1576
1576
  _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")]);
1577
+ const filled = _chunkBB6TR422cjs.contentAwareFill.call(void 0, pixels.width, pixels.height, 4, pixels.data, job.hole, {
1578
+ seed: job.seed,
1579
+ exclude: job.exclude,
1580
+ onProgress: (f) => _optionalChain([onProgress, 'optionalCall', _33 => _33(f * 0.99, "Filling")])
1581
+ });
1582
+ _optionalChain([onProgress, 'optionalCall', _34 => _34(1, "Done")]);
1579
1583
  return { mask: _chunkSS53NNZOcjs.createSelectionMask.call(void 0, 1, 1), pixels: Uint8ClampedArray.from(filled) };
1580
1584
  }
1581
1585
  case "refine-preview": {
1582
- _optionalChain([onProgress, 'optionalCall', _34 => _34(0, "Refining")]);
1586
+ _optionalChain([onProgress, 'optionalCall', _35 => _35(0, "Refining")]);
1583
1587
  const base = job.base ? deserializeMask(job.base) : null;
1584
1588
  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")]);
1589
+ 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);
1590
+ _optionalChain([onProgress, 'optionalCall', _37 => _37(1, "Done")]);
1587
1591
  return { mask };
1588
1592
  }
1589
1593
  }
@@ -1624,7 +1628,7 @@ var SelectionWorkerClient = (_class2 = class {
1624
1628
  __init9() {this.pending = /* @__PURE__ */ new Map()}
1625
1629
  /** Runs `job` on `pixels` in the worker. Aborting `signal` terminates the worker and rejects with an `AbortError`. */
1626
1630
  run(job, pixels, options = {}) {
1627
- if (_optionalChain([options, 'access', _37 => _37.signal, 'optionalAccess', _38 => _38.aborted])) return Promise.reject(abortError());
1631
+ if (_optionalChain([options, 'access', _38 => _38.signal, 'optionalAccess', _39 => _39.aborted])) return Promise.reject(abortError());
1628
1632
  const worker = this.ensure();
1629
1633
  let key = this.sent.get(pixels.data);
1630
1634
  if (key === void 0) {
@@ -1635,7 +1639,7 @@ var SelectionWorkerClient = (_class2 = class {
1635
1639
  const id = this.nextId++;
1636
1640
  return new Promise((resolve, reject) => {
1637
1641
  this.pending.set(id, { resolve, reject, onProgress: options.onProgress });
1638
- _optionalChain([options, 'access', _39 => _39.signal, 'optionalAccess', _40 => _40.addEventListener, 'call', _41 => _41(
1642
+ _optionalChain([options, 'access', _40 => _40.signal, 'optionalAccess', _41 => _41.addEventListener, 'call', _42 => _42(
1639
1643
  "abort",
1640
1644
  () => {
1641
1645
  if (this.pending.has(id)) this.terminate(abortError());
@@ -1647,7 +1651,7 @@ var SelectionWorkerClient = (_class2 = class {
1647
1651
  }
1648
1652
  /** Stops the worker; jobs still running reject with `reason` (default an `AbortError`). */
1649
1653
  terminate(reason = abortError()) {
1650
- _optionalChain([this, 'access', _42 => _42.worker, 'optionalAccess', _43 => _43.terminate, 'optionalCall', _44 => _44()]);
1654
+ _optionalChain([this, 'access', _43 => _43.worker, 'optionalAccess', _44 => _44.terminate, 'optionalCall', _45 => _45()]);
1651
1655
  this.worker = null;
1652
1656
  this.sent = /* @__PURE__ */ new WeakMap();
1653
1657
  const pending = [...this.pending.values()];
@@ -1661,14 +1665,14 @@ var SelectionWorkerClient = (_class2 = class {
1661
1665
  const msg = event.data;
1662
1666
  const p = this.pending.get(msg.id);
1663
1667
  if (!p) return;
1664
- if (msg.type === "progress") _optionalChain([p, 'access', _45 => _45.onProgress, 'optionalCall', _46 => _46(msg.fraction, msg.stage)]);
1668
+ if (msg.type === "progress") _optionalChain([p, 'access', _46 => _46.onProgress, 'optionalCall', _47 => _47(msg.fraction, msg.stage)]);
1665
1669
  else {
1666
1670
  this.pending.delete(msg.id);
1667
1671
  if (msg.type === "error") p.reject(new Error(msg.message));
1668
1672
  else p.resolve({ mask: deserializeMask(msg.mask), reason: msg.reason, noiseLevel: msg.noiseLevel, pixels: msg.pixels });
1669
1673
  }
1670
1674
  };
1671
- worker.onerror = (event) => this.terminate(new Error(_optionalChain([event, 'optionalAccess', _47 => _47.message]) || "The selection worker failed."));
1675
+ worker.onerror = (event) => this.terminate(new Error(_optionalChain([event, 'optionalAccess', _48 => _48.message]) || "The selection worker failed."));
1672
1676
  this.worker = worker;
1673
1677
  return worker;
1674
1678
  }
@@ -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;
@@ -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) {
@@ -1574,7 +1574,11 @@ function runSelectionJob(job, pixels, onProgress) {
1574
1574
  };
1575
1575
  case "fill": {
1576
1576
  onProgress?.(0, "Filling");
1577
- const filled = contentAwareFill(pixels.width, pixels.height, 4, pixels.data, job.hole, { seed: job.seed, exclude: job.exclude });
1577
+ const filled = contentAwareFill(pixels.width, pixels.height, 4, pixels.data, job.hole, {
1578
+ seed: job.seed,
1579
+ exclude: job.exclude,
1580
+ onProgress: (f) => onProgress?.(f * 0.99, "Filling")
1581
+ });
1578
1582
  onProgress?.(1, "Done");
1579
1583
  return { mask: createSelectionMask(1, 1), pixels: Uint8ClampedArray.from(filled) };
1580
1584
  }
@@ -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.