@pixerate/editor 0.195.0 → 0.197.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 +18 -0
- package/dist/image-editor/controller.d.ts +18 -0
- package/dist/image-editor/index.cjs +978 -300
- package/dist/image-editor/index.js +684 -6
- package/dist/image-editor/selection/gesture.d.cts +6 -0
- package/dist/image-editor/selection/gesture.d.ts +6 -0
- package/dist/image-editor/selection/index.d.cts +1 -0
- package/dist/image-editor/selection/index.d.ts +1 -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/maxflow.d.cts +30 -0
- package/dist/image-editor/selection/segment/maxflow.d.ts +30 -0
- package/dist/image-editor/selection/segment/quick.d.cts +47 -0
- package/dist/image-editor/selection/segment/quick.d.ts +47 -0
- package/dist/image-editor/selection/tool-options.d.cts +7 -1
- package/dist/image-editor/selection/tool-options.d.ts +7 -1
- package/dist/vector/controller.d.cts +6 -0
- package/dist/vector/controller.d.ts +6 -0
- package/dist/vector/index.cjs +94 -1
- package/dist/vector/index.d.cts +1 -0
- package/dist/vector/index.d.ts +1 -0
- package/dist/vector/index.js +93 -0
- package/dist/vector/layers.d.cts +32 -0
- package/dist/vector/layers.d.ts +32 -0
- package/package.json +1 -1
- package/skills/pixerate-image-editor/SKILL.md +1 -0
- package/skills/pixerate-image-selection-masking/SKILL.md +5 -0
|
@@ -2803,6 +2803,589 @@ function magneticAnchorSpacing(frequency = 57) {
|
|
|
2803
2803
|
return 8 + (100 - f);
|
|
2804
2804
|
}
|
|
2805
2805
|
|
|
2806
|
+
// src/image-editor/selection/segment/maxflow.ts
|
|
2807
|
+
var TERMINAL = -2;
|
|
2808
|
+
var NONE = -1;
|
|
2809
|
+
var FREE = 0;
|
|
2810
|
+
var SOURCE = 1;
|
|
2811
|
+
var SINK = 2;
|
|
2812
|
+
var GridGraph = class {
|
|
2813
|
+
width;
|
|
2814
|
+
height;
|
|
2815
|
+
/** Residual capacity of the edge leaving node `p` in direction `d`, at `p * 4 + d`. */
|
|
2816
|
+
cap;
|
|
2817
|
+
/** Net terminal capacity: above 0 to the source, below 0 to the sink. */
|
|
2818
|
+
tr;
|
|
2819
|
+
tree;
|
|
2820
|
+
constructor(width, height) {
|
|
2821
|
+
this.width = width;
|
|
2822
|
+
this.height = height;
|
|
2823
|
+
const n = width * height;
|
|
2824
|
+
this.cap = new Float64Array(n * 4);
|
|
2825
|
+
this.tr = new Float64Array(n);
|
|
2826
|
+
this.tree = new Uint8Array(n);
|
|
2827
|
+
}
|
|
2828
|
+
/** Sets the capacity of the edge between `p` and its neighbour in direction `d`, both ways. */
|
|
2829
|
+
setEdge(p, d, capacity) {
|
|
2830
|
+
const q = d === 0 ? p + 1 : p + this.width;
|
|
2831
|
+
this.cap[p * 4 + d] = capacity;
|
|
2832
|
+
this.cap[q * 4 + (d ^ 2)] = capacity;
|
|
2833
|
+
}
|
|
2834
|
+
/** Adds `source` (cost of putting `p` on the sink side) and `sink` (the other way) to `p`. */
|
|
2835
|
+
addTerminal(p, source, sink) {
|
|
2836
|
+
this.tr[p] += source - sink;
|
|
2837
|
+
}
|
|
2838
|
+
/** Whether `p` is on the source side of the minimum cut (after {@link maxflow}). */
|
|
2839
|
+
inSource(p) {
|
|
2840
|
+
return this.tree[p] === SOURCE;
|
|
2841
|
+
}
|
|
2842
|
+
/** Runs max-flow and returns the flow (the cut's cost, less the constant terminal parts). */
|
|
2843
|
+
maxflow() {
|
|
2844
|
+
const { width: w, height: h, cap, tr, tree } = this;
|
|
2845
|
+
const n = w * h;
|
|
2846
|
+
const nb = (p, d) => {
|
|
2847
|
+
const x = p % w;
|
|
2848
|
+
if (d === 0) return x < w - 1 ? p + 1 : -1;
|
|
2849
|
+
if (d === 1) return p + w < n ? p + w : -1;
|
|
2850
|
+
if (d === 2) return x > 0 ? p - 1 : -1;
|
|
2851
|
+
return p - w >= 0 ? p - w : -1;
|
|
2852
|
+
};
|
|
2853
|
+
const parent = new Int8Array(n).fill(NONE);
|
|
2854
|
+
const ts = new Int32Array(n);
|
|
2855
|
+
const dist = new Int32Array(n);
|
|
2856
|
+
const queue = new Int32Array(n + 1);
|
|
2857
|
+
const queued = new Uint8Array(n);
|
|
2858
|
+
let qHead = 0;
|
|
2859
|
+
let qTail = 0;
|
|
2860
|
+
const activate = (p) => {
|
|
2861
|
+
if (queued[p]) return;
|
|
2862
|
+
queued[p] = 1;
|
|
2863
|
+
queue[qTail] = p;
|
|
2864
|
+
qTail = qTail === n ? 0 : qTail + 1;
|
|
2865
|
+
};
|
|
2866
|
+
const nextActive = () => {
|
|
2867
|
+
while (qHead !== qTail) {
|
|
2868
|
+
const p = queue[qHead];
|
|
2869
|
+
qHead = qHead === n ? 0 : qHead + 1;
|
|
2870
|
+
queued[p] = 0;
|
|
2871
|
+
if (tree[p] !== FREE) return p;
|
|
2872
|
+
}
|
|
2873
|
+
return -1;
|
|
2874
|
+
};
|
|
2875
|
+
let orphans = new Int32Array(64);
|
|
2876
|
+
let orphanCount = 0;
|
|
2877
|
+
const orphan = (p) => {
|
|
2878
|
+
parent[p] = NONE;
|
|
2879
|
+
if (orphanCount === orphans.length) {
|
|
2880
|
+
const grown = new Int32Array(orphans.length * 2);
|
|
2881
|
+
grown.set(orphans);
|
|
2882
|
+
orphans = grown;
|
|
2883
|
+
}
|
|
2884
|
+
orphans[orphanCount++] = p;
|
|
2885
|
+
};
|
|
2886
|
+
tree.fill(FREE);
|
|
2887
|
+
for (let p = 0; p < n; p++) {
|
|
2888
|
+
if (tr[p] > 0) tree[p] = SOURCE;
|
|
2889
|
+
else if (tr[p] < 0) tree[p] = SINK;
|
|
2890
|
+
else continue;
|
|
2891
|
+
parent[p] = TERMINAL;
|
|
2892
|
+
dist[p] = 1;
|
|
2893
|
+
activate(p);
|
|
2894
|
+
}
|
|
2895
|
+
let flow = 0;
|
|
2896
|
+
let time = 0;
|
|
2897
|
+
let current = -1;
|
|
2898
|
+
for (; ; ) {
|
|
2899
|
+
if (current < 0 || tree[current] === FREE) {
|
|
2900
|
+
current = nextActive();
|
|
2901
|
+
if (current < 0) break;
|
|
2902
|
+
}
|
|
2903
|
+
const p = current;
|
|
2904
|
+
const side = tree[p];
|
|
2905
|
+
let s = -1;
|
|
2906
|
+
let t = -1;
|
|
2907
|
+
let dir = 0;
|
|
2908
|
+
for (let d = 0; d < 4; d++) {
|
|
2909
|
+
const q = nb(p, d);
|
|
2910
|
+
if (q < 0) continue;
|
|
2911
|
+
if (side === SOURCE ? cap[p * 4 + d] <= 0 : cap[q * 4 + (d ^ 2)] <= 0) continue;
|
|
2912
|
+
if (tree[q] === FREE) {
|
|
2913
|
+
tree[q] = side;
|
|
2914
|
+
parent[q] = d ^ 2;
|
|
2915
|
+
ts[q] = ts[p];
|
|
2916
|
+
dist[q] = dist[p] + 1;
|
|
2917
|
+
activate(q);
|
|
2918
|
+
} else if (tree[q] !== side) {
|
|
2919
|
+
if (side === SOURCE) {
|
|
2920
|
+
s = p;
|
|
2921
|
+
t = q;
|
|
2922
|
+
dir = d;
|
|
2923
|
+
} else {
|
|
2924
|
+
s = q;
|
|
2925
|
+
t = p;
|
|
2926
|
+
dir = d ^ 2;
|
|
2927
|
+
}
|
|
2928
|
+
break;
|
|
2929
|
+
} else if (ts[q] <= ts[p] && dist[q] > dist[p]) {
|
|
2930
|
+
parent[q] = d ^ 2;
|
|
2931
|
+
ts[q] = ts[p];
|
|
2932
|
+
dist[q] = dist[p] + 1;
|
|
2933
|
+
}
|
|
2934
|
+
}
|
|
2935
|
+
if (s < 0) {
|
|
2936
|
+
current = -1;
|
|
2937
|
+
continue;
|
|
2938
|
+
}
|
|
2939
|
+
time++;
|
|
2940
|
+
let f = cap[s * 4 + dir];
|
|
2941
|
+
for (let x = s; ; ) {
|
|
2942
|
+
const a = parent[x];
|
|
2943
|
+
if (a === TERMINAL) {
|
|
2944
|
+
f = Math.min(f, tr[x]);
|
|
2945
|
+
break;
|
|
2946
|
+
}
|
|
2947
|
+
const up = nb(x, a);
|
|
2948
|
+
f = Math.min(f, cap[up * 4 + (a ^ 2)]);
|
|
2949
|
+
x = up;
|
|
2950
|
+
}
|
|
2951
|
+
for (let x = t; ; ) {
|
|
2952
|
+
const a = parent[x];
|
|
2953
|
+
if (a === TERMINAL) {
|
|
2954
|
+
f = Math.min(f, -tr[x]);
|
|
2955
|
+
break;
|
|
2956
|
+
}
|
|
2957
|
+
f = Math.min(f, cap[x * 4 + a]);
|
|
2958
|
+
x = nb(x, a);
|
|
2959
|
+
}
|
|
2960
|
+
cap[s * 4 + dir] -= f;
|
|
2961
|
+
cap[t * 4 + (dir ^ 2)] += f;
|
|
2962
|
+
for (let x = s; ; ) {
|
|
2963
|
+
const a = parent[x];
|
|
2964
|
+
if (a === TERMINAL) {
|
|
2965
|
+
tr[x] -= f;
|
|
2966
|
+
if (tr[x] <= 0) orphan(x);
|
|
2967
|
+
break;
|
|
2968
|
+
}
|
|
2969
|
+
const up = nb(x, a);
|
|
2970
|
+
cap[up * 4 + (a ^ 2)] -= f;
|
|
2971
|
+
cap[x * 4 + a] += f;
|
|
2972
|
+
if (cap[up * 4 + (a ^ 2)] <= 0) orphan(x);
|
|
2973
|
+
x = up;
|
|
2974
|
+
}
|
|
2975
|
+
for (let x = t; ; ) {
|
|
2976
|
+
const a = parent[x];
|
|
2977
|
+
if (a === TERMINAL) {
|
|
2978
|
+
tr[x] += f;
|
|
2979
|
+
if (tr[x] >= 0) orphan(x);
|
|
2980
|
+
break;
|
|
2981
|
+
}
|
|
2982
|
+
const up = nb(x, a);
|
|
2983
|
+
cap[x * 4 + a] -= f;
|
|
2984
|
+
cap[up * 4 + (a ^ 2)] += f;
|
|
2985
|
+
if (cap[x * 4 + a] <= 0) orphan(x);
|
|
2986
|
+
x = up;
|
|
2987
|
+
}
|
|
2988
|
+
flow += f;
|
|
2989
|
+
while (orphanCount) {
|
|
2990
|
+
const o = orphans[--orphanCount];
|
|
2991
|
+
const oSide = tree[o];
|
|
2992
|
+
let best = NONE;
|
|
2993
|
+
let bestDist = Infinity;
|
|
2994
|
+
for (let d = 0; d < 4; d++) {
|
|
2995
|
+
const q = nb(o, d);
|
|
2996
|
+
if (q < 0 || tree[q] !== oSide) continue;
|
|
2997
|
+
if (oSide === SOURCE ? cap[q * 4 + (d ^ 2)] <= 0 : cap[o * 4 + d] <= 0) continue;
|
|
2998
|
+
let k = 0;
|
|
2999
|
+
let x = q;
|
|
3000
|
+
for (; ; ) {
|
|
3001
|
+
if (ts[x] === time) {
|
|
3002
|
+
k += dist[x];
|
|
3003
|
+
break;
|
|
3004
|
+
}
|
|
3005
|
+
const a = parent[x];
|
|
3006
|
+
k++;
|
|
3007
|
+
if (a === TERMINAL) {
|
|
3008
|
+
ts[x] = time;
|
|
3009
|
+
dist[x] = 1;
|
|
3010
|
+
break;
|
|
3011
|
+
}
|
|
3012
|
+
if (a === NONE) {
|
|
3013
|
+
k = Infinity;
|
|
3014
|
+
break;
|
|
3015
|
+
}
|
|
3016
|
+
x = nb(x, a);
|
|
3017
|
+
}
|
|
3018
|
+
if (k === Infinity) continue;
|
|
3019
|
+
if (k < bestDist) {
|
|
3020
|
+
bestDist = k;
|
|
3021
|
+
best = d;
|
|
3022
|
+
}
|
|
3023
|
+
for (let y = q, dd = k; ts[y] !== time; y = nb(y, parent[y]), dd--) {
|
|
3024
|
+
ts[y] = time;
|
|
3025
|
+
dist[y] = dd;
|
|
3026
|
+
}
|
|
3027
|
+
}
|
|
3028
|
+
if (best !== NONE) {
|
|
3029
|
+
parent[o] = best;
|
|
3030
|
+
ts[o] = time;
|
|
3031
|
+
dist[o] = bestDist + 1;
|
|
3032
|
+
continue;
|
|
3033
|
+
}
|
|
3034
|
+
for (let d = 0; d < 4; d++) {
|
|
3035
|
+
const q = nb(o, d);
|
|
3036
|
+
if (q < 0 || tree[q] !== oSide) continue;
|
|
3037
|
+
if (oSide === SOURCE ? cap[q * 4 + (d ^ 2)] > 0 : cap[o * 4 + d] > 0) activate(q);
|
|
3038
|
+
const a = parent[q];
|
|
3039
|
+
if (a !== TERMINAL && a !== NONE && nb(q, a) === o) orphan(q);
|
|
3040
|
+
}
|
|
3041
|
+
tree[o] = FREE;
|
|
3042
|
+
}
|
|
3043
|
+
}
|
|
3044
|
+
return flow;
|
|
3045
|
+
}
|
|
3046
|
+
};
|
|
3047
|
+
|
|
3048
|
+
// src/image-editor/selection/segment/quick.ts
|
|
3049
|
+
var WORK_SIZE = 512;
|
|
3050
|
+
var workCache = /* @__PURE__ */ new WeakMap();
|
|
3051
|
+
function workImage(pixels, maxSide = WORK_SIZE) {
|
|
3052
|
+
const cached = workCache.get(pixels.data);
|
|
3053
|
+
if (cached && cached.maxSide === maxSide && cached.work.sourceWidth === pixels.width && cached.work.sourceHeight === pixels.height) return cached.work;
|
|
3054
|
+
const { width: W, height: H, data } = pixels;
|
|
3055
|
+
const scale = Math.min(1, maxSide / Math.max(W, H));
|
|
3056
|
+
const w = Math.max(1, Math.round(W * scale));
|
|
3057
|
+
const h = Math.max(1, Math.round(H * scale));
|
|
3058
|
+
const sum = new Float64Array(w * h * 3);
|
|
3059
|
+
const count = new Float64Array(w * h);
|
|
3060
|
+
const xs = new Int32Array(W);
|
|
3061
|
+
for (let x = 0; x < W; x++) xs[x] = Math.min(w - 1, Math.floor(x * w / W));
|
|
3062
|
+
for (let y = 0; y < H; y++) {
|
|
3063
|
+
const row = Math.min(h - 1, Math.floor(y * h / H)) * w;
|
|
3064
|
+
for (let x = 0, p = y * W * 4; x < W; x++, p += 4) {
|
|
3065
|
+
const o = row + xs[x];
|
|
3066
|
+
const a = data[p + 3] / 255;
|
|
3067
|
+
const k = o * 3;
|
|
3068
|
+
sum[k] += data[p] * a + 255 * (1 - a);
|
|
3069
|
+
sum[k + 1] += data[p + 1] * a + 255 * (1 - a);
|
|
3070
|
+
sum[k + 2] += data[p + 2] * a + 255 * (1 - a);
|
|
3071
|
+
count[o]++;
|
|
3072
|
+
}
|
|
3073
|
+
}
|
|
3074
|
+
const rgb = new Float32Array(w * h * 3);
|
|
3075
|
+
for (let i = 0; i < w * h; i++) {
|
|
3076
|
+
const c = count[i] || 1;
|
|
3077
|
+
rgb[i * 3] = sum[i * 3] / c;
|
|
3078
|
+
rgb[i * 3 + 1] = sum[i * 3 + 1] / c;
|
|
3079
|
+
rgb[i * 3 + 2] = sum[i * 3 + 2] / c;
|
|
3080
|
+
}
|
|
3081
|
+
const work = { sourceWidth: W, sourceHeight: H, width: w, height: h, scale: w / W, rgb };
|
|
3082
|
+
workCache.set(pixels.data, { maxSide, work });
|
|
3083
|
+
return work;
|
|
3084
|
+
}
|
|
3085
|
+
var BINS = 16;
|
|
3086
|
+
var binOf = (rgb, i) => (Math.min(BINS - 1, rgb[i * 3] >> 4) * BINS + Math.min(BINS - 1, rgb[i * 3 + 1] >> 4)) * BINS + Math.min(BINS - 1, rgb[i * 3 + 2] >> 4);
|
|
3087
|
+
function costTable(hist, total) {
|
|
3088
|
+
let a = hist;
|
|
3089
|
+
for (const stride of [1, BINS, BINS * BINS]) {
|
|
3090
|
+
const b = new Float64Array(a.length);
|
|
3091
|
+
for (let i = 0; i < a.length; i++) {
|
|
3092
|
+
const pos = Math.floor(i / stride) % BINS;
|
|
3093
|
+
const lo = pos > 0 ? a[i - stride] : a[i];
|
|
3094
|
+
const hi = pos < BINS - 1 ? a[i + stride] : a[i];
|
|
3095
|
+
b[i] = (lo + 2 * a[i] + hi) / 4;
|
|
3096
|
+
}
|
|
3097
|
+
a = b;
|
|
3098
|
+
}
|
|
3099
|
+
const out = new Float64Array(a.length);
|
|
3100
|
+
const floor = 1e-4;
|
|
3101
|
+
const denom = Math.max(1, total) * (1 + floor * a.length);
|
|
3102
|
+
for (let i = 0; i < a.length; i++) out[i] = -Math.log((a[i] + floor * Math.max(1, total)) / denom);
|
|
3103
|
+
return out;
|
|
3104
|
+
}
|
|
3105
|
+
var GAMMA = 50;
|
|
3106
|
+
var UNITS = 64;
|
|
3107
|
+
function strokePixels(points, r, w, h) {
|
|
3108
|
+
const mask = new Uint8Array(w * h);
|
|
3109
|
+
const list = [];
|
|
3110
|
+
const add2 = (g) => {
|
|
3111
|
+
if (!mask[g]) {
|
|
3112
|
+
mask[g] = 1;
|
|
3113
|
+
list.push(g);
|
|
3114
|
+
}
|
|
3115
|
+
};
|
|
3116
|
+
const segs = points.length === 1 ? [[points[0], points[0]]] : points.slice(1).map((p, i) => [points[i], p]);
|
|
3117
|
+
for (const [a, b] of segs) {
|
|
3118
|
+
const x0 = Math.max(0, Math.floor(Math.min(a.x, b.x) - r));
|
|
3119
|
+
const x1 = Math.min(w - 1, Math.ceil(Math.max(a.x, b.x) + r));
|
|
3120
|
+
const y0 = Math.max(0, Math.floor(Math.min(a.y, b.y) - r));
|
|
3121
|
+
const y1 = Math.min(h - 1, Math.ceil(Math.max(a.y, b.y) + r));
|
|
3122
|
+
const dx = b.x - a.x;
|
|
3123
|
+
const dy = b.y - a.y;
|
|
3124
|
+
const len2 = dx * dx + dy * dy;
|
|
3125
|
+
for (let y = y0; y <= y1; y++) {
|
|
3126
|
+
for (let x = x0; x <= x1; x++) {
|
|
3127
|
+
const px2 = x + 0.5 - a.x;
|
|
3128
|
+
const py = y + 0.5 - a.y;
|
|
3129
|
+
const t = len2 > 0 ? Math.min(1, Math.max(0, (px2 * dx + py * dy) / len2)) : 0;
|
|
3130
|
+
if ((px2 - t * dx) ** 2 + (py - t * dy) ** 2 <= r * r) add2(y * w + x);
|
|
3131
|
+
}
|
|
3132
|
+
}
|
|
3133
|
+
}
|
|
3134
|
+
if (!list.length) {
|
|
3135
|
+
for (const p of points) add2(Math.min(h - 1, Math.max(0, Math.floor(p.y))) * w + Math.min(w - 1, Math.max(0, Math.floor(p.x))));
|
|
3136
|
+
}
|
|
3137
|
+
return { mask, list };
|
|
3138
|
+
}
|
|
3139
|
+
function segmentStroke(work, stroke, grown, start) {
|
|
3140
|
+
const { width: w, height: h, rgb } = work;
|
|
3141
|
+
let roi = start;
|
|
3142
|
+
for (; ; ) {
|
|
3143
|
+
const { x: rx, y: ry, width: rw, height: rh } = roi;
|
|
3144
|
+
const n = rw * rh;
|
|
3145
|
+
const fgHist = new Float64Array(BINS ** 3);
|
|
3146
|
+
const bgHist = new Float64Array(BINS ** 3);
|
|
3147
|
+
let fgN = 0;
|
|
3148
|
+
let bgN = 0;
|
|
3149
|
+
const atImageEdge = { l: rx === 0, t: ry === 0, r: rx + rw === w, b: ry + rh === h };
|
|
3150
|
+
for (let y = 0; y < rh; y++) {
|
|
3151
|
+
for (let x = 0; x < rw; x++) {
|
|
3152
|
+
const g = (ry + y) * w + (rx + x);
|
|
3153
|
+
const bin = binOf(rgb, g);
|
|
3154
|
+
bgHist[bin]++;
|
|
3155
|
+
bgN++;
|
|
3156
|
+
if (stroke.mask[g] || grown?.[g]) {
|
|
3157
|
+
fgHist[bin]++;
|
|
3158
|
+
fgN++;
|
|
3159
|
+
}
|
|
3160
|
+
}
|
|
3161
|
+
}
|
|
3162
|
+
const fgCost = costTable(fgHist, fgN);
|
|
3163
|
+
const bgCost = costTable(bgHist, bgN);
|
|
3164
|
+
let sumSq = 0;
|
|
3165
|
+
let pairs = 0;
|
|
3166
|
+
for (let y = 0; y < rh; y++) {
|
|
3167
|
+
for (let x = 0; x < rw; x++) {
|
|
3168
|
+
const g = ((ry + y) * w + rx + x) * 3;
|
|
3169
|
+
if (x + 1 < rw) {
|
|
3170
|
+
sumSq += (rgb[g] - rgb[g + 3]) ** 2 + (rgb[g + 1] - rgb[g + 4]) ** 2 + (rgb[g + 2] - rgb[g + 5]) ** 2;
|
|
3171
|
+
pairs++;
|
|
3172
|
+
}
|
|
3173
|
+
if (y + 1 < rh) {
|
|
3174
|
+
const k = g + w * 3;
|
|
3175
|
+
sumSq += (rgb[g] - rgb[k]) ** 2 + (rgb[g + 1] - rgb[k + 1]) ** 2 + (rgb[g + 2] - rgb[k + 2]) ** 2;
|
|
3176
|
+
pairs++;
|
|
3177
|
+
}
|
|
3178
|
+
}
|
|
3179
|
+
}
|
|
3180
|
+
const beta = 1 / (2 * Math.max(1e-6, sumSq / Math.max(1, pairs)));
|
|
3181
|
+
const graph = new GridGraph(rw, rh);
|
|
3182
|
+
const HARD = 1e9;
|
|
3183
|
+
for (let y = 0; y < rh; y++) {
|
|
3184
|
+
for (let x = 0; x < rw; x++) {
|
|
3185
|
+
const p = y * rw + x;
|
|
3186
|
+
const g = (ry + y) * w + rx + x;
|
|
3187
|
+
if (stroke.mask[g]) graph.addTerminal(p, HARD, 0);
|
|
3188
|
+
else {
|
|
3189
|
+
const bin = binOf(rgb, g);
|
|
3190
|
+
graph.addTerminal(p, Math.round(bgCost[bin] * UNITS), Math.round(fgCost[bin] * UNITS));
|
|
3191
|
+
}
|
|
3192
|
+
const c = g * 3;
|
|
3193
|
+
if (x + 1 < rw) {
|
|
3194
|
+
const d = (rgb[c] - rgb[c + 3]) ** 2 + (rgb[c + 1] - rgb[c + 4]) ** 2 + (rgb[c + 2] - rgb[c + 5]) ** 2;
|
|
3195
|
+
graph.setEdge(p, 0, Math.round(GAMMA * UNITS * Math.exp(-beta * d)));
|
|
3196
|
+
}
|
|
3197
|
+
if (y + 1 < rh) {
|
|
3198
|
+
const k = c + w * 3;
|
|
3199
|
+
const d = (rgb[c] - rgb[k]) ** 2 + (rgb[c + 1] - rgb[k + 1]) ** 2 + (rgb[c + 2] - rgb[k + 2]) ** 2;
|
|
3200
|
+
graph.setEdge(p, 1, Math.round(GAMMA * UNITS * Math.exp(-beta * d)));
|
|
3201
|
+
}
|
|
3202
|
+
}
|
|
3203
|
+
}
|
|
3204
|
+
graph.maxflow();
|
|
3205
|
+
const label = new Uint8Array(n);
|
|
3206
|
+
const stack = [];
|
|
3207
|
+
for (const g of stroke.list) {
|
|
3208
|
+
const x = g % w - rx;
|
|
3209
|
+
const y = Math.floor(g / w) - ry;
|
|
3210
|
+
if (x < 0 || y < 0 || x >= rw || y >= rh) continue;
|
|
3211
|
+
const p = y * rw + x;
|
|
3212
|
+
if (!label[p]) {
|
|
3213
|
+
label[p] = 1;
|
|
3214
|
+
stack.push(p);
|
|
3215
|
+
}
|
|
3216
|
+
}
|
|
3217
|
+
while (stack.length) {
|
|
3218
|
+
const p = stack.pop();
|
|
3219
|
+
const x = p % rw;
|
|
3220
|
+
for (const q of [x + 1 < rw ? p + 1 : -1, x > 0 ? p - 1 : -1, p + rw < n ? p + rw : -1, p - rw]) {
|
|
3221
|
+
if (q >= 0 && !label[q] && graph.inSource(q)) {
|
|
3222
|
+
label[q] = 1;
|
|
3223
|
+
stack.push(q);
|
|
3224
|
+
}
|
|
3225
|
+
}
|
|
3226
|
+
}
|
|
3227
|
+
let touches = false;
|
|
3228
|
+
for (let y = 0; y < rh; y++) {
|
|
3229
|
+
for (let x = 0; x < rw; x++) {
|
|
3230
|
+
const p = y * rw + x;
|
|
3231
|
+
if (!label[p]) continue;
|
|
3232
|
+
if (!atImageEdge.l && x === 0 || !atImageEdge.t && y === 0 || !atImageEdge.r && x === rw - 1 || !atImageEdge.b && y === rh - 1) touches = true;
|
|
3233
|
+
}
|
|
3234
|
+
}
|
|
3235
|
+
if (!touches || atImageEdge.l && atImageEdge.t && atImageEdge.r && atImageEdge.b) return { roi, label };
|
|
3236
|
+
const nx = Math.max(0, rx - Math.ceil(rw / 2));
|
|
3237
|
+
const ny = Math.max(0, ry - Math.ceil(rh / 2));
|
|
3238
|
+
roi = { x: nx, y: ny, width: Math.min(w, rx + rw + Math.ceil(rw / 2)) - nx, height: Math.min(h, ry + rh + Math.ceil(rh / 2)) - ny };
|
|
3239
|
+
if (roi.width * roi.height * 2.5 > w * h) roi = { x: 0, y: 0, width: w, height: h };
|
|
3240
|
+
}
|
|
3241
|
+
}
|
|
3242
|
+
function boxBlur(src, w, h, passes) {
|
|
3243
|
+
let a = src;
|
|
3244
|
+
for (let k = 0; k < passes; k++) {
|
|
3245
|
+
const b = new Float32Array(a.length);
|
|
3246
|
+
for (let y = 0; y < h; y++) {
|
|
3247
|
+
for (let x = 0; x < w; x++) {
|
|
3248
|
+
let s = 0;
|
|
3249
|
+
let c = 0;
|
|
3250
|
+
for (let j = Math.max(0, y - 1); j <= Math.min(h - 1, y + 1); j++) {
|
|
3251
|
+
for (let i = Math.max(0, x - 1); i <= Math.min(w - 1, x + 1); i++) {
|
|
3252
|
+
s += a[j * w + i];
|
|
3253
|
+
c++;
|
|
3254
|
+
}
|
|
3255
|
+
}
|
|
3256
|
+
b[y * w + x] = s / c;
|
|
3257
|
+
}
|
|
3258
|
+
}
|
|
3259
|
+
a = b;
|
|
3260
|
+
}
|
|
3261
|
+
return a;
|
|
3262
|
+
}
|
|
3263
|
+
function upscaleRegion(work, pixels, roi, label, enhance) {
|
|
3264
|
+
const { sourceWidth: W, sourceHeight: H, scale, rgb, width: ww } = work;
|
|
3265
|
+
const { x: rx, y: ry, width: rw, height: rh } = roi;
|
|
3266
|
+
let soft = Float32Array.from(label);
|
|
3267
|
+
if (enhance) {
|
|
3268
|
+
soft = boxBlur(soft, rw, rh, 2);
|
|
3269
|
+
for (let i = 0; i < soft.length; i++) soft[i] = Math.min(1, Math.max(0, (soft[i] - 0.5) * 3 + 0.5));
|
|
3270
|
+
}
|
|
3271
|
+
const X0 = Math.max(0, Math.floor(rx / scale));
|
|
3272
|
+
const Y0 = Math.max(0, Math.floor(ry / scale));
|
|
3273
|
+
const X1 = Math.min(W, Math.ceil((rx + rw) / scale));
|
|
3274
|
+
const Y1 = Math.min(H, Math.ceil((ry + rh) / scale));
|
|
3275
|
+
const RW = X1 - X0;
|
|
3276
|
+
const RH = Y1 - Y0;
|
|
3277
|
+
const out = new Uint8Array(RW * RH);
|
|
3278
|
+
const at = (x, y) => soft[Math.min(rh - 1, Math.max(0, y)) * rw + Math.min(rw - 1, Math.max(0, x))];
|
|
3279
|
+
const data = pixels.data;
|
|
3280
|
+
const colOf = new Int32Array(RW);
|
|
3281
|
+
const fxOf = new Float32Array(RW);
|
|
3282
|
+
for (let X = 0; X < RW; X++) {
|
|
3283
|
+
const u = (X0 + X + 0.5) * scale - 0.5 - rx;
|
|
3284
|
+
colOf[X] = Math.floor(u);
|
|
3285
|
+
fxOf[X] = u - colOf[X];
|
|
3286
|
+
}
|
|
3287
|
+
const cellState = (i, j) => {
|
|
3288
|
+
const a = at(i, j), b = at(i + 1, j), c = at(i, j + 1), d = at(i + 1, j + 1);
|
|
3289
|
+
if (a === 1 && b === 1 && c === 1 && d === 1) return 1;
|
|
3290
|
+
if (a === 0 && b === 0 && c === 0 && d === 0) return 0;
|
|
3291
|
+
return 2;
|
|
3292
|
+
};
|
|
3293
|
+
for (let Y = 0; Y < RH; Y++) {
|
|
3294
|
+
const v = (Y0 + Y + 0.5) * scale - 0.5 - ry;
|
|
3295
|
+
const j = Math.floor(v);
|
|
3296
|
+
const fy = v - j;
|
|
3297
|
+
for (let X = 0; X < RW; X++) {
|
|
3298
|
+
const i = colOf[X];
|
|
3299
|
+
const state = cellState(i, j);
|
|
3300
|
+
if (state !== 2) {
|
|
3301
|
+
let end = X + 1;
|
|
3302
|
+
while (end < RW && colOf[end] === i) end++;
|
|
3303
|
+
if (state === 1) out.fill(255, Y * RW + X, Y * RW + end);
|
|
3304
|
+
X = end - 1;
|
|
3305
|
+
continue;
|
|
3306
|
+
}
|
|
3307
|
+
const fx = fxOf[X];
|
|
3308
|
+
const m = (at(i, j) * (1 - fx) + at(i + 1, j) * fx) * (1 - fy) + (at(i, j + 1) * (1 - fx) + at(i + 1, j + 1) * fx) * fy;
|
|
3309
|
+
if (m <= 1e-3 || m >= 0.999) {
|
|
3310
|
+
out[Y * RW + X] = m >= 0.999 ? 255 : 0;
|
|
3311
|
+
continue;
|
|
3312
|
+
}
|
|
3313
|
+
let fr = 0, fg = 0, fb = 0, fn = 0, br = 0, bg = 0, bb = 0, bn = 0;
|
|
3314
|
+
for (let jj = j - 1; jj <= j + 2; jj++) {
|
|
3315
|
+
for (let ii = i - 1; ii <= i + 2; ii++) {
|
|
3316
|
+
if (ii < 0 || jj < 0 || ii >= rw || jj >= rh) continue;
|
|
3317
|
+
const k = ((ry + jj) * ww + rx + ii) * 3;
|
|
3318
|
+
if (soft[jj * rw + ii] >= 0.5) {
|
|
3319
|
+
fr += rgb[k];
|
|
3320
|
+
fg += rgb[k + 1];
|
|
3321
|
+
fb += rgb[k + 2];
|
|
3322
|
+
fn++;
|
|
3323
|
+
} else {
|
|
3324
|
+
br += rgb[k];
|
|
3325
|
+
bg += rgb[k + 1];
|
|
3326
|
+
bb += rgb[k + 2];
|
|
3327
|
+
bn++;
|
|
3328
|
+
}
|
|
3329
|
+
}
|
|
3330
|
+
}
|
|
3331
|
+
let alpha = m;
|
|
3332
|
+
if (fn && bn) {
|
|
3333
|
+
fr /= fn;
|
|
3334
|
+
fg /= fn;
|
|
3335
|
+
fb /= fn;
|
|
3336
|
+
br /= bn;
|
|
3337
|
+
bg /= bn;
|
|
3338
|
+
bb /= bn;
|
|
3339
|
+
const dr = fr - br, dg = fg - bg, db = fb - bb;
|
|
3340
|
+
const len2 = dr * dr + dg * dg + db * db;
|
|
3341
|
+
if (len2 > 400) {
|
|
3342
|
+
const p = ((Y0 + Y) * W + X0 + X) * 4;
|
|
3343
|
+
const a = data[p + 3] / 255;
|
|
3344
|
+
const cr = data[p] * a + 255 * (1 - a) - br;
|
|
3345
|
+
const cg = data[p + 1] * a + 255 * (1 - a) - bg;
|
|
3346
|
+
const cb = data[p + 2] * a + 255 * (1 - a) - bb;
|
|
3347
|
+
const proj = Math.min(1, Math.max(0, (cr * dr + cg * dg + cb * db) / len2));
|
|
3348
|
+
alpha = Math.min(1, Math.max(0, proj * 0.75 + m * 0.25));
|
|
3349
|
+
} else alpha = Math.min(1, Math.max(0, (m - 0.5) * 2 + 0.5));
|
|
3350
|
+
}
|
|
3351
|
+
out[Y * RW + X] = Math.round(alpha * 255);
|
|
3352
|
+
}
|
|
3353
|
+
}
|
|
3354
|
+
return selectionFromRegion(W, H, { x: X0, y: Y0, width: RW, height: RH }, out);
|
|
3355
|
+
}
|
|
3356
|
+
function quickSelectStroke(pixels, stroke, options = {}) {
|
|
3357
|
+
if (!stroke.length) throw new Error("A stroke needs at least one point.");
|
|
3358
|
+
const work = workImage(pixels);
|
|
3359
|
+
const { width: w, height: h, scale } = work;
|
|
3360
|
+
const r = Math.max(0.5, (options.size ?? 30) / 2 * scale);
|
|
3361
|
+
const pts = stroke.map((p) => ({ x: p.x * scale, y: p.y * scale }));
|
|
3362
|
+
const seeds = strokePixels(pts, r, w, h);
|
|
3363
|
+
let x0 = Infinity, y0 = Infinity, x1 = -Infinity, y1 = -Infinity;
|
|
3364
|
+
for (const p of pts) {
|
|
3365
|
+
x0 = Math.min(x0, p.x);
|
|
3366
|
+
y0 = Math.min(y0, p.y);
|
|
3367
|
+
x1 = Math.max(x1, p.x);
|
|
3368
|
+
y1 = Math.max(y1, p.y);
|
|
3369
|
+
}
|
|
3370
|
+
const margin = Math.max(24, 4 * r);
|
|
3371
|
+
const rx = Math.max(0, Math.floor(x0 - margin));
|
|
3372
|
+
const ry = Math.max(0, Math.floor(y0 - margin));
|
|
3373
|
+
const roi = { x: rx, y: ry, width: Math.min(w, Math.ceil(x1 + margin)) - rx, height: Math.min(h, Math.ceil(y1 + margin)) - ry };
|
|
3374
|
+
let grown = null;
|
|
3375
|
+
const sel = options.grow ? options.selection : null;
|
|
3376
|
+
if (sel?.bounds) {
|
|
3377
|
+
grown = new Uint8Array(w * h);
|
|
3378
|
+
const b = sel.bounds;
|
|
3379
|
+
for (let y = Math.max(0, Math.floor(b.y * scale)); y < Math.min(h, Math.ceil((b.y + b.height) * scale)); y++) {
|
|
3380
|
+
for (let x = Math.max(0, Math.floor(b.x * scale)); x < Math.min(w, Math.ceil((b.x + b.width) * scale)); x++) {
|
|
3381
|
+
if (selectionValueAt(sel, Math.min(sel.width - 1, Math.floor((x + 0.5) / scale)), Math.min(sel.height - 1, Math.floor((y + 0.5) / scale))) >= 0.5) grown[y * w + x] = 1;
|
|
3382
|
+
}
|
|
3383
|
+
}
|
|
3384
|
+
}
|
|
3385
|
+
const { roi: done, label } = segmentStroke(work, seeds, grown, roi);
|
|
3386
|
+
return upscaleRegion(work, pixels, done, label, options.enhanceEdge ?? false);
|
|
3387
|
+
}
|
|
3388
|
+
|
|
2806
3389
|
// src/image-editor/layers/shape-type-ui.ts
|
|
2807
3390
|
var DEFAULT_SHAPE_TOOL_OPTIONS = {
|
|
2808
3391
|
kind: "rect",
|
|
@@ -8747,6 +9330,31 @@ var ImageEditorController = class {
|
|
|
8747
9330
|
const pixels = await this.requirePixels(options);
|
|
8748
9331
|
return snapPolygonToEdges(pixels, points, options);
|
|
8749
9332
|
}
|
|
9333
|
+
/**
|
|
9334
|
+
* Quick Selection: each stroke (the brush's path in natural pixels) grows into the region it is
|
|
9335
|
+
* painted on, out to that region's edges, and is combined with the selection by `mode`; with
|
|
9336
|
+
* `replace`, the first stroke replaces it and the rest add, as the brush does. All strokes are
|
|
9337
|
+
* one undo step. Resolves to the resulting selection.
|
|
9338
|
+
*/
|
|
9339
|
+
async quickSelect(strokes, options = {}) {
|
|
9340
|
+
if (!strokes.length || strokes.some((s) => !s.length)) throw new Error("Pass at least one stroke, each with at least one point.");
|
|
9341
|
+
const { width, height } = this.naturalSize;
|
|
9342
|
+
for (const s of strokes) {
|
|
9343
|
+
if (!s.some((p) => p.x >= 0 && p.y >= 0 && p.x < width && p.y < height)) {
|
|
9344
|
+
throw new Error(`A stroke lies entirely outside the image (0..${width - 1}, 0..${height - 1}); paint over the area to select.`);
|
|
9345
|
+
}
|
|
9346
|
+
}
|
|
9347
|
+
const pixels = await this.requirePixels({ sample: options.sampleAllLayers === false ? "active-layer" : "composite" });
|
|
9348
|
+
const mode = options.mode ?? "replace";
|
|
9349
|
+
let sel = this.state.selection ?? null;
|
|
9350
|
+
strokes.forEach((stroke, i) => {
|
|
9351
|
+
const m = mode === "replace" && i > 0 ? "add" : mode;
|
|
9352
|
+
const region = quickSelectStroke(pixels, stroke, { size: options.size, selection: sel, grow: m === "add", enhanceEdge: options.enhanceEdge });
|
|
9353
|
+
sel = combineSelections(sel, region, m);
|
|
9354
|
+
});
|
|
9355
|
+
this.setSelection(sel, "replace");
|
|
9356
|
+
return this.state.selection ?? null;
|
|
9357
|
+
}
|
|
8750
9358
|
/**
|
|
8751
9359
|
* Supplies the pixels the selection tools sample (natural size RGBA), e.g. decoded in a worker
|
|
8752
9360
|
* or in a test; `null` goes back to reading the source image. Without a canvas (Node, workers)
|
|
@@ -11634,7 +12242,7 @@ var DEPTH_MASK_CONTROLS = [
|
|
|
11634
12242
|
// src/image-editor/playback.ts
|
|
11635
12243
|
var IMAGE_PLAYBACK_KIND = "layer";
|
|
11636
12244
|
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)$/;
|
|
11637
|
-
var SELECT = /^(select_region|select_subject|select_by_color|select_all|deselect|invert_selection|select_rect|select_ellipse|select_polygon|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)$/;
|
|
12245
|
+
var SELECT = /^(select_region|select_subject|select_by_color|select_all|deselect|invert_selection|select_rect|select_ellipse|select_polygon|quick_select|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)$/;
|
|
11638
12246
|
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)$/;
|
|
11639
12247
|
var CROP = /^(set_crop|crop_to_selection|reset_crop|set_rotation|flip_image|transform_layer|warp_layer|flip_layer|offset_layer)$/;
|
|
11640
12248
|
var ADJUST = /^(set_adjustments|add_adjustment_layer|update_adjustment_layer|remove_adjustment_layer|move_adjustment_layer)$/;
|
|
@@ -11756,6 +12364,7 @@ var SELECTION_TOOLS = [
|
|
|
11756
12364
|
{ id: "lasso", label: "Lasso" },
|
|
11757
12365
|
{ id: "polygon", label: "Polygonal Lasso" },
|
|
11758
12366
|
{ id: "magnetic", label: "Magnetic Lasso" },
|
|
12367
|
+
{ id: "quick", label: "Quick Selection" },
|
|
11759
12368
|
{ id: "range", label: "Color Range" },
|
|
11760
12369
|
{ id: "type", label: "Type Mask" },
|
|
11761
12370
|
{ id: "type-vertical", label: "Vertical Type Mask" }
|
|
@@ -11793,6 +12402,9 @@ var DEFAULT_SELECTION_TOOL_OPTIONS = {
|
|
|
11793
12402
|
magneticWidth: 10,
|
|
11794
12403
|
magneticContrast: 10,
|
|
11795
12404
|
magneticFrequency: 57,
|
|
12405
|
+
quickSize: 30,
|
|
12406
|
+
quickSampleAll: true,
|
|
12407
|
+
quickEnhance: false,
|
|
11796
12408
|
typeText: "Type",
|
|
11797
12409
|
typeSize: 72,
|
|
11798
12410
|
typeFamily: "sans-serif"
|
|
@@ -12192,6 +12804,42 @@ function createImageSelectionTools(controller) {
|
|
|
12192
12804
|
return { ...summary(controller), outlinePoints: outline.length };
|
|
12193
12805
|
}
|
|
12194
12806
|
};
|
|
12807
|
+
const quickSelectTool = {
|
|
12808
|
+
name: "quick_select",
|
|
12809
|
+
title: "Quick Selection",
|
|
12810
|
+
description: "Quick Selection: paints brush strokes (paths in natural image pixels) that each grow into the region they are painted on, out to its edges (a graph cut on colour and edges), combined with the selection by mode. With replace (default) the first stroke replaces the selection and later ones add, as the brush does; use mode subtract to take an area back out. Best for a subject or area that differs from its surroundings: paint inside it, away from the edge, then check with get_selection and capture_view and add or subtract strokes where it leaked or fell short. All strokes are one undoable step. Returns the selection.",
|
|
12811
|
+
inputSchema: {
|
|
12812
|
+
type: "object",
|
|
12813
|
+
properties: {
|
|
12814
|
+
strokes: {
|
|
12815
|
+
type: "array",
|
|
12816
|
+
minItems: 1,
|
|
12817
|
+
items: { type: "array", minItems: 1, items: pointSchema, description: "One stroke: the brush path, in order (one point is a dab)." },
|
|
12818
|
+
description: "Brush strokes in natural image pixels, applied in order."
|
|
12819
|
+
},
|
|
12820
|
+
size: { type: "number", minimum: 1, maximum: 2500, description: "Brush diameter in natural image pixels (default 30). Smaller for a narrow area, larger for a broad one." },
|
|
12821
|
+
mode: modeSchema,
|
|
12822
|
+
sampleAllLayers: { type: "boolean", description: "Read every layer (default true); false reads only the active pixel layer." },
|
|
12823
|
+
enhanceEdge: { type: "boolean", description: "Smoother, less blocky edges (Auto-Enhance; default false). Use it before masking a smooth-edged subject." }
|
|
12824
|
+
},
|
|
12825
|
+
required: ["strokes"],
|
|
12826
|
+
additionalProperties: false
|
|
12827
|
+
},
|
|
12828
|
+
annotations: writeAnnotations("Quick Selection"),
|
|
12829
|
+
handler: async (args) => {
|
|
12830
|
+
const strokes = Array.isArray(args.strokes) ? args.strokes.map((s) => Array.isArray(s) ? s.map((p) => ({ x: num2(p?.x, NaN), y: num2(p?.y, NaN) })) : []) : [];
|
|
12831
|
+
if (!strokes.length || strokes.some((s) => !s.length || !s.every((p) => Number.isFinite(p.x) && Number.isFinite(p.y)))) {
|
|
12832
|
+
throw new Error("strokes needs at least one stroke of { x, y } points in natural image pixels.");
|
|
12833
|
+
}
|
|
12834
|
+
await controller.quickSelect(strokes, {
|
|
12835
|
+
size: args.size === void 0 ? void 0 : num2(args.size, 30),
|
|
12836
|
+
mode: parseSelectionMode(args.mode),
|
|
12837
|
+
sampleAllLayers: args.sampleAllLayers,
|
|
12838
|
+
enhanceEdge: args.enhanceEdge
|
|
12839
|
+
});
|
|
12840
|
+
return summary(controller);
|
|
12841
|
+
}
|
|
12842
|
+
};
|
|
12195
12843
|
const magicWandTool = {
|
|
12196
12844
|
name: "magic_wand",
|
|
12197
12845
|
title: "Magic wand",
|
|
@@ -12744,6 +13392,7 @@ function createImageSelectionTools(controller) {
|
|
|
12744
13392
|
selectRect,
|
|
12745
13393
|
selectEllipse,
|
|
12746
13394
|
selectPolygonTool,
|
|
13395
|
+
quickSelectTool,
|
|
12747
13396
|
lineTool,
|
|
12748
13397
|
magicWandTool,
|
|
12749
13398
|
colorRangeTool,
|
|
@@ -14131,9 +14780,14 @@ var SelectionGesture = class {
|
|
|
14131
14780
|
closeRadius = 8;
|
|
14132
14781
|
/** The last error (e.g. pixels unavailable for the wand), for the UI to show. */
|
|
14133
14782
|
error = null;
|
|
14783
|
+
pending = Promise.resolve();
|
|
14134
14784
|
get active() {
|
|
14135
14785
|
return this.start !== null || this.polygon !== null || this.magnetic !== null;
|
|
14136
14786
|
}
|
|
14787
|
+
/** Resolves once a Quick Selection stroke has been applied. */
|
|
14788
|
+
get settled() {
|
|
14789
|
+
return this.pending;
|
|
14790
|
+
}
|
|
14137
14791
|
/** The anchors of a Magnetic Lasso being drawn (empty otherwise). */
|
|
14138
14792
|
get magneticAnchors() {
|
|
14139
14793
|
const m = this.magnetic;
|
|
@@ -14145,7 +14799,7 @@ var SelectionGesture = class {
|
|
|
14145
14799
|
if (this.polygon) return this.current ? [...this.polygon, this.current] : this.polygon.slice();
|
|
14146
14800
|
if (!this.start || !this.current) return null;
|
|
14147
14801
|
const { tool } = this.getOptions();
|
|
14148
|
-
if (tool === "lasso") return this.points.length > 1 ? this.points.slice() : null;
|
|
14802
|
+
if (tool === "lasso" || tool === "quick") return this.points.length > 1 ? this.points.slice() : null;
|
|
14149
14803
|
const rect = normalizeRect({
|
|
14150
14804
|
x: this.start.x,
|
|
14151
14805
|
y: this.start.y,
|
|
@@ -14279,7 +14933,8 @@ var SelectionGesture = class {
|
|
|
14279
14933
|
}
|
|
14280
14934
|
if (!this.start) return false;
|
|
14281
14935
|
this.current = point;
|
|
14282
|
-
|
|
14936
|
+
const drawn = this.getOptions().tool;
|
|
14937
|
+
if (drawn === "lasso" || drawn === "quick") {
|
|
14283
14938
|
const last = this.points[this.points.length - 1];
|
|
14284
14939
|
if (Math.hypot(point.x - last.x, point.y - last.y) < 0.5) return false;
|
|
14285
14940
|
this.points.push(point);
|
|
@@ -14299,6 +14954,25 @@ var SelectionGesture = class {
|
|
|
14299
14954
|
if (point) this.move(point);
|
|
14300
14955
|
const options = this.getOptions();
|
|
14301
14956
|
const mode = selectionModeFromModifiers(modifiers ?? this.modifiers, options.mode);
|
|
14957
|
+
if (options.tool === "quick") {
|
|
14958
|
+
const stroke = this.points;
|
|
14959
|
+
this.start = null;
|
|
14960
|
+
this.current = null;
|
|
14961
|
+
this.points = [];
|
|
14962
|
+
const strokeMode = mode === "replace" && this.controller.getSelection() ? "add" : mode;
|
|
14963
|
+
this.pending = this.controller.quickSelect([stroke], {
|
|
14964
|
+
size: options.quickSize ?? 30,
|
|
14965
|
+
mode: strokeMode,
|
|
14966
|
+
sampleAllLayers: options.quickSampleAll ?? true,
|
|
14967
|
+
enhanceEdge: options.quickEnhance ?? false
|
|
14968
|
+
}).then(
|
|
14969
|
+
() => void 0,
|
|
14970
|
+
(err) => {
|
|
14971
|
+
this.error = err instanceof Error ? err.message : String(err);
|
|
14972
|
+
}
|
|
14973
|
+
);
|
|
14974
|
+
return false;
|
|
14975
|
+
}
|
|
14302
14976
|
const path = this.draftPath;
|
|
14303
14977
|
const rect = normalizeRect({
|
|
14304
14978
|
x: this.start.x,
|
|
@@ -15154,7 +15828,7 @@ function checkSource(controller, source) {
|
|
|
15154
15828
|
}
|
|
15155
15829
|
return p;
|
|
15156
15830
|
}
|
|
15157
|
-
var
|
|
15831
|
+
var SOURCE2 = {
|
|
15158
15832
|
type: "object",
|
|
15159
15833
|
description: "Source point in document pixels (an Alt-click). Given, it replaces the clone source and the offset starts afresh at this stroke; missing, the current clone source is used (set_clone_source).",
|
|
15160
15834
|
properties: { x: { type: "number", description: "Source x in px." }, y: { type: "number", description: "Source y in px." } },
|
|
@@ -15178,7 +15852,7 @@ function createImageRetouchTools(controller) {
|
|
|
15178
15852
|
type: "object",
|
|
15179
15853
|
properties: {
|
|
15180
15854
|
points: POINTS,
|
|
15181
|
-
source:
|
|
15855
|
+
source: SOURCE2,
|
|
15182
15856
|
aligned: { type: "boolean", description: "Keep the offset for the next strokes (default the current option, true)." },
|
|
15183
15857
|
sample: SAMPLE,
|
|
15184
15858
|
brush: BRUSH2,
|
|
@@ -15198,7 +15872,7 @@ function createImageRetouchTools(controller) {
|
|
|
15198
15872
|
type: "object",
|
|
15199
15873
|
properties: {
|
|
15200
15874
|
points: POINTS,
|
|
15201
|
-
source:
|
|
15875
|
+
source: SOURCE2,
|
|
15202
15876
|
aligned: { type: "boolean", description: "Keep the offset for the next strokes (default the current option, true)." },
|
|
15203
15877
|
sample: SAMPLE,
|
|
15204
15878
|
brush: BRUSH2,
|
|
@@ -18981,6 +19655,7 @@ export {
|
|
|
18981
19655
|
GRAIN_PATTERNS,
|
|
18982
19656
|
GRAIN_TILE,
|
|
18983
19657
|
GUIDED_FILTER_EPS,
|
|
19658
|
+
GridGraph,
|
|
18984
19659
|
HIDE,
|
|
18985
19660
|
HISTOGRAM_BINS,
|
|
18986
19661
|
HUE_RANGE_LABELS,
|
|
@@ -19149,6 +19824,7 @@ export {
|
|
|
19149
19824
|
WGSL_SHADE,
|
|
19150
19825
|
WGSL_STEP,
|
|
19151
19826
|
WORKING_SPACE_OPTIONS,
|
|
19827
|
+
WORK_SIZE,
|
|
19152
19828
|
WetGpuEngine,
|
|
19153
19829
|
WetMedium,
|
|
19154
19830
|
WetWorkerPool,
|
|
@@ -19699,6 +20375,7 @@ export {
|
|
|
19699
20375
|
put255,
|
|
19700
20376
|
quadFrameOverlay,
|
|
19701
20377
|
quickMaskPixels,
|
|
20378
|
+
quickSelectStroke,
|
|
19702
20379
|
rand01,
|
|
19703
20380
|
randSigned,
|
|
19704
20381
|
rangeLightness,
|
|
@@ -19941,6 +20618,7 @@ export {
|
|
|
19941
20618
|
withFilterMaskSurface,
|
|
19942
20619
|
withImagePlayback,
|
|
19943
20620
|
withNewIds,
|
|
20621
|
+
workImage,
|
|
19944
20622
|
writeAdjustmentBlock,
|
|
19945
20623
|
writeDeepRegion,
|
|
19946
20624
|
writeLfx2,
|