@pixerate/editor 0.197.0 → 0.198.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 +914 -548
- package/dist/image-editor/index.js +512 -146
- package/dist/image-editor/selection/gesture.d.cts +3 -1
- package/dist/image-editor/selection/gesture.d.ts +3 -1
- package/dist/image-editor/selection/segment/grabcut.d.cts +45 -0
- package/dist/image-editor/selection/segment/grabcut.d.ts +45 -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/quick.d.cts +0 -19
- package/dist/image-editor/selection/segment/quick.d.ts +0 -19
- package/dist/image-editor/selection/segment/work.d.cts +54 -0
- package/dist/image-editor/selection/segment/work.d.ts +54 -0
- package/dist/image-editor/selection/tool-options.d.cts +5 -1
- package/dist/image-editor/selection/tool-options.d.ts +5 -1
- package/package.json +1 -1
- package/skills/pixerate-image-editor/SKILL.md +1 -0
- package/skills/pixerate-image-selection-masking/SKILL.md +7 -1
|
@@ -3045,7 +3045,7 @@ var GridGraph = class {
|
|
|
3045
3045
|
}
|
|
3046
3046
|
};
|
|
3047
3047
|
|
|
3048
|
-
// src/image-editor/selection/segment/
|
|
3048
|
+
// src/image-editor/selection/segment/work.ts
|
|
3049
3049
|
var WORK_SIZE = 512;
|
|
3050
3050
|
var workCache = /* @__PURE__ */ new WeakMap();
|
|
3051
3051
|
function workImage(pixels, maxSide = WORK_SIZE) {
|
|
@@ -3082,6 +3082,155 @@ function workImage(pixels, maxSide = WORK_SIZE) {
|
|
|
3082
3082
|
workCache.set(pixels.data, { maxSide, work });
|
|
3083
3083
|
return work;
|
|
3084
3084
|
}
|
|
3085
|
+
var GAMMA = 50;
|
|
3086
|
+
var UNITS = 64;
|
|
3087
|
+
function smoothnessWeights(work, roi) {
|
|
3088
|
+
const { width: w, rgb } = work;
|
|
3089
|
+
const { x: rx, y: ry, width: rw, height: rh } = roi;
|
|
3090
|
+
const right = new Float64Array(rw * rh);
|
|
3091
|
+
const down = new Float64Array(rw * rh);
|
|
3092
|
+
let sumSq = 0;
|
|
3093
|
+
let pairs = 0;
|
|
3094
|
+
for (let y = 0; y < rh; y++) {
|
|
3095
|
+
for (let x = 0; x < rw; x++) {
|
|
3096
|
+
const p = y * rw + x;
|
|
3097
|
+
const c = ((ry + y) * w + rx + x) * 3;
|
|
3098
|
+
if (x + 1 < rw) {
|
|
3099
|
+
right[p] = (rgb[c] - rgb[c + 3]) ** 2 + (rgb[c + 1] - rgb[c + 4]) ** 2 + (rgb[c + 2] - rgb[c + 5]) ** 2;
|
|
3100
|
+
sumSq += right[p];
|
|
3101
|
+
pairs++;
|
|
3102
|
+
}
|
|
3103
|
+
if (y + 1 < rh) {
|
|
3104
|
+
const k = c + w * 3;
|
|
3105
|
+
down[p] = (rgb[c] - rgb[k]) ** 2 + (rgb[c + 1] - rgb[k + 1]) ** 2 + (rgb[c + 2] - rgb[k + 2]) ** 2;
|
|
3106
|
+
sumSq += down[p];
|
|
3107
|
+
pairs++;
|
|
3108
|
+
}
|
|
3109
|
+
}
|
|
3110
|
+
}
|
|
3111
|
+
const beta = 1 / (2 * Math.max(1e-6, sumSq / Math.max(1, pairs)));
|
|
3112
|
+
for (let p = 0; p < right.length; p++) {
|
|
3113
|
+
right[p] = Math.round(GAMMA * UNITS * Math.exp(-beta * right[p]));
|
|
3114
|
+
down[p] = Math.round(GAMMA * UNITS * Math.exp(-beta * down[p]));
|
|
3115
|
+
}
|
|
3116
|
+
return { right, down };
|
|
3117
|
+
}
|
|
3118
|
+
function boxBlur(src, w, h, passes) {
|
|
3119
|
+
let a = src;
|
|
3120
|
+
for (let k = 0; k < passes; k++) {
|
|
3121
|
+
const b = new Float32Array(a.length);
|
|
3122
|
+
for (let y = 0; y < h; y++) {
|
|
3123
|
+
for (let x = 0; x < w; x++) {
|
|
3124
|
+
let s = 0;
|
|
3125
|
+
let c = 0;
|
|
3126
|
+
for (let j = Math.max(0, y - 1); j <= Math.min(h - 1, y + 1); j++) {
|
|
3127
|
+
for (let i = Math.max(0, x - 1); i <= Math.min(w - 1, x + 1); i++) {
|
|
3128
|
+
s += a[j * w + i];
|
|
3129
|
+
c++;
|
|
3130
|
+
}
|
|
3131
|
+
}
|
|
3132
|
+
b[y * w + x] = s / c;
|
|
3133
|
+
}
|
|
3134
|
+
}
|
|
3135
|
+
a = b;
|
|
3136
|
+
}
|
|
3137
|
+
return a;
|
|
3138
|
+
}
|
|
3139
|
+
function upscaleRegion(work, pixels, roi, label, enhance) {
|
|
3140
|
+
const { sourceWidth: W, sourceHeight: H, scale, rgb, width: ww } = work;
|
|
3141
|
+
const { x: rx, y: ry, width: rw, height: rh } = roi;
|
|
3142
|
+
let soft = Float32Array.from(label);
|
|
3143
|
+
if (enhance) {
|
|
3144
|
+
soft = boxBlur(soft, rw, rh, 2);
|
|
3145
|
+
for (let i = 0; i < soft.length; i++) soft[i] = Math.min(1, Math.max(0, (soft[i] - 0.5) * 3 + 0.5));
|
|
3146
|
+
}
|
|
3147
|
+
const X0 = Math.max(0, Math.floor(rx / scale));
|
|
3148
|
+
const Y0 = Math.max(0, Math.floor(ry / scale));
|
|
3149
|
+
const X1 = Math.min(W, Math.ceil((rx + rw) / scale));
|
|
3150
|
+
const Y1 = Math.min(H, Math.ceil((ry + rh) / scale));
|
|
3151
|
+
const RW = X1 - X0;
|
|
3152
|
+
const RH = Y1 - Y0;
|
|
3153
|
+
const out = new Uint8Array(RW * RH);
|
|
3154
|
+
const at = (x, y) => soft[Math.min(rh - 1, Math.max(0, y)) * rw + Math.min(rw - 1, Math.max(0, x))];
|
|
3155
|
+
const data = pixels.data;
|
|
3156
|
+
const colOf = new Int32Array(RW);
|
|
3157
|
+
const fxOf = new Float32Array(RW);
|
|
3158
|
+
for (let X = 0; X < RW; X++) {
|
|
3159
|
+
const u = (X0 + X + 0.5) * scale - 0.5 - rx;
|
|
3160
|
+
colOf[X] = Math.floor(u);
|
|
3161
|
+
fxOf[X] = u - colOf[X];
|
|
3162
|
+
}
|
|
3163
|
+
const cellState = (i, j) => {
|
|
3164
|
+
const a = at(i, j), b = at(i + 1, j), c = at(i, j + 1), d = at(i + 1, j + 1);
|
|
3165
|
+
if (a === 1 && b === 1 && c === 1 && d === 1) return 1;
|
|
3166
|
+
if (a === 0 && b === 0 && c === 0 && d === 0) return 0;
|
|
3167
|
+
return 2;
|
|
3168
|
+
};
|
|
3169
|
+
for (let Y = 0; Y < RH; Y++) {
|
|
3170
|
+
const v = (Y0 + Y + 0.5) * scale - 0.5 - ry;
|
|
3171
|
+
const j = Math.floor(v);
|
|
3172
|
+
const fy = v - j;
|
|
3173
|
+
for (let X = 0; X < RW; X++) {
|
|
3174
|
+
const i = colOf[X];
|
|
3175
|
+
const state = cellState(i, j);
|
|
3176
|
+
if (state !== 2) {
|
|
3177
|
+
let end = X + 1;
|
|
3178
|
+
while (end < RW && colOf[end] === i) end++;
|
|
3179
|
+
if (state === 1) out.fill(255, Y * RW + X, Y * RW + end);
|
|
3180
|
+
X = end - 1;
|
|
3181
|
+
continue;
|
|
3182
|
+
}
|
|
3183
|
+
const fx = fxOf[X];
|
|
3184
|
+
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;
|
|
3185
|
+
if (m <= 1e-3 || m >= 0.999) {
|
|
3186
|
+
out[Y * RW + X] = m >= 0.999 ? 255 : 0;
|
|
3187
|
+
continue;
|
|
3188
|
+
}
|
|
3189
|
+
let fr = 0, fg = 0, fb = 0, fn = 0, br = 0, bg = 0, bb = 0, bn = 0;
|
|
3190
|
+
for (let jj = j - 1; jj <= j + 2; jj++) {
|
|
3191
|
+
for (let ii = i - 1; ii <= i + 2; ii++) {
|
|
3192
|
+
if (ii < 0 || jj < 0 || ii >= rw || jj >= rh) continue;
|
|
3193
|
+
const k = ((ry + jj) * ww + rx + ii) * 3;
|
|
3194
|
+
if (soft[jj * rw + ii] >= 0.5) {
|
|
3195
|
+
fr += rgb[k];
|
|
3196
|
+
fg += rgb[k + 1];
|
|
3197
|
+
fb += rgb[k + 2];
|
|
3198
|
+
fn++;
|
|
3199
|
+
} else {
|
|
3200
|
+
br += rgb[k];
|
|
3201
|
+
bg += rgb[k + 1];
|
|
3202
|
+
bb += rgb[k + 2];
|
|
3203
|
+
bn++;
|
|
3204
|
+
}
|
|
3205
|
+
}
|
|
3206
|
+
}
|
|
3207
|
+
let alpha = m;
|
|
3208
|
+
if (fn && bn) {
|
|
3209
|
+
fr /= fn;
|
|
3210
|
+
fg /= fn;
|
|
3211
|
+
fb /= fn;
|
|
3212
|
+
br /= bn;
|
|
3213
|
+
bg /= bn;
|
|
3214
|
+
bb /= bn;
|
|
3215
|
+
const dr = fr - br, dg = fg - bg, db = fb - bb;
|
|
3216
|
+
const len2 = dr * dr + dg * dg + db * db;
|
|
3217
|
+
if (len2 > 400) {
|
|
3218
|
+
const p = ((Y0 + Y) * W + X0 + X) * 4;
|
|
3219
|
+
const a = data[p + 3] / 255;
|
|
3220
|
+
const cr = data[p] * a + 255 * (1 - a) - br;
|
|
3221
|
+
const cg = data[p + 1] * a + 255 * (1 - a) - bg;
|
|
3222
|
+
const cb = data[p + 2] * a + 255 * (1 - a) - bb;
|
|
3223
|
+
const proj = Math.min(1, Math.max(0, (cr * dr + cg * dg + cb * db) / len2));
|
|
3224
|
+
alpha = Math.min(1, Math.max(0, proj * 0.75 + m * 0.25));
|
|
3225
|
+
} else alpha = Math.min(1, Math.max(0, (m - 0.5) * 2 + 0.5));
|
|
3226
|
+
}
|
|
3227
|
+
out[Y * RW + X] = Math.round(alpha * 255);
|
|
3228
|
+
}
|
|
3229
|
+
}
|
|
3230
|
+
return selectionFromRegion(W, H, { x: X0, y: Y0, width: RW, height: RH }, out);
|
|
3231
|
+
}
|
|
3232
|
+
|
|
3233
|
+
// src/image-editor/selection/segment/quick.ts
|
|
3085
3234
|
var BINS = 16;
|
|
3086
3235
|
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
3236
|
function costTable(hist, total) {
|
|
@@ -3102,8 +3251,6 @@ function costTable(hist, total) {
|
|
|
3102
3251
|
for (let i = 0; i < a.length; i++) out[i] = -Math.log((a[i] + floor * Math.max(1, total)) / denom);
|
|
3103
3252
|
return out;
|
|
3104
3253
|
}
|
|
3105
|
-
var GAMMA = 50;
|
|
3106
|
-
var UNITS = 64;
|
|
3107
3254
|
function strokePixels(points, r, w, h) {
|
|
3108
3255
|
const mask = new Uint8Array(w * h);
|
|
3109
3256
|
const list = [];
|
|
@@ -3161,23 +3308,7 @@ function segmentStroke(work, stroke, grown, start) {
|
|
|
3161
3308
|
}
|
|
3162
3309
|
const fgCost = costTable(fgHist, fgN);
|
|
3163
3310
|
const bgCost = costTable(bgHist, bgN);
|
|
3164
|
-
|
|
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)));
|
|
3311
|
+
const { right, down } = smoothnessWeights(work, roi);
|
|
3181
3312
|
const graph = new GridGraph(rw, rh);
|
|
3182
3313
|
const HARD = 1e9;
|
|
3183
3314
|
for (let y = 0; y < rh; y++) {
|
|
@@ -3189,16 +3320,8 @@ function segmentStroke(work, stroke, grown, start) {
|
|
|
3189
3320
|
const bin = binOf(rgb, g);
|
|
3190
3321
|
graph.addTerminal(p, Math.round(bgCost[bin] * UNITS), Math.round(fgCost[bin] * UNITS));
|
|
3191
3322
|
}
|
|
3192
|
-
|
|
3193
|
-
if (
|
|
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
|
-
}
|
|
3323
|
+
if (x + 1 < rw) graph.setEdge(p, 0, right[p]);
|
|
3324
|
+
if (y + 1 < rh) graph.setEdge(p, 1, down[p]);
|
|
3202
3325
|
}
|
|
3203
3326
|
}
|
|
3204
3327
|
graph.maxflow();
|
|
@@ -3239,120 +3362,6 @@ function segmentStroke(work, stroke, grown, start) {
|
|
|
3239
3362
|
if (roi.width * roi.height * 2.5 > w * h) roi = { x: 0, y: 0, width: w, height: h };
|
|
3240
3363
|
}
|
|
3241
3364
|
}
|
|
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
3365
|
function quickSelectStroke(pixels, stroke, options = {}) {
|
|
3357
3366
|
if (!stroke.length) throw new Error("A stroke needs at least one point.");
|
|
3358
3367
|
const work = workImage(pixels);
|
|
@@ -3386,6 +3395,283 @@ function quickSelectStroke(pixels, stroke, options = {}) {
|
|
|
3386
3395
|
return upscaleRegion(work, pixels, done, label, options.enhanceEdge ?? false);
|
|
3387
3396
|
}
|
|
3388
3397
|
|
|
3398
|
+
// src/image-editor/selection/segment/grabcut.ts
|
|
3399
|
+
var K = 5;
|
|
3400
|
+
var ColorGmm = class {
|
|
3401
|
+
logWeight = new Float64Array(K);
|
|
3402
|
+
mean = new Float64Array(K * 3);
|
|
3403
|
+
/** Inverse covariances, row-major 3 × 3. */
|
|
3404
|
+
inv = new Float64Array(K * 9);
|
|
3405
|
+
/** ln(weight) − ½ ln((2π)³ |Σ|) per component; −∞ for an empty one. */
|
|
3406
|
+
logNorm = new Float64Array(K).fill(-Infinity);
|
|
3407
|
+
/** Fits the components to the pixels `ids` of `rgb`, each in component `comp[id]`. */
|
|
3408
|
+
fit(rgb, ids, comp) {
|
|
3409
|
+
const n = new Float64Array(K);
|
|
3410
|
+
const sum = new Float64Array(K * 3);
|
|
3411
|
+
const prod = new Float64Array(K * 9);
|
|
3412
|
+
for (let t = 0; t < ids.length; t++) {
|
|
3413
|
+
const i = ids[t];
|
|
3414
|
+
const k = comp[i];
|
|
3415
|
+
const c = [rgb[i * 3], rgb[i * 3 + 1], rgb[i * 3 + 2]];
|
|
3416
|
+
n[k]++;
|
|
3417
|
+
for (let a = 0; a < 3; a++) {
|
|
3418
|
+
sum[k * 3 + a] += c[a];
|
|
3419
|
+
for (let b = 0; b < 3; b++) prod[k * 9 + a * 3 + b] += c[a] * c[b];
|
|
3420
|
+
}
|
|
3421
|
+
}
|
|
3422
|
+
const total = ids.length || 1;
|
|
3423
|
+
for (let k = 0; k < K; k++) {
|
|
3424
|
+
if (!n[k]) {
|
|
3425
|
+
this.logNorm[k] = -Infinity;
|
|
3426
|
+
continue;
|
|
3427
|
+
}
|
|
3428
|
+
const m = [sum[k * 3] / n[k], sum[k * 3 + 1] / n[k], sum[k * 3 + 2] / n[k]];
|
|
3429
|
+
const cov = new Float64Array(9);
|
|
3430
|
+
for (let a = 0; a < 3; a++) {
|
|
3431
|
+
for (let b = 0; b < 3; b++) cov[a * 3 + b] = prod[k * 9 + a * 3 + b] / n[k] - m[a] * m[b];
|
|
3432
|
+
cov[a * 4] += 4;
|
|
3433
|
+
}
|
|
3434
|
+
const det = cov[0] * (cov[4] * cov[8] - cov[5] * cov[7]) - cov[1] * (cov[3] * cov[8] - cov[5] * cov[6]) + cov[2] * (cov[3] * cov[7] - cov[4] * cov[6]);
|
|
3435
|
+
const d = Math.max(det, 1e-6);
|
|
3436
|
+
const inv = [
|
|
3437
|
+
(cov[4] * cov[8] - cov[5] * cov[7]) / d,
|
|
3438
|
+
(cov[2] * cov[7] - cov[1] * cov[8]) / d,
|
|
3439
|
+
(cov[1] * cov[5] - cov[2] * cov[4]) / d,
|
|
3440
|
+
(cov[5] * cov[6] - cov[3] * cov[8]) / d,
|
|
3441
|
+
(cov[0] * cov[8] - cov[2] * cov[6]) / d,
|
|
3442
|
+
(cov[2] * cov[3] - cov[0] * cov[5]) / d,
|
|
3443
|
+
(cov[3] * cov[7] - cov[4] * cov[6]) / d,
|
|
3444
|
+
(cov[1] * cov[6] - cov[0] * cov[7]) / d,
|
|
3445
|
+
(cov[0] * cov[4] - cov[1] * cov[3]) / d
|
|
3446
|
+
];
|
|
3447
|
+
this.mean.set(m, k * 3);
|
|
3448
|
+
this.inv.set(inv, k * 9);
|
|
3449
|
+
this.logWeight[k] = Math.log(n[k] / total);
|
|
3450
|
+
this.logNorm[k] = this.logWeight[k] - 0.5 * Math.log(Math.pow(2 * Math.PI, 3) * d);
|
|
3451
|
+
}
|
|
3452
|
+
}
|
|
3453
|
+
/** ln p(colour | component k), up to the shared constant. */
|
|
3454
|
+
componentLog(k, r, g, b) {
|
|
3455
|
+
const ln = this.logNorm[k];
|
|
3456
|
+
if (ln === -Infinity) return -Infinity;
|
|
3457
|
+
const dr = r - this.mean[k * 3];
|
|
3458
|
+
const dg = g - this.mean[k * 3 + 1];
|
|
3459
|
+
const db = b - this.mean[k * 3 + 2];
|
|
3460
|
+
const v = this.inv;
|
|
3461
|
+
const o = k * 9;
|
|
3462
|
+
const q = dr * (v[o] * dr + v[o + 1] * dg + v[o + 2] * db) + dg * (v[o + 3] * dr + v[o + 4] * dg + v[o + 5] * db) + db * (v[o + 6] * dr + v[o + 7] * dg + v[o + 8] * db);
|
|
3463
|
+
return ln - 0.5 * q;
|
|
3464
|
+
}
|
|
3465
|
+
/** −ln p(colour) under the mixture (large for colours the model hasn't seen). */
|
|
3466
|
+
cost(r, g, b) {
|
|
3467
|
+
let best = -Infinity;
|
|
3468
|
+
const logs = new Array(K);
|
|
3469
|
+
for (let k = 0; k < K; k++) {
|
|
3470
|
+
logs[k] = this.componentLog(k, r, g, b);
|
|
3471
|
+
if (logs[k] > best) best = logs[k];
|
|
3472
|
+
}
|
|
3473
|
+
if (best === -Infinity) return 50;
|
|
3474
|
+
let s = 0;
|
|
3475
|
+
for (let k = 0; k < K; k++) if (logs[k] > -Infinity) s += Math.exp(logs[k] - best);
|
|
3476
|
+
return Math.min(50, -(best + Math.log(s)));
|
|
3477
|
+
}
|
|
3478
|
+
/** The component most likely to have produced the colour. */
|
|
3479
|
+
nearest(r, g, b) {
|
|
3480
|
+
let best = 0;
|
|
3481
|
+
let bestLog = -Infinity;
|
|
3482
|
+
for (let k = 0; k < K; k++) {
|
|
3483
|
+
const l = this.componentLog(k, r, g, b);
|
|
3484
|
+
if (l > bestLog) {
|
|
3485
|
+
bestLog = l;
|
|
3486
|
+
best = k;
|
|
3487
|
+
}
|
|
3488
|
+
}
|
|
3489
|
+
return best;
|
|
3490
|
+
}
|
|
3491
|
+
};
|
|
3492
|
+
function initialComponents(rgb, ids, comp) {
|
|
3493
|
+
if (!ids.length) return;
|
|
3494
|
+
const step = Math.max(1, Math.floor(ids.length / 6e3));
|
|
3495
|
+
const sample = [];
|
|
3496
|
+
for (let t = 0; t < ids.length; t += step) sample.push(ids[t]);
|
|
3497
|
+
const centres = [];
|
|
3498
|
+
const dist2 = (i, c) => (rgb[i * 3] - c[0]) ** 2 + (rgb[i * 3 + 1] - c[1]) ** 2 + (rgb[i * 3 + 2] - c[2]) ** 2;
|
|
3499
|
+
centres.push([rgb[sample[0] * 3], rgb[sample[0] * 3 + 1], rgb[sample[0] * 3 + 2]]);
|
|
3500
|
+
const near = sample.map((i) => dist2(i, centres[0]));
|
|
3501
|
+
while (centres.length < K) {
|
|
3502
|
+
let far = 0;
|
|
3503
|
+
for (let t = 1; t < sample.length; t++) if (near[t] > near[far]) far = t;
|
|
3504
|
+
const i = sample[far];
|
|
3505
|
+
const c = [rgb[i * 3], rgb[i * 3 + 1], rgb[i * 3 + 2]];
|
|
3506
|
+
centres.push(c);
|
|
3507
|
+
for (let t = 0; t < sample.length; t++) near[t] = Math.min(near[t], dist2(sample[t], c));
|
|
3508
|
+
}
|
|
3509
|
+
const closest = (i) => {
|
|
3510
|
+
let best = 0;
|
|
3511
|
+
for (let k = 1; k < K; k++) if (dist2(i, centres[k]) < dist2(i, centres[best])) best = k;
|
|
3512
|
+
return best;
|
|
3513
|
+
};
|
|
3514
|
+
for (let round6 = 0; round6 < 6; round6++) {
|
|
3515
|
+
const sum = Array.from({ length: K }, () => [0, 0, 0, 0]);
|
|
3516
|
+
for (const i of sample) {
|
|
3517
|
+
const s = sum[closest(i)];
|
|
3518
|
+
s[0] += rgb[i * 3];
|
|
3519
|
+
s[1] += rgb[i * 3 + 1];
|
|
3520
|
+
s[2] += rgb[i * 3 + 2];
|
|
3521
|
+
s[3]++;
|
|
3522
|
+
}
|
|
3523
|
+
for (let k = 0; k < K; k++) if (sum[k][3]) centres[k] = [sum[k][0] / sum[k][3], sum[k][1] / sum[k][3], sum[k][2] / sum[k][3]];
|
|
3524
|
+
}
|
|
3525
|
+
for (const i of ids) comp[i] = closest(i);
|
|
3526
|
+
}
|
|
3527
|
+
function grabCutLabels(work, setup, iterations) {
|
|
3528
|
+
const { width: w, rgb } = work;
|
|
3529
|
+
const { roi, hardBg, hardFg } = setup;
|
|
3530
|
+
const { x: rx, y: ry, width: rw, height: rh } = roi;
|
|
3531
|
+
const n = rw * rh;
|
|
3532
|
+
const label = setup.initial.slice();
|
|
3533
|
+
const global = new Int32Array(n);
|
|
3534
|
+
for (let y = 0; y < rh; y++) for (let x = 0; x < rw; x++) global[y * rw + x] = (ry + y) * w + rx + x;
|
|
3535
|
+
const comp = new Uint8Array(w * work.height);
|
|
3536
|
+
const fg = new ColorGmm();
|
|
3537
|
+
const bg = new ColorGmm();
|
|
3538
|
+
const { right, down } = smoothnessWeights(work, roi);
|
|
3539
|
+
const HARD = 1e9;
|
|
3540
|
+
for (let iter = 0; iter < iterations; iter++) {
|
|
3541
|
+
const fgIds = [];
|
|
3542
|
+
const bgIds = [];
|
|
3543
|
+
for (let p = 0; p < n; p++) (label[p] ? fgIds : bgIds).push(global[p]);
|
|
3544
|
+
if (!fgIds.length) return label;
|
|
3545
|
+
if (iter === 0) {
|
|
3546
|
+
initialComponents(rgb, fgIds, comp);
|
|
3547
|
+
initialComponents(rgb, bgIds, comp);
|
|
3548
|
+
} else {
|
|
3549
|
+
for (const i of fgIds) comp[i] = fg.nearest(rgb[i * 3], rgb[i * 3 + 1], rgb[i * 3 + 2]);
|
|
3550
|
+
for (const i of bgIds) comp[i] = bg.nearest(rgb[i * 3], rgb[i * 3 + 1], rgb[i * 3 + 2]);
|
|
3551
|
+
}
|
|
3552
|
+
fg.fit(rgb, fgIds, comp);
|
|
3553
|
+
bg.fit(rgb, bgIds, comp);
|
|
3554
|
+
const graph = new GridGraph(rw, rh);
|
|
3555
|
+
for (let p = 0; p < n; p++) {
|
|
3556
|
+
if (hardBg[p]) graph.addTerminal(p, 0, HARD);
|
|
3557
|
+
else if (hardFg?.[p]) graph.addTerminal(p, HARD, 0);
|
|
3558
|
+
else {
|
|
3559
|
+
const i = global[p] * 3;
|
|
3560
|
+
graph.addTerminal(p, Math.round(bg.cost(rgb[i], rgb[i + 1], rgb[i + 2]) * UNITS), Math.round(fg.cost(rgb[i], rgb[i + 1], rgb[i + 2]) * UNITS));
|
|
3561
|
+
}
|
|
3562
|
+
if (p % rw + 1 < rw) graph.setEdge(p, 0, right[p]);
|
|
3563
|
+
if (p + rw < n) graph.setEdge(p, 1, down[p]);
|
|
3564
|
+
}
|
|
3565
|
+
graph.maxflow();
|
|
3566
|
+
let changed = 0;
|
|
3567
|
+
for (let p = 0; p < n; p++) {
|
|
3568
|
+
const v = graph.inSource(p) ? 1 : 0;
|
|
3569
|
+
if (v !== label[p]) changed++;
|
|
3570
|
+
label[p] = v;
|
|
3571
|
+
}
|
|
3572
|
+
if (changed <= n * 1e-3) break;
|
|
3573
|
+
}
|
|
3574
|
+
return label;
|
|
3575
|
+
}
|
|
3576
|
+
function keepMainPieces(label, rw, rh, seed) {
|
|
3577
|
+
const n = rw * rh;
|
|
3578
|
+
const piece = new Int32Array(n).fill(-1);
|
|
3579
|
+
const sizes = [];
|
|
3580
|
+
const stack = [];
|
|
3581
|
+
for (let s = 0; s < n; s++) {
|
|
3582
|
+
if (!label[s] || piece[s] >= 0) continue;
|
|
3583
|
+
const id = sizes.length;
|
|
3584
|
+
let size = 0;
|
|
3585
|
+
piece[s] = id;
|
|
3586
|
+
stack.push(s);
|
|
3587
|
+
while (stack.length) {
|
|
3588
|
+
const p = stack.pop();
|
|
3589
|
+
size++;
|
|
3590
|
+
const x = p % rw;
|
|
3591
|
+
for (const q of [x + 1 < rw ? p + 1 : -1, x > 0 ? p - 1 : -1, p + rw < n ? p + rw : -1, p - rw]) {
|
|
3592
|
+
if (q >= 0 && label[q] && piece[q] < 0) {
|
|
3593
|
+
piece[q] = id;
|
|
3594
|
+
stack.push(q);
|
|
3595
|
+
}
|
|
3596
|
+
}
|
|
3597
|
+
}
|
|
3598
|
+
sizes.push(size);
|
|
3599
|
+
}
|
|
3600
|
+
if (!sizes.length) return;
|
|
3601
|
+
const largest = Math.max(...sizes);
|
|
3602
|
+
const keep = (id) => seed !== null && piece[seed] >= 0 ? id === piece[seed] : sizes[id] * 5 >= largest;
|
|
3603
|
+
for (let p = 0; p < n; p++) if (label[p] && !keep(piece[p])) label[p] = 0;
|
|
3604
|
+
}
|
|
3605
|
+
function selectObjectIn(pixels, target, options = {}) {
|
|
3606
|
+
const work = workImage(pixels);
|
|
3607
|
+
const { width: w, height: h, scale } = work;
|
|
3608
|
+
const iterations = Math.max(1, Math.round(options.iterations ?? 5));
|
|
3609
|
+
const enhance = options.enhanceEdge ?? false;
|
|
3610
|
+
if ("rect" in target) {
|
|
3611
|
+
const r = target.rect;
|
|
3612
|
+
const bx0 = Math.max(0, Math.floor(r.x * scale));
|
|
3613
|
+
const by0 = Math.max(0, Math.floor(r.y * scale));
|
|
3614
|
+
const bx1 = Math.min(w, Math.ceil((r.x + r.width) * scale));
|
|
3615
|
+
const by1 = Math.min(h, Math.ceil((r.y + r.height) * scale));
|
|
3616
|
+
if (bx1 - bx0 < 2 || by1 - by0 < 2) return createSelectionMask(pixels.width, pixels.height);
|
|
3617
|
+
const m = Math.max(8, Math.round(0.25 * Math.max(bx1 - bx0, by1 - by0)));
|
|
3618
|
+
const rx = Math.max(0, bx0 - m);
|
|
3619
|
+
const ry = Math.max(0, by0 - m);
|
|
3620
|
+
const roi = { x: rx, y: ry, width: Math.min(w, bx1 + m) - rx, height: Math.min(h, by1 + m) - ry };
|
|
3621
|
+
const n = roi.width * roi.height;
|
|
3622
|
+
const initial = new Uint8Array(n);
|
|
3623
|
+
const hardBg = new Uint8Array(n);
|
|
3624
|
+
for (let y = 0; y < roi.height; y++) {
|
|
3625
|
+
for (let x = 0; x < roi.width; x++) {
|
|
3626
|
+
const gx = rx + x;
|
|
3627
|
+
const gy = ry + y;
|
|
3628
|
+
const inside = gx >= bx0 && gx < bx1 && gy >= by0 && gy < by1;
|
|
3629
|
+
initial[y * roi.width + x] = inside ? 1 : 0;
|
|
3630
|
+
hardBg[y * roi.width + x] = inside ? 0 : 1;
|
|
3631
|
+
}
|
|
3632
|
+
}
|
|
3633
|
+
const label = grabCutLabels(work, { roi, initial, hardBg, hardFg: null }, iterations);
|
|
3634
|
+
keepMainPieces(label, roi.width, roi.height, null);
|
|
3635
|
+
return upscaleRegion(work, pixels, roi, label, enhance);
|
|
3636
|
+
}
|
|
3637
|
+
const px2 = Math.min(w - 1, Math.max(0, Math.floor(target.point.x * scale)));
|
|
3638
|
+
const py = Math.min(h - 1, Math.max(0, Math.floor(target.point.y * scale)));
|
|
3639
|
+
let half = Math.max(24, Math.round(0.3 * Math.max(w, h)));
|
|
3640
|
+
for (; ; ) {
|
|
3641
|
+
const rx = Math.max(0, px2 - half);
|
|
3642
|
+
const ry = Math.max(0, py - half);
|
|
3643
|
+
const roi = { x: rx, y: ry, width: Math.min(w, px2 + half + 1) - rx, height: Math.min(h, py + half + 1) - ry };
|
|
3644
|
+
const whole = roi.width === w && roi.height === h;
|
|
3645
|
+
const band = Math.max(4, Math.round(0.15 * half));
|
|
3646
|
+
const n = roi.width * roi.height;
|
|
3647
|
+
const initial = new Uint8Array(n);
|
|
3648
|
+
const hardBg = new Uint8Array(n);
|
|
3649
|
+
const hardFg = new Uint8Array(n);
|
|
3650
|
+
const inBand = (x, y) => rx > 0 && x < band || ry > 0 && y < band || rx + roi.width < w && x >= roi.width - band || ry + roi.height < h && y >= roi.height - band;
|
|
3651
|
+
for (let y = 0; y < roi.height; y++) {
|
|
3652
|
+
for (let x = 0; x < roi.width; x++) {
|
|
3653
|
+
const p = y * roi.width + x;
|
|
3654
|
+
if (inBand(x, y)) hardBg[p] = 1;
|
|
3655
|
+
else initial[p] = 1;
|
|
3656
|
+
if (Math.hypot(rx + x - px2, ry + y - py) <= 1.5) hardFg[p] = 1;
|
|
3657
|
+
}
|
|
3658
|
+
}
|
|
3659
|
+
const label = grabCutLabels(work, { roi, initial, hardBg, hardFg }, iterations);
|
|
3660
|
+
keepMainPieces(label, roi.width, roi.height, (py - ry) * roi.width + (px2 - rx));
|
|
3661
|
+
let touches = false;
|
|
3662
|
+
for (let y = 0; y < roi.height && !touches; y++) {
|
|
3663
|
+
for (let x = 0; x < roi.width; x++) {
|
|
3664
|
+
if (label[y * roi.width + x] && (inBand(x - 1, y) || inBand(x + 1, y) || inBand(x, y - 1) || inBand(x, y + 1))) {
|
|
3665
|
+
touches = true;
|
|
3666
|
+
break;
|
|
3667
|
+
}
|
|
3668
|
+
}
|
|
3669
|
+
}
|
|
3670
|
+
if (!touches || whole) return upscaleRegion(work, pixels, roi, label, enhance);
|
|
3671
|
+
half = Math.round(half * 1.6);
|
|
3672
|
+
}
|
|
3673
|
+
}
|
|
3674
|
+
|
|
3389
3675
|
// src/image-editor/layers/shape-type-ui.ts
|
|
3390
3676
|
var DEFAULT_SHAPE_TOOL_OPTIONS = {
|
|
3391
3677
|
kind: "rect",
|
|
@@ -9355,6 +9641,28 @@ var ImageEditorController = class {
|
|
|
9355
9641
|
this.setSelection(sel, "replace");
|
|
9356
9642
|
return this.state.selection ?? null;
|
|
9357
9643
|
}
|
|
9644
|
+
/**
|
|
9645
|
+
* Object Selection: the object inside `target.rect` (natural pixels; what is outside the box is
|
|
9646
|
+
* background) or under `target.point`, found by GrabCut (colour models of object and background
|
|
9647
|
+
* plus a graph cut along edges), combined with the selection by `mode` as one undo step.
|
|
9648
|
+
* Resolves to the resulting selection.
|
|
9649
|
+
*/
|
|
9650
|
+
async selectObject(target, options = {}) {
|
|
9651
|
+
const { width, height } = this.naturalSize;
|
|
9652
|
+
if ("rect" in target) {
|
|
9653
|
+
const r = target.rect;
|
|
9654
|
+
if (!(r.width > 0 && r.height > 0)) throw new Error("The box needs a positive width and height.");
|
|
9655
|
+
if (r.x >= width || r.y >= height || r.x + r.width <= 0 || r.y + r.height <= 0) {
|
|
9656
|
+
throw new Error(`The box lies outside the image (0..${width}, 0..${height}); draw it around the object.`);
|
|
9657
|
+
}
|
|
9658
|
+
} else if (!(target.point.x >= 0 && target.point.y >= 0 && target.point.x < width && target.point.y < height)) {
|
|
9659
|
+
throw new Error(`(${target.point.x}, ${target.point.y}) is outside the image (0..${width - 1}, 0..${height - 1}); click on the object.`);
|
|
9660
|
+
}
|
|
9661
|
+
const pixels = await this.requirePixels({ sample: options.sampleAllLayers === false ? "active-layer" : "composite" });
|
|
9662
|
+
const region = selectObjectIn(pixels, target, { enhanceEdge: options.enhanceEdge });
|
|
9663
|
+
this.setSelection(region, options.mode ?? "replace");
|
|
9664
|
+
return this.state.selection ?? null;
|
|
9665
|
+
}
|
|
9358
9666
|
/**
|
|
9359
9667
|
* Supplies the pixels the selection tools sample (natural size RGBA), e.g. decoded in a worker
|
|
9360
9668
|
* or in a test; `null` goes back to reading the source image. Without a canvas (Node, workers)
|
|
@@ -12242,7 +12550,7 @@ var DEPTH_MASK_CONTROLS = [
|
|
|
12242
12550
|
// src/image-editor/playback.ts
|
|
12243
12551
|
var IMAGE_PLAYBACK_KIND = "layer";
|
|
12244
12552
|
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)$/;
|
|
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)$/;
|
|
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)$/;
|
|
12246
12554
|
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)$/;
|
|
12247
12555
|
var CROP = /^(set_crop|crop_to_selection|reset_crop|set_rotation|flip_image|transform_layer|warp_layer|flip_layer|offset_layer)$/;
|
|
12248
12556
|
var ADJUST = /^(set_adjustments|add_adjustment_layer|update_adjustment_layer|remove_adjustment_layer|move_adjustment_layer)$/;
|
|
@@ -12365,6 +12673,7 @@ var SELECTION_TOOLS = [
|
|
|
12365
12673
|
{ id: "polygon", label: "Polygonal Lasso" },
|
|
12366
12674
|
{ id: "magnetic", label: "Magnetic Lasso" },
|
|
12367
12675
|
{ id: "quick", label: "Quick Selection" },
|
|
12676
|
+
{ id: "object", label: "Object Selection" },
|
|
12368
12677
|
{ id: "range", label: "Color Range" },
|
|
12369
12678
|
{ id: "type", label: "Type Mask" },
|
|
12370
12679
|
{ id: "type-vertical", label: "Vertical Type Mask" }
|
|
@@ -12405,6 +12714,8 @@ var DEFAULT_SELECTION_TOOL_OPTIONS = {
|
|
|
12405
12714
|
quickSize: 30,
|
|
12406
12715
|
quickSampleAll: true,
|
|
12407
12716
|
quickEnhance: false,
|
|
12717
|
+
objectSampleAll: true,
|
|
12718
|
+
objectEnhance: false,
|
|
12408
12719
|
typeText: "Type",
|
|
12409
12720
|
typeSize: 72,
|
|
12410
12721
|
typeFamily: "sans-serif"
|
|
@@ -12840,6 +13151,39 @@ function createImageSelectionTools(controller) {
|
|
|
12840
13151
|
return summary(controller);
|
|
12841
13152
|
}
|
|
12842
13153
|
};
|
|
13154
|
+
const selectObjectTool = {
|
|
13155
|
+
name: "select_object",
|
|
13156
|
+
title: "Object Selection",
|
|
13157
|
+
description: "Object Selection: selects the object inside rect (a box drawn loosely around it, natural image pixels; what lies outside the box is treated as background) or the object under point, found from the colours of object and background and the edges between them (GrabCut). Combined with the selection by mode. A box is more reliable than a point: make it a little larger than the object on every side. Check with get_selection and capture_view, then fix with quick_select strokes or select_object with mode add / subtract. One undoable step. Returns the selection.",
|
|
13158
|
+
inputSchema: {
|
|
13159
|
+
type: "object",
|
|
13160
|
+
properties: {
|
|
13161
|
+
rect: {
|
|
13162
|
+
type: "object",
|
|
13163
|
+
properties: rectProperties,
|
|
13164
|
+
required: ["x", "y", "width", "height"],
|
|
13165
|
+
additionalProperties: false,
|
|
13166
|
+
description: "A box around the object (x, y, width, height in natural image pixels). Pass this or point."
|
|
13167
|
+
},
|
|
13168
|
+
point: { ...pointSchema, description: "A point on the object (natural image pixels). Pass this or rect." },
|
|
13169
|
+
mode: modeSchema,
|
|
13170
|
+
sampleAllLayers: { type: "boolean", description: "Read every layer (default true); false reads only the active pixel layer." },
|
|
13171
|
+
enhanceEdge: { type: "boolean", description: "Smoother, less blocky edges (Auto-Enhance; default false)." }
|
|
13172
|
+
},
|
|
13173
|
+
additionalProperties: false
|
|
13174
|
+
},
|
|
13175
|
+
annotations: writeAnnotations("Object Selection"),
|
|
13176
|
+
handler: async (args) => {
|
|
13177
|
+
const rect = args.rect;
|
|
13178
|
+
const point = args.point;
|
|
13179
|
+
if (!rect && !point) throw new Error("Pass rect (a box around the object) or point (a point on it).");
|
|
13180
|
+
if (rect && point) throw new Error("Pass rect or point, not both.");
|
|
13181
|
+
const options = { mode: parseSelectionMode(args.mode), sampleAllLayers: args.sampleAllLayers, enhanceEdge: args.enhanceEdge };
|
|
13182
|
+
if (rect) await controller.selectObject({ rect: { x: num2(rect.x, NaN), y: num2(rect.y, NaN), width: num2(rect.width, NaN), height: num2(rect.height, NaN) } }, options);
|
|
13183
|
+
else await controller.selectObject({ point: { x: num2(point.x, NaN), y: num2(point.y, NaN) } }, options);
|
|
13184
|
+
return summary(controller);
|
|
13185
|
+
}
|
|
13186
|
+
};
|
|
12843
13187
|
const magicWandTool = {
|
|
12844
13188
|
name: "magic_wand",
|
|
12845
13189
|
title: "Magic wand",
|
|
@@ -13393,6 +13737,7 @@ function createImageSelectionTools(controller) {
|
|
|
13393
13737
|
selectEllipse,
|
|
13394
13738
|
selectPolygonTool,
|
|
13395
13739
|
quickSelectTool,
|
|
13740
|
+
selectObjectTool,
|
|
13396
13741
|
lineTool,
|
|
13397
13742
|
magicWandTool,
|
|
13398
13743
|
colorRangeTool,
|
|
@@ -14784,7 +15129,7 @@ var SelectionGesture = class {
|
|
|
14784
15129
|
get active() {
|
|
14785
15130
|
return this.start !== null || this.polygon !== null || this.magnetic !== null;
|
|
14786
15131
|
}
|
|
14787
|
-
/** Resolves once a Quick Selection stroke has been applied. */
|
|
15132
|
+
/** Resolves once a Quick Selection stroke or an Object Selection has been applied. */
|
|
14788
15133
|
get settled() {
|
|
14789
15134
|
return this.pending;
|
|
14790
15135
|
}
|
|
@@ -14954,6 +15299,20 @@ var SelectionGesture = class {
|
|
|
14954
15299
|
if (point) this.move(point);
|
|
14955
15300
|
const options = this.getOptions();
|
|
14956
15301
|
const mode = selectionModeFromModifiers(modifiers ?? this.modifiers, options.mode);
|
|
15302
|
+
if (options.tool === "object") {
|
|
15303
|
+
const rect2 = normalizeRect({ x: this.start.x, y: this.start.y, width: this.current.x - this.start.x, height: this.current.y - this.start.y });
|
|
15304
|
+
this.start = null;
|
|
15305
|
+
this.current = null;
|
|
15306
|
+
this.points = [];
|
|
15307
|
+
const target = rect2.width >= 4 && rect2.height >= 4 ? { rect: rect2 } : { point: { x: rect2.x + rect2.width / 2, y: rect2.y + rect2.height / 2 } };
|
|
15308
|
+
this.pending = this.controller.selectObject(target, { mode, sampleAllLayers: options.objectSampleAll ?? true, enhanceEdge: options.objectEnhance ?? false }).then(
|
|
15309
|
+
() => void 0,
|
|
15310
|
+
(err) => {
|
|
15311
|
+
this.error = err instanceof Error ? err.message : String(err);
|
|
15312
|
+
}
|
|
15313
|
+
);
|
|
15314
|
+
return false;
|
|
15315
|
+
}
|
|
14957
15316
|
if (options.tool === "quick") {
|
|
14958
15317
|
const stroke = this.points;
|
|
14959
15318
|
this.start = null;
|
|
@@ -19600,6 +19959,7 @@ export {
|
|
|
19600
19959
|
COLOR_MODE_OPTIONS,
|
|
19601
19960
|
COLOR_RANGE_PRESETS,
|
|
19602
19961
|
COLOR_RANGE_PRESET_LABELS,
|
|
19962
|
+
ColorGmm,
|
|
19603
19963
|
CoverageMap,
|
|
19604
19964
|
D50,
|
|
19605
19965
|
D65,
|
|
@@ -19652,6 +20012,7 @@ export {
|
|
|
19652
20012
|
FREE_TRANSFORM_MODE_OPTIONS,
|
|
19653
20013
|
FreeTransformSession,
|
|
19654
20014
|
FreeTransformTool,
|
|
20015
|
+
GAMMA,
|
|
19655
20016
|
GRAIN_PATTERNS,
|
|
19656
20017
|
GRAIN_TILE,
|
|
19657
20018
|
GUIDED_FILTER_EPS,
|
|
@@ -19810,6 +20171,7 @@ export {
|
|
|
19810
20171
|
TransformTooLargeError,
|
|
19811
20172
|
TransformedPixelSource,
|
|
19812
20173
|
TypeToolGesture,
|
|
20174
|
+
UNITS,
|
|
19813
20175
|
U_MAX,
|
|
19814
20176
|
VIBRANCE_GREY,
|
|
19815
20177
|
W9,
|
|
@@ -19893,6 +20255,7 @@ export {
|
|
|
19893
20255
|
blenderDab,
|
|
19894
20256
|
blurFilter,
|
|
19895
20257
|
borderCoverage,
|
|
20258
|
+
boxBlur,
|
|
19896
20259
|
boxBlurPixels,
|
|
19897
20260
|
boxFrameHit,
|
|
19898
20261
|
boxFrameOverlay,
|
|
@@ -20454,6 +20817,7 @@ export {
|
|
|
20454
20817
|
segmentToSelection,
|
|
20455
20818
|
segmentationOutlines,
|
|
20456
20819
|
segmentsToSelection,
|
|
20820
|
+
selectObjectIn,
|
|
20457
20821
|
selectSimilar,
|
|
20458
20822
|
selectionArea,
|
|
20459
20823
|
selectionFromCoverage,
|
|
@@ -20503,6 +20867,7 @@ export {
|
|
|
20503
20867
|
smartObjectLayers,
|
|
20504
20868
|
smoothCoverage,
|
|
20505
20869
|
smoothPoints,
|
|
20870
|
+
smoothnessWeights,
|
|
20506
20871
|
smudgeEffect,
|
|
20507
20872
|
snapPolygonToEdges,
|
|
20508
20873
|
snapTo45,
|
|
@@ -20589,6 +20954,7 @@ export {
|
|
|
20589
20954
|
updateShapeLayer,
|
|
20590
20955
|
updateSmartFilter,
|
|
20591
20956
|
updateTypeLayer,
|
|
20957
|
+
upscaleRegion,
|
|
20592
20958
|
userErrorMessage,
|
|
20593
20959
|
uvToXy,
|
|
20594
20960
|
validateEffectGradient,
|