@pixerate/editor 0.198.0 → 0.200.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 +26 -3
- package/dist/image-editor/controller.d.ts +26 -3
- package/dist/image-editor/index.cjs +1534 -1158
- package/dist/image-editor/index.js +631 -255
- package/dist/image-editor/selection/segment/focus.d.cts +22 -0
- package/dist/image-editor/selection/segment/focus.d.ts +22 -0
- package/dist/image-editor/selection/segment/grabcut.d.cts +18 -0
- package/dist/image-editor/selection/segment/grabcut.d.ts +18 -0
- package/dist/image-editor/selection/segment/guided.d.cts +13 -0
- package/dist/image-editor/selection/segment/guided.d.ts +13 -0
- package/dist/image-editor/selection/segment/index.d.cts +3 -0
- package/dist/image-editor/selection/segment/index.d.ts +3 -0
- package/dist/image-editor/selection/segment/subject.d.cts +44 -0
- package/dist/image-editor/selection/segment/subject.d.ts +44 -0
- package/dist/image-editor/selection/segment/work.d.cts +2 -2
- package/dist/image-editor/selection/segment/work.d.ts +2 -2
- package/dist/image-editor/selection/tool-options.d.cts +6 -0
- package/dist/image-editor/selection/tool-options.d.ts +6 -0
- package/dist/image-editor/selection-tools.d.cts +3 -3
- package/dist/image-editor/selection-tools.d.ts +3 -3
- package/package.json +1 -1
- package/skills/pixerate-image-editor/SKILL.md +2 -1
- package/skills/pixerate-image-selection-masking/SKILL.md +7 -2
|
@@ -3049,8 +3049,9 @@ var GridGraph = class {
|
|
|
3049
3049
|
var WORK_SIZE = 512;
|
|
3050
3050
|
var workCache = /* @__PURE__ */ new WeakMap();
|
|
3051
3051
|
function workImage(pixels, maxSide = WORK_SIZE) {
|
|
3052
|
-
const
|
|
3053
|
-
|
|
3052
|
+
const sizes = workCache.get(pixels.data);
|
|
3053
|
+
const cached = sizes?.get(maxSide);
|
|
3054
|
+
if (cached && cached.sourceWidth === pixels.width && cached.sourceHeight === pixels.height) return cached;
|
|
3054
3055
|
const { width: W, height: H, data } = pixels;
|
|
3055
3056
|
const scale = Math.min(1, maxSide / Math.max(W, H));
|
|
3056
3057
|
const w = Math.max(1, Math.round(W * scale));
|
|
@@ -3079,7 +3080,8 @@ function workImage(pixels, maxSide = WORK_SIZE) {
|
|
|
3079
3080
|
rgb[i * 3 + 2] = sum[i * 3 + 2] / c;
|
|
3080
3081
|
}
|
|
3081
3082
|
const work = { sourceWidth: W, sourceHeight: H, width: w, height: h, scale: w / W, rgb };
|
|
3082
|
-
|
|
3083
|
+
if (sizes) sizes.set(maxSide, work);
|
|
3084
|
+
else workCache.set(pixels.data, /* @__PURE__ */ new Map([[maxSide, work]]));
|
|
3083
3085
|
return work;
|
|
3084
3086
|
}
|
|
3085
3087
|
var GAMMA = 50;
|
|
@@ -3672,6 +3674,545 @@ function selectObjectIn(pixels, target, options = {}) {
|
|
|
3672
3674
|
}
|
|
3673
3675
|
}
|
|
3674
3676
|
|
|
3677
|
+
// src/image-editor/selection/segment/subject.ts
|
|
3678
|
+
function toLab(r, g, b) {
|
|
3679
|
+
const lin = (v) => {
|
|
3680
|
+
v /= 255;
|
|
3681
|
+
return v <= 0.04045 ? v / 12.92 : Math.pow((v + 0.055) / 1.055, 2.4);
|
|
3682
|
+
};
|
|
3683
|
+
const R = lin(r);
|
|
3684
|
+
const G = lin(g);
|
|
3685
|
+
const B = lin(b);
|
|
3686
|
+
const f = (t) => t > 8856e-6 ? Math.cbrt(t) : 7.787 * t + 16 / 116;
|
|
3687
|
+
const x = f((0.4124 * R + 0.3576 * G + 0.1805 * B) / 0.95047);
|
|
3688
|
+
const y = f(0.2126 * R + 0.7152 * G + 0.0722 * B);
|
|
3689
|
+
const z = f((0.0193 * R + 0.1192 * G + 0.9505 * B) / 1.08883);
|
|
3690
|
+
return [116 * y - 16, 500 * (x - y), 200 * (y - z)];
|
|
3691
|
+
}
|
|
3692
|
+
function slicSuperpixels(work, target = 400, compactness = 10, rounds = 5) {
|
|
3693
|
+
const { width: w, height: h, rgb } = work;
|
|
3694
|
+
const n = w * h;
|
|
3695
|
+
const lab = new Float32Array(n * 3);
|
|
3696
|
+
for (let i = 0; i < n; i++) lab.set(toLab(rgb[i * 3], rgb[i * 3 + 1], rgb[i * 3 + 2]), i * 3);
|
|
3697
|
+
const S = Math.max(2, Math.round(Math.sqrt(n / target)));
|
|
3698
|
+
const seeds = [];
|
|
3699
|
+
for (let y = Math.floor(S / 2); y < h; y += S) {
|
|
3700
|
+
for (let x = Math.floor(S / 2); x < w; x += S) {
|
|
3701
|
+
const i = y * w + x;
|
|
3702
|
+
seeds.push([lab[i * 3], lab[i * 3 + 1], lab[i * 3 + 2], x, y]);
|
|
3703
|
+
}
|
|
3704
|
+
}
|
|
3705
|
+
const k = seeds.length;
|
|
3706
|
+
const label = new Int32Array(n).fill(-1);
|
|
3707
|
+
const best = new Float64Array(n);
|
|
3708
|
+
const m2 = (compactness / S) ** 2;
|
|
3709
|
+
for (let round6 = 0; round6 < rounds; round6++) {
|
|
3710
|
+
best.fill(Infinity);
|
|
3711
|
+
for (let c = 0; c < k; c++) {
|
|
3712
|
+
const [L, A, B, cx, cy] = seeds[c];
|
|
3713
|
+
const x0 = Math.max(0, Math.floor(cx - S));
|
|
3714
|
+
const x1 = Math.min(w - 1, Math.ceil(cx + S));
|
|
3715
|
+
const y0 = Math.max(0, Math.floor(cy - S));
|
|
3716
|
+
const y1 = Math.min(h - 1, Math.ceil(cy + S));
|
|
3717
|
+
for (let y = y0; y <= y1; y++) {
|
|
3718
|
+
for (let x = x0; x <= x1; x++) {
|
|
3719
|
+
const i = y * w + x;
|
|
3720
|
+
const d = (lab[i * 3] - L) ** 2 + (lab[i * 3 + 1] - A) ** 2 + (lab[i * 3 + 2] - B) ** 2 + m2 * ((x - cx) ** 2 + (y - cy) ** 2);
|
|
3721
|
+
if (d < best[i]) {
|
|
3722
|
+
best[i] = d;
|
|
3723
|
+
label[i] = c;
|
|
3724
|
+
}
|
|
3725
|
+
}
|
|
3726
|
+
}
|
|
3727
|
+
}
|
|
3728
|
+
const sum = new Float64Array(k * 6);
|
|
3729
|
+
for (let i = 0; i < n; i++) {
|
|
3730
|
+
const c = label[i];
|
|
3731
|
+
sum[c * 6] += lab[i * 3];
|
|
3732
|
+
sum[c * 6 + 1] += lab[i * 3 + 1];
|
|
3733
|
+
sum[c * 6 + 2] += lab[i * 3 + 2];
|
|
3734
|
+
sum[c * 6 + 3] += i % w;
|
|
3735
|
+
sum[c * 6 + 4] += Math.floor(i / w);
|
|
3736
|
+
sum[c * 6 + 5]++;
|
|
3737
|
+
}
|
|
3738
|
+
for (let c = 0; c < k; c++) {
|
|
3739
|
+
const s = sum[c * 6 + 5];
|
|
3740
|
+
if (s) seeds[c] = [sum[c * 6] / s, sum[c * 6 + 1] / s, sum[c * 6 + 2] / s, sum[c * 6 + 3] / s, sum[c * 6 + 4] / s];
|
|
3741
|
+
}
|
|
3742
|
+
}
|
|
3743
|
+
const remap = new Int32Array(k).fill(-1);
|
|
3744
|
+
let count = 0;
|
|
3745
|
+
for (let i = 0; i < n; i++) if (remap[label[i]] < 0) remap[label[i]] = count++;
|
|
3746
|
+
const meanLab = new Float64Array(count * 3);
|
|
3747
|
+
const centre = new Float64Array(count * 2);
|
|
3748
|
+
const size = new Int32Array(count);
|
|
3749
|
+
for (let i = 0; i < n; i++) {
|
|
3750
|
+
const c = label[i] = remap[label[i]];
|
|
3751
|
+
meanLab[c * 3] += lab[i * 3];
|
|
3752
|
+
meanLab[c * 3 + 1] += lab[i * 3 + 1];
|
|
3753
|
+
meanLab[c * 3 + 2] += lab[i * 3 + 2];
|
|
3754
|
+
centre[c * 2] += i % w;
|
|
3755
|
+
centre[c * 2 + 1] += Math.floor(i / w);
|
|
3756
|
+
size[c]++;
|
|
3757
|
+
}
|
|
3758
|
+
for (let c = 0; c < count; c++) {
|
|
3759
|
+
for (let j = 0; j < 3; j++) meanLab[c * 3 + j] /= size[c];
|
|
3760
|
+
centre[c * 2] /= size[c];
|
|
3761
|
+
centre[c * 2 + 1] /= size[c];
|
|
3762
|
+
}
|
|
3763
|
+
return { label, count, lab: meanLab, centre, size };
|
|
3764
|
+
}
|
|
3765
|
+
function superpixelSaliency(sp, width, height) {
|
|
3766
|
+
const { count, lab, centre, size, label } = sp;
|
|
3767
|
+
const dist = (a, b) => Math.hypot(lab[a * 3] - lab[b * 3], lab[a * 3 + 1] - lab[b * 3 + 1], lab[a * 3 + 2] - lab[b * 3 + 2]);
|
|
3768
|
+
const onBorder = new Uint8Array(count);
|
|
3769
|
+
for (let x = 0; x < width; x++) {
|
|
3770
|
+
onBorder[label[x]] = 1;
|
|
3771
|
+
onBorder[label[(height - 1) * width + x]] = 1;
|
|
3772
|
+
}
|
|
3773
|
+
for (let y = 0; y < height; y++) {
|
|
3774
|
+
onBorder[label[y * width]] = 1;
|
|
3775
|
+
onBorder[label[y * width + width - 1]] = 1;
|
|
3776
|
+
}
|
|
3777
|
+
const border = [];
|
|
3778
|
+
for (let c = 0; c < count; c++) if (onBorder[c]) border.push(c);
|
|
3779
|
+
const diag = Math.hypot(width, height);
|
|
3780
|
+
const unlikeBorder = new Float64Array(count);
|
|
3781
|
+
const contrast = new Float64Array(count);
|
|
3782
|
+
const centreBias = new Float64Array(count);
|
|
3783
|
+
for (let i = 0; i < count; i++) {
|
|
3784
|
+
const ds = border.map((j) => dist(i, j)).sort((a, b) => a - b);
|
|
3785
|
+
unlikeBorder[i] = ds.length ? ds[Math.floor(ds.length * 0.25)] : 0;
|
|
3786
|
+
let s = 0;
|
|
3787
|
+
let wsum = 0;
|
|
3788
|
+
for (let j = 0; j < count; j++) {
|
|
3789
|
+
if (j === i) continue;
|
|
3790
|
+
const dx = (centre[i * 2] - centre[j * 2]) / diag;
|
|
3791
|
+
const dy = (centre[i * 2 + 1] - centre[j * 2 + 1]) / diag;
|
|
3792
|
+
const wgt = size[j] * Math.exp(-(dx * dx + dy * dy) / (2 * 0.25 * 0.25));
|
|
3793
|
+
s += wgt * dist(i, j);
|
|
3794
|
+
wsum += wgt;
|
|
3795
|
+
}
|
|
3796
|
+
contrast[i] = wsum ? s / wsum : 0;
|
|
3797
|
+
const cx = centre[i * 2] / width - 0.5;
|
|
3798
|
+
const cy = centre[i * 2 + 1] / height - 0.5;
|
|
3799
|
+
centreBias[i] = 0.4 + 0.6 * Math.exp(-(cx * cx + cy * cy) / (2 * 0.3 * 0.3));
|
|
3800
|
+
}
|
|
3801
|
+
let farthest = 0;
|
|
3802
|
+
for (const v of unlikeBorder) farthest = Math.max(farthest, v);
|
|
3803
|
+
if (farthest < 6) return new Float64Array(count);
|
|
3804
|
+
const norm = (a) => {
|
|
3805
|
+
let max = 0;
|
|
3806
|
+
for (const v of a) max = Math.max(max, v);
|
|
3807
|
+
if (max > 0) for (let i = 0; i < a.length; i++) a[i] /= max;
|
|
3808
|
+
};
|
|
3809
|
+
norm(unlikeBorder);
|
|
3810
|
+
norm(contrast);
|
|
3811
|
+
const out = new Float64Array(count);
|
|
3812
|
+
for (let i = 0; i < count; i++) out[i] = unlikeBorder[i] * (0.3 + 0.7 * contrast[i]) * centreBias[i];
|
|
3813
|
+
norm(out);
|
|
3814
|
+
return out;
|
|
3815
|
+
}
|
|
3816
|
+
function otsuThreshold(values, weights) {
|
|
3817
|
+
const BINS2 = 64;
|
|
3818
|
+
const hist = new Float64Array(BINS2);
|
|
3819
|
+
let total = 0;
|
|
3820
|
+
for (let i = 0; i < values.length; i++) {
|
|
3821
|
+
hist[Math.min(BINS2 - 1, Math.floor(values[i] * BINS2))] += weights[i];
|
|
3822
|
+
total += weights[i];
|
|
3823
|
+
}
|
|
3824
|
+
let sumAll = 0;
|
|
3825
|
+
for (let b = 0; b < BINS2; b++) sumAll += b * hist[b];
|
|
3826
|
+
let wB = 0;
|
|
3827
|
+
let sumB = 0;
|
|
3828
|
+
let best = 0;
|
|
3829
|
+
let bestT = 0.5;
|
|
3830
|
+
for (let b = 0; b < BINS2 - 1; b++) {
|
|
3831
|
+
wB += hist[b];
|
|
3832
|
+
if (!wB) continue;
|
|
3833
|
+
const wF = total - wB;
|
|
3834
|
+
if (!wF) break;
|
|
3835
|
+
sumB += b * hist[b];
|
|
3836
|
+
const between = wB * wF * (sumB / wB - (sumAll - sumB) / wF) ** 2;
|
|
3837
|
+
if (between > best) {
|
|
3838
|
+
best = between;
|
|
3839
|
+
bestT = (b + 1) / BINS2;
|
|
3840
|
+
}
|
|
3841
|
+
}
|
|
3842
|
+
return bestT;
|
|
3843
|
+
}
|
|
3844
|
+
function findSubject(pixels, options = {}) {
|
|
3845
|
+
const work = workImage(pixels);
|
|
3846
|
+
const { width: w, height: h } = work;
|
|
3847
|
+
const sp = slicSuperpixels(work);
|
|
3848
|
+
const sal = superpixelSaliency(sp, w, h);
|
|
3849
|
+
const t = otsuThreshold(sal, sp.size);
|
|
3850
|
+
const n = w * h;
|
|
3851
|
+
const initial = new Uint8Array(n);
|
|
3852
|
+
const hardBg = new Uint8Array(n);
|
|
3853
|
+
const hardFg = new Uint8Array(n);
|
|
3854
|
+
const strong = t + 0.6 * (1 - t);
|
|
3855
|
+
for (let i = 0; i < n; i++) {
|
|
3856
|
+
const s = sal[sp.label[i]];
|
|
3857
|
+
if (s >= t) initial[i] = 1;
|
|
3858
|
+
if (s >= strong) hardFg[i] = 1;
|
|
3859
|
+
}
|
|
3860
|
+
for (let x = 0; x < w; x++) {
|
|
3861
|
+
for (const y of [0, h - 1]) if (sal[sp.label[y * w + x]] < t * 0.5) hardBg[y * w + x] = 1;
|
|
3862
|
+
}
|
|
3863
|
+
for (let y = 0; y < h; y++) {
|
|
3864
|
+
for (const x of [0, w - 1]) if (sal[sp.label[y * w + x]] < t * 0.5) hardBg[y * w + x] = 1;
|
|
3865
|
+
}
|
|
3866
|
+
const empty = (reason) => ({ selection: createSelectionMask(pixels.width, pixels.height), reason });
|
|
3867
|
+
let guessed = 0;
|
|
3868
|
+
for (let i = 0; i < n; i++) guessed += initial[i];
|
|
3869
|
+
if (!guessed || guessed > n * 0.95) return empty("Nothing stands out from the background.");
|
|
3870
|
+
const roi = { x: 0, y: 0, width: w, height: h };
|
|
3871
|
+
const label = grabCutLabels(work, { roi, initial, hardBg, hardFg }, 5);
|
|
3872
|
+
keepMainPieces(label, w, h, null);
|
|
3873
|
+
let kept = 0;
|
|
3874
|
+
for (let i = 0; i < n; i++) kept += label[i];
|
|
3875
|
+
if (kept < n * 2e-3) return empty("Nothing stands out from the background.");
|
|
3876
|
+
if (kept > n * 0.97) return empty("The whole image looks like one subject; select it with select_all instead.");
|
|
3877
|
+
return { selection: upscaleRegion(work, pixels, roi, label, options.enhanceEdge ?? false) };
|
|
3878
|
+
}
|
|
3879
|
+
|
|
3880
|
+
// src/image-editor/selection/refine.ts
|
|
3881
|
+
var DEFAULT_REFINE_EDGE_PARAMS = {
|
|
3882
|
+
radius: 0,
|
|
3883
|
+
smartRadius: false,
|
|
3884
|
+
smooth: 0,
|
|
3885
|
+
feather: 0,
|
|
3886
|
+
contrast: 0,
|
|
3887
|
+
shiftEdge: 0
|
|
3888
|
+
};
|
|
3889
|
+
var GUIDED_FILTER_EPS = 1e-4;
|
|
3890
|
+
var fin = (v, d) => typeof v === "number" && Number.isFinite(v) ? v : d;
|
|
3891
|
+
function normalizeRefineEdgeParams(p = {}) {
|
|
3892
|
+
return {
|
|
3893
|
+
radius: Math.min(250, Math.max(0, fin(p.radius, 0))),
|
|
3894
|
+
smartRadius: p.smartRadius === true,
|
|
3895
|
+
smooth: Math.min(100, Math.max(0, fin(p.smooth, 0))),
|
|
3896
|
+
feather: Math.min(1e3, Math.max(0, fin(p.feather, 0))),
|
|
3897
|
+
contrast: Math.min(100, Math.max(0, fin(p.contrast, 0))),
|
|
3898
|
+
shiftEdge: Math.min(100, Math.max(-100, fin(p.shiftEdge, 0)))
|
|
3899
|
+
};
|
|
3900
|
+
}
|
|
3901
|
+
var gfRadius = (p) => Math.min(64, Math.max(1, Math.ceil(p.radius)));
|
|
3902
|
+
var smoothSigma = (p) => p.smooth / 100 * 4;
|
|
3903
|
+
var featherSigma = (p) => p.feather / 2;
|
|
3904
|
+
function shiftAmount(p) {
|
|
3905
|
+
const px2 = p.shiftEdge / 100 * Math.max(p.radius, 4);
|
|
3906
|
+
return Number.isFinite(px2) ? Math.min(64, Math.max(-64, px2)) : 0;
|
|
3907
|
+
}
|
|
3908
|
+
function refineEdgeHalo(params = {}) {
|
|
3909
|
+
const p = normalizeRefineEdgeParams(params);
|
|
3910
|
+
const g = gfRadius(p);
|
|
3911
|
+
const edge = p.radius > 0 ? Math.max(Math.ceil(p.radius), 2 * g + (p.smartRadius ? g + 1 : 0)) : 0;
|
|
3912
|
+
return edge + Math.ceil(3 * smoothSigma(p)) + Math.ceil(3 * featherSigma(p)) + Math.ceil(Math.abs(shiftAmount(p))) + 2;
|
|
3913
|
+
}
|
|
3914
|
+
function boxMean(src, w, h, r) {
|
|
3915
|
+
const tmp = new Float32Array(w * h);
|
|
3916
|
+
const pre = new Float64Array(Math.max(w, h) + 1);
|
|
3917
|
+
for (let y = 0; y < h; y++) {
|
|
3918
|
+
const row = y * w;
|
|
3919
|
+
for (let x = 0; x < w; x++) pre[x + 1] = pre[x] + src[row + x];
|
|
3920
|
+
for (let x = 0; x < w; x++) {
|
|
3921
|
+
const lo = Math.max(0, x - r);
|
|
3922
|
+
const hi = Math.min(w, x + r + 1);
|
|
3923
|
+
tmp[row + x] = (pre[hi] - pre[lo]) / (hi - lo);
|
|
3924
|
+
}
|
|
3925
|
+
}
|
|
3926
|
+
const out = new Float32Array(w * h);
|
|
3927
|
+
for (let x = 0; x < w; x++) {
|
|
3928
|
+
for (let y = 0; y < h; y++) pre[y + 1] = pre[y] + tmp[y * w + x];
|
|
3929
|
+
for (let y = 0; y < h; y++) {
|
|
3930
|
+
const lo = Math.max(0, y - r);
|
|
3931
|
+
const hi = Math.min(h, y + r + 1);
|
|
3932
|
+
out[y * w + x] = (pre[hi] - pre[lo]) / (hi - lo);
|
|
3933
|
+
}
|
|
3934
|
+
}
|
|
3935
|
+
return out;
|
|
3936
|
+
}
|
|
3937
|
+
function guidedFilterColor(guide, p, w, h, r, eps = GUIDED_FILTER_EPS) {
|
|
3938
|
+
const n = w * h;
|
|
3939
|
+
const [c0, c1, c2] = guide;
|
|
3940
|
+
const prod = (a, b2) => {
|
|
3941
|
+
const o = new Float32Array(n);
|
|
3942
|
+
for (let i = 0; i < n; i++) o[i] = a[i] * b2[i];
|
|
3943
|
+
return o;
|
|
3944
|
+
};
|
|
3945
|
+
const m0 = boxMean(c0, w, h, r);
|
|
3946
|
+
const m1 = boxMean(c1, w, h, r);
|
|
3947
|
+
const m2 = boxMean(c2, w, h, r);
|
|
3948
|
+
const mp = boxMean(p, w, h, r);
|
|
3949
|
+
const mip0 = boxMean(prod(c0, p), w, h, r);
|
|
3950
|
+
const mip1 = boxMean(prod(c1, p), w, h, r);
|
|
3951
|
+
const mip2 = boxMean(prod(c2, p), w, h, r);
|
|
3952
|
+
const s00 = boxMean(prod(c0, c0), w, h, r);
|
|
3953
|
+
const s01 = boxMean(prod(c0, c1), w, h, r);
|
|
3954
|
+
const s02 = boxMean(prod(c0, c2), w, h, r);
|
|
3955
|
+
const s11 = boxMean(prod(c1, c1), w, h, r);
|
|
3956
|
+
const s12 = boxMean(prod(c1, c2), w, h, r);
|
|
3957
|
+
const s22 = boxMean(prod(c2, c2), w, h, r);
|
|
3958
|
+
const a0 = new Float32Array(n);
|
|
3959
|
+
const a1 = new Float32Array(n);
|
|
3960
|
+
const a2 = new Float32Array(n);
|
|
3961
|
+
const b = new Float32Array(n);
|
|
3962
|
+
for (let i = 0; i < n; i++) {
|
|
3963
|
+
const u0 = m0[i], u1 = m1[i], u2 = m2[i], up = mp[i];
|
|
3964
|
+
const rr = s00[i] - u0 * u0 + eps;
|
|
3965
|
+
const rg = s01[i] - u0 * u1;
|
|
3966
|
+
const rb = s02[i] - u0 * u2;
|
|
3967
|
+
const gg = s11[i] - u1 * u1 + eps;
|
|
3968
|
+
const gb = s12[i] - u1 * u2;
|
|
3969
|
+
const bb = s22[i] - u2 * u2 + eps;
|
|
3970
|
+
const v0 = mip0[i] - u0 * up;
|
|
3971
|
+
const v1 = mip1[i] - u1 * up;
|
|
3972
|
+
const v2 = mip2[i] - u2 * up;
|
|
3973
|
+
const i00 = gg * bb - gb * gb;
|
|
3974
|
+
const i01 = gb * rb - rg * bb;
|
|
3975
|
+
const i02 = rg * gb - gg * rb;
|
|
3976
|
+
const i11 = rr * bb - rb * rb;
|
|
3977
|
+
const i12 = rb * rg - rr * gb;
|
|
3978
|
+
const i22 = rr * gg - rg * rg;
|
|
3979
|
+
const det = rr * i00 + rg * i01 + rb * i02;
|
|
3980
|
+
let x0 = 0, x1 = 0, x2 = 0;
|
|
3981
|
+
if (Math.abs(det) >= 1e-30) {
|
|
3982
|
+
x0 = (i00 * v0 + i01 * v1 + i02 * v2) / det;
|
|
3983
|
+
x1 = (i01 * v0 + i11 * v1 + i12 * v2) / det;
|
|
3984
|
+
x2 = (i02 * v0 + i12 * v1 + i22 * v2) / det;
|
|
3985
|
+
}
|
|
3986
|
+
a0[i] = x0;
|
|
3987
|
+
a1[i] = x1;
|
|
3988
|
+
a2[i] = x2;
|
|
3989
|
+
b[i] = up - x0 * u0 - x1 * u1 - x2 * u2;
|
|
3990
|
+
}
|
|
3991
|
+
const ma0 = boxMean(a0, w, h, r);
|
|
3992
|
+
const ma1 = boxMean(a1, w, h, r);
|
|
3993
|
+
const ma2 = boxMean(a2, w, h, r);
|
|
3994
|
+
const mb = boxMean(b, w, h, r);
|
|
3995
|
+
const out = new Float32Array(n);
|
|
3996
|
+
for (let i = 0; i < n; i++) out[i] = ma0[i] * c0[i] + ma1[i] * c1[i] + ma2[i] * c2[i] + mb[i];
|
|
3997
|
+
return out;
|
|
3998
|
+
}
|
|
3999
|
+
function edgeWidth(guide, w, h, r) {
|
|
4000
|
+
const n = w * h;
|
|
4001
|
+
const y = new Float32Array(n);
|
|
4002
|
+
for (let i = 0; i < n; i++) y[i] = 0.299 * guide[0][i] + 0.587 * guide[1][i] + 0.114 * guide[2][i];
|
|
4003
|
+
const g = new Float32Array(n);
|
|
4004
|
+
for (let yy = 0; yy < h; yy++) {
|
|
4005
|
+
for (let xx = 0; xx < w; xx++) {
|
|
4006
|
+
const gx = (y[yy * w + Math.min(w - 1, xx + 1)] - y[yy * w + Math.max(0, xx - 1)]) / 2;
|
|
4007
|
+
const gy = (y[Math.min(h - 1, yy + 1) * w + xx] - y[Math.max(0, yy - 1) * w + xx]) / 2;
|
|
4008
|
+
g[yy * w + xx] = Math.sqrt(gx * gx + gy * gy);
|
|
4009
|
+
}
|
|
4010
|
+
}
|
|
4011
|
+
const gm = greyMorphology(g, w, h, r, true);
|
|
4012
|
+
const hi = greyMorphology(y, w, h, r, true);
|
|
4013
|
+
const lo = greyMorphology(y, w, h, r, false);
|
|
4014
|
+
const out = new Float32Array(n);
|
|
4015
|
+
for (let i = 0; i < n; i++) out[i] = (hi[i] - lo[i]) / Math.max(gm[i], 1e-4);
|
|
4016
|
+
return out;
|
|
4017
|
+
}
|
|
4018
|
+
function refineCoverage(guide, m, w, h, params = {}) {
|
|
4019
|
+
const p = normalizeRefineEdgeParams(params);
|
|
4020
|
+
let a = Float32Array.from(m);
|
|
4021
|
+
if (p.radius > 0 && guide) {
|
|
4022
|
+
const n = w * h;
|
|
4023
|
+
const boundary = new Uint8Array(n);
|
|
4024
|
+
let any = false;
|
|
4025
|
+
for (let y = 0; y < h; y++) {
|
|
4026
|
+
for (let x = 0; x < w; x++) {
|
|
4027
|
+
const i = y * w + x;
|
|
4028
|
+
const v = m[i] >= 0.5;
|
|
4029
|
+
const diff = x > 0 && m[i - 1] >= 0.5 !== v || x + 1 < w && m[i + 1] >= 0.5 !== v || y > 0 && m[i - w] >= 0.5 !== v || y + 1 < h && m[i + w] >= 0.5 !== v;
|
|
4030
|
+
if (diff) {
|
|
4031
|
+
boundary[i] = 1;
|
|
4032
|
+
any = true;
|
|
4033
|
+
}
|
|
4034
|
+
}
|
|
4035
|
+
}
|
|
4036
|
+
if (any) {
|
|
4037
|
+
const dist = distanceTransform(boundary, w, h);
|
|
4038
|
+
const r = gfRadius(p);
|
|
4039
|
+
const q = guidedFilterColor(guide, m, w, h, r);
|
|
4040
|
+
const widths = p.smartRadius ? edgeWidth(guide, w, h, r) : null;
|
|
4041
|
+
for (let i = 0; i < n; i++) {
|
|
4042
|
+
const reff = widths ? Math.min(Math.max(p.radius, 1.5), Math.max(1.5, widths[i])) : p.radius;
|
|
4043
|
+
const wgt = Math.min(1, Math.max(0, reff + 1 - dist[i]));
|
|
4044
|
+
if (wgt > 0) {
|
|
4045
|
+
const qi = Math.min(1, Math.max(0, q[i]));
|
|
4046
|
+
a[i] = m[i] + (qi - m[i]) * wgt;
|
|
4047
|
+
}
|
|
4048
|
+
}
|
|
4049
|
+
}
|
|
4050
|
+
}
|
|
4051
|
+
const ss = smoothSigma(p);
|
|
4052
|
+
if (ss >= 0.3) {
|
|
4053
|
+
a = gaussianBlur(a, w, h, ss, "clamp");
|
|
4054
|
+
const gain = 1 + ss;
|
|
4055
|
+
for (let i = 0; i < a.length; i++) a[i] = Math.min(1, Math.max(0, (a[i] - 0.5) * gain + 0.5));
|
|
4056
|
+
}
|
|
4057
|
+
a = gaussianBlur(a, w, h, featherSigma(p), "clamp");
|
|
4058
|
+
if (p.contrast > 0) {
|
|
4059
|
+
const gain = 1 / (1 - 0.99 * p.contrast / 100);
|
|
4060
|
+
for (let i = 0; i < a.length; i++) a[i] = Math.min(1, Math.max(0, (a[i] - 0.5) * gain + 0.5));
|
|
4061
|
+
}
|
|
4062
|
+
const amount = shiftAmount(p);
|
|
4063
|
+
if (Math.abs(amount) >= 1e-4) a = shiftCoverage(a, w, h, amount);
|
|
4064
|
+
return a;
|
|
4065
|
+
}
|
|
4066
|
+
function guidePlanes(pixels, win) {
|
|
4067
|
+
const box = win ?? { x: 0, y: 0, width: pixels.width, height: pixels.height };
|
|
4068
|
+
const n = box.width * box.height;
|
|
4069
|
+
const r = new Float32Array(n);
|
|
4070
|
+
const g = new Float32Array(n);
|
|
4071
|
+
const b = new Float32Array(n);
|
|
4072
|
+
const d = pixels.data;
|
|
4073
|
+
for (let y = 0; y < box.height; y++) {
|
|
4074
|
+
for (let x = 0; x < box.width; x++) {
|
|
4075
|
+
const i = y * box.width + x;
|
|
4076
|
+
const p = ((box.y + y) * pixels.width + box.x + x) * 4;
|
|
4077
|
+
r[i] = d[p] / 255;
|
|
4078
|
+
g[i] = d[p + 1] / 255;
|
|
4079
|
+
b[i] = d[p + 2] / 255;
|
|
4080
|
+
}
|
|
4081
|
+
}
|
|
4082
|
+
return [r, g, b];
|
|
4083
|
+
}
|
|
4084
|
+
function refineSelection(pixels, mask, params = {}) {
|
|
4085
|
+
const p = normalizeRefineEdgeParams(params);
|
|
4086
|
+
if (pixels && (pixels.width !== mask.width || pixels.height !== mask.height)) {
|
|
4087
|
+
throw new Error("Refine Edge needs pixels the size of the selection");
|
|
4088
|
+
}
|
|
4089
|
+
if (!mask.bounds) return mask;
|
|
4090
|
+
const halo = refineEdgeHalo(p);
|
|
4091
|
+
const x0 = Math.max(0, mask.bounds.x - halo);
|
|
4092
|
+
const y0 = Math.max(0, mask.bounds.y - halo);
|
|
4093
|
+
const x1 = Math.min(mask.width, mask.bounds.x + mask.bounds.width + halo);
|
|
4094
|
+
const y1 = Math.min(mask.height, mask.bounds.y + mask.bounds.height + halo);
|
|
4095
|
+
const ww = x1 - x0;
|
|
4096
|
+
const wh = y1 - y0;
|
|
4097
|
+
const m = new Float32Array(ww * wh);
|
|
4098
|
+
for (let y = 0; y < wh; y++) {
|
|
4099
|
+
const src = (y0 + y) * mask.width + x0;
|
|
4100
|
+
for (let x = 0; x < ww; x++) m[y * ww + x] = mask.data[src + x] / 255;
|
|
4101
|
+
}
|
|
4102
|
+
const guide = pixels && p.radius > 0 ? guidePlanes(pixels, { x: x0, y: y0, width: ww, height: wh }) : null;
|
|
4103
|
+
const refined = refineCoverage(guide, m, ww, wh, p);
|
|
4104
|
+
const out = new Uint8Array(mask.width * mask.height);
|
|
4105
|
+
for (let y = 0; y < wh; y++) {
|
|
4106
|
+
const dst = (y0 + y) * mask.width + x0;
|
|
4107
|
+
for (let x = 0; x < ww; x++) out[dst + x] = coverageToByte(refined[y * ww + x]);
|
|
4108
|
+
}
|
|
4109
|
+
return createSelectionMask(mask.width, mask.height, out);
|
|
4110
|
+
}
|
|
4111
|
+
|
|
4112
|
+
// src/image-editor/selection/segment/guided.ts
|
|
4113
|
+
function workPlanes(work) {
|
|
4114
|
+
const n = work.width * work.height;
|
|
4115
|
+
const planes = [new Float32Array(n), new Float32Array(n), new Float32Array(n)];
|
|
4116
|
+
for (let i = 0; i < n; i++) for (let c = 0; c < 3; c++) planes[c][i] = work.rgb[i * 3 + c] / 255;
|
|
4117
|
+
return planes;
|
|
4118
|
+
}
|
|
4119
|
+
function workLuma(work) {
|
|
4120
|
+
const n = work.width * work.height;
|
|
4121
|
+
const out = new Float32Array(n);
|
|
4122
|
+
const { rgb } = work;
|
|
4123
|
+
for (let i = 0; i < n; i++) out[i] = (0.299 * rgb[i * 3] + 0.587 * rgb[i * 3 + 1] + 0.114 * rgb[i * 3 + 2]) / 255;
|
|
4124
|
+
return out;
|
|
4125
|
+
}
|
|
4126
|
+
function softMaskToSelection(work, mask) {
|
|
4127
|
+
const { sourceWidth: W, sourceHeight: H, width: w, height: h, scale } = work;
|
|
4128
|
+
const out = new Uint8Array(W * H);
|
|
4129
|
+
const col = new Int32Array(W);
|
|
4130
|
+
const fx = new Float32Array(W);
|
|
4131
|
+
for (let X = 0; X < W; X++) {
|
|
4132
|
+
const u = Math.min(w - 1, Math.max(0, (X + 0.5) * scale - 0.5));
|
|
4133
|
+
col[X] = Math.min(w - 2, Math.floor(u));
|
|
4134
|
+
fx[X] = u - col[X];
|
|
4135
|
+
if (w === 1) {
|
|
4136
|
+
col[X] = 0;
|
|
4137
|
+
fx[X] = 0;
|
|
4138
|
+
}
|
|
4139
|
+
}
|
|
4140
|
+
for (let Y = 0; Y < H; Y++) {
|
|
4141
|
+
const v = Math.min(h - 1, Math.max(0, (Y + 0.5) * scale - 0.5));
|
|
4142
|
+
const j = h === 1 ? 0 : Math.min(h - 2, Math.floor(v));
|
|
4143
|
+
const fy = h === 1 ? 0 : v - j;
|
|
4144
|
+
const r0 = j * w;
|
|
4145
|
+
const r1 = h === 1 ? r0 : r0 + w;
|
|
4146
|
+
for (let X = 0; X < W; X++) {
|
|
4147
|
+
const i = col[X];
|
|
4148
|
+
const i1 = w === 1 ? i : i + 1;
|
|
4149
|
+
const t = fx[X];
|
|
4150
|
+
const m = (mask[r0 + i] * (1 - t) + mask[r0 + i1] * t) * (1 - fy) + (mask[r1 + i] * (1 - t) + mask[r1 + i1] * t) * fy;
|
|
4151
|
+
out[Y * W + X] = Math.round(Math.min(1, Math.max(0, m)) * 255);
|
|
4152
|
+
}
|
|
4153
|
+
}
|
|
4154
|
+
return selectionFromRegion(W, H, { x: 0, y: 0, width: W, height: H }, out);
|
|
4155
|
+
}
|
|
4156
|
+
|
|
4157
|
+
// src/image-editor/selection/segment/focus.ts
|
|
4158
|
+
var FOCUS_SIZE = 1024;
|
|
4159
|
+
function estimateNoise(luma2, w, h) {
|
|
4160
|
+
if (w < 3 || h < 3) return 0;
|
|
4161
|
+
let sum = 0;
|
|
4162
|
+
for (let y = 1; y < h - 1; y++) {
|
|
4163
|
+
for (let x = 1; x < w - 1; x++) {
|
|
4164
|
+
const i = y * w + x;
|
|
4165
|
+
const v = luma2[i - w - 1] - 2 * luma2[i - w] + luma2[i - w + 1] - 2 * luma2[i - 1] + 4 * luma2[i] - 2 * luma2[i + 1] + luma2[i + w - 1] - 2 * luma2[i + w] + luma2[i + w + 1];
|
|
4166
|
+
sum += Math.abs(v);
|
|
4167
|
+
}
|
|
4168
|
+
}
|
|
4169
|
+
return Math.sqrt(Math.PI / 2) * sum / (6 * (w - 2) * (h - 2));
|
|
4170
|
+
}
|
|
4171
|
+
function focusArea(pixels, options = {}) {
|
|
4172
|
+
const work = workImage(pixels, FOCUS_SIZE);
|
|
4173
|
+
const { width: w, height: h, scale } = work;
|
|
4174
|
+
const n = w * h;
|
|
4175
|
+
const luma2 = workLuma(work);
|
|
4176
|
+
const sigma = estimateNoise(luma2, w, h);
|
|
4177
|
+
const noise = Math.min(100, Math.max(0, options.noise ?? 0));
|
|
4178
|
+
const floor = 2 * sigma * (1 + noise / 25);
|
|
4179
|
+
const detail = new Float32Array(n);
|
|
4180
|
+
for (let y = 1; y < h - 1; y++) {
|
|
4181
|
+
for (let x = 1; x < w - 1; x++) {
|
|
4182
|
+
const i = y * w + x;
|
|
4183
|
+
const lap = Math.abs(4 * luma2[i] - luma2[i - 1] - luma2[i + 1] - luma2[i - w] - luma2[i + w]);
|
|
4184
|
+
const d = Math.max(0, lap - floor);
|
|
4185
|
+
detail[i] = d * d;
|
|
4186
|
+
}
|
|
4187
|
+
}
|
|
4188
|
+
const r = Math.max(2, Math.round(0.012 * Math.max(w, h)));
|
|
4189
|
+
const energy = boxMean(detail, w, h, r);
|
|
4190
|
+
const sharp = new Float32Array(n);
|
|
4191
|
+
for (let i = 0; i < n; i++) sharp[i] = Math.log1p(Math.sqrt(energy[i]) * 400);
|
|
4192
|
+
const sorted = Float32Array.from(sharp).sort();
|
|
4193
|
+
const top = sorted[Math.floor(n * 0.995)] || 1;
|
|
4194
|
+
for (let i = 0; i < n; i++) sharp[i] = Math.min(1, sharp[i] / top);
|
|
4195
|
+
const ones = new Float32Array(n).fill(1);
|
|
4196
|
+
const otsu = otsuThreshold(sharp, ones);
|
|
4197
|
+
const range = Math.min(100, Math.max(0, options.range ?? 50));
|
|
4198
|
+
const t = Math.min(0.98, otsu * Math.pow(2, (50 - range) / 25));
|
|
4199
|
+
let mask = new Float32Array(n);
|
|
4200
|
+
for (let i = 0; i < n; i++) mask[i] = sharp[i] >= t ? 1 : 0;
|
|
4201
|
+
const c = 2 * r;
|
|
4202
|
+
const grown = boxMean(mask, w, h, c);
|
|
4203
|
+
for (let i = 0; i < n; i++) grown[i] = grown[i] > 1e-3 ? 1 : 0;
|
|
4204
|
+
const shrunk = boxMean(grown, w, h, c);
|
|
4205
|
+
for (let i = 0; i < n; i++) mask[i] = shrunk[i] >= 0.999 ? 1 : 0;
|
|
4206
|
+
const pulled = boxMean(mask, w, h, Math.max(1, Math.round(r / 2)));
|
|
4207
|
+
for (let i = 0; i < n; i++) mask[i] = pulled[i] >= 0.999 ? 1 : 0;
|
|
4208
|
+
const softness = Math.max(0, options.softness ?? 4) * scale;
|
|
4209
|
+
if (softness > 0) {
|
|
4210
|
+
const fitted = guidedFilterColor(workPlanes(work), mask, w, h, Math.max(1, Math.round(softness * 2)), 1e-3);
|
|
4211
|
+
for (let i = 0; i < n; i++) mask[i] = Math.min(1, Math.max(0, fitted[i]));
|
|
4212
|
+
}
|
|
4213
|
+
return { selection: softMaskToSelection(work, mask), noiseLevel: Math.round(sigma * 255 * 10) / 10 };
|
|
4214
|
+
}
|
|
4215
|
+
|
|
3675
4216
|
// src/image-editor/layers/shape-type-ui.ts
|
|
3676
4217
|
var DEFAULT_SHAPE_TOOL_OPTIONS = {
|
|
3677
4218
|
kind: "rect",
|
|
@@ -4597,238 +5138,6 @@ function segmentsToSelection(width, height, results) {
|
|
|
4597
5138
|
return out;
|
|
4598
5139
|
}
|
|
4599
5140
|
|
|
4600
|
-
// src/image-editor/selection/refine.ts
|
|
4601
|
-
var DEFAULT_REFINE_EDGE_PARAMS = {
|
|
4602
|
-
radius: 0,
|
|
4603
|
-
smartRadius: false,
|
|
4604
|
-
smooth: 0,
|
|
4605
|
-
feather: 0,
|
|
4606
|
-
contrast: 0,
|
|
4607
|
-
shiftEdge: 0
|
|
4608
|
-
};
|
|
4609
|
-
var GUIDED_FILTER_EPS = 1e-4;
|
|
4610
|
-
var fin = (v, d) => typeof v === "number" && Number.isFinite(v) ? v : d;
|
|
4611
|
-
function normalizeRefineEdgeParams(p = {}) {
|
|
4612
|
-
return {
|
|
4613
|
-
radius: Math.min(250, Math.max(0, fin(p.radius, 0))),
|
|
4614
|
-
smartRadius: p.smartRadius === true,
|
|
4615
|
-
smooth: Math.min(100, Math.max(0, fin(p.smooth, 0))),
|
|
4616
|
-
feather: Math.min(1e3, Math.max(0, fin(p.feather, 0))),
|
|
4617
|
-
contrast: Math.min(100, Math.max(0, fin(p.contrast, 0))),
|
|
4618
|
-
shiftEdge: Math.min(100, Math.max(-100, fin(p.shiftEdge, 0)))
|
|
4619
|
-
};
|
|
4620
|
-
}
|
|
4621
|
-
var gfRadius = (p) => Math.min(64, Math.max(1, Math.ceil(p.radius)));
|
|
4622
|
-
var smoothSigma = (p) => p.smooth / 100 * 4;
|
|
4623
|
-
var featherSigma = (p) => p.feather / 2;
|
|
4624
|
-
function shiftAmount(p) {
|
|
4625
|
-
const px2 = p.shiftEdge / 100 * Math.max(p.radius, 4);
|
|
4626
|
-
return Number.isFinite(px2) ? Math.min(64, Math.max(-64, px2)) : 0;
|
|
4627
|
-
}
|
|
4628
|
-
function refineEdgeHalo(params = {}) {
|
|
4629
|
-
const p = normalizeRefineEdgeParams(params);
|
|
4630
|
-
const g = gfRadius(p);
|
|
4631
|
-
const edge = p.radius > 0 ? Math.max(Math.ceil(p.radius), 2 * g + (p.smartRadius ? g + 1 : 0)) : 0;
|
|
4632
|
-
return edge + Math.ceil(3 * smoothSigma(p)) + Math.ceil(3 * featherSigma(p)) + Math.ceil(Math.abs(shiftAmount(p))) + 2;
|
|
4633
|
-
}
|
|
4634
|
-
function boxMean(src, w, h, r) {
|
|
4635
|
-
const tmp = new Float32Array(w * h);
|
|
4636
|
-
const pre = new Float64Array(Math.max(w, h) + 1);
|
|
4637
|
-
for (let y = 0; y < h; y++) {
|
|
4638
|
-
const row = y * w;
|
|
4639
|
-
for (let x = 0; x < w; x++) pre[x + 1] = pre[x] + src[row + x];
|
|
4640
|
-
for (let x = 0; x < w; x++) {
|
|
4641
|
-
const lo = Math.max(0, x - r);
|
|
4642
|
-
const hi = Math.min(w, x + r + 1);
|
|
4643
|
-
tmp[row + x] = (pre[hi] - pre[lo]) / (hi - lo);
|
|
4644
|
-
}
|
|
4645
|
-
}
|
|
4646
|
-
const out = new Float32Array(w * h);
|
|
4647
|
-
for (let x = 0; x < w; x++) {
|
|
4648
|
-
for (let y = 0; y < h; y++) pre[y + 1] = pre[y] + tmp[y * w + x];
|
|
4649
|
-
for (let y = 0; y < h; y++) {
|
|
4650
|
-
const lo = Math.max(0, y - r);
|
|
4651
|
-
const hi = Math.min(h, y + r + 1);
|
|
4652
|
-
out[y * w + x] = (pre[hi] - pre[lo]) / (hi - lo);
|
|
4653
|
-
}
|
|
4654
|
-
}
|
|
4655
|
-
return out;
|
|
4656
|
-
}
|
|
4657
|
-
function guidedFilterColor(guide, p, w, h, r, eps = GUIDED_FILTER_EPS) {
|
|
4658
|
-
const n = w * h;
|
|
4659
|
-
const [c0, c1, c2] = guide;
|
|
4660
|
-
const prod = (a, b2) => {
|
|
4661
|
-
const o = new Float32Array(n);
|
|
4662
|
-
for (let i = 0; i < n; i++) o[i] = a[i] * b2[i];
|
|
4663
|
-
return o;
|
|
4664
|
-
};
|
|
4665
|
-
const m0 = boxMean(c0, w, h, r);
|
|
4666
|
-
const m1 = boxMean(c1, w, h, r);
|
|
4667
|
-
const m2 = boxMean(c2, w, h, r);
|
|
4668
|
-
const mp = boxMean(p, w, h, r);
|
|
4669
|
-
const mip0 = boxMean(prod(c0, p), w, h, r);
|
|
4670
|
-
const mip1 = boxMean(prod(c1, p), w, h, r);
|
|
4671
|
-
const mip2 = boxMean(prod(c2, p), w, h, r);
|
|
4672
|
-
const s00 = boxMean(prod(c0, c0), w, h, r);
|
|
4673
|
-
const s01 = boxMean(prod(c0, c1), w, h, r);
|
|
4674
|
-
const s02 = boxMean(prod(c0, c2), w, h, r);
|
|
4675
|
-
const s11 = boxMean(prod(c1, c1), w, h, r);
|
|
4676
|
-
const s12 = boxMean(prod(c1, c2), w, h, r);
|
|
4677
|
-
const s22 = boxMean(prod(c2, c2), w, h, r);
|
|
4678
|
-
const a0 = new Float32Array(n);
|
|
4679
|
-
const a1 = new Float32Array(n);
|
|
4680
|
-
const a2 = new Float32Array(n);
|
|
4681
|
-
const b = new Float32Array(n);
|
|
4682
|
-
for (let i = 0; i < n; i++) {
|
|
4683
|
-
const u0 = m0[i], u1 = m1[i], u2 = m2[i], up = mp[i];
|
|
4684
|
-
const rr = s00[i] - u0 * u0 + eps;
|
|
4685
|
-
const rg = s01[i] - u0 * u1;
|
|
4686
|
-
const rb = s02[i] - u0 * u2;
|
|
4687
|
-
const gg = s11[i] - u1 * u1 + eps;
|
|
4688
|
-
const gb = s12[i] - u1 * u2;
|
|
4689
|
-
const bb = s22[i] - u2 * u2 + eps;
|
|
4690
|
-
const v0 = mip0[i] - u0 * up;
|
|
4691
|
-
const v1 = mip1[i] - u1 * up;
|
|
4692
|
-
const v2 = mip2[i] - u2 * up;
|
|
4693
|
-
const i00 = gg * bb - gb * gb;
|
|
4694
|
-
const i01 = gb * rb - rg * bb;
|
|
4695
|
-
const i02 = rg * gb - gg * rb;
|
|
4696
|
-
const i11 = rr * bb - rb * rb;
|
|
4697
|
-
const i12 = rb * rg - rr * gb;
|
|
4698
|
-
const i22 = rr * gg - rg * rg;
|
|
4699
|
-
const det = rr * i00 + rg * i01 + rb * i02;
|
|
4700
|
-
let x0 = 0, x1 = 0, x2 = 0;
|
|
4701
|
-
if (Math.abs(det) >= 1e-30) {
|
|
4702
|
-
x0 = (i00 * v0 + i01 * v1 + i02 * v2) / det;
|
|
4703
|
-
x1 = (i01 * v0 + i11 * v1 + i12 * v2) / det;
|
|
4704
|
-
x2 = (i02 * v0 + i12 * v1 + i22 * v2) / det;
|
|
4705
|
-
}
|
|
4706
|
-
a0[i] = x0;
|
|
4707
|
-
a1[i] = x1;
|
|
4708
|
-
a2[i] = x2;
|
|
4709
|
-
b[i] = up - x0 * u0 - x1 * u1 - x2 * u2;
|
|
4710
|
-
}
|
|
4711
|
-
const ma0 = boxMean(a0, w, h, r);
|
|
4712
|
-
const ma1 = boxMean(a1, w, h, r);
|
|
4713
|
-
const ma2 = boxMean(a2, w, h, r);
|
|
4714
|
-
const mb = boxMean(b, w, h, r);
|
|
4715
|
-
const out = new Float32Array(n);
|
|
4716
|
-
for (let i = 0; i < n; i++) out[i] = ma0[i] * c0[i] + ma1[i] * c1[i] + ma2[i] * c2[i] + mb[i];
|
|
4717
|
-
return out;
|
|
4718
|
-
}
|
|
4719
|
-
function edgeWidth(guide, w, h, r) {
|
|
4720
|
-
const n = w * h;
|
|
4721
|
-
const y = new Float32Array(n);
|
|
4722
|
-
for (let i = 0; i < n; i++) y[i] = 0.299 * guide[0][i] + 0.587 * guide[1][i] + 0.114 * guide[2][i];
|
|
4723
|
-
const g = new Float32Array(n);
|
|
4724
|
-
for (let yy = 0; yy < h; yy++) {
|
|
4725
|
-
for (let xx = 0; xx < w; xx++) {
|
|
4726
|
-
const gx = (y[yy * w + Math.min(w - 1, xx + 1)] - y[yy * w + Math.max(0, xx - 1)]) / 2;
|
|
4727
|
-
const gy = (y[Math.min(h - 1, yy + 1) * w + xx] - y[Math.max(0, yy - 1) * w + xx]) / 2;
|
|
4728
|
-
g[yy * w + xx] = Math.sqrt(gx * gx + gy * gy);
|
|
4729
|
-
}
|
|
4730
|
-
}
|
|
4731
|
-
const gm = greyMorphology(g, w, h, r, true);
|
|
4732
|
-
const hi = greyMorphology(y, w, h, r, true);
|
|
4733
|
-
const lo = greyMorphology(y, w, h, r, false);
|
|
4734
|
-
const out = new Float32Array(n);
|
|
4735
|
-
for (let i = 0; i < n; i++) out[i] = (hi[i] - lo[i]) / Math.max(gm[i], 1e-4);
|
|
4736
|
-
return out;
|
|
4737
|
-
}
|
|
4738
|
-
function refineCoverage(guide, m, w, h, params = {}) {
|
|
4739
|
-
const p = normalizeRefineEdgeParams(params);
|
|
4740
|
-
let a = Float32Array.from(m);
|
|
4741
|
-
if (p.radius > 0 && guide) {
|
|
4742
|
-
const n = w * h;
|
|
4743
|
-
const boundary = new Uint8Array(n);
|
|
4744
|
-
let any = false;
|
|
4745
|
-
for (let y = 0; y < h; y++) {
|
|
4746
|
-
for (let x = 0; x < w; x++) {
|
|
4747
|
-
const i = y * w + x;
|
|
4748
|
-
const v = m[i] >= 0.5;
|
|
4749
|
-
const diff = x > 0 && m[i - 1] >= 0.5 !== v || x + 1 < w && m[i + 1] >= 0.5 !== v || y > 0 && m[i - w] >= 0.5 !== v || y + 1 < h && m[i + w] >= 0.5 !== v;
|
|
4750
|
-
if (diff) {
|
|
4751
|
-
boundary[i] = 1;
|
|
4752
|
-
any = true;
|
|
4753
|
-
}
|
|
4754
|
-
}
|
|
4755
|
-
}
|
|
4756
|
-
if (any) {
|
|
4757
|
-
const dist = distanceTransform(boundary, w, h);
|
|
4758
|
-
const r = gfRadius(p);
|
|
4759
|
-
const q = guidedFilterColor(guide, m, w, h, r);
|
|
4760
|
-
const widths = p.smartRadius ? edgeWidth(guide, w, h, r) : null;
|
|
4761
|
-
for (let i = 0; i < n; i++) {
|
|
4762
|
-
const reff = widths ? Math.min(Math.max(p.radius, 1.5), Math.max(1.5, widths[i])) : p.radius;
|
|
4763
|
-
const wgt = Math.min(1, Math.max(0, reff + 1 - dist[i]));
|
|
4764
|
-
if (wgt > 0) {
|
|
4765
|
-
const qi = Math.min(1, Math.max(0, q[i]));
|
|
4766
|
-
a[i] = m[i] + (qi - m[i]) * wgt;
|
|
4767
|
-
}
|
|
4768
|
-
}
|
|
4769
|
-
}
|
|
4770
|
-
}
|
|
4771
|
-
const ss = smoothSigma(p);
|
|
4772
|
-
if (ss >= 0.3) {
|
|
4773
|
-
a = gaussianBlur(a, w, h, ss, "clamp");
|
|
4774
|
-
const gain = 1 + ss;
|
|
4775
|
-
for (let i = 0; i < a.length; i++) a[i] = Math.min(1, Math.max(0, (a[i] - 0.5) * gain + 0.5));
|
|
4776
|
-
}
|
|
4777
|
-
a = gaussianBlur(a, w, h, featherSigma(p), "clamp");
|
|
4778
|
-
if (p.contrast > 0) {
|
|
4779
|
-
const gain = 1 / (1 - 0.99 * p.contrast / 100);
|
|
4780
|
-
for (let i = 0; i < a.length; i++) a[i] = Math.min(1, Math.max(0, (a[i] - 0.5) * gain + 0.5));
|
|
4781
|
-
}
|
|
4782
|
-
const amount = shiftAmount(p);
|
|
4783
|
-
if (Math.abs(amount) >= 1e-4) a = shiftCoverage(a, w, h, amount);
|
|
4784
|
-
return a;
|
|
4785
|
-
}
|
|
4786
|
-
function guidePlanes(pixels, win) {
|
|
4787
|
-
const box = win ?? { x: 0, y: 0, width: pixels.width, height: pixels.height };
|
|
4788
|
-
const n = box.width * box.height;
|
|
4789
|
-
const r = new Float32Array(n);
|
|
4790
|
-
const g = new Float32Array(n);
|
|
4791
|
-
const b = new Float32Array(n);
|
|
4792
|
-
const d = pixels.data;
|
|
4793
|
-
for (let y = 0; y < box.height; y++) {
|
|
4794
|
-
for (let x = 0; x < box.width; x++) {
|
|
4795
|
-
const i = y * box.width + x;
|
|
4796
|
-
const p = ((box.y + y) * pixels.width + box.x + x) * 4;
|
|
4797
|
-
r[i] = d[p] / 255;
|
|
4798
|
-
g[i] = d[p + 1] / 255;
|
|
4799
|
-
b[i] = d[p + 2] / 255;
|
|
4800
|
-
}
|
|
4801
|
-
}
|
|
4802
|
-
return [r, g, b];
|
|
4803
|
-
}
|
|
4804
|
-
function refineSelection(pixels, mask, params = {}) {
|
|
4805
|
-
const p = normalizeRefineEdgeParams(params);
|
|
4806
|
-
if (pixels && (pixels.width !== mask.width || pixels.height !== mask.height)) {
|
|
4807
|
-
throw new Error("Refine Edge needs pixels the size of the selection");
|
|
4808
|
-
}
|
|
4809
|
-
if (!mask.bounds) return mask;
|
|
4810
|
-
const halo = refineEdgeHalo(p);
|
|
4811
|
-
const x0 = Math.max(0, mask.bounds.x - halo);
|
|
4812
|
-
const y0 = Math.max(0, mask.bounds.y - halo);
|
|
4813
|
-
const x1 = Math.min(mask.width, mask.bounds.x + mask.bounds.width + halo);
|
|
4814
|
-
const y1 = Math.min(mask.height, mask.bounds.y + mask.bounds.height + halo);
|
|
4815
|
-
const ww = x1 - x0;
|
|
4816
|
-
const wh = y1 - y0;
|
|
4817
|
-
const m = new Float32Array(ww * wh);
|
|
4818
|
-
for (let y = 0; y < wh; y++) {
|
|
4819
|
-
const src = (y0 + y) * mask.width + x0;
|
|
4820
|
-
for (let x = 0; x < ww; x++) m[y * ww + x] = mask.data[src + x] / 255;
|
|
4821
|
-
}
|
|
4822
|
-
const guide = pixels && p.radius > 0 ? guidePlanes(pixels, { x: x0, y: y0, width: ww, height: wh }) : null;
|
|
4823
|
-
const refined = refineCoverage(guide, m, ww, wh, p);
|
|
4824
|
-
const out = new Uint8Array(mask.width * mask.height);
|
|
4825
|
-
for (let y = 0; y < wh; y++) {
|
|
4826
|
-
const dst = (y0 + y) * mask.width + x0;
|
|
4827
|
-
for (let x = 0; x < ww; x++) out[dst + x] = coverageToByte(refined[y * ww + x]);
|
|
4828
|
-
}
|
|
4829
|
-
return createSelectionMask(mask.width, mask.height, out);
|
|
4830
|
-
}
|
|
4831
|
-
|
|
4832
5141
|
// src/image-editor/selection/stroke.ts
|
|
4833
5142
|
function selectionRing(sel, width, location) {
|
|
4834
5143
|
const half = location === "center" ? width / 2 : width;
|
|
@@ -6459,7 +6768,7 @@ var ImageEditorController = class {
|
|
|
6459
6768
|
this.state.layers = next;
|
|
6460
6769
|
this.notify();
|
|
6461
6770
|
}
|
|
6462
|
-
/** The registered segmentation provider, or `null` (then `selectSubject`
|
|
6771
|
+
/** The registered segmentation provider, or `null` (then `selectSubject` finds the most salient subject without a model). */
|
|
6463
6772
|
get segmentationProvider() {
|
|
6464
6773
|
return this.segmentation;
|
|
6465
6774
|
}
|
|
@@ -9663,6 +9972,17 @@ var ImageEditorController = class {
|
|
|
9663
9972
|
this.setSelection(region, options.mode ?? "replace");
|
|
9664
9973
|
return this.state.selection ?? null;
|
|
9665
9974
|
}
|
|
9975
|
+
/**
|
|
9976
|
+
* Select › Focus Area: selects the parts of the image that are in focus (fine detail above the
|
|
9977
|
+
* image's noise), fitted to its edges, combined with the selection by `mode` as one undo step.
|
|
9978
|
+
* Resolves to the selection and the noise level measured (σ of luminance in levels).
|
|
9979
|
+
*/
|
|
9980
|
+
async selectFocusArea(options = {}) {
|
|
9981
|
+
const pixels = await this.requirePixels({ sample: options.sampleAllLayers === false ? "active-layer" : "composite" });
|
|
9982
|
+
const { selection, noiseLevel } = focusArea(pixels, options);
|
|
9983
|
+
this.setSelection(selection, options.mode ?? "replace");
|
|
9984
|
+
return { selection: this.state.selection ?? null, noiseLevel };
|
|
9985
|
+
}
|
|
9666
9986
|
/**
|
|
9667
9987
|
* Supplies the pixels the selection tools sample (natural size RGBA), e.g. decoded in a worker
|
|
9668
9988
|
* or in a test; `null` goes back to reading the source image. Without a canvas (Node, workers)
|
|
@@ -9831,11 +10151,24 @@ var ImageEditorController = class {
|
|
|
9831
10151
|
* Select › Subject: asks the segmentation provider for the objects matching `target` ("the
|
|
9832
10152
|
* dog", "the sky") in the flattened image and selects their shapes, combined with the
|
|
9833
10153
|
* selection by `mode`, as one undo step. Returns what was found (labels and bounds in natural
|
|
9834
|
-
* pixels); an empty list leaves the selection as it was.
|
|
10154
|
+
* pixels); an empty list leaves the selection as it was. Without a provider, the subject that
|
|
10155
|
+
* stands out most is found classically (superpixels, saliency, Otsu, GrabCut): `target` can't
|
|
10156
|
+
* choose it, and `method: 'saliency'` and a `note` say so.
|
|
9835
10157
|
*/
|
|
9836
|
-
async selectSubject(target, options = {}) {
|
|
10158
|
+
async selectSubject(target = "", options = {}) {
|
|
9837
10159
|
const provider = this.segmentation;
|
|
9838
|
-
if (!provider)
|
|
10160
|
+
if (!provider) {
|
|
10161
|
+
const pixels = await this.requirePixels({});
|
|
10162
|
+
options.signal?.throwIfAborted();
|
|
10163
|
+
const { selection, reason } = findSubject(pixels, { enhanceEdge: options.enhanceEdge });
|
|
10164
|
+
if (selection.bounds) this.shapeCommand(selection, options);
|
|
10165
|
+
const unused = target.trim() ? ` "${target.trim()}" was not used to choose it.` : "";
|
|
10166
|
+
return {
|
|
10167
|
+
segments: selection.bounds ? [{ label: "subject", bounds: selection.bounds }] : [],
|
|
10168
|
+
method: "saliency",
|
|
10169
|
+
note: reason ?? `No segmentation model is set, so the subject that stands out most was selected.${unused}`
|
|
10170
|
+
};
|
|
10171
|
+
}
|
|
9839
10172
|
const description = target.trim();
|
|
9840
10173
|
if (!description) throw new Error("Describe the subject to select.");
|
|
9841
10174
|
const { width, height } = this.naturalSize;
|
|
@@ -9861,7 +10194,7 @@ var ImageEditorController = class {
|
|
|
9861
10194
|
options.signal?.throwIfAborted();
|
|
9862
10195
|
}
|
|
9863
10196
|
if (mask) this.shapeCommand(mask, options);
|
|
9864
|
-
return { segments: segments.filter((s) => s.bounds) };
|
|
10197
|
+
return { segments: segments.filter((s) => s.bounds), method: "model" };
|
|
9865
10198
|
}
|
|
9866
10199
|
/** Quick Mask view (tints the unselected area). View state: no undo step. */
|
|
9867
10200
|
setQuickMask(on) {
|
|
@@ -11156,7 +11489,7 @@ var ImageEditorController = class {
|
|
|
11156
11489
|
}
|
|
11157
11490
|
if (src.colorSpace !== "rgb") throw new LayerOperationError("Colours are given as #rrggbb, so the source profile must be RGB.");
|
|
11158
11491
|
const t = Transform.create(src, dst, { intent: options.intent ?? "relative", bpc: options.bpc ?? true });
|
|
11159
|
-
const
|
|
11492
|
+
const toLab2 = Transform.create(src, builtinProfile("lab-d50"), { intent: "relative", bpc: false });
|
|
11160
11493
|
return colors.map((input) => {
|
|
11161
11494
|
const m = /^#?([0-9a-f]{6})$/i.exec(input.trim());
|
|
11162
11495
|
if (!m) throw new LayerOperationError(`Invalid colour "${input}"; use #rrggbb.`);
|
|
@@ -11166,7 +11499,7 @@ var ImageEditorController = class {
|
|
|
11166
11499
|
t.eval(rgb, out);
|
|
11167
11500
|
const values = Array.from(out.subarray(0, t.outputs), (v) => Math.round(Math.min(1, Math.max(0, v)) * 1e4) / 1e4);
|
|
11168
11501
|
const l = new Float32Array(3);
|
|
11169
|
-
|
|
11502
|
+
toLab2.eval(rgb, l);
|
|
11170
11503
|
const lab = [Math.round(l[0] * 1e4) / 100, Math.round((l[1] * 255 - 128) * 100) / 100, Math.round((l[2] * 255 - 128) * 100) / 100];
|
|
11171
11504
|
const res = { input, values, lab };
|
|
11172
11505
|
if (dst.colorSpace === "rgb") res.hex = `#${values.map((v) => Math.round(v * 255).toString(16).padStart(2, "0")).join("")}`;
|
|
@@ -12550,7 +12883,7 @@ var DEPTH_MASK_CONTROLS = [
|
|
|
12550
12883
|
// src/image-editor/playback.ts
|
|
12551
12884
|
var IMAGE_PLAYBACK_KIND = "layer";
|
|
12552
12885
|
var PAINT = /^(paint_stroke|clone_stamp|heal_brush|spot_heal|dodge|burn|sponge|smudge|blur_stroke|sharpen_stroke|history_brush|set_clone_source|set_clone_transform|fill_pixels|clear_pixels|set_paint|set_retouch_options|load_mixer_brush|clean_mixer_brush)$/;
|
|
12553
|
-
var SELECT = /^(select_region|select_subject|select_by_color|select_all|deselect|invert_selection|select_rect|select_ellipse|select_polygon|quick_select|select_object|select_line|magic_wand|grow_selection|select_similar|reselect|select_layer_transparency|select_color_range|load_selection_from_image|load_selection_from_mask|set_selection_display|modify_selection|refine_selection|transform_selection|apply_selection_as_mask|layer_via_copy|stroke_selection|content_aware_fill|copy_pixels|cut_pixels|paste_pixels|save_selection|load_selection_from_channel|set_channel_properties|duplicate_channel|delete_channel|load_channel_from_image|set_quick_mask|path_to_selection|select_text_shape)$/;
|
|
12886
|
+
var SELECT = /^(select_region|select_subject|select_by_color|select_all|deselect|invert_selection|select_rect|select_ellipse|select_polygon|quick_select|select_object|select_focus_area|select_line|magic_wand|grow_selection|select_similar|reselect|select_layer_transparency|select_color_range|load_selection_from_image|load_selection_from_mask|set_selection_display|modify_selection|refine_selection|transform_selection|apply_selection_as_mask|layer_via_copy|stroke_selection|content_aware_fill|copy_pixels|cut_pixels|paste_pixels|save_selection|load_selection_from_channel|set_channel_properties|duplicate_channel|delete_channel|load_channel_from_image|set_quick_mask|path_to_selection|select_text_shape)$/;
|
|
12554
12887
|
var ANNOTATE = /^(add_shape_layer|update_shape|add_shape_component|set_shape_component|remove_shape_component|merge_shape_components|add_text_layer|set_text|set_text_style|set_paragraph_style|set_text_frame|set_text_orientation|convert_text_to_shape)$/;
|
|
12555
12888
|
var CROP = /^(set_crop|crop_to_selection|reset_crop|set_rotation|flip_image|transform_layer|warp_layer|flip_layer|offset_layer)$/;
|
|
12556
12889
|
var ADJUST = /^(set_adjustments|add_adjustment_layer|update_adjustment_layer|remove_adjustment_layer|move_adjustment_layer)$/;
|
|
@@ -12716,6 +13049,9 @@ var DEFAULT_SELECTION_TOOL_OPTIONS = {
|
|
|
12716
13049
|
quickEnhance: false,
|
|
12717
13050
|
objectSampleAll: true,
|
|
12718
13051
|
objectEnhance: false,
|
|
13052
|
+
focusRange: 50,
|
|
13053
|
+
focusNoise: 0,
|
|
13054
|
+
focusSoftness: 4,
|
|
12719
13055
|
typeText: "Type",
|
|
12720
13056
|
typeSize: 72,
|
|
12721
13057
|
typeFamily: "sans-serif"
|
|
@@ -13184,6 +13520,33 @@ function createImageSelectionTools(controller) {
|
|
|
13184
13520
|
return summary(controller);
|
|
13185
13521
|
}
|
|
13186
13522
|
};
|
|
13523
|
+
const selectFocusAreaTool = {
|
|
13524
|
+
name: "select_focus_area",
|
|
13525
|
+
title: "Select the focus area",
|
|
13526
|
+
description: "Select \u203A Focus Area: selects the parts of the image that are in focus (sharp detail, above the image noise), with the edge fitted to the image, combined with the selection by mode. Use it to separate a sharp subject from a blurred background (shallow depth of field), e.g. before blurring or darkening the background. Raise range to take in less sharp areas, raise noise if grain in the background gets selected. One undoable step. Returns the selection and noiseLevel (\u03C3 of the image noise in levels of 255).",
|
|
13527
|
+
inputSchema: {
|
|
13528
|
+
type: "object",
|
|
13529
|
+
properties: {
|
|
13530
|
+
range: { type: "number", minimum: 0, maximum: 100, description: "In-Focus Range 0..100 (default 50): higher takes in less sharp areas too." },
|
|
13531
|
+
noise: { type: "number", minimum: 0, maximum: 100, description: "Image Noise Level 0..100 (default 0): raise it when grain is selected as detail." },
|
|
13532
|
+
softness: { type: "number", minimum: 0, maximum: 100, description: "Soften the edge over this many natural pixels (default 4; 0 for a hard edge)." },
|
|
13533
|
+
mode: modeSchema,
|
|
13534
|
+
sampleAllLayers: { type: "boolean", description: "Read every layer (default true); false reads only the active pixel layer." }
|
|
13535
|
+
},
|
|
13536
|
+
additionalProperties: false
|
|
13537
|
+
},
|
|
13538
|
+
annotations: writeAnnotations("Select the focus area", true),
|
|
13539
|
+
handler: async (args) => {
|
|
13540
|
+
const { noiseLevel } = await controller.selectFocusArea({
|
|
13541
|
+
range: args.range === void 0 ? void 0 : num2(args.range, 50),
|
|
13542
|
+
noise: args.noise === void 0 ? void 0 : num2(args.noise, 0),
|
|
13543
|
+
softness: args.softness === void 0 ? void 0 : num2(args.softness, 4),
|
|
13544
|
+
mode: parseSelectionMode(args.mode),
|
|
13545
|
+
sampleAllLayers: args.sampleAllLayers
|
|
13546
|
+
});
|
|
13547
|
+
return { ...summary(controller), noiseLevel };
|
|
13548
|
+
}
|
|
13549
|
+
};
|
|
13187
13550
|
const magicWandTool = {
|
|
13188
13551
|
name: "magic_wand",
|
|
13189
13552
|
title: "Magic wand",
|
|
@@ -13738,6 +14101,8 @@ function createImageSelectionTools(controller) {
|
|
|
13738
14101
|
selectPolygonTool,
|
|
13739
14102
|
quickSelectTool,
|
|
13740
14103
|
selectObjectTool,
|
|
14104
|
+
createImageSubjectSelectionTool(controller),
|
|
14105
|
+
selectFocusAreaTool,
|
|
13741
14106
|
lineTool,
|
|
13742
14107
|
magicWandTool,
|
|
13743
14108
|
colorRangeTool,
|
|
@@ -13774,49 +14139,50 @@ function createImageSubjectSelectionTool(controller) {
|
|
|
13774
14139
|
return {
|
|
13775
14140
|
name: "select_subject",
|
|
13776
14141
|
title: "Select a subject",
|
|
13777
|
-
description: 'Select \u203A Subject:
|
|
14142
|
+
description: 'Select \u203A Subject: selects the main subject of the flattened image, combined with the selection by mode. With a segmentation model set by the host, target (a description such as "the dog" or "the house") chooses which objects; describe one object precisely to get only that one. Without a model, the subject that stands out most from the background is found classically (superpixel saliency and GrabCut): target cannot choose it, and method "saliency" and a note say so. Then check it with capture_view and fix it with quick_select or select_object. One undoable step. Returns the selection, the objects found (label, bounds), method ("model" or "saliency") and any note; none found leaves the selection unchanged.',
|
|
13778
14143
|
inputSchema: {
|
|
13779
14144
|
type: "object",
|
|
13780
14145
|
properties: {
|
|
13781
|
-
target: { type: "string",
|
|
14146
|
+
target: { type: "string", description: "What to select, in words. Needed with a model; ignored without one." },
|
|
13782
14147
|
maxSegments: {
|
|
13783
14148
|
type: "integer",
|
|
13784
14149
|
minimum: 1,
|
|
13785
14150
|
maximum: 32,
|
|
13786
|
-
description: "
|
|
14151
|
+
description: "With a model: select at most this many matching objects (default the model\u2019s; 1 for a single subject)."
|
|
13787
14152
|
},
|
|
13788
14153
|
mode: modeSchema,
|
|
13789
|
-
feather: { type: "number", minimum: 0, description: "Feather radius in pixels (default 0)." }
|
|
14154
|
+
feather: { type: "number", minimum: 0, description: "Feather radius in pixels (default 0)." },
|
|
14155
|
+
enhanceEdge: { type: "boolean", description: "Without a model: smoother, less blocky edges (Auto-Enhance; default false)." }
|
|
13790
14156
|
},
|
|
13791
|
-
required: ["target"],
|
|
13792
14157
|
additionalProperties: false
|
|
13793
14158
|
},
|
|
13794
14159
|
annotations: { ...writeAnnotations("Select a subject"), openWorldHint: true },
|
|
13795
|
-
handler: async ({ target, maxSegments, mode, feather }, context) => {
|
|
13796
|
-
if (typeof target !== "string"
|
|
14160
|
+
handler: async ({ target, maxSegments, mode, feather, enhanceEdge }, context) => {
|
|
14161
|
+
if (target !== void 0 && typeof target !== "string") throw new Error("target must be a description in words.");
|
|
14162
|
+
if (controller.segmentationProvider && !String(target ?? "").trim()) throw new Error("target must describe what to select (a model is set, and it selects by description).");
|
|
13797
14163
|
const perform = context?.perform;
|
|
13798
|
-
const { segments } = await controller.selectSubject(target, {
|
|
14164
|
+
const { segments, method, note } = await controller.selectSubject(String(target ?? ""), {
|
|
13799
14165
|
mode: parseSelectionMode(mode),
|
|
13800
14166
|
feather: num2(feather, 0),
|
|
13801
14167
|
maxSegments: typeof maxSegments === "number" ? Math.max(1, Math.round(maxSegments)) : void 0,
|
|
14168
|
+
enhanceEdge: enhanceEdge === true,
|
|
13802
14169
|
signal: context?.signal,
|
|
13803
14170
|
// Under playback the cursor traces what the model found, as a lasso, before it is selected.
|
|
13804
14171
|
beforeApply: perform ? (outlines) => perform(planOutlineTrace(outlines)) : void 0
|
|
13805
14172
|
});
|
|
13806
|
-
return { ...summary(controller), found: segments };
|
|
14173
|
+
return { ...summary(controller), found: segments, method, ...note ? { note } : {} };
|
|
13807
14174
|
}
|
|
13808
14175
|
};
|
|
13809
14176
|
}
|
|
13810
14177
|
function createImageSegmentationToolProvider(controller, options = {}) {
|
|
13811
|
-
const tool = createImageSubjectSelectionTool(controller);
|
|
13812
14178
|
return withImagePlayback(
|
|
13813
14179
|
{
|
|
13814
14180
|
id: options.id ?? "image-segmentation",
|
|
13815
14181
|
namespace: options.namespace,
|
|
13816
14182
|
title: "Image segmentation",
|
|
13817
|
-
description: "
|
|
14183
|
+
description: "Deprecated: select_subject is in the image editor tool provider.",
|
|
13818
14184
|
skill: "pixerate-image-editor",
|
|
13819
|
-
tools: () =>
|
|
14185
|
+
tools: () => []
|
|
13820
14186
|
},
|
|
13821
14187
|
options.playbackSurface
|
|
13822
14188
|
);
|
|
@@ -20450,6 +20816,7 @@ export {
|
|
|
20450
20816
|
findAdjustmentControl,
|
|
20451
20817
|
findLayer,
|
|
20452
20818
|
findLayerControl,
|
|
20819
|
+
findSubject,
|
|
20453
20820
|
findWebGpu,
|
|
20454
20821
|
finishTile,
|
|
20455
20822
|
fitParagraphRuns,
|
|
@@ -20458,6 +20825,7 @@ export {
|
|
|
20458
20825
|
flattenPath,
|
|
20459
20826
|
flipCorners,
|
|
20460
20827
|
flowField,
|
|
20828
|
+
focusArea,
|
|
20461
20829
|
followLayerMove,
|
|
20462
20830
|
fontOf,
|
|
20463
20831
|
formatControlValue,
|
|
@@ -20471,6 +20839,7 @@ export {
|
|
|
20471
20839
|
getViewportMetrics,
|
|
20472
20840
|
getViewportProjection,
|
|
20473
20841
|
globalImageCache,
|
|
20842
|
+
grabCutLabels,
|
|
20474
20843
|
gradientDabColor,
|
|
20475
20844
|
grainCatch,
|
|
20476
20845
|
grainHeights,
|
|
@@ -20562,6 +20931,7 @@ export {
|
|
|
20562
20931
|
isTransformAnchor,
|
|
20563
20932
|
isValidWarp,
|
|
20564
20933
|
keepDeepSamples,
|
|
20934
|
+
keepMainPieces,
|
|
20565
20935
|
kelvinToTemperature,
|
|
20566
20936
|
keysWeight,
|
|
20567
20937
|
latticeViscosity,
|
|
@@ -20674,6 +21044,7 @@ export {
|
|
|
20674
21044
|
offsetVectorMask,
|
|
20675
21045
|
openMaskFile,
|
|
20676
21046
|
openPsdFile,
|
|
21047
|
+
otsuThreshold,
|
|
20677
21048
|
packRgba,
|
|
20678
21049
|
packWetParams,
|
|
20679
21050
|
paintGradientMappingFor,
|
|
@@ -20863,6 +21234,7 @@ export {
|
|
|
20863
21234
|
shiftCoverage,
|
|
20864
21235
|
simplifyPixelPath,
|
|
20865
21236
|
skinToneRange,
|
|
21237
|
+
slicSuperpixels,
|
|
20866
21238
|
smartFilterRows,
|
|
20867
21239
|
smartObjectLayers,
|
|
20868
21240
|
smoothCoverage,
|
|
@@ -20872,6 +21244,7 @@ export {
|
|
|
20872
21244
|
snapPolygonToEdges,
|
|
20873
21245
|
snapTo45,
|
|
20874
21246
|
snapToEdge,
|
|
21247
|
+
softMaskToSelection,
|
|
20875
21248
|
solidPaint,
|
|
20876
21249
|
solidSurface,
|
|
20877
21250
|
solveMembrane,
|
|
@@ -20893,6 +21266,7 @@ export {
|
|
|
20893
21266
|
styleAt,
|
|
20894
21267
|
subpathsToSvgPath,
|
|
20895
21268
|
sumHistograms,
|
|
21269
|
+
superpixelSaliency,
|
|
20896
21270
|
surfaceBytes,
|
|
20897
21271
|
surfaceDepth,
|
|
20898
21272
|
surfaceFromDeepPixels,
|
|
@@ -20985,6 +21359,8 @@ export {
|
|
|
20985
21359
|
withImagePlayback,
|
|
20986
21360
|
withNewIds,
|
|
20987
21361
|
workImage,
|
|
21362
|
+
workLuma,
|
|
21363
|
+
workPlanes,
|
|
20988
21364
|
writeAdjustmentBlock,
|
|
20989
21365
|
writeDeepRegion,
|
|
20990
21366
|
writeLfx2,
|