@pixerate/editor 0.215.0 → 0.217.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 +45 -0
- package/dist/image-editor/controller.d.ts +45 -0
- package/dist/image-editor/index.cjs +1025 -592
- package/dist/image-editor/index.js +467 -34
- package/dist/image-editor/renderer.d.cts +2 -0
- package/dist/image-editor/renderer.d.ts +2 -0
- package/dist/image-editor/selection/diff.d.cts +59 -0
- package/dist/image-editor/selection/diff.d.ts +59 -0
- package/dist/image-editor/selection/index.d.cts +2 -0
- package/dist/image-editor/selection/index.d.ts +2 -0
- package/dist/image-editor/selection/inspect.d.cts +9 -0
- package/dist/image-editor/selection/inspect.d.ts +9 -0
- package/dist/image-editor/selection/uncertainty.d.cts +40 -0
- package/dist/image-editor/selection/uncertainty.d.ts +40 -0
- package/dist/image-editor/types.d.cts +9 -0
- package/dist/image-editor/types.d.ts +9 -0
- package/package.json +1 -1
- package/skills/pixerate-image-editor/SKILL.md +3 -3
- package/skills/pixerate-image-selection-masking/SKILL.md +6 -6
|
@@ -1070,6 +1070,105 @@ function startMarchingAnts(tick, stepMs = MARCHING_ANTS_STEP_MS) {
|
|
|
1070
1070
|
return () => clearInterval(id);
|
|
1071
1071
|
}
|
|
1072
1072
|
|
|
1073
|
+
// src/image-editor/selection/diff.ts
|
|
1074
|
+
function compareSelectionMasks(a, b, width, height, options = {}) {
|
|
1075
|
+
const minArea = Math.max(1, options.minArea ?? 4);
|
|
1076
|
+
const maxRegions = Math.max(1, options.maxRegions ?? 10);
|
|
1077
|
+
const n = width * height;
|
|
1078
|
+
const ad = a?.data;
|
|
1079
|
+
const bd = b?.data;
|
|
1080
|
+
const added = new Uint8Array(n);
|
|
1081
|
+
const removed = new Uint8Array(n);
|
|
1082
|
+
let areaA = 0;
|
|
1083
|
+
let areaB = 0;
|
|
1084
|
+
let inter = 0;
|
|
1085
|
+
let change = 0;
|
|
1086
|
+
let addedArea = 0;
|
|
1087
|
+
let removedArea = 0;
|
|
1088
|
+
for (let i = 0; i < n; i++) {
|
|
1089
|
+
const va = ad ? ad[i] : 0;
|
|
1090
|
+
const vb = bd ? bd[i] : 0;
|
|
1091
|
+
if (va !== vb) change += Math.abs(vb - va);
|
|
1092
|
+
const ia = va >= 128;
|
|
1093
|
+
const ib = vb >= 128;
|
|
1094
|
+
if (ia) areaA++;
|
|
1095
|
+
if (ib) areaB++;
|
|
1096
|
+
if (ia && ib) inter++;
|
|
1097
|
+
else if (ib) {
|
|
1098
|
+
added[i] = 255;
|
|
1099
|
+
addedArea++;
|
|
1100
|
+
} else if (ia) {
|
|
1101
|
+
removed[i] = 255;
|
|
1102
|
+
removedArea++;
|
|
1103
|
+
}
|
|
1104
|
+
}
|
|
1105
|
+
const union = areaA + areaB - inter;
|
|
1106
|
+
const addedMask = createSelectionMask(width, height, added);
|
|
1107
|
+
const removedMask = createSelectionMask(width, height, removed);
|
|
1108
|
+
return {
|
|
1109
|
+
a: { area: areaA, bounds: a?.bounds ?? null },
|
|
1110
|
+
b: { area: areaB, bounds: b?.bounds ?? null },
|
|
1111
|
+
added: { area: addedArea, ...pieces(added, width, height, addedMask.bounds, minArea, maxRegions) },
|
|
1112
|
+
removed: { area: removedArea, ...pieces(removed, width, height, removedMask.bounds, minArea, maxRegions) },
|
|
1113
|
+
iou: union ? Math.round(inter / union * 1e4) / 1e4 : 1,
|
|
1114
|
+
coverageChange: Math.round(change / 255),
|
|
1115
|
+
identical: change === 0,
|
|
1116
|
+
addedMask,
|
|
1117
|
+
removedMask
|
|
1118
|
+
};
|
|
1119
|
+
}
|
|
1120
|
+
function pieces(bits, W, H, bounds, minArea, maxRegions) {
|
|
1121
|
+
if (!bounds) return { count: 0, regions: [] };
|
|
1122
|
+
const { x: x0, y: y0, width: w, height: h } = bounds;
|
|
1123
|
+
const seen = new Uint8Array(w * h);
|
|
1124
|
+
const stack = new Int32Array(w * h);
|
|
1125
|
+
const found = [];
|
|
1126
|
+
for (let start = 0; start < w * h; start++) {
|
|
1127
|
+
if (seen[start] || !bits[(y0 + Math.floor(start / w)) * W + x0 + start % w]) continue;
|
|
1128
|
+
let area = 0;
|
|
1129
|
+
let bx0 = w;
|
|
1130
|
+
let by0 = h;
|
|
1131
|
+
let bx1 = -1;
|
|
1132
|
+
let by1 = -1;
|
|
1133
|
+
let top = 0;
|
|
1134
|
+
stack[top++] = start;
|
|
1135
|
+
seen[start] = 1;
|
|
1136
|
+
while (top) {
|
|
1137
|
+
const k = stack[--top];
|
|
1138
|
+
const i = k % w;
|
|
1139
|
+
const j = (k - i) / w;
|
|
1140
|
+
area++;
|
|
1141
|
+
if (i < bx0) bx0 = i;
|
|
1142
|
+
if (i > bx1) bx1 = i;
|
|
1143
|
+
if (j < by0) by0 = j;
|
|
1144
|
+
if (j > by1) by1 = j;
|
|
1145
|
+
for (let dy = -1; dy <= 1; dy++) {
|
|
1146
|
+
for (let dx = -1; dx <= 1; dx++) {
|
|
1147
|
+
const ni = i + dx;
|
|
1148
|
+
const nj = j + dy;
|
|
1149
|
+
if (ni < 0 || nj < 0 || ni >= w || nj >= h) continue;
|
|
1150
|
+
const nk = nj * w + ni;
|
|
1151
|
+
if (seen[nk] || !bits[(y0 + nj) * W + x0 + ni]) continue;
|
|
1152
|
+
seen[nk] = 1;
|
|
1153
|
+
stack[top++] = nk;
|
|
1154
|
+
}
|
|
1155
|
+
}
|
|
1156
|
+
}
|
|
1157
|
+
if (area >= minArea) found.push({ area, bounds: { x: x0 + bx0, y: y0 + by0, width: bx1 - bx0 + 1, height: by1 - by0 + 1 } });
|
|
1158
|
+
}
|
|
1159
|
+
found.sort((p, q) => q.area - p.area);
|
|
1160
|
+
return { count: found.length, regions: found.slice(0, maxRegions) };
|
|
1161
|
+
}
|
|
1162
|
+
function selectionDiffPixels(added, removed) {
|
|
1163
|
+
const n = added.width * added.height;
|
|
1164
|
+
const out = new Uint8ClampedArray(n * 4);
|
|
1165
|
+
for (let i = 0; i < n; i++) {
|
|
1166
|
+
if (added.data[i]) out.set([34, 197, 94, 140], i * 4);
|
|
1167
|
+
else if (removed.data[i]) out.set([239, 68, 68, 140], i * 4);
|
|
1168
|
+
}
|
|
1169
|
+
return out;
|
|
1170
|
+
}
|
|
1171
|
+
|
|
1073
1172
|
// src/image-editor/layers/render.ts
|
|
1074
1173
|
function isQuarterTurn(rotate) {
|
|
1075
1174
|
return normalizeAngle(rotate) % 90 === 0;
|
|
@@ -1515,6 +1614,19 @@ var ImageEditorRenderer = class {
|
|
|
1515
1614
|
}
|
|
1516
1615
|
ctx.restore();
|
|
1517
1616
|
}
|
|
1617
|
+
const diff = state.selectionDiff;
|
|
1618
|
+
if (diff && diff.selection === (state.selection ?? null)) {
|
|
1619
|
+
ctx.save();
|
|
1620
|
+
try {
|
|
1621
|
+
applyImageProjectionToContext(ctx, projection);
|
|
1622
|
+
ctx.beginPath();
|
|
1623
|
+
ctx.rect(region.x, region.y, region.width, region.height);
|
|
1624
|
+
ctx.clip();
|
|
1625
|
+
this.drawSelectionDiff(ctx, diff);
|
|
1626
|
+
} catch {
|
|
1627
|
+
}
|
|
1628
|
+
ctx.restore();
|
|
1629
|
+
}
|
|
1518
1630
|
if (options.brushCursor) {
|
|
1519
1631
|
const b = options.brushCursor;
|
|
1520
1632
|
ctx.save();
|
|
@@ -1970,6 +2082,7 @@ var ImageEditorRenderer = class {
|
|
|
1970
2082
|
this.onPreviewHistogram?.(histogram);
|
|
1971
2083
|
}
|
|
1972
2084
|
quickMaskCache = /* @__PURE__ */ new WeakMap();
|
|
2085
|
+
diffCache = /* @__PURE__ */ new WeakMap();
|
|
1973
2086
|
outlineCache = /* @__PURE__ */ new WeakMap();
|
|
1974
2087
|
emptyMaskCache = null;
|
|
1975
2088
|
/** An empty selection of the image's size (Quick Mask mode with nothing selected). */
|
|
@@ -2023,6 +2136,19 @@ var ImageEditorRenderer = class {
|
|
|
2023
2136
|
ctx.drawImage(cached.canvas, 0, 0, mask.width, mask.height);
|
|
2024
2137
|
}
|
|
2025
2138
|
/** Rubylith over the unselected area, drawn in natural image space. */
|
|
2139
|
+
drawSelectionDiff(ctx, diff) {
|
|
2140
|
+
let canvas = this.diffCache.get(diff);
|
|
2141
|
+
if (!canvas) {
|
|
2142
|
+
canvas = this.createCanvas(diff.added.width, diff.added.height);
|
|
2143
|
+
const mctx = canvas.getContext("2d");
|
|
2144
|
+
if (!mctx || typeof mctx.createImageData !== "function") return;
|
|
2145
|
+
const img = mctx.createImageData(diff.added.width, diff.added.height);
|
|
2146
|
+
img.data.set(selectionDiffPixels(diff.added, diff.removed));
|
|
2147
|
+
mctx.putImageData(img, 0, 0);
|
|
2148
|
+
this.diffCache.set(diff, canvas);
|
|
2149
|
+
}
|
|
2150
|
+
ctx.drawImage(canvas, 0, 0, diff.added.width, diff.added.height);
|
|
2151
|
+
}
|
|
2026
2152
|
drawQuickMask(ctx, selection) {
|
|
2027
2153
|
let canvas = this.quickMaskCache.get(selection);
|
|
2028
2154
|
if (!canvas) {
|
|
@@ -2609,27 +2735,7 @@ function inspectMaskEdge(mask, image, options = {}) {
|
|
|
2609
2735
|
if (img[i + 3] < 128) return null;
|
|
2610
2736
|
return [img[i], img[i + 1], img[i + 2]];
|
|
2611
2737
|
};
|
|
2612
|
-
const
|
|
2613
|
-
const ix = Math.min(image.width - 1, Math.max(0, x - image.x));
|
|
2614
|
-
const iy = Math.min(image.height - 1, Math.max(0, y - image.y));
|
|
2615
|
-
const i = (iy * image.width + ix) * 4;
|
|
2616
|
-
return img[i + c] * img[i + 3] / 255;
|
|
2617
|
-
};
|
|
2618
|
-
const gradCache = /* @__PURE__ */ new Map();
|
|
2619
|
-
const gradient = (x, y) => {
|
|
2620
|
-
const k = y * W + x;
|
|
2621
|
-
const hit = gradCache.get(k);
|
|
2622
|
-
if (hit !== void 0) return hit;
|
|
2623
|
-
let best = 0;
|
|
2624
|
-
for (let c = 0; c < 3; c++) {
|
|
2625
|
-
const p = (dx, dy) => premul(x + dx, y + dy, c);
|
|
2626
|
-
const gx = p(1, -1) + 2 * p(1, 0) + p(1, 1) - p(-1, -1) - 2 * p(-1, 0) - p(-1, 1);
|
|
2627
|
-
const gy = p(-1, 1) + 2 * p(0, 1) + p(1, 1) - p(-1, -1) - 2 * p(0, -1) - p(1, -1);
|
|
2628
|
-
best = Math.max(best, Math.hypot(gx, gy) / 1020);
|
|
2629
|
-
}
|
|
2630
|
-
gradCache.set(k, best);
|
|
2631
|
-
return best;
|
|
2632
|
-
};
|
|
2738
|
+
const gradient = createGradientSampler(image, W);
|
|
2633
2739
|
const B = Math.min(256, Math.max(24, Math.round(Math.max(W, H) / 12)));
|
|
2634
2740
|
const BW = Math.ceil(W / B);
|
|
2635
2741
|
const blocks = /* @__PURE__ */ new Map();
|
|
@@ -2806,6 +2912,30 @@ function inspectMaskEdge(mask, image, options = {}) {
|
|
|
2806
2912
|
issues
|
|
2807
2913
|
};
|
|
2808
2914
|
}
|
|
2915
|
+
function createGradientSampler(image, W) {
|
|
2916
|
+
const img = image.data;
|
|
2917
|
+
const premul = (x, y, c) => {
|
|
2918
|
+
const ix = Math.min(image.width - 1, Math.max(0, x - image.x));
|
|
2919
|
+
const iy = Math.min(image.height - 1, Math.max(0, y - image.y));
|
|
2920
|
+
const i = (iy * image.width + ix) * 4;
|
|
2921
|
+
return img[i + c] * img[i + 3] / 255;
|
|
2922
|
+
};
|
|
2923
|
+
const cache = /* @__PURE__ */ new Map();
|
|
2924
|
+
return (x, y) => {
|
|
2925
|
+
const k = y * W + x;
|
|
2926
|
+
const hit = cache.get(k);
|
|
2927
|
+
if (hit !== void 0) return hit;
|
|
2928
|
+
let best = 0;
|
|
2929
|
+
for (let c = 0; c < 3; c++) {
|
|
2930
|
+
const p = (dx, dy) => premul(x + dx, y + dy, c);
|
|
2931
|
+
const gx = p(1, -1) + 2 * p(1, 0) + p(1, 1) - p(-1, -1) - 2 * p(-1, 0) - p(-1, 1);
|
|
2932
|
+
const gy = p(-1, 1) + 2 * p(0, 1) + p(1, 1) - p(-1, -1) - 2 * p(0, -1) - p(1, -1);
|
|
2933
|
+
best = Math.max(best, Math.hypot(gx, gy) / 1020);
|
|
2934
|
+
}
|
|
2935
|
+
cache.set(k, best);
|
|
2936
|
+
return best;
|
|
2937
|
+
};
|
|
2938
|
+
}
|
|
2809
2939
|
function traceLoops(a, W, H, rx0, ry0, rx1, ry1) {
|
|
2810
2940
|
const w = rx1 - rx0;
|
|
2811
2941
|
const h = ry1 - ry0;
|
|
@@ -2983,10 +3113,139 @@ function regions(a, W, H, b, minArea) {
|
|
|
2983
3113
|
const kept = list.filter((r) => r.area >= minArea).sort((p, q) => q.area - p.area);
|
|
2984
3114
|
return { count: kept.length, totalArea: kept.reduce((s, r) => s + r.area, 0), largest: kept.slice(0, 10).map((r) => ({ area: r.area, bounds: box(r) })) };
|
|
2985
3115
|
};
|
|
2986
|
-
const
|
|
3116
|
+
const pieces2 = found.filter((r) => r.fg).sort((p, q) => q.area - p.area);
|
|
2987
3117
|
return {
|
|
2988
3118
|
holes: summarize(found.filter((r) => !r.fg && !r.outer)),
|
|
2989
|
-
islands: summarize(
|
|
3119
|
+
islands: summarize(pieces2.slice(1))
|
|
3120
|
+
};
|
|
3121
|
+
}
|
|
3122
|
+
|
|
3123
|
+
// src/image-editor/selection/uncertainty.ts
|
|
3124
|
+
var BACKED = 0.2;
|
|
3125
|
+
function selectionUncertainty(mask, image, options = {}) {
|
|
3126
|
+
const W = mask.width;
|
|
3127
|
+
const H = mask.height;
|
|
3128
|
+
const empty = () => ({ unknown: createSelectionMask(W, H), regions: [], count: 0, confidence: 1 });
|
|
3129
|
+
const b = mask.bounds;
|
|
3130
|
+
if (!b) return empty();
|
|
3131
|
+
const band = Math.max(1, Math.round(options.band ?? Math.max(2, Math.min(W, H) / 200)));
|
|
3132
|
+
const minArea = Math.max(1, options.minArea ?? 16);
|
|
3133
|
+
const maxRegions = Math.max(1, options.maxRegions ?? 20);
|
|
3134
|
+
const a = mask.data;
|
|
3135
|
+
const gradient = createGradientSampler(image, W);
|
|
3136
|
+
const x0 = Math.max(0, b.x - band - 1);
|
|
3137
|
+
const y0 = Math.max(0, b.y - band - 1);
|
|
3138
|
+
const x1 = Math.min(W, b.x + b.width + band + 1);
|
|
3139
|
+
const y1 = Math.min(H, b.y + b.height + band + 1);
|
|
3140
|
+
const w = x1 - x0;
|
|
3141
|
+
const h = y1 - y0;
|
|
3142
|
+
const evidence = new Float32Array(w * h).fill(NaN);
|
|
3143
|
+
const seeds = new Uint8Array(w * h);
|
|
3144
|
+
let sum = 0;
|
|
3145
|
+
let n = 0;
|
|
3146
|
+
const on = (x, y) => a[y * W + x] >= 128;
|
|
3147
|
+
for (let y = y0; y < y1; y++) {
|
|
3148
|
+
for (let x = x0; x < x1; x++) {
|
|
3149
|
+
const v = on(x, y);
|
|
3150
|
+
const differs = (nx, ny) => nx >= 0 && ny >= 0 && nx < W && ny < H && on(nx, ny) !== v;
|
|
3151
|
+
if (!(differs(x - 1, y) || differs(x + 1, y) || differs(x, y - 1) || differs(x, y + 1))) continue;
|
|
3152
|
+
let g = 0;
|
|
3153
|
+
for (let dy = -2; dy <= 2; dy++) {
|
|
3154
|
+
for (let dx = -2; dx <= 2; dx++) {
|
|
3155
|
+
const nx = x + dx;
|
|
3156
|
+
const ny = y + dy;
|
|
3157
|
+
if (nx >= 0 && ny >= 0 && nx < W && ny < H) g = Math.max(g, gradient(nx, ny));
|
|
3158
|
+
}
|
|
3159
|
+
}
|
|
3160
|
+
const e = Math.min(1, g / BACKED);
|
|
3161
|
+
const k = (y - y0) * w + (x - x0);
|
|
3162
|
+
evidence[k] = e;
|
|
3163
|
+
sum += e;
|
|
3164
|
+
n++;
|
|
3165
|
+
if (e < 0.5) seeds[k] = 1;
|
|
3166
|
+
}
|
|
3167
|
+
}
|
|
3168
|
+
if (!n) return empty();
|
|
3169
|
+
const rows = new Uint8Array(w * h);
|
|
3170
|
+
for (let y = 0; y < h; y++) {
|
|
3171
|
+
let last = -Infinity;
|
|
3172
|
+
for (let x = 0; x < w; x++) {
|
|
3173
|
+
if (seeds[y * w + x]) last = x;
|
|
3174
|
+
if (x - last <= band) rows[y * w + x] = 1;
|
|
3175
|
+
}
|
|
3176
|
+
last = Infinity;
|
|
3177
|
+
for (let x = w - 1; x >= 0; x--) {
|
|
3178
|
+
if (seeds[y * w + x]) last = x;
|
|
3179
|
+
if (last - x <= band) rows[y * w + x] = 1;
|
|
3180
|
+
}
|
|
3181
|
+
}
|
|
3182
|
+
const grown = new Uint8Array(w * h);
|
|
3183
|
+
for (let x = 0; x < w; x++) {
|
|
3184
|
+
let last = -Infinity;
|
|
3185
|
+
for (let y = 0; y < h; y++) {
|
|
3186
|
+
if (rows[y * w + x]) last = y;
|
|
3187
|
+
if (y - last <= band) grown[y * w + x] = 1;
|
|
3188
|
+
}
|
|
3189
|
+
last = Infinity;
|
|
3190
|
+
for (let y = h - 1; y >= 0; y--) {
|
|
3191
|
+
if (rows[y * w + x]) last = y;
|
|
3192
|
+
if (last - y <= band) grown[y * w + x] = 1;
|
|
3193
|
+
}
|
|
3194
|
+
}
|
|
3195
|
+
const label = new Int32Array(w * h);
|
|
3196
|
+
const stack = new Int32Array(w * h);
|
|
3197
|
+
const found = [];
|
|
3198
|
+
for (let start = 0; start < w * h; start++) {
|
|
3199
|
+
if (!grown[start] || label[start]) continue;
|
|
3200
|
+
const r = { area: 0, e: 0, en: 0, bx0: w, by0: h, bx1: -1, by1: -1 };
|
|
3201
|
+
const id = found.length + 1;
|
|
3202
|
+
let top = 0;
|
|
3203
|
+
stack[top++] = start;
|
|
3204
|
+
label[start] = id;
|
|
3205
|
+
while (top) {
|
|
3206
|
+
const k = stack[--top];
|
|
3207
|
+
const i = k % w;
|
|
3208
|
+
const j = (k - i) / w;
|
|
3209
|
+
r.area++;
|
|
3210
|
+
if (!Number.isNaN(evidence[k])) {
|
|
3211
|
+
r.e += evidence[k];
|
|
3212
|
+
r.en++;
|
|
3213
|
+
}
|
|
3214
|
+
if (i < r.bx0) r.bx0 = i;
|
|
3215
|
+
if (i > r.bx1) r.bx1 = i;
|
|
3216
|
+
if (j < r.by0) r.by0 = j;
|
|
3217
|
+
if (j > r.by1) r.by1 = j;
|
|
3218
|
+
for (let dy = -1; dy <= 1; dy++) {
|
|
3219
|
+
for (let dx = -1; dx <= 1; dx++) {
|
|
3220
|
+
const ni = i + dx;
|
|
3221
|
+
const nj = j + dy;
|
|
3222
|
+
if (ni < 0 || nj < 0 || ni >= w || nj >= h) continue;
|
|
3223
|
+
const nk = nj * w + ni;
|
|
3224
|
+
if (!grown[nk] || label[nk]) continue;
|
|
3225
|
+
label[nk] = id;
|
|
3226
|
+
stack[top++] = nk;
|
|
3227
|
+
}
|
|
3228
|
+
}
|
|
3229
|
+
}
|
|
3230
|
+
found.push(r);
|
|
3231
|
+
}
|
|
3232
|
+
const unknownData = new Uint8Array(W * H);
|
|
3233
|
+
for (let y = 0; y < h; y++) {
|
|
3234
|
+
for (let x = 0; x < w; x++) {
|
|
3235
|
+
const id = label[y * w + x];
|
|
3236
|
+
if (id && found[id - 1].area >= minArea) unknownData[(y + y0) * W + x + x0] = 255;
|
|
3237
|
+
}
|
|
3238
|
+
}
|
|
3239
|
+
const kept = found.filter((r) => r.area >= minArea).map((r) => ({
|
|
3240
|
+
bounds: { x: x0 + r.bx0, y: y0 + r.by0, width: r.bx1 - r.bx0 + 1, height: r.by1 - r.by0 + 1 },
|
|
3241
|
+
area: r.area,
|
|
3242
|
+
confidence: Math.round((r.en ? r.e / r.en : 0) * 1e3) / 1e3
|
|
3243
|
+
})).sort((p, q) => p.confidence - q.confidence || q.area - p.area);
|
|
3244
|
+
return {
|
|
3245
|
+
unknown: createSelectionMask(W, H, unknownData),
|
|
3246
|
+
regions: kept.slice(0, maxRegions),
|
|
3247
|
+
count: kept.length,
|
|
3248
|
+
confidence: Math.round(sum / n * 1e3) / 1e3
|
|
2990
3249
|
};
|
|
2991
3250
|
}
|
|
2992
3251
|
|
|
@@ -9730,6 +9989,7 @@ var ImageEditorController = class {
|
|
|
9730
9989
|
*/
|
|
9731
9990
|
async refineSelectionTo(params, output = "selection", options = {}) {
|
|
9732
9991
|
const edits = options.edits ?? [];
|
|
9992
|
+
const regions2 = this.checkRegions(options.regions);
|
|
9733
9993
|
const refineMode = options.refineMode ?? "color";
|
|
9734
9994
|
if (!this.state.selection?.bounds && !edits.length) throw new Error("Nothing is selected; select first, or pass edits that paint a selection.");
|
|
9735
9995
|
if (refineMode === "object" && !this.segmentation) {
|
|
@@ -9764,7 +10024,14 @@ var ImageEditorController = class {
|
|
|
9764
10024
|
if (!clipped?.bounds) throw new Error("The segmentation model found no object in the selection; use refineMode color.");
|
|
9765
10025
|
sel = clipped;
|
|
9766
10026
|
}
|
|
9767
|
-
|
|
10027
|
+
let refined = refineSelection(pixels, sel, params, edited.refineBrush);
|
|
10028
|
+
if (regions2) {
|
|
10029
|
+
const data = Uint8Array.from(sel.data);
|
|
10030
|
+
for (const r of regions2) {
|
|
10031
|
+
for (let y = r.y; y < r.y + r.height; y++) data.set(refined.data.subarray(y * width + r.x, y * width + r.x + r.width), y * width + r.x);
|
|
10032
|
+
}
|
|
10033
|
+
refined = createSelectionMask(width, height, data);
|
|
10034
|
+
}
|
|
9768
10035
|
if (!refined.bounds) throw new Error("Refining left nothing selected; use a smaller shiftEdge or contrast.");
|
|
9769
10036
|
if (toDocument) {
|
|
9770
10037
|
const document2 = this.documentFromSelection({ layerId, withMask: output === "new-document-with-mask", selection: refined, decontaminate: amount * 100, radius: params.radius });
|
|
@@ -10209,6 +10476,96 @@ var ImageEditorController = class {
|
|
|
10209
10476
|
const report = inspectMaskEdge(mask, image, options);
|
|
10210
10477
|
return { target, ...layer ? { layerId: layer.id } : {}, ...report };
|
|
10211
10478
|
}
|
|
10479
|
+
/** Whole-pixel rectangles inside the canvas, or a useful error; undefined stays undefined. */
|
|
10480
|
+
checkRegions(regions2) {
|
|
10481
|
+
if (regions2 === void 0) return void 0;
|
|
10482
|
+
const { width, height } = this.naturalSize;
|
|
10483
|
+
if (!Array.isArray(regions2) || !regions2.length) throw new Error("regions must be a non-empty list of { x, y, width, height } (e.g. the uncertainRegions bounds); leave it out to refine everywhere.");
|
|
10484
|
+
return regions2.map((r, i) => {
|
|
10485
|
+
const x = Math.max(0, Math.floor(r?.x));
|
|
10486
|
+
const y = Math.max(0, Math.floor(r?.y));
|
|
10487
|
+
const x1 = Math.min(width, Math.ceil(r?.x + r?.width));
|
|
10488
|
+
const y1 = Math.min(height, Math.ceil(r?.y + r?.height));
|
|
10489
|
+
if (![x, y, x1, y1].every(Number.isFinite) || x1 <= x || y1 <= y) throw new Error(`regions[${i}] must overlap the image (0..${width} \xD7 0..${height}), got ${JSON.stringify(r)}.`);
|
|
10490
|
+
return { x, y, width: x1 - x, height: y1 - y };
|
|
10491
|
+
});
|
|
10492
|
+
}
|
|
10493
|
+
/**
|
|
10494
|
+
* Where the selection is unsure of itself: the stretches of its edge with no image edge under
|
|
10495
|
+
* them, widened into a band (a trimap's unknown), as a mask and regions with bounds and
|
|
10496
|
+
* confidence, least confident first. Measured against the composite. Read-only; an empty
|
|
10497
|
+
* selection has none.
|
|
10498
|
+
*/
|
|
10499
|
+
selectionUncertainty(options = {}) {
|
|
10500
|
+
const { width, height } = this.naturalSize;
|
|
10501
|
+
const sel = this.getSelection();
|
|
10502
|
+
if (!sel?.bounds) return selectionUncertainty(createSelectionMask(width, height), { x: 0, y: 0, width: 0, height: 0, data: new Uint8ClampedArray(0) }, options);
|
|
10503
|
+
const b = sel.bounds;
|
|
10504
|
+
const m = Math.max(8, Math.ceil(options.band ?? Math.max(2, Math.min(width, height) / 200)) + 4);
|
|
10505
|
+
const x = Math.max(0, b.x - m);
|
|
10506
|
+
const y = Math.max(0, b.y - m);
|
|
10507
|
+
const region = { x, y, width: Math.min(width, b.x + b.width + m) - x, height: Math.min(height, b.y + b.height + m) - y };
|
|
10508
|
+
return selectionUncertainty(sel, { ...this.compositePixels(region), x, y }, options);
|
|
10509
|
+
}
|
|
10510
|
+
/**
|
|
10511
|
+
* A selection to compare (`compareSelections`): the current one, the one `steps` undo steps
|
|
10512
|
+
* ago, a saved channel, or a layer's painted or vector mask. `null` is nothing selected.
|
|
10513
|
+
*/
|
|
10514
|
+
selectionFrom(source) {
|
|
10515
|
+
const { width, height } = this.naturalSize;
|
|
10516
|
+
switch (source?.kind) {
|
|
10517
|
+
case "selection":
|
|
10518
|
+
return this.getSelection();
|
|
10519
|
+
case "history": {
|
|
10520
|
+
const steps = source.steps ?? 1;
|
|
10521
|
+
const depth = this.undoStack.length;
|
|
10522
|
+
if (!Number.isInteger(steps) || steps < 1) throw new LayerOperationError(`steps must be a whole number of undo steps back, at least 1; got ${String(source.steps)}.`);
|
|
10523
|
+
if (steps > depth) throw new LayerOperationError(`There ${depth === 1 ? "is 1 undo step" : `are ${depth} undo steps`}; steps must be 1..${depth || 1}${depth ? "" : " once something has been done"}.`);
|
|
10524
|
+
return this.undoStack[depth - steps].selection ?? null;
|
|
10525
|
+
}
|
|
10526
|
+
case "channel":
|
|
10527
|
+
return this.requireChannel(source.channelId).mask;
|
|
10528
|
+
case "layer-mask": {
|
|
10529
|
+
const layer = this.getLayer(source.layerId);
|
|
10530
|
+
if (!layer) throw new LayerOperationError(`There is no layer "${source.layerId}" (see list_layers).`);
|
|
10531
|
+
const mask = this.layerMaskCoverage(layer.id);
|
|
10532
|
+
if (!mask) throw new LayerOperationError(`Layer "${layer.name || layer.id}" has no painted mask; add one with add_layer_mask, or compare its vector-mask.`);
|
|
10533
|
+
return mask;
|
|
10534
|
+
}
|
|
10535
|
+
case "vector-mask": {
|
|
10536
|
+
const layer = this.getLayer(source.layerId);
|
|
10537
|
+
if (!layer) throw new LayerOperationError(`There is no layer "${source.layerId}" (see list_layers).`);
|
|
10538
|
+
const { vm } = this.requireVectorMask(layer.id);
|
|
10539
|
+
const mask = pathToSelection(vm.components, width, height, { fillRule: vm.fillRule });
|
|
10540
|
+
return vm.inverted ? invertSelection(mask) : mask;
|
|
10541
|
+
}
|
|
10542
|
+
default:
|
|
10543
|
+
throw new LayerOperationError(`Unknown selection source ${JSON.stringify(source)}. Use selection, history, channel, layer-mask or vector-mask.`);
|
|
10544
|
+
}
|
|
10545
|
+
}
|
|
10546
|
+
/**
|
|
10547
|
+
* What changed from selection `a` (default: before the last undo step) to `b` (default: the
|
|
10548
|
+
* current selection): added and removed area with their largest pieces, IoU and the coverage
|
|
10549
|
+
* change. With `show` (default true) the canvas tints added area green and removed red until the
|
|
10550
|
+
* selection next changes (view state, no undo step). Read-only otherwise.
|
|
10551
|
+
*/
|
|
10552
|
+
compareSelections(a = { kind: "history", steps: 1 }, b = { kind: "selection" }, options = {}) {
|
|
10553
|
+
const { width, height } = this.naturalSize;
|
|
10554
|
+
const ma = this.selectionFrom(a);
|
|
10555
|
+
const mb = this.selectionFrom(b);
|
|
10556
|
+
const { addedMask, removedMask, ...diff } = compareSelectionMasks(ma, mb, width, height, options);
|
|
10557
|
+
if (options.show !== false) {
|
|
10558
|
+
this.state.selectionDiff = diff.identical ? null : { added: addedMask, removed: removedMask, selection: this.state.selection ?? null };
|
|
10559
|
+
this.notify();
|
|
10560
|
+
}
|
|
10561
|
+
return diff;
|
|
10562
|
+
}
|
|
10563
|
+
/** Hides the `compareSelections` overlay. */
|
|
10564
|
+
clearSelectionDiff() {
|
|
10565
|
+
if (!this.state.selectionDiff) return;
|
|
10566
|
+
this.state.selectionDiff = null;
|
|
10567
|
+
this.notify();
|
|
10568
|
+
}
|
|
10212
10569
|
/** A layer's painted mask over the canvas as a selection mask; null without one (or for a depth mask). */
|
|
10213
10570
|
layerMaskCoverage(layerId) {
|
|
10214
10571
|
const layer = this.getLayer(layerId);
|
|
@@ -12921,6 +13278,10 @@ function rectOf(args) {
|
|
|
12921
13278
|
if (rect2.width <= 0 || rect2.height <= 0) throw new Error("width and height must be greater than 0.");
|
|
12922
13279
|
return rect2;
|
|
12923
13280
|
}
|
|
13281
|
+
function uncertainty(controller) {
|
|
13282
|
+
const u = controller.selectionUncertainty();
|
|
13283
|
+
return { uncertainRegions: u.regions, uncertainCount: u.count, edgeConfidence: u.confidence };
|
|
13284
|
+
}
|
|
12924
13285
|
function summary(controller) {
|
|
12925
13286
|
const sel = controller.getSelection();
|
|
12926
13287
|
const { width, height } = controller.getState().imageDimensions;
|
|
@@ -12970,6 +13331,65 @@ function createImageSelectionTools(controller) {
|
|
|
12970
13331
|
return controller.inspectMaskEdge(t, { layerId, minArea });
|
|
12971
13332
|
}
|
|
12972
13333
|
};
|
|
13334
|
+
const sourceSchema = (which, dflt) => ({
|
|
13335
|
+
type: "object",
|
|
13336
|
+
properties: {
|
|
13337
|
+
source: { type: "string", enum: ["selection", "history", "channel", "layer-mask", "vector-mask"], description: "selection: the current one; history: as it was steps undo steps ago; channel: a saved selection (channelId, see list_channels); layer-mask / vector-mask: a layer's mask (layerId)." },
|
|
13338
|
+
steps: { type: "integer", minimum: 1, description: "history only: how many undo steps back (default 1, before the last edit)." },
|
|
13339
|
+
channelId: { type: "string", description: "channel only: the channel id from list_channels." },
|
|
13340
|
+
layerId: { type: "string", description: "layer-mask and vector-mask only: the layer id from list_layers." }
|
|
13341
|
+
},
|
|
13342
|
+
required: ["source"],
|
|
13343
|
+
additionalProperties: false,
|
|
13344
|
+
description: `${which} (default ${dflt}).`
|
|
13345
|
+
});
|
|
13346
|
+
const toSource = (v, fallback) => {
|
|
13347
|
+
if (v === void 0) return fallback;
|
|
13348
|
+
const o = v;
|
|
13349
|
+
const extra = (keys) => {
|
|
13350
|
+
const bad = Object.keys(o).filter((k) => k !== "source" && !keys.includes(k));
|
|
13351
|
+
if (bad.length) throw new Error(`${bad.join(", ")} ${bad.length === 1 ? "does" : "do"} not apply to source ${o.source}.`);
|
|
13352
|
+
};
|
|
13353
|
+
switch (o.source) {
|
|
13354
|
+
case "selection":
|
|
13355
|
+
extra([]);
|
|
13356
|
+
return { kind: "selection" };
|
|
13357
|
+
case "history":
|
|
13358
|
+
extra(["steps"]);
|
|
13359
|
+
return { kind: "history", steps: o.steps ?? 1 };
|
|
13360
|
+
case "channel":
|
|
13361
|
+
extra(["channelId"]);
|
|
13362
|
+
if (!o.channelId) throw new Error("source channel needs channelId (see list_channels).");
|
|
13363
|
+
return { kind: "channel", channelId: o.channelId };
|
|
13364
|
+
case "layer-mask":
|
|
13365
|
+
case "vector-mask":
|
|
13366
|
+
extra(["layerId"]);
|
|
13367
|
+
if (!o.layerId) throw new Error(`source ${o.source} needs layerId (see list_layers).`);
|
|
13368
|
+
return { kind: o.source, layerId: o.layerId };
|
|
13369
|
+
default:
|
|
13370
|
+
throw new Error(`Unknown source "${String(o.source)}". Use selection, history, channel, layer-mask or vector-mask.`);
|
|
13371
|
+
}
|
|
13372
|
+
};
|
|
13373
|
+
const compareTool = {
|
|
13374
|
+
name: "compare_selections",
|
|
13375
|
+
title: "Compare two selections",
|
|
13376
|
+
description: "Shows what changed between two selections, to check that a modify, refine or select step did what you meant. a (default: the selection before the last undo step) and b (default: the current selection) can each be the current selection, a history step, a saved channel or a layer's mask. Returns a and b (area, bounds), added and removed (area in pixels, count and the 10 largest pieces with bounds, ready for capture_view), iou 0-1, coverageChange (pixels of coverage changed, counting edges that only softened) and identical. With show (default true) the canvas tints added area green and removed red until the selection next changes (view only, no undo step).",
|
|
13377
|
+
inputSchema: {
|
|
13378
|
+
type: "object",
|
|
13379
|
+
properties: {
|
|
13380
|
+
a: sourceSchema("The selection before", "history, 1 step back"),
|
|
13381
|
+
b: sourceSchema("The selection after", "the current selection"),
|
|
13382
|
+
minArea: { type: "number", minimum: 1, maximum: 1e6, description: "Pieces smaller than this many pixels are counted but not listed (default 4)." },
|
|
13383
|
+
show: { type: "boolean", description: "Tint the change on the canvas (default true)." }
|
|
13384
|
+
},
|
|
13385
|
+
additionalProperties: false
|
|
13386
|
+
},
|
|
13387
|
+
annotations: { title: "Compare two selections", readOnlyHint: false, destructiveHint: false, idempotentHint: true, openWorldHint: false },
|
|
13388
|
+
handler: ({ a, b, minArea, show }) => controller.compareSelections(toSource(a, { kind: "history", steps: 1 }), toSource(b, { kind: "selection" }), {
|
|
13389
|
+
...minArea !== void 0 ? { minArea } : {},
|
|
13390
|
+
show: show !== false
|
|
13391
|
+
})
|
|
13392
|
+
};
|
|
12973
13393
|
const selectRegion = {
|
|
12974
13394
|
name: "select_region",
|
|
12975
13395
|
title: "Select a region",
|
|
@@ -13145,7 +13565,14 @@ function createImageSelectionTools(controller) {
|
|
|
13145
13565
|
additionalProperties: false
|
|
13146
13566
|
}
|
|
13147
13567
|
},
|
|
13148
|
-
refineMode: { type: "string", enum: ["color", "object"], description: "color (default): refine from the image colours; object: from a host segmentation model's outline of the object (needs a model)." }
|
|
13568
|
+
refineMode: { type: "string", enum: ["color", "object"], description: "color (default): refine from the image colours; object: from a host segmentation model's outline of the object (needs a model)." },
|
|
13569
|
+
regions: {
|
|
13570
|
+
type: "array",
|
|
13571
|
+
minItems: 1,
|
|
13572
|
+
maxItems: 50,
|
|
13573
|
+
items: rectSchemaStrict,
|
|
13574
|
+
description: "Refine only inside these rectangles (natural image pixels), e.g. the uncertainRegions bounds a smart selection returned; the rest of the selection stays as it is. Default: everywhere."
|
|
13575
|
+
}
|
|
13149
13576
|
},
|
|
13150
13577
|
additionalProperties: false
|
|
13151
13578
|
},
|
|
@@ -13176,7 +13603,8 @@ function createImageSelectionTools(controller) {
|
|
|
13176
13603
|
layerId: typeof params.layerId === "string" ? params.layerId : void 0,
|
|
13177
13604
|
decontaminate: num2(params.decontaminate, 0),
|
|
13178
13605
|
edits,
|
|
13179
|
-
refineMode: params.refineMode === "object" ? "object" : "color"
|
|
13606
|
+
refineMode: params.refineMode === "object" ? "object" : "color",
|
|
13607
|
+
...params.regions !== void 0 ? { regions: params.regions } : {}
|
|
13180
13608
|
}
|
|
13181
13609
|
);
|
|
13182
13610
|
return { ...summary(controller), ...result };
|
|
@@ -13332,7 +13760,7 @@ function createImageSelectionTools(controller) {
|
|
|
13332
13760
|
const quickSelectTool = {
|
|
13333
13761
|
name: "quick_select",
|
|
13334
13762
|
title: "Quick Selection",
|
|
13335
|
-
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.",
|
|
13763
|
+
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. Also returns uncertainRegions (bounds, area, confidence 0-1; up to 20, least confident first), uncertainCount and edgeConfidence: where the edge has no image edge under it. Look at those with capture_view and fix only them (refine_selection with regions).",
|
|
13336
13764
|
inputSchema: {
|
|
13337
13765
|
type: "object",
|
|
13338
13766
|
properties: {
|
|
@@ -13363,13 +13791,13 @@ function createImageSelectionTools(controller) {
|
|
|
13363
13791
|
enhanceEdge: args.enhanceEdge,
|
|
13364
13792
|
signal: context?.signal
|
|
13365
13793
|
});
|
|
13366
|
-
return summary(controller);
|
|
13794
|
+
return { ...summary(controller), ...uncertainty(controller) };
|
|
13367
13795
|
}
|
|
13368
13796
|
};
|
|
13369
13797
|
const selectObjectTool = {
|
|
13370
13798
|
name: "select_object",
|
|
13371
13799
|
title: "Object Selection",
|
|
13372
|
-
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. With a host segmentation model it asks the model (falling back if nothing it returns fits); otherwise it finds the object 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 and method ("model" or "grabcut").',
|
|
13800
|
+
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. With a host segmentation model it asks the model (falling back if nothing it returns fits); otherwise it finds the object 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 and method ("model" or "grabcut"). Also returns uncertainRegions (bounds, area, confidence 0-1; up to 20, least confident first), uncertainCount and edgeConfidence: where the edge has no image edge under it. Look at those with capture_view and fix only them (refine_selection with regions).',
|
|
13373
13801
|
inputSchema: {
|
|
13374
13802
|
type: "object",
|
|
13375
13803
|
properties: {
|
|
@@ -13402,7 +13830,7 @@ function createImageSelectionTools(controller) {
|
|
|
13402
13830
|
signal: context?.signal
|
|
13403
13831
|
};
|
|
13404
13832
|
const { method } = rect2 ? await controller.selectObject({ rect: { x: num2(rect2.x, NaN), y: num2(rect2.y, NaN), width: num2(rect2.width, NaN), height: num2(rect2.height, NaN) } }, options) : await controller.selectObject({ point: { x: num2(point.x, NaN), y: num2(point.y, NaN) } }, options);
|
|
13405
|
-
return { ...summary(controller), method };
|
|
13833
|
+
return { ...summary(controller), method, ...uncertainty(controller) };
|
|
13406
13834
|
}
|
|
13407
13835
|
};
|
|
13408
13836
|
const selectFocusAreaTool = {
|
|
@@ -13436,7 +13864,7 @@ function createImageSelectionTools(controller) {
|
|
|
13436
13864
|
const selectSkyTool = {
|
|
13437
13865
|
name: "select_sky",
|
|
13438
13866
|
title: "Select the sky",
|
|
13439
|
-
description: 'Select \u203A Sky: selects the sky, combined with the selection by mode. With a host segmentation model it asks the model; otherwise it finds it classically (blue, or bright and grey when overcast, smooth, reaching down from the top of the image, including patches seen through branches or gaps) with the edge fitted to the skyline. Use it to replace or recolour a sky or to adjust the land without it (invert_selection). Raise threshold to take in haze or a sunset glow, lower it if bright buildings come in. One undoable step. Returns the selection, method ("model" or "classical"; threshold and softness apply to classical), and a note when no sky was found (the selection is then unchanged).',
|
|
13867
|
+
description: 'Select \u203A Sky: selects the sky, combined with the selection by mode. With a host segmentation model it asks the model; otherwise it finds it classically (blue, or bright and grey when overcast, smooth, reaching down from the top of the image, including patches seen through branches or gaps) with the edge fitted to the skyline. Use it to replace or recolour a sky or to adjust the land without it (invert_selection). Raise threshold to take in haze or a sunset glow, lower it if bright buildings come in. One undoable step. Returns the selection, method ("model" or "classical"; threshold and softness apply to classical), and a note when no sky was found (the selection is then unchanged). Also returns uncertainRegions (bounds, area, confidence 0-1; up to 20, least confident first), uncertainCount and edgeConfidence: where the edge has no image edge under it. Look at those with capture_view and fix only them (refine_selection with regions).',
|
|
13440
13868
|
inputSchema: {
|
|
13441
13869
|
type: "object",
|
|
13442
13870
|
properties: {
|
|
@@ -13458,7 +13886,7 @@ function createImageSelectionTools(controller) {
|
|
|
13458
13886
|
useModel: args.useModel,
|
|
13459
13887
|
signal: context?.signal
|
|
13460
13888
|
});
|
|
13461
|
-
return { ...summary(controller), method, ...note ? { note } : {} };
|
|
13889
|
+
return { ...summary(controller), method, ...note ? { note } : {}, ...uncertainty(controller) };
|
|
13462
13890
|
}
|
|
13463
13891
|
};
|
|
13464
13892
|
const magicWandTool = {
|
|
@@ -14016,6 +14444,7 @@ function createImageSelectionTools(controller) {
|
|
|
14016
14444
|
return [
|
|
14017
14445
|
getSelection,
|
|
14018
14446
|
inspectEdge,
|
|
14447
|
+
compareTool,
|
|
14019
14448
|
selectAll,
|
|
14020
14449
|
deselect,
|
|
14021
14450
|
invert2,
|
|
@@ -14063,7 +14492,7 @@ function createImageSubjectSelectionTool(controller) {
|
|
|
14063
14492
|
return {
|
|
14064
14493
|
name: "select_subject",
|
|
14065
14494
|
title: "Select a subject",
|
|
14066
|
-
description: 'Select \u203A Subject: selects the main subject of the flattened image, combined with the selection by mode. With a segmentation model set by the host, target (a description such as "the dog" or "the house") chooses which objects; describe one object precisely to get only that one. Without a model, the subject that stands out most from the background is found classically (superpixel saliency and GrabCut): target cannot choose it, and method "saliency" and a note say so. Then check it with capture_view and fix it with quick_select or select_object. One undoable step. Returns the selection, the objects found (label, bounds), method ("model" or "saliency") and any note; none found leaves the selection unchanged.',
|
|
14495
|
+
description: 'Select \u203A Subject: selects the main subject of the flattened image, combined with the selection by mode. With a segmentation model set by the host, target (a description such as "the dog" or "the house") chooses which objects; describe one object precisely to get only that one. Without a model, the subject that stands out most from the background is found classically (superpixel saliency and GrabCut): target cannot choose it, and method "saliency" and a note say so. Then check it with capture_view and fix it with quick_select or select_object. One undoable step. Returns the selection, the objects found (label, bounds), method ("model" or "saliency") and any note; none found leaves the selection unchanged. Also returns uncertainRegions (bounds, area, confidence 0-1; up to 20, least confident first), uncertainCount and edgeConfidence: where the edge has no image edge under it. Look at those with capture_view and fix only them (refine_selection with regions).',
|
|
14067
14496
|
inputSchema: {
|
|
14068
14497
|
type: "object",
|
|
14069
14498
|
properties: {
|
|
@@ -14094,7 +14523,7 @@ function createImageSubjectSelectionTool(controller) {
|
|
|
14094
14523
|
// Under playback the cursor traces what the model found, as a lasso, before it is selected.
|
|
14095
14524
|
beforeApply: perform ? (outlines) => perform(planOutlineTrace(outlines)) : void 0
|
|
14096
14525
|
});
|
|
14097
|
-
return { ...summary(controller), found: segments, method, ...note ? { note } : {} };
|
|
14526
|
+
return { ...summary(controller), found: segments, method, ...note ? { note } : {}, ...uncertainty(controller) };
|
|
14098
14527
|
}
|
|
14099
14528
|
};
|
|
14100
14529
|
}
|
|
@@ -22043,6 +22472,7 @@ export {
|
|
|
22043
22472
|
colorRange,
|
|
22044
22473
|
colorRangePreset,
|
|
22045
22474
|
combineSelections,
|
|
22475
|
+
compareSelectionMasks,
|
|
22046
22476
|
compileAdjustmentLayer,
|
|
22047
22477
|
componentPath,
|
|
22048
22478
|
componentsCoverage,
|
|
@@ -22070,6 +22500,7 @@ export {
|
|
|
22070
22500
|
createAdjustmentLayer,
|
|
22071
22501
|
createBlenderScratch,
|
|
22072
22502
|
createCanvasTextShaper,
|
|
22503
|
+
createGradientSampler,
|
|
22073
22504
|
createImageCaptureSurface,
|
|
22074
22505
|
createImageColorTools,
|
|
22075
22506
|
createImageEditTools,
|
|
@@ -22587,6 +23018,7 @@ export {
|
|
|
22587
23018
|
selectObjectIn,
|
|
22588
23019
|
selectSimilar,
|
|
22589
23020
|
selectionArea,
|
|
23021
|
+
selectionDiffPixels,
|
|
22590
23022
|
selectionFromCoverage,
|
|
22591
23023
|
selectionFromPixels,
|
|
22592
23024
|
selectionFromRegion,
|
|
@@ -22597,6 +23029,7 @@ export {
|
|
|
22597
23029
|
selectionToCoverage,
|
|
22598
23030
|
selectionToPixels,
|
|
22599
23031
|
selectionToSubpaths,
|
|
23032
|
+
selectionUncertainty,
|
|
22600
23033
|
selectionValueAt,
|
|
22601
23034
|
serializeChannels,
|
|
22602
23035
|
serializeColorMode,
|