@pixerate/editor 0.218.0 → 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.
- package/dist/{chunk-YF3YZZ6G.cjs → chunk-2ONK3ERD.cjs} +8 -8
- package/dist/{chunk-ZGJGSCUW.js → chunk-4P6H35HU.js} +1 -1
- package/dist/{chunk-MQDIP4CF.cjs → chunk-BB6TR422.cjs} +5 -1
- package/dist/{chunk-UC3PWFM3.cjs → chunk-KALCIDWH.cjs} +15 -11
- package/dist/{chunk-XI2NV5U7.js → chunk-VWEWY4FY.js} +4 -0
- package/dist/{chunk-U7C655AM.js → chunk-YFFLSP2C.js} +6 -2
- package/dist/image-editor/controller.d.cts +31 -0
- package/dist/image-editor/controller.d.ts +31 -0
- package/dist/image-editor/index.cjs +539 -428
- package/dist/image-editor/index.js +122 -11
- package/dist/image-editor/selection/overlay.d.cts +7 -0
- package/dist/image-editor/selection/overlay.d.ts +7 -0
- package/dist/image-editor/selection-worker.cjs +4 -4
- package/dist/image-editor/selection-worker.js +2 -2
- package/dist/paint/heal.d.cts +2 -0
- package/dist/paint/heal.d.ts +2 -0
- package/dist/paint/index.cjs +3 -3
- package/dist/paint/index.js +2 -2
- package/package.json +1 -1
- package/skills/pixerate-image-editor/SKILL.md +2 -3
- package/skills/pixerate-image-selection-masking/SKILL.md +5 -5
|
@@ -5,7 +5,7 @@
|
|
|
5
5
|
|
|
6
6
|
|
|
7
7
|
|
|
8
|
-
var
|
|
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 =
|
|
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 =
|
|
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 =
|
|
502
|
-
const healMask =
|
|
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 =
|
|
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 =
|
|
523
|
-
if (!filled) filled =
|
|
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) {
|
|
@@ -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
|
|
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 =
|
|
1578
|
-
|
|
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',
|
|
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',
|
|
1586
|
-
_optionalChain([onProgress, 'optionalCall',
|
|
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',
|
|
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',
|
|
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',
|
|
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',
|
|
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',
|
|
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-
|
|
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, {
|
|
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.
|