@pixerate/editor 0.208.0 → 0.210.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-X4LUGTQX.cjs → chunk-L52VMQZS.cjs} +21 -10
- package/dist/{chunk-DWYQPL2A.js → chunk-LQ5SALHK.js} +13 -2
- package/dist/chunk-MQDIP4CF.cjs +462 -0
- package/dist/chunk-XI2NV5U7.js +462 -0
- package/dist/{chunk-3EHPLDFQ.cjs → chunk-YF3YZZ6G.cjs} +16 -453
- package/dist/{chunk-USQCKRZG.js → chunk-ZGJGSCUW.js} +8 -445
- package/dist/image-editor/content-aware-fill-session.d.cts +109 -0
- package/dist/image-editor/content-aware-fill-session.d.ts +109 -0
- package/dist/image-editor/controller.d.cts +85 -3
- package/dist/image-editor/controller.d.ts +85 -3
- package/dist/image-editor/index.cjs +847 -396
- package/dist/image-editor/index.d.cts +1 -0
- package/dist/image-editor/index.d.ts +1 -0
- package/dist/image-editor/index.js +492 -41
- package/dist/image-editor/selection/tool-options.d.cts +3 -1
- package/dist/image-editor/selection/tool-options.d.ts +3 -1
- package/dist/image-editor/selection/worker.d.cts +10 -0
- package/dist/image-editor/selection/worker.d.ts +10 -0
- package/dist/image-editor/selection-worker.cjs +4 -3
- package/dist/image-editor/selection-worker.js +2 -1
- package/dist/image-editor/types.d.cts +14 -0
- package/dist/image-editor/types.d.ts +14 -0
- package/dist/paint/heal.d.cts +3 -1
- package/dist/paint/heal.d.ts +3 -1
- package/dist/paint/index.cjs +4 -2
- package/dist/paint/index.js +10 -8
- package/package.json +1 -1
- package/skills/pixerate-image-editor/SKILL.md +21 -23
- package/skills/pixerate-image-editor/references/layer-effects.md +24 -0
- package/skills/pixerate-image-selection-masking/SKILL.md +2 -1
|
@@ -12,6 +12,9 @@ var _chunkQYLWG22Ncjs = require('./chunk-QYLWG22N.cjs');
|
|
|
12
12
|
|
|
13
13
|
var _chunkK6PXS6DOcjs = require('./chunk-K6PXS6DO.cjs');
|
|
14
14
|
|
|
15
|
+
|
|
16
|
+
var _chunkMQDIP4CFcjs = require('./chunk-MQDIP4CF.cjs');
|
|
17
|
+
|
|
15
18
|
// src/image-editor/selection/refine.ts
|
|
16
19
|
var DEFAULT_REFINE_EDGE_PARAMS = {
|
|
17
20
|
radius: 0,
|
|
@@ -1864,12 +1867,18 @@ function runSelectionJob(job, pixels, onProgress) {
|
|
|
1864
1867
|
selection: job.selection ? deserializeMask(job.selection) : null
|
|
1865
1868
|
})
|
|
1866
1869
|
};
|
|
1870
|
+
case "fill": {
|
|
1871
|
+
_optionalChain([onProgress, 'optionalCall', _33 => _33(0, "Filling")]);
|
|
1872
|
+
const filled = _chunkMQDIP4CFcjs.contentAwareFill.call(void 0, pixels.width, pixels.height, 4, pixels.data, job.hole, { seed: job.seed, exclude: job.exclude });
|
|
1873
|
+
_optionalChain([onProgress, 'optionalCall', _34 => _34(1, "Done")]);
|
|
1874
|
+
return { mask: _chunkQYLWG22Ncjs.createSelectionMask.call(void 0, 1, 1), pixels: Uint8ClampedArray.from(filled) };
|
|
1875
|
+
}
|
|
1867
1876
|
case "refine-preview": {
|
|
1868
|
-
_optionalChain([onProgress, 'optionalCall',
|
|
1877
|
+
_optionalChain([onProgress, 'optionalCall', _35 => _35(0, "Refining")]);
|
|
1869
1878
|
const base = job.base ? deserializeMask(job.base) : null;
|
|
1870
1879
|
const { selection, refineBrush } = applyRefineEdits(pixels, base, job.edits, pixels.width, pixels.height);
|
|
1871
|
-
const mask = _optionalChain([selection, 'optionalAccess',
|
|
1872
|
-
_optionalChain([onProgress, 'optionalCall',
|
|
1880
|
+
const mask = _optionalChain([selection, 'optionalAccess', _36 => _36.bounds]) ? refineSelection(pixels, selection, job.params, refineBrush) : _chunkQYLWG22Ncjs.createSelectionMask.call(void 0, pixels.width, pixels.height);
|
|
1881
|
+
_optionalChain([onProgress, 'optionalCall', _37 => _37(1, "Done")]);
|
|
1873
1882
|
return { mask };
|
|
1874
1883
|
}
|
|
1875
1884
|
}
|
|
@@ -1888,7 +1897,9 @@ function startSelectionWorker(scope) {
|
|
|
1888
1897
|
if (!pixels) throw new Error("The selection worker has no image for this job; send the pixels first.");
|
|
1889
1898
|
const result = runSelectionJob(msg.job, pixels, (fraction, stage) => scope.postMessage({ type: "progress", id: msg.id, fraction, stage }));
|
|
1890
1899
|
const mask = serializeMask(result.mask);
|
|
1891
|
-
|
|
1900
|
+
const transfer = [mask.bytes.buffer];
|
|
1901
|
+
if (result.pixels) transfer.push(result.pixels.buffer);
|
|
1902
|
+
scope.postMessage({ type: "done", id: msg.id, mask, reason: result.reason, noiseLevel: result.noiseLevel, pixels: result.pixels }, transfer);
|
|
1892
1903
|
} catch (err) {
|
|
1893
1904
|
scope.postMessage({ type: "error", id: msg.id, message: err instanceof Error ? err.message : String(err) });
|
|
1894
1905
|
}
|
|
@@ -1908,7 +1919,7 @@ var SelectionWorkerClient = (_class2 = class {
|
|
|
1908
1919
|
__init9() {this.pending = /* @__PURE__ */ new Map()}
|
|
1909
1920
|
/** Runs `job` on `pixels` in the worker. Aborting `signal` terminates the worker and rejects with an `AbortError`. */
|
|
1910
1921
|
run(job, pixels, options = {}) {
|
|
1911
|
-
if (_optionalChain([options, 'access',
|
|
1922
|
+
if (_optionalChain([options, 'access', _38 => _38.signal, 'optionalAccess', _39 => _39.aborted])) return Promise.reject(abortError());
|
|
1912
1923
|
const worker = this.ensure();
|
|
1913
1924
|
let key = this.sent.get(pixels.data);
|
|
1914
1925
|
if (key === void 0) {
|
|
@@ -1919,7 +1930,7 @@ var SelectionWorkerClient = (_class2 = class {
|
|
|
1919
1930
|
const id = this.nextId++;
|
|
1920
1931
|
return new Promise((resolve, reject) => {
|
|
1921
1932
|
this.pending.set(id, { resolve, reject, onProgress: options.onProgress });
|
|
1922
|
-
_optionalChain([options, 'access',
|
|
1933
|
+
_optionalChain([options, 'access', _40 => _40.signal, 'optionalAccess', _41 => _41.addEventListener, 'call', _42 => _42(
|
|
1923
1934
|
"abort",
|
|
1924
1935
|
() => {
|
|
1925
1936
|
if (this.pending.has(id)) this.terminate(abortError());
|
|
@@ -1931,7 +1942,7 @@ var SelectionWorkerClient = (_class2 = class {
|
|
|
1931
1942
|
}
|
|
1932
1943
|
/** Stops the worker; jobs still running reject with `reason` (default an `AbortError`). */
|
|
1933
1944
|
terminate(reason = abortError()) {
|
|
1934
|
-
_optionalChain([this, 'access',
|
|
1945
|
+
_optionalChain([this, 'access', _43 => _43.worker, 'optionalAccess', _44 => _44.terminate, 'optionalCall', _45 => _45()]);
|
|
1935
1946
|
this.worker = null;
|
|
1936
1947
|
this.sent = /* @__PURE__ */ new WeakMap();
|
|
1937
1948
|
const pending = [...this.pending.values()];
|
|
@@ -1945,14 +1956,14 @@ var SelectionWorkerClient = (_class2 = class {
|
|
|
1945
1956
|
const msg = event.data;
|
|
1946
1957
|
const p = this.pending.get(msg.id);
|
|
1947
1958
|
if (!p) return;
|
|
1948
|
-
if (msg.type === "progress") _optionalChain([p, 'access',
|
|
1959
|
+
if (msg.type === "progress") _optionalChain([p, 'access', _46 => _46.onProgress, 'optionalCall', _47 => _47(msg.fraction, msg.stage)]);
|
|
1949
1960
|
else {
|
|
1950
1961
|
this.pending.delete(msg.id);
|
|
1951
1962
|
if (msg.type === "error") p.reject(new Error(msg.message));
|
|
1952
|
-
else p.resolve({ mask: deserializeMask(msg.mask), reason: msg.reason, noiseLevel: msg.noiseLevel });
|
|
1963
|
+
else p.resolve({ mask: deserializeMask(msg.mask), reason: msg.reason, noiseLevel: msg.noiseLevel, pixels: msg.pixels });
|
|
1953
1964
|
}
|
|
1954
1965
|
};
|
|
1955
|
-
worker.onerror = (event) => this.terminate(new Error(_optionalChain([event, 'optionalAccess',
|
|
1966
|
+
worker.onerror = (event) => this.terminate(new Error(_optionalChain([event, 'optionalAccess', _48 => _48.message]) || "The selection worker failed."));
|
|
1956
1967
|
this.worker = worker;
|
|
1957
1968
|
return worker;
|
|
1958
1969
|
}
|
|
@@ -11,6 +11,9 @@ import {
|
|
|
11
11
|
import {
|
|
12
12
|
gaussianBlur
|
|
13
13
|
} from "./chunk-ZIF4UJJ6.js";
|
|
14
|
+
import {
|
|
15
|
+
contentAwareFill
|
|
16
|
+
} from "./chunk-XI2NV5U7.js";
|
|
14
17
|
|
|
15
18
|
// src/image-editor/selection/refine.ts
|
|
16
19
|
var DEFAULT_REFINE_EDGE_PARAMS = {
|
|
@@ -1864,6 +1867,12 @@ function runSelectionJob(job, pixels, onProgress) {
|
|
|
1864
1867
|
selection: job.selection ? deserializeMask(job.selection) : null
|
|
1865
1868
|
})
|
|
1866
1869
|
};
|
|
1870
|
+
case "fill": {
|
|
1871
|
+
onProgress?.(0, "Filling");
|
|
1872
|
+
const filled = contentAwareFill(pixels.width, pixels.height, 4, pixels.data, job.hole, { seed: job.seed, exclude: job.exclude });
|
|
1873
|
+
onProgress?.(1, "Done");
|
|
1874
|
+
return { mask: createSelectionMask(1, 1), pixels: Uint8ClampedArray.from(filled) };
|
|
1875
|
+
}
|
|
1867
1876
|
case "refine-preview": {
|
|
1868
1877
|
onProgress?.(0, "Refining");
|
|
1869
1878
|
const base = job.base ? deserializeMask(job.base) : null;
|
|
@@ -1888,7 +1897,9 @@ function startSelectionWorker(scope) {
|
|
|
1888
1897
|
if (!pixels) throw new Error("The selection worker has no image for this job; send the pixels first.");
|
|
1889
1898
|
const result = runSelectionJob(msg.job, pixels, (fraction, stage) => scope.postMessage({ type: "progress", id: msg.id, fraction, stage }));
|
|
1890
1899
|
const mask = serializeMask(result.mask);
|
|
1891
|
-
|
|
1900
|
+
const transfer = [mask.bytes.buffer];
|
|
1901
|
+
if (result.pixels) transfer.push(result.pixels.buffer);
|
|
1902
|
+
scope.postMessage({ type: "done", id: msg.id, mask, reason: result.reason, noiseLevel: result.noiseLevel, pixels: result.pixels }, transfer);
|
|
1892
1903
|
} catch (err) {
|
|
1893
1904
|
scope.postMessage({ type: "error", id: msg.id, message: err instanceof Error ? err.message : String(err) });
|
|
1894
1905
|
}
|
|
@@ -1949,7 +1960,7 @@ var SelectionWorkerClient = class {
|
|
|
1949
1960
|
else {
|
|
1950
1961
|
this.pending.delete(msg.id);
|
|
1951
1962
|
if (msg.type === "error") p.reject(new Error(msg.message));
|
|
1952
|
-
else p.resolve({ mask: deserializeMask(msg.mask), reason: msg.reason, noiseLevel: msg.noiseLevel });
|
|
1963
|
+
else p.resolve({ mask: deserializeMask(msg.mask), reason: msg.reason, noiseLevel: msg.noiseLevel, pixels: msg.pixels });
|
|
1953
1964
|
}
|
|
1954
1965
|
};
|
|
1955
1966
|
worker.onerror = (event) => this.terminate(new Error(event?.message || "The selection worker failed."));
|
|
@@ -0,0 +1,462 @@
|
|
|
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
|
|
2
|
+
var TOL = 2e-3;
|
|
3
|
+
var FINE_ITERS = 30;
|
|
4
|
+
var FINE_OMEGA = 1.7;
|
|
5
|
+
function solveMembrane(w, h, unknown, v) {
|
|
6
|
+
if (unknown.length !== w * h || v.length !== w * h) throw new RangeError(`solveMembrane: expected ${w * h} cells, got ${unknown.length} and ${v.length}.`);
|
|
7
|
+
const u = new Uint8Array(w * h);
|
|
8
|
+
for (let i = 0; i < u.length; i++) u[i] = unknown[i] ? 1 : 0;
|
|
9
|
+
solveLevel(w, h, u, v, 0);
|
|
10
|
+
}
|
|
11
|
+
function solveLevel(w, h, unknown, v, depth) {
|
|
12
|
+
let nUnknown = 0;
|
|
13
|
+
let knownSum = 0;
|
|
14
|
+
let nKnown = 0;
|
|
15
|
+
for (let i = 0; i < unknown.length; i++) {
|
|
16
|
+
if (unknown[i]) nUnknown++;
|
|
17
|
+
else {
|
|
18
|
+
knownSum += v[i];
|
|
19
|
+
nKnown++;
|
|
20
|
+
}
|
|
21
|
+
}
|
|
22
|
+
if (nUnknown === 0 || nKnown === 0) return;
|
|
23
|
+
if (w >= 8 && h >= 8 && nUnknown > 64 && depth < 16) {
|
|
24
|
+
const cw = Math.ceil(w / 2);
|
|
25
|
+
const chh = Math.ceil(h / 2);
|
|
26
|
+
const cu = new Uint8Array(cw * chh).fill(1);
|
|
27
|
+
const cv = new Float32Array(cw * chh);
|
|
28
|
+
for (let cy = 0; cy < chh; cy++) {
|
|
29
|
+
for (let cx = 0; cx < cw; cx++) {
|
|
30
|
+
let s = 0;
|
|
31
|
+
let n = 0;
|
|
32
|
+
for (let k = 0; k < 4; k++) {
|
|
33
|
+
const x = cx * 2 + (k & 1);
|
|
34
|
+
const y = cy * 2 + (k >> 1);
|
|
35
|
+
if (x < w && y < h && !unknown[y * w + x]) {
|
|
36
|
+
s += v[y * w + x];
|
|
37
|
+
n++;
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
if (n > 0) {
|
|
41
|
+
cu[cy * cw + cx] = 0;
|
|
42
|
+
cv[cy * cw + cx] = s / n;
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
solveLevel(cw, chh, cu, cv, depth + 1);
|
|
47
|
+
for (let y = 0; y < h; y++) {
|
|
48
|
+
const fy = Math.min(chh - 1, Math.max(0, (y + 0.5) / 2 - 0.5));
|
|
49
|
+
const y0 = Math.floor(fy);
|
|
50
|
+
const y1 = Math.min(chh - 1, y0 + 1);
|
|
51
|
+
const ty = fy - y0;
|
|
52
|
+
for (let x = 0; x < w; x++) {
|
|
53
|
+
if (!unknown[y * w + x]) continue;
|
|
54
|
+
const fx = Math.min(cw - 1, Math.max(0, (x + 0.5) / 2 - 0.5));
|
|
55
|
+
const x0 = Math.floor(fx);
|
|
56
|
+
const x1 = Math.min(cw - 1, x0 + 1);
|
|
57
|
+
const tx = fx - x0;
|
|
58
|
+
const a = cv[y0 * cw + x0] + (cv[y0 * cw + x1] - cv[y0 * cw + x0]) * tx;
|
|
59
|
+
const b = cv[y1 * cw + x0] + (cv[y1 * cw + x1] - cv[y1 * cw + x0]) * tx;
|
|
60
|
+
v[y * w + x] = a + (b - a) * ty;
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
sor(w, h, unknown, v, FINE_ITERS, FINE_OMEGA);
|
|
64
|
+
} else {
|
|
65
|
+
const mean = knownSum / nKnown;
|
|
66
|
+
for (let i = 0; i < v.length; i++) if (unknown[i]) v[i] = mean;
|
|
67
|
+
sor(w, h, unknown, v, 2e3, 1.85);
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
function sor(w, h, unknown, v, maxIter, omega) {
|
|
71
|
+
let n = 0;
|
|
72
|
+
for (let i = 0; i < unknown.length; i++) if (unknown[i]) n++;
|
|
73
|
+
const cells = new Uint32Array(n);
|
|
74
|
+
const masks = new Uint8Array(n);
|
|
75
|
+
for (let i = 0, k = 0; i < unknown.length; i++) {
|
|
76
|
+
if (!unknown[i]) continue;
|
|
77
|
+
const x = i % w;
|
|
78
|
+
const y = (i - x) / w;
|
|
79
|
+
cells[k] = i;
|
|
80
|
+
masks[k++] = (x > 0 ? 1 : 0) | (x + 1 < w ? 2 : 0) | (y > 0 ? 4 : 0) | (y + 1 < h ? 8 : 0);
|
|
81
|
+
}
|
|
82
|
+
for (let it = 0; it < maxIter; it++) {
|
|
83
|
+
let maxD = 0;
|
|
84
|
+
for (let k = 0; k < n; k++) {
|
|
85
|
+
const i = cells[k];
|
|
86
|
+
const m = masks[k];
|
|
87
|
+
let s;
|
|
88
|
+
let c;
|
|
89
|
+
if (m === 15) {
|
|
90
|
+
s = v[i - 1] + v[i + 1] + v[i - w] + v[i + w];
|
|
91
|
+
c = 4;
|
|
92
|
+
} else {
|
|
93
|
+
s = 0;
|
|
94
|
+
c = 0;
|
|
95
|
+
if (m & 1) s += v[i - 1], c++;
|
|
96
|
+
if (m & 2) s += v[i + 1], c++;
|
|
97
|
+
if (m & 4) s += v[i - w], c++;
|
|
98
|
+
if (m & 8) s += v[i + w], c++;
|
|
99
|
+
if (c === 0) continue;
|
|
100
|
+
}
|
|
101
|
+
const d = omega * (s / c - v[i]);
|
|
102
|
+
v[i] += d;
|
|
103
|
+
const ad = d < 0 ? -d : d;
|
|
104
|
+
if (ad > maxD) maxD = ad;
|
|
105
|
+
}
|
|
106
|
+
if (maxD < TOL) break;
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
function seamlessClone(w, h, ch, src, dst, mask) {
|
|
110
|
+
const n = w * h;
|
|
111
|
+
if (src.length !== n * ch || dst.length !== n * ch || mask.length !== n) throw new RangeError("seamlessClone: src, dst and mask must cover w \xD7 h pixels.");
|
|
112
|
+
const out = new Float32Array(n * ch);
|
|
113
|
+
const hv = new Float32Array(n);
|
|
114
|
+
for (let c = 0; c < ch; c++) {
|
|
115
|
+
for (let i = 0; i < n; i++) hv[i] = mask[i] ? 0 : dst[i * ch + c] - src[i * ch + c];
|
|
116
|
+
solveMembrane(w, h, mask, hv);
|
|
117
|
+
for (let i = 0; i < n; i++) out[i * ch + c] = mask[i] ? src[i * ch + c] + hv[i] : dst[i * ch + c];
|
|
118
|
+
}
|
|
119
|
+
return out;
|
|
120
|
+
}
|
|
121
|
+
function membraneFill(w, h, ch, img, hole) {
|
|
122
|
+
const n = w * h;
|
|
123
|
+
const out = new Float32Array(n * ch);
|
|
124
|
+
const v = new Float32Array(n);
|
|
125
|
+
for (let c = 0; c < ch; c++) {
|
|
126
|
+
for (let i = 0; i < n; i++) v[i] = hole[i] ? 0 : img[i * ch + c];
|
|
127
|
+
solveMembrane(w, h, hole, v);
|
|
128
|
+
for (let i = 0; i < n; i++) out[i * ch + c] = v[i];
|
|
129
|
+
}
|
|
130
|
+
return out;
|
|
131
|
+
}
|
|
132
|
+
function dilateMask(w, h, mask, r) {
|
|
133
|
+
const tmp = new Uint8Array(w * h);
|
|
134
|
+
for (let y = 0; y < h; y++) {
|
|
135
|
+
for (let x = 0; x < w; x++) {
|
|
136
|
+
if (!mask[y * w + x]) continue;
|
|
137
|
+
for (let xx = Math.max(0, x - r); xx <= Math.min(w - 1, x + r); xx++) tmp[y * w + xx] = 1;
|
|
138
|
+
}
|
|
139
|
+
}
|
|
140
|
+
const out = new Uint8Array(w * h);
|
|
141
|
+
for (let y = 0; y < h; y++) {
|
|
142
|
+
for (let x = 0; x < w; x++) {
|
|
143
|
+
if (!tmp[y * w + x]) continue;
|
|
144
|
+
for (let yy = Math.max(0, y - r); yy <= Math.min(h - 1, y + r); yy++) out[yy * w + x] = 1;
|
|
145
|
+
}
|
|
146
|
+
}
|
|
147
|
+
return out;
|
|
148
|
+
}
|
|
149
|
+
var MaskIntegral = class {
|
|
150
|
+
constructor(w, h, mask) {
|
|
151
|
+
this.w = w;
|
|
152
|
+
this.h = h;
|
|
153
|
+
const W = w + 1;
|
|
154
|
+
this.s = new Uint32Array(W * (h + 1));
|
|
155
|
+
for (let y = 0; y < h; y++) {
|
|
156
|
+
let row = 0;
|
|
157
|
+
for (let x = 0; x < w; x++) {
|
|
158
|
+
row += mask[y * w + x] ? 1 : 0;
|
|
159
|
+
this.s[(y + 1) * W + x + 1] = this.s[y * W + x + 1] + row;
|
|
160
|
+
}
|
|
161
|
+
}
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
|
|
165
|
+
|
|
166
|
+
/** Masked cells in [x0, x1) × [y0, y1) (clipped to the grid). */
|
|
167
|
+
count(x0, y0, x1, y1) {
|
|
168
|
+
const W = this.w + 1;
|
|
169
|
+
const a = Math.max(0, Math.min(this.w, x0));
|
|
170
|
+
const b = Math.max(0, Math.min(this.h, y0));
|
|
171
|
+
const c = Math.max(0, Math.min(this.w, x1));
|
|
172
|
+
const d = Math.max(0, Math.min(this.h, y1));
|
|
173
|
+
if (c <= a || d <= b) return 0;
|
|
174
|
+
return this.s[d * W + c] - this.s[b * W + c] - this.s[d * W + a] + this.s[b * W + a];
|
|
175
|
+
}
|
|
176
|
+
};
|
|
177
|
+
function maskBounds(w, h, mask) {
|
|
178
|
+
let x0 = w;
|
|
179
|
+
let y0 = h;
|
|
180
|
+
let x1 = -1;
|
|
181
|
+
let y1 = -1;
|
|
182
|
+
for (let y = 0; y < h; y++) {
|
|
183
|
+
for (let x = 0; x < w; x++) {
|
|
184
|
+
if (!mask[y * w + x]) continue;
|
|
185
|
+
if (x < x0) x0 = x;
|
|
186
|
+
if (x > x1) x1 = x;
|
|
187
|
+
if (y < y0) y0 = y;
|
|
188
|
+
if (y > y1) y1 = y;
|
|
189
|
+
}
|
|
190
|
+
}
|
|
191
|
+
return x1 < 0 ? null : [x0, y0, x1 + 1, y1 + 1];
|
|
192
|
+
}
|
|
193
|
+
function bestOffset(w, h, ch, img, hole, ring, maxRadius) {
|
|
194
|
+
const bb = maskBounds(w, h, hole);
|
|
195
|
+
if (!bb) return null;
|
|
196
|
+
const extent = Math.max(w, h);
|
|
197
|
+
const R = Math.max(0, Math.min(extent, Math.floor(maxRadius)));
|
|
198
|
+
if (R === 0) return null;
|
|
199
|
+
const rg = Math.max(1, Math.min(extent, Math.floor(ring)));
|
|
200
|
+
const hs = new MaskIntegral(w, h, hole);
|
|
201
|
+
const x0 = bb[0] - rg;
|
|
202
|
+
const y0 = bb[1] - rg;
|
|
203
|
+
const x1 = bb[2] + rg;
|
|
204
|
+
const y1 = bb[3] + rg;
|
|
205
|
+
const pts = [];
|
|
206
|
+
for (let y = Math.max(0, y0); y < Math.min(h, y1); y++) {
|
|
207
|
+
for (let x = Math.max(0, x0); x < Math.min(w, x1); x++) {
|
|
208
|
+
if (hole[y * w + x]) continue;
|
|
209
|
+
if (hs.count(x - rg, y - rg, x + rg + 1, y + rg + 1) > 0) pts.push(x, y);
|
|
210
|
+
}
|
|
211
|
+
}
|
|
212
|
+
const stride = Math.max(1, Math.floor(pts.length / 2 / 2e3));
|
|
213
|
+
const sx = [];
|
|
214
|
+
const sy = [];
|
|
215
|
+
for (let i = 0; i < pts.length; i += 2 * stride) {
|
|
216
|
+
sx.push(pts[i]);
|
|
217
|
+
sy.push(pts[i + 1]);
|
|
218
|
+
}
|
|
219
|
+
const n = Math.max(1, sx.length);
|
|
220
|
+
const evalAt = (dx, dy, cutoff) => {
|
|
221
|
+
if (dx === 0 && dy === 0) return null;
|
|
222
|
+
const hx0 = x0 + dx;
|
|
223
|
+
const hy0 = y0 + dy;
|
|
224
|
+
const hx1 = x1 + dx;
|
|
225
|
+
const hy1 = y1 + dy;
|
|
226
|
+
if (hx0 < 0 || hy0 < 0 || hx1 > w || hy1 > h) return null;
|
|
227
|
+
if (hs.count(hx0, hy0, hx1, hy1) !== 0) return null;
|
|
228
|
+
const penalty = 1 + 1e-3 * (Math.hypot(dx, dy) / R);
|
|
229
|
+
let e = 0;
|
|
230
|
+
for (let i = 0; i < sx.length; i++) {
|
|
231
|
+
const a = (sy[i] * w + sx[i]) * ch;
|
|
232
|
+
const b = ((sy[i] + dy) * w + sx[i] + dx) * ch;
|
|
233
|
+
for (let c = 0; c < ch; c++) {
|
|
234
|
+
const d = img[a + c] - img[b + c];
|
|
235
|
+
e += d * d;
|
|
236
|
+
}
|
|
237
|
+
if ((i & 31) === 31 && e / n * penalty >= cutoff) return null;
|
|
238
|
+
}
|
|
239
|
+
return e / n * penalty;
|
|
240
|
+
};
|
|
241
|
+
const found = { best: null, e: Infinity };
|
|
242
|
+
const consider = (dx, dy) => {
|
|
243
|
+
const e = evalAt(dx, dy, found.e);
|
|
244
|
+
if (e !== null && e < found.e) {
|
|
245
|
+
found.e = e;
|
|
246
|
+
found.best = [dx, dy];
|
|
247
|
+
}
|
|
248
|
+
};
|
|
249
|
+
const step = Math.max(1, Math.floor(R / 24));
|
|
250
|
+
for (let dy = -R; dy <= R; dy += step) for (let dx = -R; dx <= R; dx += step) consider(dx, dy);
|
|
251
|
+
if (step > 1 && found.best) {
|
|
252
|
+
const [bx, by] = found.best;
|
|
253
|
+
for (let dy = by - step; dy <= by + step; dy++) for (let dx = bx - step; dx <= bx + step; dx++) consider(dx, dy);
|
|
254
|
+
}
|
|
255
|
+
return found.best;
|
|
256
|
+
}
|
|
257
|
+
var PATCH_R = 3;
|
|
258
|
+
function rng(seed) {
|
|
259
|
+
let a = seed >>> 0;
|
|
260
|
+
return () => {
|
|
261
|
+
a = a + 1831565813 >>> 0;
|
|
262
|
+
let t = a;
|
|
263
|
+
t = Math.imul(t ^ t >>> 15, t | 1);
|
|
264
|
+
t ^= t + Math.imul(t ^ t >>> 7, t | 61);
|
|
265
|
+
return ((t ^ t >>> 14) >>> 0) / 4294967296;
|
|
266
|
+
};
|
|
267
|
+
}
|
|
268
|
+
function downsample(l, ch) {
|
|
269
|
+
const w = Math.max(1, l.w >> 1);
|
|
270
|
+
const h = Math.max(1, l.h >> 1);
|
|
271
|
+
const img = new Float32Array(w * h * ch);
|
|
272
|
+
const hole = new Uint8Array(w * h);
|
|
273
|
+
const exclude = l.exclude ? new Uint8Array(w * h) : null;
|
|
274
|
+
for (let y = 0; y < h; y++) {
|
|
275
|
+
for (let x = 0; x < w; x++) {
|
|
276
|
+
let n = 0;
|
|
277
|
+
const o = (y * w + x) * ch;
|
|
278
|
+
for (let dy = 0; dy < 2; dy++) {
|
|
279
|
+
for (let dx = 0; dx < 2; dx++) {
|
|
280
|
+
const sx = Math.min(l.w - 1, 2 * x + dx);
|
|
281
|
+
const sy = Math.min(l.h - 1, 2 * y + dy);
|
|
282
|
+
const i = sy * l.w + sx;
|
|
283
|
+
if (exclude && l.exclude[i]) exclude[y * w + x] = 1;
|
|
284
|
+
if (l.hole[i]) {
|
|
285
|
+
hole[y * w + x] = 1;
|
|
286
|
+
continue;
|
|
287
|
+
}
|
|
288
|
+
for (let c = 0; c < ch; c++) img[o + c] += l.img[i * ch + c];
|
|
289
|
+
n++;
|
|
290
|
+
}
|
|
291
|
+
}
|
|
292
|
+
if (n) for (let c = 0; c < ch; c++) img[o + c] /= n;
|
|
293
|
+
}
|
|
294
|
+
}
|
|
295
|
+
return { w, h, img, hole, exclude };
|
|
296
|
+
}
|
|
297
|
+
function contentAwareFill(w, h, ch, img, hole, options = {}) {
|
|
298
|
+
const iters = _nullishCoalesce(options.iterations, () => ( 4));
|
|
299
|
+
const base = {
|
|
300
|
+
w,
|
|
301
|
+
h,
|
|
302
|
+
img: Float32Array.from(img),
|
|
303
|
+
hole: Uint8Array.from(hole, (v) => v ? 1 : 0),
|
|
304
|
+
exclude: options.exclude ? Uint8Array.from(options.exclude, (v) => v ? 1 : 0) : null
|
|
305
|
+
};
|
|
306
|
+
const levels = [base];
|
|
307
|
+
while (levels.length < 4) {
|
|
308
|
+
const top = levels[levels.length - 1];
|
|
309
|
+
const b = maskBounds(top.w, top.h, top.hole);
|
|
310
|
+
if (!b || Math.max(b[2] - b[0], b[3] - b[1]) < 4 * PATCH_R || top.w < 8 * PATCH_R || top.h < 8 * PATCH_R) break;
|
|
311
|
+
levels.push(downsample(top, ch));
|
|
312
|
+
}
|
|
313
|
+
const random = rng(_nullishCoalesce(options.seed, () => ( w * 73856093 + h * 19349663)));
|
|
314
|
+
let prevFill = null;
|
|
315
|
+
let prevNnf = null;
|
|
316
|
+
let prevW = 0;
|
|
317
|
+
for (let li = levels.length - 1; li >= 0; li--) {
|
|
318
|
+
const { w: W, h: H, hole: holeL, exclude: excludeL } = levels[li];
|
|
319
|
+
const cur = new Float32Array(levels[li].img);
|
|
320
|
+
if (prevFill) {
|
|
321
|
+
for (let i = 0; i < W * H; i++) {
|
|
322
|
+
if (!holeL[i]) continue;
|
|
323
|
+
const x = Math.min(prevW - 1, i % W >> 1);
|
|
324
|
+
const y = Math.min(Math.floor(prevFill.length / ch / prevW) - 1, Math.floor(i / W) >> 1);
|
|
325
|
+
for (let c = 0; c < ch; c++) cur[i * ch + c] = prevFill[(y * prevW + x) * ch + c];
|
|
326
|
+
}
|
|
327
|
+
} else cur.set(membraneFill(W, H, ch, cur, holeL));
|
|
328
|
+
const holeBlocked = dilateMask(W, H, holeL, PATCH_R);
|
|
329
|
+
let blocked = holeBlocked;
|
|
330
|
+
if (excludeL) {
|
|
331
|
+
const off = dilateMask(W, H, excludeL, PATCH_R);
|
|
332
|
+
const sourceBlocked = new Uint8Array(W * H);
|
|
333
|
+
for (let i = 0; i < W * H; i++) sourceBlocked[i] = holeBlocked[i] || off[i] ? 1 : 0;
|
|
334
|
+
blocked = sourceBlocked;
|
|
335
|
+
}
|
|
336
|
+
const valid = [];
|
|
337
|
+
for (let y = PATCH_R; y < H - PATCH_R; y++) for (let x = PATCH_R; x < W - PATCH_R; x++) if (!blocked[y * W + x]) valid.push(y * W + x);
|
|
338
|
+
if (!valid.length) {
|
|
339
|
+
prevFill = cur;
|
|
340
|
+
prevNnf = null;
|
|
341
|
+
prevW = W;
|
|
342
|
+
continue;
|
|
343
|
+
}
|
|
344
|
+
const targets = [];
|
|
345
|
+
for (let y = 0; y < H; y++) for (let x = 0; x < W; x++) if (holeBlocked[y * W + x]) targets.push(y * W + x);
|
|
346
|
+
const nnf = new Int32Array(W * H).fill(-1);
|
|
347
|
+
const cost = new Float64Array(W * H);
|
|
348
|
+
const clampC = (v, n) => Math.min(n - 1 - PATCH_R, Math.max(PATCH_R, v));
|
|
349
|
+
const dist = (t, s, limit) => {
|
|
350
|
+
const tx = clampC(t % W, W);
|
|
351
|
+
const ty = clampC(Math.floor(t / W), H);
|
|
352
|
+
const sx = s % W;
|
|
353
|
+
const sy = Math.floor(s / W);
|
|
354
|
+
let d = 0;
|
|
355
|
+
for (let dy = -PATCH_R; dy <= PATCH_R; dy++) {
|
|
356
|
+
const tr = (ty + dy) * W;
|
|
357
|
+
const sr = (sy + dy) * W;
|
|
358
|
+
for (let dx = -PATCH_R; dx <= PATCH_R; dx++) {
|
|
359
|
+
const a = (tr + tx + dx) * ch;
|
|
360
|
+
const b = (sr + sx + dx) * ch;
|
|
361
|
+
for (let c = 0; c < ch; c++) {
|
|
362
|
+
const e = cur[a + c] - cur[b + c];
|
|
363
|
+
d += e * e;
|
|
364
|
+
}
|
|
365
|
+
}
|
|
366
|
+
if (d >= limit) return d;
|
|
367
|
+
}
|
|
368
|
+
return d;
|
|
369
|
+
};
|
|
370
|
+
const isValid = (s) => {
|
|
371
|
+
const x = s % W;
|
|
372
|
+
const y = Math.floor(s / W);
|
|
373
|
+
return s >= 0 && x >= PATCH_R && y >= PATCH_R && x < W - PATCH_R && y < H - PATCH_R && !blocked[s];
|
|
374
|
+
};
|
|
375
|
+
for (const t of targets) {
|
|
376
|
+
let s = -1;
|
|
377
|
+
if (prevNnf) {
|
|
378
|
+
const px = Math.min(prevW - 1, t % W >> 1);
|
|
379
|
+
const py = Math.floor(t / W) >> 1;
|
|
380
|
+
const ps = prevNnf[py * prevW + px];
|
|
381
|
+
if (ps >= 0) {
|
|
382
|
+
const cand = (Math.floor(ps / prevW) * 2 + (Math.floor(t / W) & 1)) * W + ps % prevW * 2 + (t % W & 1);
|
|
383
|
+
if (isValid(cand)) s = cand;
|
|
384
|
+
}
|
|
385
|
+
}
|
|
386
|
+
if (s < 0) s = valid[Math.floor(random() * valid.length)];
|
|
387
|
+
nnf[t] = s;
|
|
388
|
+
}
|
|
389
|
+
for (let em = 0; em < iters; em++) {
|
|
390
|
+
for (const t of targets) cost[t] = dist(t, nnf[t], Infinity);
|
|
391
|
+
for (let pass = 0; pass < 4; pass++) {
|
|
392
|
+
const fwd = pass % 2 === 0;
|
|
393
|
+
const step = fwd ? 1 : -1;
|
|
394
|
+
for (let k = 0; k < targets.length; k++) {
|
|
395
|
+
const t = targets[fwd ? k : targets.length - 1 - k];
|
|
396
|
+
const tx = t % W;
|
|
397
|
+
const ty = Math.floor(t / W);
|
|
398
|
+
for (const [nx, ny] of [[tx - step, ty], [tx, ty - step]]) {
|
|
399
|
+
if (nx < 0 || ny < 0 || nx >= W || ny >= H) continue;
|
|
400
|
+
const ns = nnf[ny * W + nx];
|
|
401
|
+
if (ns < 0) continue;
|
|
402
|
+
const cand = ns + step * (nx === tx ? W : 1);
|
|
403
|
+
if (!isValid(cand) || cand === nnf[t]) continue;
|
|
404
|
+
const d = dist(t, cand, cost[t]);
|
|
405
|
+
if (d < cost[t]) {
|
|
406
|
+
cost[t] = d;
|
|
407
|
+
nnf[t] = cand;
|
|
408
|
+
}
|
|
409
|
+
}
|
|
410
|
+
const bx = nnf[t] % W;
|
|
411
|
+
const by = Math.floor(nnf[t] / W);
|
|
412
|
+
for (let r = Math.max(W, H); r >= 1; r >>= 1) {
|
|
413
|
+
const cx = Math.round(bx + (random() * 2 - 1) * r);
|
|
414
|
+
const cy = Math.round(by + (random() * 2 - 1) * r);
|
|
415
|
+
const cand = cy * W + cx;
|
|
416
|
+
if (cx < 0 || cy < 0 || cx >= W || cy >= H || !isValid(cand)) continue;
|
|
417
|
+
const d = dist(t, cand, cost[t]);
|
|
418
|
+
if (d < cost[t]) {
|
|
419
|
+
cost[t] = d;
|
|
420
|
+
nnf[t] = cand;
|
|
421
|
+
}
|
|
422
|
+
}
|
|
423
|
+
}
|
|
424
|
+
}
|
|
425
|
+
const acc = new Float64Array(W * H * ch);
|
|
426
|
+
const wsum = new Float64Array(W * H);
|
|
427
|
+
for (const t of targets) {
|
|
428
|
+
const tx = clampC(t % W, W);
|
|
429
|
+
const ty = clampC(Math.floor(t / W), H);
|
|
430
|
+
const s = nnf[t];
|
|
431
|
+
const sx = s % W;
|
|
432
|
+
const sy = Math.floor(s / W);
|
|
433
|
+
for (let dy = -PATCH_R; dy <= PATCH_R; dy++) {
|
|
434
|
+
for (let dx = -PATCH_R; dx <= PATCH_R; dx++) {
|
|
435
|
+
const q = (ty + dy) * W + tx + dx;
|
|
436
|
+
if (!holeL[q]) continue;
|
|
437
|
+
const src = ((sy + dy) * W + sx + dx) * ch;
|
|
438
|
+
for (let c = 0; c < ch; c++) acc[q * ch + c] += cur[src + c];
|
|
439
|
+
wsum[q] += 1;
|
|
440
|
+
}
|
|
441
|
+
}
|
|
442
|
+
}
|
|
443
|
+
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];
|
|
444
|
+
}
|
|
445
|
+
prevFill = cur;
|
|
446
|
+
prevNnf = nnf;
|
|
447
|
+
prevW = W;
|
|
448
|
+
}
|
|
449
|
+
const out = Float32Array.from(img);
|
|
450
|
+
for (let i = 0; i < w * h; i++) if (base.hole[i]) for (let c = 0; c < ch; c++) out[i * ch + c] = prevFill[i * ch + c];
|
|
451
|
+
return out;
|
|
452
|
+
}
|
|
453
|
+
|
|
454
|
+
|
|
455
|
+
|
|
456
|
+
|
|
457
|
+
|
|
458
|
+
|
|
459
|
+
|
|
460
|
+
|
|
461
|
+
|
|
462
|
+
exports.solveMembrane = solveMembrane; exports.seamlessClone = seamlessClone; exports.membraneFill = membraneFill; exports.dilateMask = dilateMask; exports.maskBounds = maskBounds; exports.bestOffset = bestOffset; exports.contentAwareFill = contentAwareFill;
|