@pixerate/editor 0.214.0 → 0.216.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/{chunk-UOGKSVRQ.cjs → chunk-B46UQUDC.cjs} +770 -733
- package/dist/{chunk-W3TOZTL7.js → chunk-F7Z2MKY2.js} +115 -4
- package/dist/{chunk-YBJXCQTR.js → chunk-QXY4YTRI.js} +738 -701
- package/dist/{chunk-H65IBXFI.cjs → chunk-S55JFWDA.cjs} +350 -239
- package/dist/{chunk-QYLWG22N.cjs → chunk-SS53NNZO.cjs} +304 -1
- package/dist/{chunk-LQ5SALHK.js → chunk-U7C655AM.js} +5 -311
- package/dist/{chunk-L52VMQZS.cjs → chunk-UC3PWFM3.cjs} +68 -374
- package/dist/{chunk-L3G23N5Y.js → chunk-ZNA3IQZF.js} +304 -1
- package/dist/image-editor/controller.d.cts +41 -0
- package/dist/image-editor/controller.d.ts +41 -0
- package/dist/image-editor/index.cjs +1461 -1125
- package/dist/image-editor/index.d.cts +1 -0
- package/dist/image-editor/index.d.ts +1 -0
- package/dist/image-editor/index.js +384 -48
- package/dist/image-editor/layers/edge-color.d.cts +27 -0
- package/dist/image-editor/layers/edge-color.d.ts +27 -0
- package/dist/image-editor/layers/serialize.d.cts +13 -0
- package/dist/image-editor/layers/serialize.d.ts +13 -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/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/selection-worker.cjs +4 -4
- package/dist/image-editor/selection-worker.js +2 -2
- package/dist/paint-editor/index.cjs +79 -79
- package/dist/paint-editor/index.js +3 -3
- package/dist/raster/compositor.d.cts +10 -1
- package/dist/raster/compositor.d.ts +10 -1
- package/dist/raster/deep-compositor.d.cts +1 -1
- package/dist/raster/deep-compositor.d.ts +1 -1
- package/dist/raster/index.cjs +4 -2
- package/dist/raster/index.js +3 -1
- package/dist/raster/layers.d.cts +16 -0
- package/dist/raster/layers.d.ts +16 -0
- package/package.json +1 -1
- package/skills/pixerate-image-editor/SKILL.md +9 -7
- package/skills/pixerate-image-editor/references/paths.md +12 -0
- package/skills/pixerate-image-selection-masking/SKILL.md +11 -11
|
@@ -7,9 +7,7 @@ import {
|
|
|
7
7
|
} from "../chunk-DJBYJOHM.js";
|
|
8
8
|
import {
|
|
9
9
|
ColorGmm,
|
|
10
|
-
DEFAULT_REFINE_EDGE_PARAMS,
|
|
11
10
|
GAMMA,
|
|
12
|
-
GUIDED_FILTER_EPS,
|
|
13
11
|
GridGraph,
|
|
14
12
|
REFINE_EDIT_KINDS,
|
|
15
13
|
SelectionWorkerClient,
|
|
@@ -17,31 +15,22 @@ import {
|
|
|
17
15
|
WORK_SIZE,
|
|
18
16
|
applyRefineEdits,
|
|
19
17
|
boxBlur,
|
|
20
|
-
boxMean,
|
|
21
18
|
brushStrokeMask,
|
|
22
|
-
decontaminateColors,
|
|
23
19
|
deserializeMask,
|
|
24
|
-
edgeWidth,
|
|
25
20
|
ellipsePoints,
|
|
26
21
|
ellipseSelection,
|
|
27
22
|
findSky,
|
|
28
23
|
findSubject,
|
|
29
24
|
focusArea,
|
|
30
25
|
grabCutLabels,
|
|
31
|
-
guidePlanes,
|
|
32
|
-
guidedFilterColor,
|
|
33
26
|
keepMainPieces,
|
|
34
27
|
normalizeRect,
|
|
35
|
-
normalizeRefineEdgeParams,
|
|
36
28
|
otsuThreshold,
|
|
37
29
|
polygonCoverage,
|
|
38
30
|
polygonCoverageBox,
|
|
39
31
|
polygonSelection,
|
|
40
32
|
quickSelectStroke,
|
|
41
33
|
rectSelection,
|
|
42
|
-
refineCoverage,
|
|
43
|
-
refineEdgeHalo,
|
|
44
|
-
refineSelection,
|
|
45
34
|
runSelectionJob,
|
|
46
35
|
selectObjectIn,
|
|
47
36
|
serializeMask,
|
|
@@ -54,7 +43,7 @@ import {
|
|
|
54
43
|
workImage,
|
|
55
44
|
workLuma,
|
|
56
45
|
workPlanes
|
|
57
|
-
} from "../chunk-
|
|
46
|
+
} from "../chunk-U7C655AM.js";
|
|
58
47
|
import {
|
|
59
48
|
ADJUSTMENT_KEYS,
|
|
60
49
|
ADJUSTMENT_KINDS,
|
|
@@ -72,6 +61,7 @@ import {
|
|
|
72
61
|
DEFAULT_MITER_LIMIT,
|
|
73
62
|
DEFAULT_TRANSFORM_VALUES,
|
|
74
63
|
DEVELOP_FILTER_ID,
|
|
64
|
+
EDGE_COLOR_RADIUS,
|
|
75
65
|
FREE_TRANSFORM_MODES,
|
|
76
66
|
HUE_RANGE_LABELS,
|
|
77
67
|
IDENTITY_MATRIX3,
|
|
@@ -127,6 +117,7 @@ import {
|
|
|
127
117
|
applyBoxBlur,
|
|
128
118
|
applyBoxBlurFloat,
|
|
129
119
|
applyDepthMask,
|
|
120
|
+
applyEdgeUnmix,
|
|
130
121
|
applyImageProjectionToContext,
|
|
131
122
|
applyLegacyAdjustments,
|
|
132
123
|
applyLegacyFloat,
|
|
@@ -175,6 +166,7 @@ import {
|
|
|
175
166
|
deserializeLayer,
|
|
176
167
|
deserializeLayers,
|
|
177
168
|
deserializePatterns,
|
|
169
|
+
despillEdge,
|
|
178
170
|
developFilter,
|
|
179
171
|
developFromAdjustments,
|
|
180
172
|
developTarget,
|
|
@@ -197,7 +189,9 @@ import {
|
|
|
197
189
|
getViewportMetrics,
|
|
198
190
|
getViewportProjection,
|
|
199
191
|
groupLayers,
|
|
192
|
+
hasEdgeUnmix,
|
|
200
193
|
hasResampledLegacyMask,
|
|
194
|
+
hasSoftBand,
|
|
201
195
|
hasSurface,
|
|
202
196
|
hasWarp,
|
|
203
197
|
hexToRgb,
|
|
@@ -248,6 +242,7 @@ import {
|
|
|
248
242
|
liveShapeSubpaths,
|
|
249
243
|
mapCropHandle,
|
|
250
244
|
mapSubpaths,
|
|
245
|
+
maskCoverageOverCanvas,
|
|
251
246
|
matrixFromCorners,
|
|
252
247
|
matrixFromParams,
|
|
253
248
|
mergeShapeComponents,
|
|
@@ -302,6 +297,7 @@ import {
|
|
|
302
297
|
rasterMaskFromLegacy,
|
|
303
298
|
rasterizeCoverage,
|
|
304
299
|
rasterizeSmartObject,
|
|
300
|
+
readEdgeColor,
|
|
305
301
|
readPsd,
|
|
306
302
|
readShapeComponents,
|
|
307
303
|
referencePoint,
|
|
@@ -404,34 +400,45 @@ import {
|
|
|
404
400
|
writePsd,
|
|
405
401
|
xyToTempTint,
|
|
406
402
|
xyToUv
|
|
407
|
-
} from "../chunk-
|
|
403
|
+
} from "../chunk-F7Z2MKY2.js";
|
|
408
404
|
import {
|
|
409
405
|
parseCssColor
|
|
410
406
|
} from "../chunk-3CI7FRMK.js";
|
|
411
407
|
import {
|
|
408
|
+
DEFAULT_REFINE_EDGE_PARAMS,
|
|
412
409
|
EMPTY_DISTANCE,
|
|
410
|
+
GUIDED_FILTER_EPS,
|
|
413
411
|
SELECTION_MODES,
|
|
414
412
|
SELECTION_MODIFY_OPS,
|
|
415
413
|
borderCoverage,
|
|
414
|
+
boxMean,
|
|
416
415
|
combineSelections,
|
|
417
416
|
computeSelectionBounds,
|
|
418
417
|
contractCoverage,
|
|
419
418
|
coverageToByte,
|
|
420
419
|
createSelectionMask,
|
|
421
420
|
decodeSelectionRle,
|
|
421
|
+
decontaminateColors,
|
|
422
422
|
deserializeSelection,
|
|
423
423
|
distanceTransform,
|
|
424
|
+
edgeWidth,
|
|
424
425
|
encodeSelectionRle,
|
|
425
426
|
expandCoverage,
|
|
426
427
|
featherCoverage,
|
|
427
428
|
fullSelection,
|
|
428
429
|
greyMorphology,
|
|
430
|
+
guidePlanes,
|
|
431
|
+
guidedFilterColor,
|
|
429
432
|
hashMaskContent,
|
|
430
433
|
invertSelection,
|
|
431
434
|
isSelectionEmpty,
|
|
432
435
|
isSelectionMode,
|
|
433
436
|
modifySelection,
|
|
437
|
+
normalizeRefineEdgeParams,
|
|
434
438
|
parseSelectionMode,
|
|
439
|
+
refineCoverage,
|
|
440
|
+
refineEdgeHalo,
|
|
441
|
+
refineSelection,
|
|
435
442
|
resizeSelection,
|
|
436
443
|
selectionArea,
|
|
437
444
|
selectionFromCoverage,
|
|
@@ -444,7 +451,7 @@ import {
|
|
|
444
451
|
serializeSelection,
|
|
445
452
|
shiftCoverage,
|
|
446
453
|
smoothCoverage
|
|
447
|
-
} from "../chunk-
|
|
454
|
+
} from "../chunk-ZNA3IQZF.js";
|
|
448
455
|
import {
|
|
449
456
|
EffectRendererPool
|
|
450
457
|
} from "../chunk-EHLOCVWB.js";
|
|
@@ -500,7 +507,7 @@ import {
|
|
|
500
507
|
validateLayerEffects,
|
|
501
508
|
warpSurface,
|
|
502
509
|
workingToSamples
|
|
503
|
-
} from "../chunk-
|
|
510
|
+
} from "../chunk-QXY4YTRI.js";
|
|
504
511
|
import {
|
|
505
512
|
applyHomography,
|
|
506
513
|
boxQuad,
|
|
@@ -2602,27 +2609,7 @@ function inspectMaskEdge(mask, image, options = {}) {
|
|
|
2602
2609
|
if (img[i + 3] < 128) return null;
|
|
2603
2610
|
return [img[i], img[i + 1], img[i + 2]];
|
|
2604
2611
|
};
|
|
2605
|
-
const
|
|
2606
|
-
const ix = Math.min(image.width - 1, Math.max(0, x - image.x));
|
|
2607
|
-
const iy = Math.min(image.height - 1, Math.max(0, y - image.y));
|
|
2608
|
-
const i = (iy * image.width + ix) * 4;
|
|
2609
|
-
return img[i + c] * img[i + 3] / 255;
|
|
2610
|
-
};
|
|
2611
|
-
const gradCache = /* @__PURE__ */ new Map();
|
|
2612
|
-
const gradient = (x, y) => {
|
|
2613
|
-
const k = y * W + x;
|
|
2614
|
-
const hit = gradCache.get(k);
|
|
2615
|
-
if (hit !== void 0) return hit;
|
|
2616
|
-
let best = 0;
|
|
2617
|
-
for (let c = 0; c < 3; c++) {
|
|
2618
|
-
const p = (dx, dy) => premul(x + dx, y + dy, c);
|
|
2619
|
-
const gx = p(1, -1) + 2 * p(1, 0) + p(1, 1) - p(-1, -1) - 2 * p(-1, 0) - p(-1, 1);
|
|
2620
|
-
const gy = p(-1, 1) + 2 * p(0, 1) + p(1, 1) - p(-1, -1) - 2 * p(0, -1) - p(1, -1);
|
|
2621
|
-
best = Math.max(best, Math.hypot(gx, gy) / 1020);
|
|
2622
|
-
}
|
|
2623
|
-
gradCache.set(k, best);
|
|
2624
|
-
return best;
|
|
2625
|
-
};
|
|
2612
|
+
const gradient = createGradientSampler(image, W);
|
|
2626
2613
|
const B = Math.min(256, Math.max(24, Math.round(Math.max(W, H) / 12)));
|
|
2627
2614
|
const BW = Math.ceil(W / B);
|
|
2628
2615
|
const blocks = /* @__PURE__ */ new Map();
|
|
@@ -2777,7 +2764,7 @@ function inspectMaskEdge(mask, image, options = {}) {
|
|
|
2777
2764
|
const worst = candidates.sort((p, q) => q.severity - p.severity).slice(0, 5).map(({ bounds, reason }) => ({ bounds, reason }));
|
|
2778
2765
|
const issues = [];
|
|
2779
2766
|
if (haloScore >= T2.halo)
|
|
2780
|
-
issues.push(`Halo (haloScore ${r3(haloScore)} \u2265 ${T2.halo}): background colour is left inside the edge. Contract the edge (modify_selection contract 1\u20132 px, or refine_selection shiftEdge -20 to -40), or
|
|
2767
|
+
issues.push(`Halo (haloScore ${r3(haloScore)} \u2265 ${T2.halo}): background colour is left inside the edge. Contract the edge (modify_selection contract 1\u20132 px, or refine_selection shiftEdge -20 to -40), or on a masked cutout set_edge_decontamination 80\u2013100.`);
|
|
2781
2768
|
if (edgeLength >= 16 && edgeAlignment < T2.alignment)
|
|
2782
2769
|
issues.push(`Off the image's edges (edgeAlignment ${r3(edgeAlignment)} < ${T2.alignment}): much of the edge does not follow edges in the image. Rebuild it with select_subject, select_object or quick_select, or refine_selection with a larger radius.`);
|
|
2783
2770
|
if (jaggedness >= T2.jaggedness) issues.push(`Jagged (jaggedness ${r3(jaggedness)} \u2265 ${T2.jaggedness}): the outline zigzags. Smooth it (refine_selection smooth 20\u201340, or modify_selection smooth).`);
|
|
@@ -2799,6 +2786,30 @@ function inspectMaskEdge(mask, image, options = {}) {
|
|
|
2799
2786
|
issues
|
|
2800
2787
|
};
|
|
2801
2788
|
}
|
|
2789
|
+
function createGradientSampler(image, W) {
|
|
2790
|
+
const img = image.data;
|
|
2791
|
+
const premul = (x, y, c) => {
|
|
2792
|
+
const ix = Math.min(image.width - 1, Math.max(0, x - image.x));
|
|
2793
|
+
const iy = Math.min(image.height - 1, Math.max(0, y - image.y));
|
|
2794
|
+
const i = (iy * image.width + ix) * 4;
|
|
2795
|
+
return img[i + c] * img[i + 3] / 255;
|
|
2796
|
+
};
|
|
2797
|
+
const cache = /* @__PURE__ */ new Map();
|
|
2798
|
+
return (x, y) => {
|
|
2799
|
+
const k = y * W + x;
|
|
2800
|
+
const hit = cache.get(k);
|
|
2801
|
+
if (hit !== void 0) return hit;
|
|
2802
|
+
let best = 0;
|
|
2803
|
+
for (let c = 0; c < 3; c++) {
|
|
2804
|
+
const p = (dx, dy) => premul(x + dx, y + dy, c);
|
|
2805
|
+
const gx = p(1, -1) + 2 * p(1, 0) + p(1, 1) - p(-1, -1) - 2 * p(-1, 0) - p(-1, 1);
|
|
2806
|
+
const gy = p(-1, 1) + 2 * p(0, 1) + p(1, 1) - p(-1, -1) - 2 * p(0, -1) - p(1, -1);
|
|
2807
|
+
best = Math.max(best, Math.hypot(gx, gy) / 1020);
|
|
2808
|
+
}
|
|
2809
|
+
cache.set(k, best);
|
|
2810
|
+
return best;
|
|
2811
|
+
};
|
|
2812
|
+
}
|
|
2802
2813
|
function traceLoops(a, W, H, rx0, ry0, rx1, ry1) {
|
|
2803
2814
|
const w = rx1 - rx0;
|
|
2804
2815
|
const h = ry1 - ry0;
|
|
@@ -2983,6 +2994,135 @@ function regions(a, W, H, b, minArea) {
|
|
|
2983
2994
|
};
|
|
2984
2995
|
}
|
|
2985
2996
|
|
|
2997
|
+
// src/image-editor/selection/uncertainty.ts
|
|
2998
|
+
var BACKED = 0.2;
|
|
2999
|
+
function selectionUncertainty(mask, image, options = {}) {
|
|
3000
|
+
const W = mask.width;
|
|
3001
|
+
const H = mask.height;
|
|
3002
|
+
const empty = () => ({ unknown: createSelectionMask(W, H), regions: [], count: 0, confidence: 1 });
|
|
3003
|
+
const b = mask.bounds;
|
|
3004
|
+
if (!b) return empty();
|
|
3005
|
+
const band = Math.max(1, Math.round(options.band ?? Math.max(2, Math.min(W, H) / 200)));
|
|
3006
|
+
const minArea = Math.max(1, options.minArea ?? 16);
|
|
3007
|
+
const maxRegions = Math.max(1, options.maxRegions ?? 20);
|
|
3008
|
+
const a = mask.data;
|
|
3009
|
+
const gradient = createGradientSampler(image, W);
|
|
3010
|
+
const x0 = Math.max(0, b.x - band - 1);
|
|
3011
|
+
const y0 = Math.max(0, b.y - band - 1);
|
|
3012
|
+
const x1 = Math.min(W, b.x + b.width + band + 1);
|
|
3013
|
+
const y1 = Math.min(H, b.y + b.height + band + 1);
|
|
3014
|
+
const w = x1 - x0;
|
|
3015
|
+
const h = y1 - y0;
|
|
3016
|
+
const evidence = new Float32Array(w * h).fill(NaN);
|
|
3017
|
+
const seeds = new Uint8Array(w * h);
|
|
3018
|
+
let sum = 0;
|
|
3019
|
+
let n = 0;
|
|
3020
|
+
const on = (x, y) => a[y * W + x] >= 128;
|
|
3021
|
+
for (let y = y0; y < y1; y++) {
|
|
3022
|
+
for (let x = x0; x < x1; x++) {
|
|
3023
|
+
const v = on(x, y);
|
|
3024
|
+
const differs = (nx, ny) => nx >= 0 && ny >= 0 && nx < W && ny < H && on(nx, ny) !== v;
|
|
3025
|
+
if (!(differs(x - 1, y) || differs(x + 1, y) || differs(x, y - 1) || differs(x, y + 1))) continue;
|
|
3026
|
+
let g = 0;
|
|
3027
|
+
for (let dy = -2; dy <= 2; dy++) {
|
|
3028
|
+
for (let dx = -2; dx <= 2; dx++) {
|
|
3029
|
+
const nx = x + dx;
|
|
3030
|
+
const ny = y + dy;
|
|
3031
|
+
if (nx >= 0 && ny >= 0 && nx < W && ny < H) g = Math.max(g, gradient(nx, ny));
|
|
3032
|
+
}
|
|
3033
|
+
}
|
|
3034
|
+
const e = Math.min(1, g / BACKED);
|
|
3035
|
+
const k = (y - y0) * w + (x - x0);
|
|
3036
|
+
evidence[k] = e;
|
|
3037
|
+
sum += e;
|
|
3038
|
+
n++;
|
|
3039
|
+
if (e < 0.5) seeds[k] = 1;
|
|
3040
|
+
}
|
|
3041
|
+
}
|
|
3042
|
+
if (!n) return empty();
|
|
3043
|
+
const rows = new Uint8Array(w * h);
|
|
3044
|
+
for (let y = 0; y < h; y++) {
|
|
3045
|
+
let last = -Infinity;
|
|
3046
|
+
for (let x = 0; x < w; x++) {
|
|
3047
|
+
if (seeds[y * w + x]) last = x;
|
|
3048
|
+
if (x - last <= band) rows[y * w + x] = 1;
|
|
3049
|
+
}
|
|
3050
|
+
last = Infinity;
|
|
3051
|
+
for (let x = w - 1; x >= 0; x--) {
|
|
3052
|
+
if (seeds[y * w + x]) last = x;
|
|
3053
|
+
if (last - x <= band) rows[y * w + x] = 1;
|
|
3054
|
+
}
|
|
3055
|
+
}
|
|
3056
|
+
const grown = new Uint8Array(w * h);
|
|
3057
|
+
for (let x = 0; x < w; x++) {
|
|
3058
|
+
let last = -Infinity;
|
|
3059
|
+
for (let y = 0; y < h; y++) {
|
|
3060
|
+
if (rows[y * w + x]) last = y;
|
|
3061
|
+
if (y - last <= band) grown[y * w + x] = 1;
|
|
3062
|
+
}
|
|
3063
|
+
last = Infinity;
|
|
3064
|
+
for (let y = h - 1; y >= 0; y--) {
|
|
3065
|
+
if (rows[y * w + x]) last = y;
|
|
3066
|
+
if (last - y <= band) grown[y * w + x] = 1;
|
|
3067
|
+
}
|
|
3068
|
+
}
|
|
3069
|
+
const label = new Int32Array(w * h);
|
|
3070
|
+
const stack = new Int32Array(w * h);
|
|
3071
|
+
const found = [];
|
|
3072
|
+
for (let start = 0; start < w * h; start++) {
|
|
3073
|
+
if (!grown[start] || label[start]) continue;
|
|
3074
|
+
const r = { area: 0, e: 0, en: 0, bx0: w, by0: h, bx1: -1, by1: -1 };
|
|
3075
|
+
const id = found.length + 1;
|
|
3076
|
+
let top = 0;
|
|
3077
|
+
stack[top++] = start;
|
|
3078
|
+
label[start] = id;
|
|
3079
|
+
while (top) {
|
|
3080
|
+
const k = stack[--top];
|
|
3081
|
+
const i = k % w;
|
|
3082
|
+
const j = (k - i) / w;
|
|
3083
|
+
r.area++;
|
|
3084
|
+
if (!Number.isNaN(evidence[k])) {
|
|
3085
|
+
r.e += evidence[k];
|
|
3086
|
+
r.en++;
|
|
3087
|
+
}
|
|
3088
|
+
if (i < r.bx0) r.bx0 = i;
|
|
3089
|
+
if (i > r.bx1) r.bx1 = i;
|
|
3090
|
+
if (j < r.by0) r.by0 = j;
|
|
3091
|
+
if (j > r.by1) r.by1 = j;
|
|
3092
|
+
for (let dy = -1; dy <= 1; dy++) {
|
|
3093
|
+
for (let dx = -1; dx <= 1; dx++) {
|
|
3094
|
+
const ni = i + dx;
|
|
3095
|
+
const nj = j + dy;
|
|
3096
|
+
if (ni < 0 || nj < 0 || ni >= w || nj >= h) continue;
|
|
3097
|
+
const nk = nj * w + ni;
|
|
3098
|
+
if (!grown[nk] || label[nk]) continue;
|
|
3099
|
+
label[nk] = id;
|
|
3100
|
+
stack[top++] = nk;
|
|
3101
|
+
}
|
|
3102
|
+
}
|
|
3103
|
+
}
|
|
3104
|
+
found.push(r);
|
|
3105
|
+
}
|
|
3106
|
+
const unknownData = new Uint8Array(W * H);
|
|
3107
|
+
for (let y = 0; y < h; y++) {
|
|
3108
|
+
for (let x = 0; x < w; x++) {
|
|
3109
|
+
const id = label[y * w + x];
|
|
3110
|
+
if (id && found[id - 1].area >= minArea) unknownData[(y + y0) * W + x + x0] = 255;
|
|
3111
|
+
}
|
|
3112
|
+
}
|
|
3113
|
+
const kept = found.filter((r) => r.area >= minArea).map((r) => ({
|
|
3114
|
+
bounds: { x: x0 + r.bx0, y: y0 + r.by0, width: r.bx1 - r.bx0 + 1, height: r.by1 - r.by0 + 1 },
|
|
3115
|
+
area: r.area,
|
|
3116
|
+
confidence: Math.round((r.en ? r.e / r.en : 0) * 1e3) / 1e3
|
|
3117
|
+
})).sort((p, q) => p.confidence - q.confidence || q.area - p.area);
|
|
3118
|
+
return {
|
|
3119
|
+
unknown: createSelectionMask(W, H, unknownData),
|
|
3120
|
+
regions: kept.slice(0, maxRegions),
|
|
3121
|
+
count: kept.length,
|
|
3122
|
+
confidence: Math.round(sum / n * 1e3) / 1e3
|
|
3123
|
+
};
|
|
3124
|
+
}
|
|
3125
|
+
|
|
2986
3126
|
// src/image-editor/selection/path.ts
|
|
2987
3127
|
function selectionToSubpaths(mask, options = {}) {
|
|
2988
3128
|
const b = mask.bounds;
|
|
@@ -6822,6 +6962,85 @@ var ImageEditorController = class {
|
|
|
6822
6962
|
this.commitLayers(next);
|
|
6823
6963
|
return { parentId: loc.parent?.id ?? null, index: loc.index };
|
|
6824
6964
|
}
|
|
6965
|
+
/** A layer's edge colour as percentages (see `setEdgeColor`). */
|
|
6966
|
+
getEdgeColor(id) {
|
|
6967
|
+
const layer = this.requireEdgeLayer(id);
|
|
6968
|
+
const e = layer.edge ?? {};
|
|
6969
|
+
const pct2 = (v) => Math.round((v ?? 0) * 1e3) / 10;
|
|
6970
|
+
const hex = (c) => "#" + c.map((v) => v.toString(16).padStart(2, "0")).join("");
|
|
6971
|
+
return { layerId: layer.id, decontaminate: pct2(e.decontaminate), despill: pct2(e.despill), ambient: pct2(e.ambient), ambientSource: e.ambientColor ? hex(e.ambientColor) : "auto" };
|
|
6972
|
+
}
|
|
6973
|
+
requireEdgeLayer(id) {
|
|
6974
|
+
const layer = this.getLayer(id);
|
|
6975
|
+
if (!layer) throw new LayerOperationError(`There is no layer "${id}" (see list_layers).`);
|
|
6976
|
+
if (layer.type === "group" || layer.type === "adjustment" || layer.type === "annotation")
|
|
6977
|
+
throw new LayerOperationError(`Edge colour works on pixel, shape, type and smart object layers; "${layer.name || id}" is a ${layer.type} layer.`);
|
|
6978
|
+
return layer;
|
|
6979
|
+
}
|
|
6980
|
+
/** Whether the layer's mask (painted and vector, with feather) has a soft band. */
|
|
6981
|
+
maskHasSoftBand(layer) {
|
|
6982
|
+
const { width, height } = this.naturalSize;
|
|
6983
|
+
const mask = effectiveLayerMask(layer, width, height);
|
|
6984
|
+
if (!mask || mask.enabled === false || mask.source) return false;
|
|
6985
|
+
const ox = layer.type === "pixel" ? layer.offset.x : 0;
|
|
6986
|
+
const oy = layer.type === "pixel" ? layer.offset.y : 0;
|
|
6987
|
+
return hasSoftBand(maskCoverageOverCanvas(mask, ox, oy, width, height));
|
|
6988
|
+
}
|
|
6989
|
+
/**
|
|
6990
|
+
* Edge colour on a layer's soft edge, applied when compositing (nothing is baked); amounts in
|
|
6991
|
+
* percent, 0 turns one off. `decontaminate` unmixes the old background (the pixels the mask
|
|
6992
|
+
* hides) from the edge and `despill` limits its hue there: both need a pixel layer whose mask
|
|
6993
|
+
* (painted or vector) has a soft band. `ambient` pulls the edge toward what is behind the layer
|
|
6994
|
+
* (`ambientColor` null: sampled live) or a fixed `#rrggbb`; it needs a soft mask or soft
|
|
6995
|
+
* pixels at the edge. Validates everything before changing anything; one undo step (none with
|
|
6996
|
+
* `commitToHistory` false, for the later moves of a slider drag). Returns the effective settings.
|
|
6997
|
+
*/
|
|
6998
|
+
setEdgeColor(id, change, commitToHistory = true) {
|
|
6999
|
+
const layer = this.requireEdgeLayer(id);
|
|
7000
|
+
const name = layer.name || id;
|
|
7001
|
+
const next = { ...layer.edge ?? {} };
|
|
7002
|
+
for (const k of ["decontaminate", "despill", "ambient"]) {
|
|
7003
|
+
const v = change[k];
|
|
7004
|
+
if (v === void 0) continue;
|
|
7005
|
+
if (typeof v !== "number" || !Number.isFinite(v) || v < 0 || v > 100) throw new LayerOperationError(`${k} must be a percentage from 0 to 100, got ${String(v)}.`);
|
|
7006
|
+
if (v > 0) next[k] = v / 100;
|
|
7007
|
+
else delete next[k];
|
|
7008
|
+
}
|
|
7009
|
+
if (change.ambientColor !== void 0) {
|
|
7010
|
+
if (change.ambientColor === null || change.ambientColor === "auto") delete next.ambientColor;
|
|
7011
|
+
else {
|
|
7012
|
+
const m = /^#([0-9a-f]{6})$/i.exec(String(change.ambientColor));
|
|
7013
|
+
if (!m) throw new LayerOperationError(`ambient colour must be "auto" or #rrggbb, got "${String(change.ambientColor)}".`);
|
|
7014
|
+
const n = parseInt(m[1], 16);
|
|
7015
|
+
next.ambientColor = [n >> 16 & 255, n >> 8 & 255, n & 255];
|
|
7016
|
+
}
|
|
7017
|
+
}
|
|
7018
|
+
const unmix = (next.decontaminate ?? 0) > 0 || (next.despill ?? 0) > 0;
|
|
7019
|
+
const turningOn = (k) => (change[k] ?? 0) > 0;
|
|
7020
|
+
if (unmix && (turningOn("decontaminate") || turningOn("despill"))) {
|
|
7021
|
+
if (layer.type !== "pixel") throw new LayerOperationError(`Decontamination and despill need a pixel layer with a soft-edged mask; "${name}" is a ${layer.type} layer (rasterize it, or use ambient).`);
|
|
7022
|
+
if (!this.maskHasSoftBand(layer))
|
|
7023
|
+
throw new LayerOperationError(`"${name}" has no mask with a soft edge: decontamination and despill work in the partly hidden band of a layer mask. Add one with add_layer_mask from a refined selection (refine_selection), or feather the mask (set_layer_mask_properties feather).`);
|
|
7024
|
+
}
|
|
7025
|
+
if (turningOn("ambient") && !this.maskHasSoftBand(layer) && layer.type === "pixel") {
|
|
7026
|
+
const px2 = this.getLayerPixels(layer.id);
|
|
7027
|
+
let soft = false;
|
|
7028
|
+
if (px2) for (let i = 3; i < px2.data.length && !soft; i += 4) soft = px2.data[i] > 5 && px2.data[i] < 250;
|
|
7029
|
+
if (!soft) throw new LayerOperationError(`"${name}" has no soft edge (no soft-edged mask and no partly transparent pixels) for the edge cast to work on.`);
|
|
7030
|
+
}
|
|
7031
|
+
const edge = next.decontaminate || next.despill || next.ambient || next.ambientColor ? next : void 0;
|
|
7032
|
+
this.commitLayers(
|
|
7033
|
+
mapLayer(this.state.layers, layer.id, (l) => {
|
|
7034
|
+
const out = { ...l };
|
|
7035
|
+
if (edge) out.edge = edge;
|
|
7036
|
+
else delete out.edge;
|
|
7037
|
+
return out;
|
|
7038
|
+
}),
|
|
7039
|
+
void 0,
|
|
7040
|
+
commitToHistory
|
|
7041
|
+
);
|
|
7042
|
+
return this.getEdgeColor(layer.id);
|
|
7043
|
+
}
|
|
6825
7044
|
/**
|
|
6826
7045
|
* Changes name, visibility, opacity, fill, blend, clipping, locks or (groups) collapsed state, as
|
|
6827
7046
|
* one undo step (`commitToHistory = false` for the moves of a slider drag). Returns the layer.
|
|
@@ -9644,6 +9863,7 @@ var ImageEditorController = class {
|
|
|
9644
9863
|
*/
|
|
9645
9864
|
async refineSelectionTo(params, output = "selection", options = {}) {
|
|
9646
9865
|
const edits = options.edits ?? [];
|
|
9866
|
+
const regions2 = this.checkRegions(options.regions);
|
|
9647
9867
|
const refineMode = options.refineMode ?? "color";
|
|
9648
9868
|
if (!this.state.selection?.bounds && !edits.length) throw new Error("Nothing is selected; select first, or pass edits that paint a selection.");
|
|
9649
9869
|
if (refineMode === "object" && !this.segmentation) {
|
|
@@ -9678,7 +9898,14 @@ var ImageEditorController = class {
|
|
|
9678
9898
|
if (!clipped?.bounds) throw new Error("The segmentation model found no object in the selection; use refineMode color.");
|
|
9679
9899
|
sel = clipped;
|
|
9680
9900
|
}
|
|
9681
|
-
|
|
9901
|
+
let refined = refineSelection(pixels, sel, params, edited.refineBrush);
|
|
9902
|
+
if (regions2) {
|
|
9903
|
+
const data = Uint8Array.from(sel.data);
|
|
9904
|
+
for (const r of regions2) {
|
|
9905
|
+
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);
|
|
9906
|
+
}
|
|
9907
|
+
refined = createSelectionMask(width, height, data);
|
|
9908
|
+
}
|
|
9682
9909
|
if (!refined.bounds) throw new Error("Refining left nothing selected; use a smaller shiftEdge or contrast.");
|
|
9683
9910
|
if (toDocument) {
|
|
9684
9911
|
const document2 = this.documentFromSelection({ layerId, withMask: output === "new-document-with-mask", selection: refined, decontaminate: amount * 100, radius: params.radius });
|
|
@@ -10123,6 +10350,37 @@ var ImageEditorController = class {
|
|
|
10123
10350
|
const report = inspectMaskEdge(mask, image, options);
|
|
10124
10351
|
return { target, ...layer ? { layerId: layer.id } : {}, ...report };
|
|
10125
10352
|
}
|
|
10353
|
+
/** Whole-pixel rectangles inside the canvas, or a useful error; undefined stays undefined. */
|
|
10354
|
+
checkRegions(regions2) {
|
|
10355
|
+
if (regions2 === void 0) return void 0;
|
|
10356
|
+
const { width, height } = this.naturalSize;
|
|
10357
|
+
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.");
|
|
10358
|
+
return regions2.map((r, i) => {
|
|
10359
|
+
const x = Math.max(0, Math.floor(r?.x));
|
|
10360
|
+
const y = Math.max(0, Math.floor(r?.y));
|
|
10361
|
+
const x1 = Math.min(width, Math.ceil(r?.x + r?.width));
|
|
10362
|
+
const y1 = Math.min(height, Math.ceil(r?.y + r?.height));
|
|
10363
|
+
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)}.`);
|
|
10364
|
+
return { x, y, width: x1 - x, height: y1 - y };
|
|
10365
|
+
});
|
|
10366
|
+
}
|
|
10367
|
+
/**
|
|
10368
|
+
* Where the selection is unsure of itself: the stretches of its edge with no image edge under
|
|
10369
|
+
* them, widened into a band (a trimap's unknown), as a mask and regions with bounds and
|
|
10370
|
+
* confidence, least confident first. Measured against the composite. Read-only; an empty
|
|
10371
|
+
* selection has none.
|
|
10372
|
+
*/
|
|
10373
|
+
selectionUncertainty(options = {}) {
|
|
10374
|
+
const { width, height } = this.naturalSize;
|
|
10375
|
+
const sel = this.getSelection();
|
|
10376
|
+
if (!sel?.bounds) return selectionUncertainty(createSelectionMask(width, height), { x: 0, y: 0, width: 0, height: 0, data: new Uint8ClampedArray(0) }, options);
|
|
10377
|
+
const b = sel.bounds;
|
|
10378
|
+
const m = Math.max(8, Math.ceil(options.band ?? Math.max(2, Math.min(width, height) / 200)) + 4);
|
|
10379
|
+
const x = Math.max(0, b.x - m);
|
|
10380
|
+
const y = Math.max(0, b.y - m);
|
|
10381
|
+
const region = { x, y, width: Math.min(width, b.x + b.width + m) - x, height: Math.min(height, b.y + b.height + m) - y };
|
|
10382
|
+
return selectionUncertainty(sel, { ...this.compositePixels(region), x, y }, options);
|
|
10383
|
+
}
|
|
10126
10384
|
/** A layer's painted mask over the canvas as a selection mask; null without one (or for a depth mask). */
|
|
10127
10385
|
layerMaskCoverage(layerId) {
|
|
10128
10386
|
const layer = this.getLayer(layerId);
|
|
@@ -11372,7 +11630,9 @@ var ImageEditorController = class {
|
|
|
11372
11630
|
return baked;
|
|
11373
11631
|
};
|
|
11374
11632
|
const prepare = async (layer) => {
|
|
11633
|
+
if (layer.edge?.ambient) add2("The live edge cast (set_edge_ambient) has no PSD equivalent and was left out", layer.name);
|
|
11375
11634
|
const p = await prepareLayer(layer);
|
|
11635
|
+
if (p?.edge) delete p.edge;
|
|
11376
11636
|
if (p?.blend === "pigment") add2("The Pigment blend mode has no PSD equivalent and was written as Normal", layer.name);
|
|
11377
11637
|
return p;
|
|
11378
11638
|
};
|
|
@@ -11448,6 +11708,14 @@ var ImageEditorController = class {
|
|
|
11448
11708
|
}
|
|
11449
11709
|
if (layer.filters?.some((f) => !f.enabled)) add2("Disabled layer filters were left out", layer.name);
|
|
11450
11710
|
delete out.filters;
|
|
11711
|
+
const unmixMask = hasEdgeUnmix(layer.edge) ? effectiveLayerMask(layer, width, height) : void 0;
|
|
11712
|
+
if (unmixMask && !unmixMask.source && unmixMask.enabled !== false) {
|
|
11713
|
+
add2("Edge decontamination and despill were baked into the pixels", layer.name);
|
|
11714
|
+
const { x: ox, y: oy } = layer.offset;
|
|
11715
|
+
const data = readRegion(out.surface, { x: -ox, y: -oy, width, height });
|
|
11716
|
+
applyEdgeUnmix(data, maskCoverageOverCanvas(unmixMask, ox, oy, width, height), width, height, layer.edge);
|
|
11717
|
+
out = { ...out, surface: surfaceFromPixels({ width, height, data }, 4, -ox, -oy) };
|
|
11718
|
+
}
|
|
11451
11719
|
const mask = bakeMask(layer);
|
|
11452
11720
|
if (mask) out.mask = mask;
|
|
11453
11721
|
else delete out.mask;
|
|
@@ -12825,6 +13093,10 @@ function rectOf(args) {
|
|
|
12825
13093
|
if (rect2.width <= 0 || rect2.height <= 0) throw new Error("width and height must be greater than 0.");
|
|
12826
13094
|
return rect2;
|
|
12827
13095
|
}
|
|
13096
|
+
function uncertainty(controller) {
|
|
13097
|
+
const u = controller.selectionUncertainty();
|
|
13098
|
+
return { uncertainRegions: u.regions, uncertainCount: u.count, edgeConfidence: u.confidence };
|
|
13099
|
+
}
|
|
12828
13100
|
function summary(controller) {
|
|
12829
13101
|
const sel = controller.getSelection();
|
|
12830
13102
|
const { width, height } = controller.getState().imageDimensions;
|
|
@@ -13049,7 +13321,14 @@ function createImageSelectionTools(controller) {
|
|
|
13049
13321
|
additionalProperties: false
|
|
13050
13322
|
}
|
|
13051
13323
|
},
|
|
13052
|
-
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)." }
|
|
13324
|
+
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)." },
|
|
13325
|
+
regions: {
|
|
13326
|
+
type: "array",
|
|
13327
|
+
minItems: 1,
|
|
13328
|
+
maxItems: 50,
|
|
13329
|
+
items: rectSchemaStrict,
|
|
13330
|
+
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."
|
|
13331
|
+
}
|
|
13053
13332
|
},
|
|
13054
13333
|
additionalProperties: false
|
|
13055
13334
|
},
|
|
@@ -13080,7 +13359,8 @@ function createImageSelectionTools(controller) {
|
|
|
13080
13359
|
layerId: typeof params.layerId === "string" ? params.layerId : void 0,
|
|
13081
13360
|
decontaminate: num2(params.decontaminate, 0),
|
|
13082
13361
|
edits,
|
|
13083
|
-
refineMode: params.refineMode === "object" ? "object" : "color"
|
|
13362
|
+
refineMode: params.refineMode === "object" ? "object" : "color",
|
|
13363
|
+
...params.regions !== void 0 ? { regions: params.regions } : {}
|
|
13084
13364
|
}
|
|
13085
13365
|
);
|
|
13086
13366
|
return { ...summary(controller), ...result };
|
|
@@ -13236,7 +13516,7 @@ function createImageSelectionTools(controller) {
|
|
|
13236
13516
|
const quickSelectTool = {
|
|
13237
13517
|
name: "quick_select",
|
|
13238
13518
|
title: "Quick Selection",
|
|
13239
|
-
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.",
|
|
13519
|
+
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).",
|
|
13240
13520
|
inputSchema: {
|
|
13241
13521
|
type: "object",
|
|
13242
13522
|
properties: {
|
|
@@ -13267,13 +13547,13 @@ function createImageSelectionTools(controller) {
|
|
|
13267
13547
|
enhanceEdge: args.enhanceEdge,
|
|
13268
13548
|
signal: context?.signal
|
|
13269
13549
|
});
|
|
13270
|
-
return summary(controller);
|
|
13550
|
+
return { ...summary(controller), ...uncertainty(controller) };
|
|
13271
13551
|
}
|
|
13272
13552
|
};
|
|
13273
13553
|
const selectObjectTool = {
|
|
13274
13554
|
name: "select_object",
|
|
13275
13555
|
title: "Object Selection",
|
|
13276
|
-
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").',
|
|
13556
|
+
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).',
|
|
13277
13557
|
inputSchema: {
|
|
13278
13558
|
type: "object",
|
|
13279
13559
|
properties: {
|
|
@@ -13306,7 +13586,7 @@ function createImageSelectionTools(controller) {
|
|
|
13306
13586
|
signal: context?.signal
|
|
13307
13587
|
};
|
|
13308
13588
|
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);
|
|
13309
|
-
return { ...summary(controller), method };
|
|
13589
|
+
return { ...summary(controller), method, ...uncertainty(controller) };
|
|
13310
13590
|
}
|
|
13311
13591
|
};
|
|
13312
13592
|
const selectFocusAreaTool = {
|
|
@@ -13340,7 +13620,7 @@ function createImageSelectionTools(controller) {
|
|
|
13340
13620
|
const selectSkyTool = {
|
|
13341
13621
|
name: "select_sky",
|
|
13342
13622
|
title: "Select the sky",
|
|
13343
|
-
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).',
|
|
13623
|
+
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).',
|
|
13344
13624
|
inputSchema: {
|
|
13345
13625
|
type: "object",
|
|
13346
13626
|
properties: {
|
|
@@ -13362,7 +13642,7 @@ function createImageSelectionTools(controller) {
|
|
|
13362
13642
|
useModel: args.useModel,
|
|
13363
13643
|
signal: context?.signal
|
|
13364
13644
|
});
|
|
13365
|
-
return { ...summary(controller), method, ...note ? { note } : {} };
|
|
13645
|
+
return { ...summary(controller), method, ...note ? { note } : {}, ...uncertainty(controller) };
|
|
13366
13646
|
}
|
|
13367
13647
|
};
|
|
13368
13648
|
const magicWandTool = {
|
|
@@ -13967,7 +14247,7 @@ function createImageSubjectSelectionTool(controller) {
|
|
|
13967
14247
|
return {
|
|
13968
14248
|
name: "select_subject",
|
|
13969
14249
|
title: "Select a subject",
|
|
13970
|
-
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.',
|
|
14250
|
+
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).',
|
|
13971
14251
|
inputSchema: {
|
|
13972
14252
|
type: "object",
|
|
13973
14253
|
properties: {
|
|
@@ -13998,7 +14278,7 @@ function createImageSubjectSelectionTool(controller) {
|
|
|
13998
14278
|
// Under playback the cursor traces what the model found, as a lasso, before it is selected.
|
|
13999
14279
|
beforeApply: perform ? (outlines) => perform(planOutlineTrace(outlines)) : void 0
|
|
14000
14280
|
});
|
|
14001
|
-
return { ...summary(controller), found: segments, method, ...note ? { note } : {} };
|
|
14281
|
+
return { ...summary(controller), found: segments, method, ...note ? { note } : {}, ...uncertainty(controller) };
|
|
14002
14282
|
}
|
|
14003
14283
|
};
|
|
14004
14284
|
}
|
|
@@ -14809,6 +15089,49 @@ function createImageLayerTools(controller) {
|
|
|
14809
15089
|
return { view: shown?.mode ?? "off", layerId: shown?.layerId ?? null };
|
|
14810
15090
|
}
|
|
14811
15091
|
};
|
|
15092
|
+
const AMOUNT = (what) => ({ type: "number", minimum: 0, maximum: 100, description: `${what}, in percent 0-100; 0 turns it off.` });
|
|
15093
|
+
const EDGE_RETURNS = "Returns the layer's effective edge colour: { layerId, decontaminate, despill, ambient (percent), ambientSource }.";
|
|
15094
|
+
const getEdge = {
|
|
15095
|
+
name: "get_edge_color",
|
|
15096
|
+
title: "Read edge colour",
|
|
15097
|
+
description: `A layer's edge colour settings, applied when compositing: decontaminate, despill and ambient (percent) and ambientSource (auto or #rrggbb). ${EDGE_RETURNS}`,
|
|
15098
|
+
inputSchema: { type: "object", properties: { layerId: LAYER_ID2 }, required: ["layerId"], additionalProperties: false },
|
|
15099
|
+
annotations: { title: "Read edge colour", ...READ2 },
|
|
15100
|
+
handler: ({ layerId }) => controller.getEdgeColor(String(layerId))
|
|
15101
|
+
};
|
|
15102
|
+
const setDecontaminate = {
|
|
15103
|
+
name: "set_edge_decontamination",
|
|
15104
|
+
title: "Decontaminate edge colour",
|
|
15105
|
+
description: `Removes the old background's colour from a cutout's soft edge at composite time (nothing is baked; change or turn it off any time): each partly hidden edge pixel is unmixed from the background next to it (the pixels the layer's mask hides). Needs a pixel layer whose mask (painted or vector) has a soft band, e.g. add_layer_mask from a refined selection. Check with inspect_mask_edge (haloScore) and capture_view. One undo step. ${EDGE_RETURNS}`,
|
|
15106
|
+
inputSchema: { type: "object", properties: { layerId: LAYER_ID2, amount: AMOUNT("How much to replace edge colour with the subject's own (80-100 typical)") }, required: ["layerId", "amount"], additionalProperties: false },
|
|
15107
|
+
annotations: { title: "Decontaminate edge colour", ...WRITE_IDEMPOTENT2 },
|
|
15108
|
+
handler: ({ layerId, amount }) => controller.setEdgeColor(String(layerId), { decontaminate: amount })
|
|
15109
|
+
};
|
|
15110
|
+
const setDespill = {
|
|
15111
|
+
name: "set_edge_despill",
|
|
15112
|
+
title: "Despill edge colour",
|
|
15113
|
+
description: `Limits how much of the old background's hue a cutout's soft edge carries (a green-screen or coloured-wall fringe), at composite time: the edge colour's component toward the background's hue is taken out, luminance kept, most at the outer edge. A heuristic: it does not remove light that bounced onto the subject itself. Needs a pixel layer whose mask has a soft band. Pixels fully inside the mask never change. One undo step. ${EDGE_RETURNS}`,
|
|
15114
|
+
inputSchema: { type: "object", properties: { layerId: LAYER_ID2, amount: AMOUNT("How much of the background hue to take out (50-100 typical)") }, required: ["layerId", "amount"], additionalProperties: false },
|
|
15115
|
+
annotations: { title: "Despill edge colour", ...WRITE_IDEMPOTENT2 },
|
|
15116
|
+
handler: ({ layerId, amount }) => controller.setEdgeColor(String(layerId), { despill: amount })
|
|
15117
|
+
};
|
|
15118
|
+
const setAmbient = {
|
|
15119
|
+
name: "set_edge_ambient",
|
|
15120
|
+
title: "Cast the scene onto an edge",
|
|
15121
|
+
description: `Tints a cutout's soft edge with what is behind it now, live at composite time, so a subject moved onto a new background picks up its colour at the edge (more where the edge is more transparent). source auto samples the layers below as they change; #rrggbb uses a fixed colour. Works on soft-edged masks or partly transparent pixels (pixel, shape, type and smart object layers). One undo step. ${EDGE_RETURNS}`,
|
|
15122
|
+
inputSchema: {
|
|
15123
|
+
type: "object",
|
|
15124
|
+
properties: {
|
|
15125
|
+
layerId: LAYER_ID2,
|
|
15126
|
+
amount: AMOUNT("How far edge colour moves toward the background (20-50 typical)"),
|
|
15127
|
+
source: { type: "string", pattern: "^(auto|#[0-9a-fA-F]{6})$", description: "auto (default): what is behind the layer, sampled live; or a fixed #rrggbb." }
|
|
15128
|
+
},
|
|
15129
|
+
required: ["layerId", "amount"],
|
|
15130
|
+
additionalProperties: false
|
|
15131
|
+
},
|
|
15132
|
+
annotations: { title: "Cast the scene onto an edge", ...WRITE_IDEMPOTENT2 },
|
|
15133
|
+
handler: ({ layerId, amount, source }) => controller.setEdgeColor(String(layerId), { ambient: amount, ambientColor: source ?? "auto" })
|
|
15134
|
+
};
|
|
14812
15135
|
const maskProps = {
|
|
14813
15136
|
name: "set_layer_mask_properties",
|
|
14814
15137
|
title: "Set layer mask properties",
|
|
@@ -14951,6 +15274,10 @@ function createImageLayerTools(controller) {
|
|
|
14951
15274
|
removeMask,
|
|
14952
15275
|
maskView,
|
|
14953
15276
|
maskProps,
|
|
15277
|
+
getEdge,
|
|
15278
|
+
setDecontaminate,
|
|
15279
|
+
setDespill,
|
|
15280
|
+
setAmbient,
|
|
14954
15281
|
viaCopy,
|
|
14955
15282
|
rasterize,
|
|
14956
15283
|
addFilter,
|
|
@@ -21578,6 +21905,7 @@ export {
|
|
|
21578
21905
|
DIRECT_GAUSSIAN_MAX_SIGMA,
|
|
21579
21906
|
DRY_EPS,
|
|
21580
21907
|
DabWalker,
|
|
21908
|
+
EDGE_COLOR_RADIUS,
|
|
21581
21909
|
EFFECT_LAYER_USES,
|
|
21582
21910
|
EMPTY_DISTANCE,
|
|
21583
21911
|
EMPTY_MIXER_LOAD,
|
|
@@ -21820,6 +22148,7 @@ export {
|
|
|
21820
22148
|
applyBoxBlur,
|
|
21821
22149
|
applyBoxBlurFloat,
|
|
21822
22150
|
applyDepthMask,
|
|
22151
|
+
applyEdgeUnmix,
|
|
21823
22152
|
applyHomography,
|
|
21824
22153
|
applyImageProjectionToContext,
|
|
21825
22154
|
applyLegacyAdjustments,
|
|
@@ -21925,6 +22254,7 @@ export {
|
|
|
21925
22254
|
createAdjustmentLayer,
|
|
21926
22255
|
createBlenderScratch,
|
|
21927
22256
|
createCanvasTextShaper,
|
|
22257
|
+
createGradientSampler,
|
|
21928
22258
|
createImageCaptureSurface,
|
|
21929
22259
|
createImageColorTools,
|
|
21930
22260
|
createImageEditTools,
|
|
@@ -21995,6 +22325,7 @@ export {
|
|
|
21995
22325
|
deserializeMask,
|
|
21996
22326
|
deserializePatterns,
|
|
21997
22327
|
deserializeSelection,
|
|
22328
|
+
despillEdge,
|
|
21998
22329
|
developFilter,
|
|
21999
22330
|
developFromAdjustments,
|
|
22000
22331
|
developTarget,
|
|
@@ -22090,8 +22421,10 @@ export {
|
|
|
22090
22421
|
handleQuickMaskKey,
|
|
22091
22422
|
handleSelectionShortcut,
|
|
22092
22423
|
hasActiveEffects,
|
|
22424
|
+
hasEdgeUnmix,
|
|
22093
22425
|
hasLayersBelow,
|
|
22094
22426
|
hasResampledLegacyMask,
|
|
22427
|
+
hasSoftBand,
|
|
22095
22428
|
hasSurface,
|
|
22096
22429
|
hasWarp,
|
|
22097
22430
|
hash2,
|
|
@@ -22221,6 +22554,7 @@ export {
|
|
|
22221
22554
|
mapLayer,
|
|
22222
22555
|
mapSubpaths,
|
|
22223
22556
|
maskBounds,
|
|
22557
|
+
maskCoverageOverCanvas,
|
|
22224
22558
|
maskFileName,
|
|
22225
22559
|
maskNameFromFile,
|
|
22226
22560
|
maskSummary,
|
|
@@ -22363,6 +22697,7 @@ export {
|
|
|
22363
22697
|
rasterizeSmartObject,
|
|
22364
22698
|
readAdjustmentControls,
|
|
22365
22699
|
readDeepRegion,
|
|
22700
|
+
readEdgeColor,
|
|
22366
22701
|
readPaintSource,
|
|
22367
22702
|
readPaths,
|
|
22368
22703
|
readPixel,
|
|
@@ -22447,6 +22782,7 @@ export {
|
|
|
22447
22782
|
selectionToCoverage,
|
|
22448
22783
|
selectionToPixels,
|
|
22449
22784
|
selectionToSubpaths,
|
|
22785
|
+
selectionUncertainty,
|
|
22450
22786
|
selectionValueAt,
|
|
22451
22787
|
serializeChannels,
|
|
22452
22788
|
serializeColorMode,
|