@pixerate/editor 0.199.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 +17 -0
- package/dist/image-editor/controller.d.ts +17 -0
- package/dist/image-editor/index.cjs +667 -515
- package/dist/image-editor/index.js +388 -236
- 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/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 +2 -0
- package/dist/image-editor/selection/segment/index.d.ts +2 -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/package.json +1 -1
- package/skills/pixerate-image-editor/SKILL.md +1 -0
- package/skills/pixerate-image-selection-masking/SKILL.md +2 -0
|
@@ -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;
|
|
@@ -3875,6 +3877,342 @@ function findSubject(pixels, options = {}) {
|
|
|
3875
3877
|
return { selection: upscaleRegion(work, pixels, roi, label, options.enhanceEdge ?? false) };
|
|
3876
3878
|
}
|
|
3877
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
|
+
|
|
3878
4216
|
// src/image-editor/layers/shape-type-ui.ts
|
|
3879
4217
|
var DEFAULT_SHAPE_TOOL_OPTIONS = {
|
|
3880
4218
|
kind: "rect",
|
|
@@ -4800,238 +5138,6 @@ function segmentsToSelection(width, height, results) {
|
|
|
4800
5138
|
return out;
|
|
4801
5139
|
}
|
|
4802
5140
|
|
|
4803
|
-
// src/image-editor/selection/refine.ts
|
|
4804
|
-
var DEFAULT_REFINE_EDGE_PARAMS = {
|
|
4805
|
-
radius: 0,
|
|
4806
|
-
smartRadius: false,
|
|
4807
|
-
smooth: 0,
|
|
4808
|
-
feather: 0,
|
|
4809
|
-
contrast: 0,
|
|
4810
|
-
shiftEdge: 0
|
|
4811
|
-
};
|
|
4812
|
-
var GUIDED_FILTER_EPS = 1e-4;
|
|
4813
|
-
var fin = (v, d) => typeof v === "number" && Number.isFinite(v) ? v : d;
|
|
4814
|
-
function normalizeRefineEdgeParams(p = {}) {
|
|
4815
|
-
return {
|
|
4816
|
-
radius: Math.min(250, Math.max(0, fin(p.radius, 0))),
|
|
4817
|
-
smartRadius: p.smartRadius === true,
|
|
4818
|
-
smooth: Math.min(100, Math.max(0, fin(p.smooth, 0))),
|
|
4819
|
-
feather: Math.min(1e3, Math.max(0, fin(p.feather, 0))),
|
|
4820
|
-
contrast: Math.min(100, Math.max(0, fin(p.contrast, 0))),
|
|
4821
|
-
shiftEdge: Math.min(100, Math.max(-100, fin(p.shiftEdge, 0)))
|
|
4822
|
-
};
|
|
4823
|
-
}
|
|
4824
|
-
var gfRadius = (p) => Math.min(64, Math.max(1, Math.ceil(p.radius)));
|
|
4825
|
-
var smoothSigma = (p) => p.smooth / 100 * 4;
|
|
4826
|
-
var featherSigma = (p) => p.feather / 2;
|
|
4827
|
-
function shiftAmount(p) {
|
|
4828
|
-
const px2 = p.shiftEdge / 100 * Math.max(p.radius, 4);
|
|
4829
|
-
return Number.isFinite(px2) ? Math.min(64, Math.max(-64, px2)) : 0;
|
|
4830
|
-
}
|
|
4831
|
-
function refineEdgeHalo(params = {}) {
|
|
4832
|
-
const p = normalizeRefineEdgeParams(params);
|
|
4833
|
-
const g = gfRadius(p);
|
|
4834
|
-
const edge = p.radius > 0 ? Math.max(Math.ceil(p.radius), 2 * g + (p.smartRadius ? g + 1 : 0)) : 0;
|
|
4835
|
-
return edge + Math.ceil(3 * smoothSigma(p)) + Math.ceil(3 * featherSigma(p)) + Math.ceil(Math.abs(shiftAmount(p))) + 2;
|
|
4836
|
-
}
|
|
4837
|
-
function boxMean(src, w, h, r) {
|
|
4838
|
-
const tmp = new Float32Array(w * h);
|
|
4839
|
-
const pre = new Float64Array(Math.max(w, h) + 1);
|
|
4840
|
-
for (let y = 0; y < h; y++) {
|
|
4841
|
-
const row = y * w;
|
|
4842
|
-
for (let x = 0; x < w; x++) pre[x + 1] = pre[x] + src[row + x];
|
|
4843
|
-
for (let x = 0; x < w; x++) {
|
|
4844
|
-
const lo = Math.max(0, x - r);
|
|
4845
|
-
const hi = Math.min(w, x + r + 1);
|
|
4846
|
-
tmp[row + x] = (pre[hi] - pre[lo]) / (hi - lo);
|
|
4847
|
-
}
|
|
4848
|
-
}
|
|
4849
|
-
const out = new Float32Array(w * h);
|
|
4850
|
-
for (let x = 0; x < w; x++) {
|
|
4851
|
-
for (let y = 0; y < h; y++) pre[y + 1] = pre[y] + tmp[y * w + x];
|
|
4852
|
-
for (let y = 0; y < h; y++) {
|
|
4853
|
-
const lo = Math.max(0, y - r);
|
|
4854
|
-
const hi = Math.min(h, y + r + 1);
|
|
4855
|
-
out[y * w + x] = (pre[hi] - pre[lo]) / (hi - lo);
|
|
4856
|
-
}
|
|
4857
|
-
}
|
|
4858
|
-
return out;
|
|
4859
|
-
}
|
|
4860
|
-
function guidedFilterColor(guide, p, w, h, r, eps = GUIDED_FILTER_EPS) {
|
|
4861
|
-
const n = w * h;
|
|
4862
|
-
const [c0, c1, c2] = guide;
|
|
4863
|
-
const prod = (a, b2) => {
|
|
4864
|
-
const o = new Float32Array(n);
|
|
4865
|
-
for (let i = 0; i < n; i++) o[i] = a[i] * b2[i];
|
|
4866
|
-
return o;
|
|
4867
|
-
};
|
|
4868
|
-
const m0 = boxMean(c0, w, h, r);
|
|
4869
|
-
const m1 = boxMean(c1, w, h, r);
|
|
4870
|
-
const m2 = boxMean(c2, w, h, r);
|
|
4871
|
-
const mp = boxMean(p, w, h, r);
|
|
4872
|
-
const mip0 = boxMean(prod(c0, p), w, h, r);
|
|
4873
|
-
const mip1 = boxMean(prod(c1, p), w, h, r);
|
|
4874
|
-
const mip2 = boxMean(prod(c2, p), w, h, r);
|
|
4875
|
-
const s00 = boxMean(prod(c0, c0), w, h, r);
|
|
4876
|
-
const s01 = boxMean(prod(c0, c1), w, h, r);
|
|
4877
|
-
const s02 = boxMean(prod(c0, c2), w, h, r);
|
|
4878
|
-
const s11 = boxMean(prod(c1, c1), w, h, r);
|
|
4879
|
-
const s12 = boxMean(prod(c1, c2), w, h, r);
|
|
4880
|
-
const s22 = boxMean(prod(c2, c2), w, h, r);
|
|
4881
|
-
const a0 = new Float32Array(n);
|
|
4882
|
-
const a1 = new Float32Array(n);
|
|
4883
|
-
const a2 = new Float32Array(n);
|
|
4884
|
-
const b = new Float32Array(n);
|
|
4885
|
-
for (let i = 0; i < n; i++) {
|
|
4886
|
-
const u0 = m0[i], u1 = m1[i], u2 = m2[i], up = mp[i];
|
|
4887
|
-
const rr = s00[i] - u0 * u0 + eps;
|
|
4888
|
-
const rg = s01[i] - u0 * u1;
|
|
4889
|
-
const rb = s02[i] - u0 * u2;
|
|
4890
|
-
const gg = s11[i] - u1 * u1 + eps;
|
|
4891
|
-
const gb = s12[i] - u1 * u2;
|
|
4892
|
-
const bb = s22[i] - u2 * u2 + eps;
|
|
4893
|
-
const v0 = mip0[i] - u0 * up;
|
|
4894
|
-
const v1 = mip1[i] - u1 * up;
|
|
4895
|
-
const v2 = mip2[i] - u2 * up;
|
|
4896
|
-
const i00 = gg * bb - gb * gb;
|
|
4897
|
-
const i01 = gb * rb - rg * bb;
|
|
4898
|
-
const i02 = rg * gb - gg * rb;
|
|
4899
|
-
const i11 = rr * bb - rb * rb;
|
|
4900
|
-
const i12 = rb * rg - rr * gb;
|
|
4901
|
-
const i22 = rr * gg - rg * rg;
|
|
4902
|
-
const det = rr * i00 + rg * i01 + rb * i02;
|
|
4903
|
-
let x0 = 0, x1 = 0, x2 = 0;
|
|
4904
|
-
if (Math.abs(det) >= 1e-30) {
|
|
4905
|
-
x0 = (i00 * v0 + i01 * v1 + i02 * v2) / det;
|
|
4906
|
-
x1 = (i01 * v0 + i11 * v1 + i12 * v2) / det;
|
|
4907
|
-
x2 = (i02 * v0 + i12 * v1 + i22 * v2) / det;
|
|
4908
|
-
}
|
|
4909
|
-
a0[i] = x0;
|
|
4910
|
-
a1[i] = x1;
|
|
4911
|
-
a2[i] = x2;
|
|
4912
|
-
b[i] = up - x0 * u0 - x1 * u1 - x2 * u2;
|
|
4913
|
-
}
|
|
4914
|
-
const ma0 = boxMean(a0, w, h, r);
|
|
4915
|
-
const ma1 = boxMean(a1, w, h, r);
|
|
4916
|
-
const ma2 = boxMean(a2, w, h, r);
|
|
4917
|
-
const mb = boxMean(b, w, h, r);
|
|
4918
|
-
const out = new Float32Array(n);
|
|
4919
|
-
for (let i = 0; i < n; i++) out[i] = ma0[i] * c0[i] + ma1[i] * c1[i] + ma2[i] * c2[i] + mb[i];
|
|
4920
|
-
return out;
|
|
4921
|
-
}
|
|
4922
|
-
function edgeWidth(guide, w, h, r) {
|
|
4923
|
-
const n = w * h;
|
|
4924
|
-
const y = new Float32Array(n);
|
|
4925
|
-
for (let i = 0; i < n; i++) y[i] = 0.299 * guide[0][i] + 0.587 * guide[1][i] + 0.114 * guide[2][i];
|
|
4926
|
-
const g = new Float32Array(n);
|
|
4927
|
-
for (let yy = 0; yy < h; yy++) {
|
|
4928
|
-
for (let xx = 0; xx < w; xx++) {
|
|
4929
|
-
const gx = (y[yy * w + Math.min(w - 1, xx + 1)] - y[yy * w + Math.max(0, xx - 1)]) / 2;
|
|
4930
|
-
const gy = (y[Math.min(h - 1, yy + 1) * w + xx] - y[Math.max(0, yy - 1) * w + xx]) / 2;
|
|
4931
|
-
g[yy * w + xx] = Math.sqrt(gx * gx + gy * gy);
|
|
4932
|
-
}
|
|
4933
|
-
}
|
|
4934
|
-
const gm = greyMorphology(g, w, h, r, true);
|
|
4935
|
-
const hi = greyMorphology(y, w, h, r, true);
|
|
4936
|
-
const lo = greyMorphology(y, w, h, r, false);
|
|
4937
|
-
const out = new Float32Array(n);
|
|
4938
|
-
for (let i = 0; i < n; i++) out[i] = (hi[i] - lo[i]) / Math.max(gm[i], 1e-4);
|
|
4939
|
-
return out;
|
|
4940
|
-
}
|
|
4941
|
-
function refineCoverage(guide, m, w, h, params = {}) {
|
|
4942
|
-
const p = normalizeRefineEdgeParams(params);
|
|
4943
|
-
let a = Float32Array.from(m);
|
|
4944
|
-
if (p.radius > 0 && guide) {
|
|
4945
|
-
const n = w * h;
|
|
4946
|
-
const boundary = new Uint8Array(n);
|
|
4947
|
-
let any = false;
|
|
4948
|
-
for (let y = 0; y < h; y++) {
|
|
4949
|
-
for (let x = 0; x < w; x++) {
|
|
4950
|
-
const i = y * w + x;
|
|
4951
|
-
const v = m[i] >= 0.5;
|
|
4952
|
-
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;
|
|
4953
|
-
if (diff) {
|
|
4954
|
-
boundary[i] = 1;
|
|
4955
|
-
any = true;
|
|
4956
|
-
}
|
|
4957
|
-
}
|
|
4958
|
-
}
|
|
4959
|
-
if (any) {
|
|
4960
|
-
const dist = distanceTransform(boundary, w, h);
|
|
4961
|
-
const r = gfRadius(p);
|
|
4962
|
-
const q = guidedFilterColor(guide, m, w, h, r);
|
|
4963
|
-
const widths = p.smartRadius ? edgeWidth(guide, w, h, r) : null;
|
|
4964
|
-
for (let i = 0; i < n; i++) {
|
|
4965
|
-
const reff = widths ? Math.min(Math.max(p.radius, 1.5), Math.max(1.5, widths[i])) : p.radius;
|
|
4966
|
-
const wgt = Math.min(1, Math.max(0, reff + 1 - dist[i]));
|
|
4967
|
-
if (wgt > 0) {
|
|
4968
|
-
const qi = Math.min(1, Math.max(0, q[i]));
|
|
4969
|
-
a[i] = m[i] + (qi - m[i]) * wgt;
|
|
4970
|
-
}
|
|
4971
|
-
}
|
|
4972
|
-
}
|
|
4973
|
-
}
|
|
4974
|
-
const ss = smoothSigma(p);
|
|
4975
|
-
if (ss >= 0.3) {
|
|
4976
|
-
a = gaussianBlur(a, w, h, ss, "clamp");
|
|
4977
|
-
const gain = 1 + ss;
|
|
4978
|
-
for (let i = 0; i < a.length; i++) a[i] = Math.min(1, Math.max(0, (a[i] - 0.5) * gain + 0.5));
|
|
4979
|
-
}
|
|
4980
|
-
a = gaussianBlur(a, w, h, featherSigma(p), "clamp");
|
|
4981
|
-
if (p.contrast > 0) {
|
|
4982
|
-
const gain = 1 / (1 - 0.99 * p.contrast / 100);
|
|
4983
|
-
for (let i = 0; i < a.length; i++) a[i] = Math.min(1, Math.max(0, (a[i] - 0.5) * gain + 0.5));
|
|
4984
|
-
}
|
|
4985
|
-
const amount = shiftAmount(p);
|
|
4986
|
-
if (Math.abs(amount) >= 1e-4) a = shiftCoverage(a, w, h, amount);
|
|
4987
|
-
return a;
|
|
4988
|
-
}
|
|
4989
|
-
function guidePlanes(pixels, win) {
|
|
4990
|
-
const box = win ?? { x: 0, y: 0, width: pixels.width, height: pixels.height };
|
|
4991
|
-
const n = box.width * box.height;
|
|
4992
|
-
const r = new Float32Array(n);
|
|
4993
|
-
const g = new Float32Array(n);
|
|
4994
|
-
const b = new Float32Array(n);
|
|
4995
|
-
const d = pixels.data;
|
|
4996
|
-
for (let y = 0; y < box.height; y++) {
|
|
4997
|
-
for (let x = 0; x < box.width; x++) {
|
|
4998
|
-
const i = y * box.width + x;
|
|
4999
|
-
const p = ((box.y + y) * pixels.width + box.x + x) * 4;
|
|
5000
|
-
r[i] = d[p] / 255;
|
|
5001
|
-
g[i] = d[p + 1] / 255;
|
|
5002
|
-
b[i] = d[p + 2] / 255;
|
|
5003
|
-
}
|
|
5004
|
-
}
|
|
5005
|
-
return [r, g, b];
|
|
5006
|
-
}
|
|
5007
|
-
function refineSelection(pixels, mask, params = {}) {
|
|
5008
|
-
const p = normalizeRefineEdgeParams(params);
|
|
5009
|
-
if (pixels && (pixels.width !== mask.width || pixels.height !== mask.height)) {
|
|
5010
|
-
throw new Error("Refine Edge needs pixels the size of the selection");
|
|
5011
|
-
}
|
|
5012
|
-
if (!mask.bounds) return mask;
|
|
5013
|
-
const halo = refineEdgeHalo(p);
|
|
5014
|
-
const x0 = Math.max(0, mask.bounds.x - halo);
|
|
5015
|
-
const y0 = Math.max(0, mask.bounds.y - halo);
|
|
5016
|
-
const x1 = Math.min(mask.width, mask.bounds.x + mask.bounds.width + halo);
|
|
5017
|
-
const y1 = Math.min(mask.height, mask.bounds.y + mask.bounds.height + halo);
|
|
5018
|
-
const ww = x1 - x0;
|
|
5019
|
-
const wh = y1 - y0;
|
|
5020
|
-
const m = new Float32Array(ww * wh);
|
|
5021
|
-
for (let y = 0; y < wh; y++) {
|
|
5022
|
-
const src = (y0 + y) * mask.width + x0;
|
|
5023
|
-
for (let x = 0; x < ww; x++) m[y * ww + x] = mask.data[src + x] / 255;
|
|
5024
|
-
}
|
|
5025
|
-
const guide = pixels && p.radius > 0 ? guidePlanes(pixels, { x: x0, y: y0, width: ww, height: wh }) : null;
|
|
5026
|
-
const refined = refineCoverage(guide, m, ww, wh, p);
|
|
5027
|
-
const out = new Uint8Array(mask.width * mask.height);
|
|
5028
|
-
for (let y = 0; y < wh; y++) {
|
|
5029
|
-
const dst = (y0 + y) * mask.width + x0;
|
|
5030
|
-
for (let x = 0; x < ww; x++) out[dst + x] = coverageToByte(refined[y * ww + x]);
|
|
5031
|
-
}
|
|
5032
|
-
return createSelectionMask(mask.width, mask.height, out);
|
|
5033
|
-
}
|
|
5034
|
-
|
|
5035
5141
|
// src/image-editor/selection/stroke.ts
|
|
5036
5142
|
function selectionRing(sel, width, location) {
|
|
5037
5143
|
const half = location === "center" ? width / 2 : width;
|
|
@@ -9866,6 +9972,17 @@ var ImageEditorController = class {
|
|
|
9866
9972
|
this.setSelection(region, options.mode ?? "replace");
|
|
9867
9973
|
return this.state.selection ?? null;
|
|
9868
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
|
+
}
|
|
9869
9986
|
/**
|
|
9870
9987
|
* Supplies the pixels the selection tools sample (natural size RGBA), e.g. decoded in a worker
|
|
9871
9988
|
* or in a test; `null` goes back to reading the source image. Without a canvas (Node, workers)
|
|
@@ -12766,7 +12883,7 @@ var DEPTH_MASK_CONTROLS = [
|
|
|
12766
12883
|
// src/image-editor/playback.ts
|
|
12767
12884
|
var IMAGE_PLAYBACK_KIND = "layer";
|
|
12768
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)$/;
|
|
12769
|
-
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)$/;
|
|
12770
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)$/;
|
|
12771
12888
|
var CROP = /^(set_crop|crop_to_selection|reset_crop|set_rotation|flip_image|transform_layer|warp_layer|flip_layer|offset_layer)$/;
|
|
12772
12889
|
var ADJUST = /^(set_adjustments|add_adjustment_layer|update_adjustment_layer|remove_adjustment_layer|move_adjustment_layer)$/;
|
|
@@ -12932,6 +13049,9 @@ var DEFAULT_SELECTION_TOOL_OPTIONS = {
|
|
|
12932
13049
|
quickEnhance: false,
|
|
12933
13050
|
objectSampleAll: true,
|
|
12934
13051
|
objectEnhance: false,
|
|
13052
|
+
focusRange: 50,
|
|
13053
|
+
focusNoise: 0,
|
|
13054
|
+
focusSoftness: 4,
|
|
12935
13055
|
typeText: "Type",
|
|
12936
13056
|
typeSize: 72,
|
|
12937
13057
|
typeFamily: "sans-serif"
|
|
@@ -13400,6 +13520,33 @@ function createImageSelectionTools(controller) {
|
|
|
13400
13520
|
return summary(controller);
|
|
13401
13521
|
}
|
|
13402
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
|
+
};
|
|
13403
13550
|
const magicWandTool = {
|
|
13404
13551
|
name: "magic_wand",
|
|
13405
13552
|
title: "Magic wand",
|
|
@@ -13955,6 +14102,7 @@ function createImageSelectionTools(controller) {
|
|
|
13955
14102
|
quickSelectTool,
|
|
13956
14103
|
selectObjectTool,
|
|
13957
14104
|
createImageSubjectSelectionTool(controller),
|
|
14105
|
+
selectFocusAreaTool,
|
|
13958
14106
|
lineTool,
|
|
13959
14107
|
magicWandTool,
|
|
13960
14108
|
colorRangeTool,
|
|
@@ -20677,6 +20825,7 @@ export {
|
|
|
20677
20825
|
flattenPath,
|
|
20678
20826
|
flipCorners,
|
|
20679
20827
|
flowField,
|
|
20828
|
+
focusArea,
|
|
20680
20829
|
followLayerMove,
|
|
20681
20830
|
fontOf,
|
|
20682
20831
|
formatControlValue,
|
|
@@ -21095,6 +21244,7 @@ export {
|
|
|
21095
21244
|
snapPolygonToEdges,
|
|
21096
21245
|
snapTo45,
|
|
21097
21246
|
snapToEdge,
|
|
21247
|
+
softMaskToSelection,
|
|
21098
21248
|
solidPaint,
|
|
21099
21249
|
solidSurface,
|
|
21100
21250
|
solveMembrane,
|
|
@@ -21209,6 +21359,8 @@ export {
|
|
|
21209
21359
|
withImagePlayback,
|
|
21210
21360
|
withNewIds,
|
|
21211
21361
|
workImage,
|
|
21362
|
+
workLuma,
|
|
21363
|
+
workPlanes,
|
|
21212
21364
|
writeAdjustmentBlock,
|
|
21213
21365
|
writeDeepRegion,
|
|
21214
21366
|
writeLfx2,
|