@pixerate/editor 0.212.0 → 0.214.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-5OC22IEO.js → chunk-DJBYJOHM.js} +419 -1
- package/dist/{chunk-3T5VCDIV.cjs → chunk-LCCMOWR7.cjs} +419 -1
- package/dist/image-editor/controller.d.cts +37 -2
- package/dist/image-editor/controller.d.ts +37 -2
- package/dist/image-editor/index.cjs +1618 -585
- package/dist/image-editor/index.d.cts +1 -0
- package/dist/image-editor/index.d.ts +1 -0
- package/dist/image-editor/index.js +1333 -300
- package/dist/image-editor/path-edit.d.cts +97 -0
- package/dist/image-editor/path-edit.d.ts +97 -0
- package/dist/image-editor/paths.d.cts +14 -0
- package/dist/image-editor/paths.d.ts +14 -0
- package/dist/image-editor/renderer.d.cts +3 -0
- package/dist/image-editor/renderer.d.ts +3 -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 +70 -0
- package/dist/image-editor/selection/inspect.d.ts +70 -0
- package/dist/image-editor/tool-groups.d.cts +2 -2
- package/dist/image-editor/tool-groups.d.ts +2 -2
- package/dist/image-editor/types.d.cts +3 -1
- package/dist/image-editor/types.d.ts +3 -1
- package/dist/vector/index.cjs +46 -433
- package/dist/vector/index.js +30 -417
- package/package.json +1 -1
- package/skills/pixerate-image-editor/SKILL.md +3 -0
- package/skills/pixerate-image-selection-masking/SKILL.md +9 -9
package/dist/vector/index.js
CHANGED
|
@@ -10,13 +10,23 @@ import {
|
|
|
10
10
|
} from "../chunk-65MDXWA7.js";
|
|
11
11
|
import {
|
|
12
12
|
VECTOR_TRACE_MAX_PIXELS,
|
|
13
|
+
bendSegment,
|
|
14
|
+
curvatureMoveAnchor,
|
|
15
|
+
cutSubpathAt,
|
|
16
|
+
joinSubpaths,
|
|
13
17
|
loadOpenTypeFont,
|
|
14
18
|
parseOpenTypeFont,
|
|
15
19
|
quantizeVectorTraceColors,
|
|
20
|
+
removeAnchorKeepingShape,
|
|
21
|
+
removeAnchorsKeepingShape,
|
|
22
|
+
removeRedundantPoints,
|
|
23
|
+
smoothSubpath,
|
|
24
|
+
splitSubpathAt,
|
|
25
|
+
subpathPointAt,
|
|
16
26
|
traceField,
|
|
17
27
|
traceVectorImage,
|
|
18
28
|
vectorTraceContours
|
|
19
|
-
} from "../chunk-
|
|
29
|
+
} from "../chunk-DJBYJOHM.js";
|
|
20
30
|
import {
|
|
21
31
|
applyHomography,
|
|
22
32
|
homographyFromPoints,
|
|
@@ -540,7 +550,6 @@ import {
|
|
|
540
550
|
transformBezierPath
|
|
541
551
|
} from "../chunk-WEIK3UYB.js";
|
|
542
552
|
import {
|
|
543
|
-
fitCubicWithTangents,
|
|
544
553
|
fitCubics,
|
|
545
554
|
parseSvgPath
|
|
546
555
|
} from "../chunk-PWBZH3FK.js";
|
|
@@ -1032,8 +1041,8 @@ function vectorBlendStepCount(a, b, options, length) {
|
|
|
1032
1041
|
if (cd > 1 / 255) return Math.min(VECTOR_BLEND_MAX_SMOOTH_STEPS, Math.max(1, Math.ceil(cd * 255 / 2)));
|
|
1033
1042
|
const ba = vectorNodeBounds(a);
|
|
1034
1043
|
const bb = vectorNodeBounds(b);
|
|
1035
|
-
const
|
|
1036
|
-
return Math.min(VECTOR_BLEND_MAX_SMOOTH_STEPS, Math.max(1, Math.ceil(
|
|
1044
|
+
const dist3 = ba && bb ? Math.max(Math.abs(ba.x - bb.x), Math.abs(ba.y - bb.y), Math.abs(ba.x + ba.width - bb.x - bb.width), Math.abs(ba.y + ba.height - bb.y - bb.height)) : length;
|
|
1045
|
+
return Math.min(VECTOR_BLEND_MAX_SMOOTH_STEPS, Math.max(1, Math.ceil(dist3 / 2)));
|
|
1037
1046
|
}
|
|
1038
1047
|
return Math.min(VECTOR_BLEND_MAX_STEPS, Math.max(1, Math.round(Number.isFinite(o.steps) ? o.steps : DEFAULT_VECTOR_BLEND_STEPS)));
|
|
1039
1048
|
}
|
|
@@ -2539,7 +2548,7 @@ function vectorConstraintActions(doc, selection) {
|
|
|
2539
2548
|
const act = (kind, refs) => ({ kind, label: VECTOR_CONSTRAINT_LABELS[kind], refs, dimensional: VECTOR_DIMENSIONAL_KINDS.has(kind) });
|
|
2540
2549
|
const anchors = selection.anchors.filter((r) => located.get(r.nodeId)?.node.subpaths[r.subpath]?.anchors[r.index]);
|
|
2541
2550
|
if (anchors.length) {
|
|
2542
|
-
const
|
|
2551
|
+
const neighbours = (a, b) => {
|
|
2543
2552
|
if (a.nodeId !== b.nodeId || a.subpath !== b.subpath) return null;
|
|
2544
2553
|
const sp = located.get(a.nodeId).node.subpaths[a.subpath];
|
|
2545
2554
|
const n2 = sp.anchors.length;
|
|
@@ -2551,14 +2560,14 @@ function vectorConstraintActions(doc, selection) {
|
|
|
2551
2560
|
const segRef = (r) => ({ ...pointRef(r), segment: true });
|
|
2552
2561
|
if (anchors.length === 1) return [act("fix", [pointRef(anchors[0])]), act("tangent", [pointRef(anchors[0])]), act("smooth", [pointRef(anchors[0])])];
|
|
2553
2562
|
if (anchors.length === 2) {
|
|
2554
|
-
const start =
|
|
2563
|
+
const start = neighbours(anchors[0], anchors[1]);
|
|
2555
2564
|
if (start) return ["horizontal", "vertical", "distance", "fix"].map((k) => act(k, [segRef(start)]));
|
|
2556
2565
|
return ["coincident", "horizontal", "vertical", "distance"].map((k) => act(k, anchors.map(pointRef)));
|
|
2557
2566
|
}
|
|
2558
2567
|
if (anchors.length === 3) {
|
|
2559
2568
|
for (let i = 0; i < 3; i++) {
|
|
2560
2569
|
const others = anchors.filter((_, k) => k !== i);
|
|
2561
|
-
const start =
|
|
2570
|
+
const start = neighbours(others[0], others[1]);
|
|
2562
2571
|
if (start) return [act("coincident", [pointRef(anchors[i]), segRef(start)]), act("distance", [pointRef(anchors[i]), segRef(start)])];
|
|
2563
2572
|
}
|
|
2564
2573
|
return [];
|
|
@@ -2569,8 +2578,8 @@ function vectorConstraintActions(doc, selection) {
|
|
|
2569
2578
|
[0, 2, 1, 3],
|
|
2570
2579
|
[0, 3, 1, 2]
|
|
2571
2580
|
]) {
|
|
2572
|
-
const s1 =
|
|
2573
|
-
const s2 =
|
|
2581
|
+
const s1 = neighbours(anchors[i], anchors[j]);
|
|
2582
|
+
const s2 = neighbours(anchors[k], anchors[l]);
|
|
2574
2583
|
if (s1 && s2) return ["parallel", "perpendicular", "equal", "collinear", "angle"].map((kind) => act(kind, [segRef(s1), segRef(s2)]));
|
|
2575
2584
|
}
|
|
2576
2585
|
}
|
|
@@ -5847,402 +5856,6 @@ function solveOnSegment(seg, base, cond) {
|
|
|
5847
5856
|
return out;
|
|
5848
5857
|
}
|
|
5849
5858
|
|
|
5850
|
-
// src/vector/pathEdit.ts
|
|
5851
|
-
var EPS = 1e-9;
|
|
5852
|
-
var unit2 = (x, y) => {
|
|
5853
|
-
const l = Math.hypot(x, y);
|
|
5854
|
-
return l > 1e-12 && Number.isFinite(l) ? [x / l, y / l] : null;
|
|
5855
|
-
};
|
|
5856
|
-
function cubicOf(sp, i) {
|
|
5857
|
-
const s = subpathSegment(sp, i);
|
|
5858
|
-
if (s.length === 4) return s;
|
|
5859
|
-
const [a, b] = s;
|
|
5860
|
-
return [a, [a[0] + (b[0] - a[0]) / 3, a[1] + (b[1] - a[1]) / 3], [a[0] + 2 * (b[0] - a[0]) / 3, a[1] + 2 * (b[1] - a[1]) / 3], b];
|
|
5861
|
-
}
|
|
5862
|
-
var isLine = (sp, i) => subpathSegment(sp, i).length === 2;
|
|
5863
|
-
function startTangent(c) {
|
|
5864
|
-
for (let k = 1; k < 4; k++) {
|
|
5865
|
-
const u = unit2(c[k][0] - c[0][0], c[k][1] - c[0][1]);
|
|
5866
|
-
if (u) return u;
|
|
5867
|
-
}
|
|
5868
|
-
return [1, 0];
|
|
5869
|
-
}
|
|
5870
|
-
function endTangent(c) {
|
|
5871
|
-
for (let k = 2; k >= 0; k--) {
|
|
5872
|
-
const u = unit2(c[3][0] - c[k][0], c[3][1] - c[k][1]);
|
|
5873
|
-
if (u) return u;
|
|
5874
|
-
}
|
|
5875
|
-
return [1, 0];
|
|
5876
|
-
}
|
|
5877
|
-
function sample(c, n2, out, withStart) {
|
|
5878
|
-
for (let i = withStart ? 0 : 1; i <= n2; i++) out.push(segmentPoint(c, i / n2));
|
|
5879
|
-
}
|
|
5880
|
-
var onAnchor = (a, h) => !h || Math.abs(h.x - a.x) < EPS && Math.abs(h.y - a.y) < EPS;
|
|
5881
|
-
function tidy(a, keepType = true) {
|
|
5882
|
-
const next = cloneAnchor(a);
|
|
5883
|
-
if (onAnchor(next, next.in)) delete next.in;
|
|
5884
|
-
if (onAnchor(next, next.out)) delete next.out;
|
|
5885
|
-
const shown = inferAnchorType(next);
|
|
5886
|
-
const type = keepType && a.type ? a.type === "symmetric" && shown !== "symmetric" ? shown === "corner" ? "corner" : "smooth" : a.type === "smooth" && shown === "corner" ? "corner" : a.type : shown;
|
|
5887
|
-
if (type === "corner") delete next.type;
|
|
5888
|
-
else next.type = type;
|
|
5889
|
-
return next;
|
|
5890
|
-
}
|
|
5891
|
-
function neighbours(sp, i) {
|
|
5892
|
-
const n2 = sp.anchors.length;
|
|
5893
|
-
const wrap2 = sp.closed && n2 > 1;
|
|
5894
|
-
return [i > 0 ? i - 1 : wrap2 ? n2 - 1 : -1, i < n2 - 1 ? i + 1 : wrap2 ? 0 : -1];
|
|
5895
|
-
}
|
|
5896
|
-
function goldenMin(f, a, b) {
|
|
5897
|
-
const R = 0.618033988749895;
|
|
5898
|
-
let x1 = b - R * (b - a);
|
|
5899
|
-
let x2 = a + R * (b - a);
|
|
5900
|
-
let f1 = f(x1);
|
|
5901
|
-
let f2 = f(x2);
|
|
5902
|
-
for (let i = 0; i < 40; i++) {
|
|
5903
|
-
if (f1.error < f2.error || Number.isNaN(f2.error)) {
|
|
5904
|
-
b = x2;
|
|
5905
|
-
x2 = x1;
|
|
5906
|
-
f2 = f1;
|
|
5907
|
-
x1 = b - R * (b - a);
|
|
5908
|
-
f1 = f(x1);
|
|
5909
|
-
} else {
|
|
5910
|
-
a = x1;
|
|
5911
|
-
x1 = x2;
|
|
5912
|
-
f1 = f2;
|
|
5913
|
-
x2 = a + R * (b - a);
|
|
5914
|
-
f2 = f(x2);
|
|
5915
|
-
}
|
|
5916
|
-
}
|
|
5917
|
-
return f1.error <= f2.error ? f1 : f2;
|
|
5918
|
-
}
|
|
5919
|
-
function fitAcross(pts, N, t0, t1) {
|
|
5920
|
-
const fit = (k) => {
|
|
5921
|
-
const u = [];
|
|
5922
|
-
for (let i = 0; i <= N; i++) u.push(k * i / N);
|
|
5923
|
-
for (let i = 1; i <= N; i++) u.push(k + (1 - k) * i / N);
|
|
5924
|
-
return fitCubicWithTangents(pts, u, t0, t1);
|
|
5925
|
-
};
|
|
5926
|
-
return goldenMin(fit, 0, 1);
|
|
5927
|
-
}
|
|
5928
|
-
function removeAnchorKeepingShape(sp, index) {
|
|
5929
|
-
const anchors = sp.anchors.map(cloneAnchor);
|
|
5930
|
-
const n2 = anchors.length;
|
|
5931
|
-
if (index < 0 || index >= n2) return { anchors, closed: sp.closed };
|
|
5932
|
-
if ((sp.closed || index > 0 && index < n2 - 1) && n2 >= 3) {
|
|
5933
|
-
const pi = (index + n2 - 1) % n2;
|
|
5934
|
-
const ni = (index + 1) % n2;
|
|
5935
|
-
if (!(isLine(sp, pi) && isLine(sp, index))) {
|
|
5936
|
-
const left = cubicOf(sp, pi);
|
|
5937
|
-
const right = cubicOf(sp, index);
|
|
5938
|
-
const N = 16;
|
|
5939
|
-
const pts = [];
|
|
5940
|
-
sample(left, N, pts, true);
|
|
5941
|
-
sample(right, N, pts, false);
|
|
5942
|
-
const t0 = startTangent(left);
|
|
5943
|
-
const t1 = endTangent(right);
|
|
5944
|
-
const { curve } = fitAcross(pts, N, t0, t1);
|
|
5945
|
-
if ([curve.p1, curve.p2].every((p) => Number.isFinite(p[0]) && Number.isFinite(p[1]))) {
|
|
5946
|
-
anchors[pi] = tidy({ ...anchors[pi], out: { x: curve.p1[0], y: curve.p1[1] } });
|
|
5947
|
-
anchors[ni] = tidy({ ...anchors[ni], in: { x: curve.p2[0], y: curve.p2[1] } });
|
|
5948
|
-
}
|
|
5949
|
-
}
|
|
5950
|
-
}
|
|
5951
|
-
anchors.splice(index, 1);
|
|
5952
|
-
let closed = sp.closed;
|
|
5953
|
-
if (!closed && anchors.length) {
|
|
5954
|
-
anchors[0] = tidy({ ...anchors[0], in: null });
|
|
5955
|
-
const last = anchors.length - 1;
|
|
5956
|
-
anchors[last] = tidy({ ...anchors[last], out: null });
|
|
5957
|
-
}
|
|
5958
|
-
if (anchors.length < 3) closed = closed && anchors.length === 2 && anchors.some((a) => a.in || a.out);
|
|
5959
|
-
return { anchors, closed };
|
|
5960
|
-
}
|
|
5961
|
-
function removeAnchorsKeepingShape(sp, indices) {
|
|
5962
|
-
const order = [...new Set(indices)].filter((i) => i >= 0 && i < sp.anchors.length).sort((a, b) => b - a);
|
|
5963
|
-
return order.reduce((acc, i) => removeAnchorKeepingShape(acc, i), { anchors: sp.anchors.map(cloneAnchor), closed: sp.closed });
|
|
5964
|
-
}
|
|
5965
|
-
function bendSegment(sp, segment, t, p) {
|
|
5966
|
-
const anchors = sp.anchors.map(cloneAnchor);
|
|
5967
|
-
const n2 = anchors.length;
|
|
5968
|
-
if (segment < 0 || segment >= subpathSegmentCount(sp) || !Number.isFinite(t) || !Number.isFinite(p.x) || !Number.isFinite(p.y)) return { anchors, closed: sp.closed };
|
|
5969
|
-
const tc = Math.min(0.95, Math.max(0.05, t));
|
|
5970
|
-
const ia = segment;
|
|
5971
|
-
const ib = (segment + 1) % n2;
|
|
5972
|
-
const a = anchors[ia];
|
|
5973
|
-
const b = anchors[ib];
|
|
5974
|
-
const k = 3 * tc * (1 - tc);
|
|
5975
|
-
const ox = (p.x - (a.x + (b.x - a.x) * tc)) / k;
|
|
5976
|
-
const oy = (p.y - (a.y + (b.y - a.y) * tc)) / k;
|
|
5977
|
-
const c1 = { x: a.x + (b.x - a.x) / 3 + ox, y: a.y + (b.y - a.y) / 3 + oy };
|
|
5978
|
-
const c2 = { x: b.x + (a.x - b.x) / 3 + ox, y: b.y + (a.y - b.y) / 3 + oy };
|
|
5979
|
-
anchors[ia] = moveAnchorHandle(a, "out", c1);
|
|
5980
|
-
anchors[ib] = moveAnchorHandle(b, "in", c2);
|
|
5981
|
-
return { anchors, closed: sp.closed };
|
|
5982
|
-
}
|
|
5983
|
-
function subpathPointAt(sp, segment, t) {
|
|
5984
|
-
const [x, y] = segmentPoint(subpathSegment(sp, segment), t);
|
|
5985
|
-
return { x, y };
|
|
5986
|
-
}
|
|
5987
|
-
function curvatureMoveAnchor(sp, index, q) {
|
|
5988
|
-
const anchors = sp.anchors.map(cloneAnchor);
|
|
5989
|
-
const a = sp.anchors[index];
|
|
5990
|
-
if (!a) return { anchors, closed: sp.closed };
|
|
5991
|
-
const own = [
|
|
5992
|
-
a.in && !onAnchor(a, a.in) ? { x: a.in.x - a.x, y: a.in.y - a.y } : null,
|
|
5993
|
-
a.out && !onAnchor(a, a.out) ? { x: a.out.x - a.x, y: a.out.y - a.y } : null
|
|
5994
|
-
];
|
|
5995
|
-
const ends = [null, null];
|
|
5996
|
-
neighbours(sp, index).forEach((j, side) => {
|
|
5997
|
-
if (j < 0 || j === index) return;
|
|
5998
|
-
const b = anchors[j];
|
|
5999
|
-
const old = Math.hypot(a.x - b.x, a.y - b.y);
|
|
6000
|
-
const s = old > 1e-9 ? Math.hypot(q.x - b.x, q.y - b.y) / old : 1;
|
|
6001
|
-
const key = side === 0 ? "out" : "in";
|
|
6002
|
-
const h = b[key];
|
|
6003
|
-
if (h) b[key] = { x: b.x + (h.x - b.x) * s, y: b.y + (h.y - b.y) * s };
|
|
6004
|
-
const o = own[side];
|
|
6005
|
-
if (o) own[side] = { x: o.x * s, y: o.y * s };
|
|
6006
|
-
ends[side] = { x: b.x, y: b.y };
|
|
6007
|
-
});
|
|
6008
|
-
const moved = { x: q.x, y: q.y };
|
|
6009
|
-
if (own[0]) moved.in = { x: q.x + own[0].x, y: q.y + own[0].y };
|
|
6010
|
-
if (own[1]) moved.out = { x: q.x + own[1].x, y: q.y + own[1].y };
|
|
6011
|
-
if (a.type) moved.type = a.type;
|
|
6012
|
-
if (a.corner) moved.corner = { ...a.corner };
|
|
6013
|
-
if (a.id) moved.id = a.id;
|
|
6014
|
-
const [prev, next] = ends;
|
|
6015
|
-
if (a.type && a.type !== "corner" && prev && next) {
|
|
6016
|
-
const dx = next.x - prev.x;
|
|
6017
|
-
const dy = next.y - prev.y;
|
|
6018
|
-
const l = Math.hypot(dx, dy);
|
|
6019
|
-
if (l > 1e-9) {
|
|
6020
|
-
const len2 = (h, to) => h ? Math.hypot(h.x, h.y) : Math.hypot(q.x - to.x, q.y - to.y) / 3;
|
|
6021
|
-
let li = len2(own[0], prev);
|
|
6022
|
-
let lo = len2(own[1], next);
|
|
6023
|
-
if (a.type === "symmetric") li = lo = (li + lo) / 2;
|
|
6024
|
-
moved.in = { x: q.x - dx * li / l, y: q.y - dy * li / l };
|
|
6025
|
-
moved.out = { x: q.x + dx * lo / l, y: q.y + dy * lo / l };
|
|
6026
|
-
}
|
|
6027
|
-
}
|
|
6028
|
-
anchors[index] = moved;
|
|
6029
|
-
return { anchors, closed: sp.closed };
|
|
6030
|
-
}
|
|
6031
|
-
function cutSubpathAt(sp, at) {
|
|
6032
|
-
const n2 = sp.anchors.length;
|
|
6033
|
-
const cuts = [...new Set(at)].filter((i) => Number.isInteger(i) && i >= 0 && i < n2 && (sp.closed || i > 0 && i < n2 - 1)).sort((a, b) => a - b);
|
|
6034
|
-
if (!cuts.length || n2 < 2) return [{ anchors: sp.anchors.map(cloneAnchor), closed: sp.closed }];
|
|
6035
|
-
const first = cuts[0];
|
|
6036
|
-
const run = sp.closed ? Array.from({ length: n2 + 1 }, (_, i) => sp.anchors[(first + i) % n2]) : sp.anchors;
|
|
6037
|
-
const cutSet = new Set(sp.closed ? [...cuts.map((c) => (c - first + n2) % n2), n2] : cuts);
|
|
6038
|
-
const pieces = [];
|
|
6039
|
-
let cur = [];
|
|
6040
|
-
run.forEach((a, i) => {
|
|
6041
|
-
if (!cutSet.has(i)) {
|
|
6042
|
-
cur.push(cloneAnchor(a));
|
|
6043
|
-
return;
|
|
6044
|
-
}
|
|
6045
|
-
if (cur.length) {
|
|
6046
|
-
cur.push(tidy({ ...a, out: null }, false));
|
|
6047
|
-
pieces.push({ anchors: cur, closed: false });
|
|
6048
|
-
cur = [];
|
|
6049
|
-
}
|
|
6050
|
-
if (i < n2) cur.push(tidy({ ...a, in: null }, false));
|
|
6051
|
-
});
|
|
6052
|
-
if (cur.length > 1) pieces.push({ anchors: cur, closed: false });
|
|
6053
|
-
return pieces;
|
|
6054
|
-
}
|
|
6055
|
-
function splitSubpathAt(sp, segment, t, endTolerance = 1e-6) {
|
|
6056
|
-
const n2 = sp.anchors.length;
|
|
6057
|
-
if (segment < 0 || segment >= subpathSegmentCount(sp)) return [{ anchors: sp.anchors.map(cloneAnchor), closed: sp.closed }];
|
|
6058
|
-
if (t <= endTolerance) return cutSubpathAt(sp, [segment]);
|
|
6059
|
-
if (t >= 1 - endTolerance) return cutSubpathAt(sp, [(segment + 1) % n2]);
|
|
6060
|
-
const { subpath, index } = insertAnchor(sp, segment, t);
|
|
6061
|
-
return cutSubpathAt(subpath, [index]);
|
|
6062
|
-
}
|
|
6063
|
-
var endOf = (sp, last) => sp.anchors[last ? sp.anchors.length - 1 : 0];
|
|
6064
|
-
var dist2 = (a, b) => Math.hypot(a.x - b.x, a.y - b.y);
|
|
6065
|
-
function reverse(sp) {
|
|
6066
|
-
return {
|
|
6067
|
-
closed: sp.closed,
|
|
6068
|
-
anchors: sp.anchors.slice().reverse().map((a) => {
|
|
6069
|
-
const next = { x: a.x, y: a.y };
|
|
6070
|
-
if (a.out) next.in = { ...a.out };
|
|
6071
|
-
if (a.in) next.out = { ...a.in };
|
|
6072
|
-
if (a.type) next.type = a.type;
|
|
6073
|
-
if (a.corner) next.corner = { ...a.corner };
|
|
6074
|
-
if (a.id) next.id = a.id;
|
|
6075
|
-
return next;
|
|
6076
|
-
})
|
|
6077
|
-
};
|
|
6078
|
-
}
|
|
6079
|
-
function joinSubpaths(subpaths, tolerance) {
|
|
6080
|
-
const closed = [];
|
|
6081
|
-
let open = [];
|
|
6082
|
-
for (const sp of subpaths) {
|
|
6083
|
-
const copy = { anchors: sp.anchors.map(cloneAnchor), closed: sp.closed };
|
|
6084
|
-
if (sp.closed || sp.anchors.length < 2) {
|
|
6085
|
-
if (sp.anchors.length) closed.push(copy);
|
|
6086
|
-
} else open.push(copy);
|
|
6087
|
-
}
|
|
6088
|
-
if (open.length === 1) {
|
|
6089
|
-
const sp = open[0];
|
|
6090
|
-
const first = sp.anchors[0];
|
|
6091
|
-
const last = sp.anchors[sp.anchors.length - 1];
|
|
6092
|
-
if (sp.anchors.length > 2 && dist2(first, last) <= tolerance) {
|
|
6093
|
-
sp.anchors.pop();
|
|
6094
|
-
sp.anchors[0] = tidy({ ...first, in: last.in ?? null }, false);
|
|
6095
|
-
} else {
|
|
6096
|
-
sp.anchors[0] = tidy({ ...first, in: null }, false);
|
|
6097
|
-
sp.anchors[sp.anchors.length - 1] = tidy({ ...sp.anchors[sp.anchors.length - 1], out: null }, false);
|
|
6098
|
-
}
|
|
6099
|
-
sp.closed = true;
|
|
6100
|
-
return [...closed, sp];
|
|
6101
|
-
}
|
|
6102
|
-
while (open.length > 1) {
|
|
6103
|
-
let best = { d: Infinity, i: 0, j: 1, ei: true, ej: false };
|
|
6104
|
-
for (let i = 0; i < open.length; i++) {
|
|
6105
|
-
for (let j = i + 1; j < open.length; j++) {
|
|
6106
|
-
for (const ei of [false, true]) {
|
|
6107
|
-
for (const ej of [false, true]) {
|
|
6108
|
-
const d = dist2(endOf(open[i], ei), endOf(open[j], ej));
|
|
6109
|
-
if (d < best.d) best = { d, i, j, ei, ej };
|
|
6110
|
-
}
|
|
6111
|
-
}
|
|
6112
|
-
}
|
|
6113
|
-
}
|
|
6114
|
-
let a = open[best.i];
|
|
6115
|
-
let b = open[best.j];
|
|
6116
|
-
open = open.filter((_, k) => k !== best.j);
|
|
6117
|
-
if (!best.ei) a = reverse(a);
|
|
6118
|
-
if (best.ej) b = reverse(b);
|
|
6119
|
-
const anchors = a.anchors;
|
|
6120
|
-
if (best.d <= tolerance) {
|
|
6121
|
-
const [head, ...rest] = b.anchors;
|
|
6122
|
-
const l = anchors[anchors.length - 1];
|
|
6123
|
-
anchors[anchors.length - 1] = tidy({ ...l, out: head.out ?? null }, false);
|
|
6124
|
-
anchors.push(...rest);
|
|
6125
|
-
} else {
|
|
6126
|
-
const l = anchors[anchors.length - 1];
|
|
6127
|
-
anchors[anchors.length - 1] = tidy({ ...l, out: null }, false);
|
|
6128
|
-
anchors.push(tidy({ ...b.anchors[0], in: null }, false), ...b.anchors.slice(1));
|
|
6129
|
-
}
|
|
6130
|
-
open[best.i > best.j ? best.i - 1 : best.i] = { anchors, closed: false };
|
|
6131
|
-
}
|
|
6132
|
-
return [...closed, ...open];
|
|
6133
|
-
}
|
|
6134
|
-
function smoothSubpath(sp, amount = 1, only) {
|
|
6135
|
-
const t = Math.min(1, Math.max(0, amount));
|
|
6136
|
-
const orig = sp.anchors;
|
|
6137
|
-
const n2 = orig.length;
|
|
6138
|
-
const anchors = orig.map(cloneAnchor);
|
|
6139
|
-
if (n2 < 3 || t === 0) return { anchors, closed: sp.closed };
|
|
6140
|
-
for (let i = 0; i < n2; i++) {
|
|
6141
|
-
if (!sp.closed && (i === 0 || i === n2 - 1)) continue;
|
|
6142
|
-
if (only && !only.has(i)) continue;
|
|
6143
|
-
const prev = orig[(i + n2 - 1) % n2];
|
|
6144
|
-
const next = orig[(i + 1) % n2];
|
|
6145
|
-
const p = orig[i];
|
|
6146
|
-
const dir = unit2(next.x - prev.x, next.y - prev.y);
|
|
6147
|
-
if (!dir) continue;
|
|
6148
|
-
const li = dist2(p, prev) / 3;
|
|
6149
|
-
const lo = dist2(p, next) / 3;
|
|
6150
|
-
const hin = p.in ?? p;
|
|
6151
|
-
const hout = p.out ?? p;
|
|
6152
|
-
const target = (h, x, y) => ({ x: h.x + (x - h.x) * t, y: h.y + (y - h.y) * t });
|
|
6153
|
-
anchors[i] = tidy(
|
|
6154
|
-
{
|
|
6155
|
-
x: p.x,
|
|
6156
|
-
y: p.y,
|
|
6157
|
-
in: target(hin, p.x - dir[0] * li, p.y - dir[1] * li),
|
|
6158
|
-
out: target(hout, p.x + dir[0] * lo, p.y + dir[1] * lo)
|
|
6159
|
-
},
|
|
6160
|
-
false
|
|
6161
|
-
);
|
|
6162
|
-
}
|
|
6163
|
-
return { anchors, closed: sp.closed };
|
|
6164
|
-
}
|
|
6165
|
-
function segsOf(sp) {
|
|
6166
|
-
const out = [];
|
|
6167
|
-
for (let i = 0; i < subpathSegmentCount(sp); i++) {
|
|
6168
|
-
const s = subpathSegment(sp, i);
|
|
6169
|
-
out.push(s.length === 2 ? { c: s, line: true } : { c: s, line: false });
|
|
6170
|
-
}
|
|
6171
|
-
return out;
|
|
6172
|
-
}
|
|
6173
|
-
function segsToSubpath(segs, closed, types) {
|
|
6174
|
-
if (!segs.length) return null;
|
|
6175
|
-
const anchors = [];
|
|
6176
|
-
const at = (p) => ({ x: p[0], y: p[1] });
|
|
6177
|
-
anchors.push(at(segs[0].c[0]));
|
|
6178
|
-
for (const s of segs) {
|
|
6179
|
-
const end = s.c[s.c.length - 1];
|
|
6180
|
-
if (!s.line) anchors[anchors.length - 1].out = { x: s.c[1][0], y: s.c[1][1] };
|
|
6181
|
-
const next = at(end);
|
|
6182
|
-
if (!s.line) next.in = { x: s.c[2][0], y: s.c[2][1] };
|
|
6183
|
-
anchors.push(next);
|
|
6184
|
-
}
|
|
6185
|
-
if (closed) {
|
|
6186
|
-
const last = anchors.pop();
|
|
6187
|
-
if (last.in) anchors[0].in = last.in;
|
|
6188
|
-
}
|
|
6189
|
-
const typed2 = anchors.map((a) => {
|
|
6190
|
-
const type = types.get(`${a.x},${a.y}`);
|
|
6191
|
-
return tidy(type ? { ...a, type } : a, !!type);
|
|
6192
|
-
});
|
|
6193
|
-
return { anchors: typed2, closed };
|
|
6194
|
-
}
|
|
6195
|
-
var isZeroSeg = (s) => s.c.every((p) => Math.abs(p[0] - s.c[0][0]) < 1e-9 && Math.abs(p[1] - s.c[0][1]) < 1e-9);
|
|
6196
|
-
function mergePair(a, b, tol) {
|
|
6197
|
-
const a0 = a.c[0];
|
|
6198
|
-
const a3 = a.c[a.c.length - 1];
|
|
6199
|
-
const b3 = b.c[b.c.length - 1];
|
|
6200
|
-
if (a.line && b.line) {
|
|
6201
|
-
const cx = b3[0] - a0[0];
|
|
6202
|
-
const cy = b3[1] - a0[1];
|
|
6203
|
-
const len2 = Math.hypot(cx, cy);
|
|
6204
|
-
if (len2 < 1e-12) return null;
|
|
6205
|
-
const mx = a3[0] - a0[0];
|
|
6206
|
-
const my = a3[1] - a0[1];
|
|
6207
|
-
const along = (mx * cx + my * cy) / len2;
|
|
6208
|
-
return Math.abs((mx * cy - my * cx) / len2) <= tol && along >= 0 && along <= len2 ? { c: [a0, b3], line: true } : null;
|
|
6209
|
-
}
|
|
6210
|
-
const ca = a.line ? [a0, a0, a3, a3] : a.c;
|
|
6211
|
-
const cb = b.line ? [a3, a3, b3, b3] : b.c;
|
|
6212
|
-
const ta = endTangent(ca);
|
|
6213
|
-
const tb = startTangent(cb);
|
|
6214
|
-
if (ta[0] * tb[0] + ta[1] * tb[1] < 0.99) return null;
|
|
6215
|
-
const pts = [];
|
|
6216
|
-
sample(ca, 16, pts, true);
|
|
6217
|
-
sample(cb, 16, pts, false);
|
|
6218
|
-
const { curve, error } = fitAcross(pts, 16, startTangent(ca), endTangent(cb));
|
|
6219
|
-
return error <= tol ? { c: [curve.p0, curve.p1, curve.p2, curve.p3], line: false } : null;
|
|
6220
|
-
}
|
|
6221
|
-
function removeRedundantPoints(sp, tolerance) {
|
|
6222
|
-
const tol = Math.max(tolerance, 1e-9);
|
|
6223
|
-
const types = new Map(sp.anchors.filter((a) => a.type).map((a) => [`${a.x},${a.y}`, a.type]));
|
|
6224
|
-
const segs = segsOf(sp).filter((s) => !isZeroSeg(s));
|
|
6225
|
-
if (!segs.length) return { anchors: sp.anchors.map(cloneAnchor), closed: sp.closed };
|
|
6226
|
-
const minSegs = sp.closed ? 2 : 1;
|
|
6227
|
-
let changed = true;
|
|
6228
|
-
while (changed && segs.length > minSegs) {
|
|
6229
|
-
changed = false;
|
|
6230
|
-
const n2 = segs.length;
|
|
6231
|
-
const joints = sp.closed ? n2 : n2 - 1;
|
|
6232
|
-
for (let j = 0; j < joints; j++) {
|
|
6233
|
-
const [i0, i1] = sp.closed ? [(j + n2 - 1) % n2, j] : [j, j + 1];
|
|
6234
|
-
const m = mergePair(segs[i0], segs[i1], tol);
|
|
6235
|
-
if (!m) continue;
|
|
6236
|
-
segs[i0] = m;
|
|
6237
|
-
segs.splice(i1, 1);
|
|
6238
|
-
if (sp.closed && i1 === 0) segs.unshift(segs.pop());
|
|
6239
|
-
changed = true;
|
|
6240
|
-
break;
|
|
6241
|
-
}
|
|
6242
|
-
}
|
|
6243
|
-
return segsToSubpath(segs, sp.closed, types) ?? { anchors: sp.anchors.map(cloneAnchor), closed: sp.closed };
|
|
6244
|
-
}
|
|
6245
|
-
|
|
6246
5859
|
// src/vector/ink.ts
|
|
6247
5860
|
var LazyMouse = class {
|
|
6248
5861
|
/** `length` is the string length in the units of the points (0 turns smoothing off). */
|
|
@@ -6297,7 +5910,7 @@ function vectorInkShapeName(shape) {
|
|
|
6297
5910
|
return shape.points.length === 3 ? "triangle" : "polygon";
|
|
6298
5911
|
}
|
|
6299
5912
|
}
|
|
6300
|
-
var
|
|
5913
|
+
var dist2 = (a, b) => Math.hypot(b[0] - a[0], b[1] - a[1]);
|
|
6301
5914
|
function bbox(pts) {
|
|
6302
5915
|
if (!pts.length) return [0, 0, 0, 0];
|
|
6303
5916
|
let [x0, y0] = pts[0];
|
|
@@ -6315,7 +5928,7 @@ function segDistance(a, b, p) {
|
|
|
6315
5928
|
const dy = b[1] - a[1];
|
|
6316
5929
|
const len2 = dx * dx + dy * dy;
|
|
6317
5930
|
const t = len2 > 0 ? Math.min(1, Math.max(0, ((p[0] - a[0]) * dx + (p[1] - a[1]) * dy) / len2)) : 0;
|
|
6318
|
-
return
|
|
5931
|
+
return dist2([a[0] + t * dx, a[1] + t * dy], p);
|
|
6319
5932
|
}
|
|
6320
5933
|
function polygonDistance(c, p) {
|
|
6321
5934
|
let best = Infinity;
|
|
@@ -6377,7 +5990,7 @@ function corners(p, diag) {
|
|
|
6377
5990
|
let far = 0;
|
|
6378
5991
|
let farD = 0;
|
|
6379
5992
|
p.forEach((q, i) => {
|
|
6380
|
-
const d =
|
|
5993
|
+
const d = dist2(start, q);
|
|
6381
5994
|
if (d > farD) {
|
|
6382
5995
|
farD = d;
|
|
6383
5996
|
far = i;
|
|
@@ -6404,7 +6017,7 @@ function corners(p, diag) {
|
|
|
6404
6017
|
}
|
|
6405
6018
|
let short = -1;
|
|
6406
6019
|
for (let j = 0; j < n2; j++) {
|
|
6407
|
-
if (
|
|
6020
|
+
if (dist2(ring[j], ring[(j + 1) % n2]) < 0.1 * diag) {
|
|
6408
6021
|
short = j;
|
|
6409
6022
|
break;
|
|
6410
6023
|
}
|
|
@@ -6444,8 +6057,8 @@ function recognizeInkShape(points, options = {}) {
|
|
|
6444
6057
|
const diag = Math.hypot(w, h);
|
|
6445
6058
|
if (size < minSize) return null;
|
|
6446
6059
|
let length = 0;
|
|
6447
|
-
for (let i = 1; i < p.length; i++) length +=
|
|
6448
|
-
const gap =
|
|
6060
|
+
for (let i = 1; i < p.length; i++) length += dist2(p[i - 1], p[i]);
|
|
6061
|
+
const gap = dist2(first, last);
|
|
6449
6062
|
if (gap > 0.85 * length && gap >= minSize) {
|
|
6450
6063
|
const off = Math.max(...p.map((q) => segDistance(first, last, q)));
|
|
6451
6064
|
if (off <= Math.max(0.06 * gap, 1.5 * minSize / 6)) return { kind: "line", a: first, b: last };
|
|
@@ -10687,7 +10300,7 @@ var VectorEditorController = class _VectorEditorController {
|
|
|
10687
10300
|
if (input.radius !== void 0 && !(Number.isFinite(input.radius) && input.radius >= 0)) return { ok: false, message: "radius must be zero or more." };
|
|
10688
10301
|
if (input.gap !== void 0 && !(Number.isFinite(input.gap) && input.gap >= 0)) return { ok: false, message: "gap must be zero or more." };
|
|
10689
10302
|
const point = (e) => glued(e) ? { x: 0, y: 0 } : { x: e.x, y: e.y };
|
|
10690
|
-
const
|
|
10303
|
+
const endOf = (e) => glued(e) ? { nodeId: e.nodeId, ...e.side && e.side !== "auto" ? { side: e.side } : {} } : null;
|
|
10691
10304
|
const id = this.newId();
|
|
10692
10305
|
const style = this.styleFor(true);
|
|
10693
10306
|
const node = {
|
|
@@ -10700,8 +10313,8 @@ var VectorEditorController = class _VectorEditorController {
|
|
|
10700
10313
|
...input.startArrow !== void 0 ? { startArrow: input.startArrow } : {},
|
|
10701
10314
|
endArrow: input.endArrow !== void 0 ? input.endArrow : style.endArrow ?? "triangle",
|
|
10702
10315
|
connector: {
|
|
10703
|
-
start:
|
|
10704
|
-
end:
|
|
10316
|
+
start: endOf(input.start),
|
|
10317
|
+
end: endOf(input.end),
|
|
10705
10318
|
...route !== "straight" ? { route } : {},
|
|
10706
10319
|
...input.radius ? { radius: input.radius } : {},
|
|
10707
10320
|
...input.gap !== void 0 ? { gap: input.gap } : {}
|
|
@@ -10787,14 +10400,14 @@ var VectorEditorController = class _VectorEditorController {
|
|
|
10787
10400
|
}
|
|
10788
10401
|
}
|
|
10789
10402
|
const set = new Set(ids);
|
|
10790
|
-
const
|
|
10403
|
+
const endOf = (e) => e ? { nodeId: e.nodeId, ...e.side && e.side !== "auto" ? { side: e.side } : {} } : null;
|
|
10791
10404
|
this.change("Connector", () => {
|
|
10792
10405
|
this.setDoc(
|
|
10793
10406
|
mapTree(this.doc.nodes, (n2) => {
|
|
10794
10407
|
if (!set.has(n2.id) || n2.type !== "path") return n2;
|
|
10795
10408
|
const c = { ...n2.connector ?? {} };
|
|
10796
|
-
if (patch.start !== void 0) c.start =
|
|
10797
|
-
if (patch.end !== void 0) c.end =
|
|
10409
|
+
if (patch.start !== void 0) c.start = endOf(patch.start);
|
|
10410
|
+
if (patch.end !== void 0) c.end = endOf(patch.end);
|
|
10798
10411
|
if (patch.route !== void 0) {
|
|
10799
10412
|
if (patch.route === "straight") delete c.route;
|
|
10800
10413
|
else c.route = patch.route;
|
package/package.json
CHANGED
|
@@ -103,6 +103,8 @@ Live per-layer effects (shadows, glows, bevel, overlays, strokes): see `referenc
|
|
|
103
103
|
1. **Keep outlines as paths**: `make_work_path` turns the selection into the Work Path (the next
|
|
104
104
|
one replaces it; `rename_path` saves it), `add_path` adds one from geometry.
|
|
105
105
|
2. **Use them**: `path_to_selection` / `add_vector_mask` with `pathId`, `fill_path`, `stroke_path`.
|
|
106
|
+
3. **Edit anchors** as the Pen and Direct Selection tools do: `get_path` → change anchors and handles
|
|
107
|
+
→ `set_path_geometry` (one undo step); shapes `set_shape_component`, vector masks `set_vector_mask_path`.
|
|
106
108
|
|
|
107
109
|
### Painting
|
|
108
110
|
|
|
@@ -208,6 +210,7 @@ in the source frame at the document's bit depth, colour mode and profile. Read
|
|
|
208
210
|
| `get_adjustments` | Read the current state: `values` by control id, `process` version (1 or 2), and `layers` bottom to top with id, kind, params, opacity, blend, enabled and mask. Call first, and to find layer ids. |
|
|
209
211
|
| `list_controls` | Get every base control (id, label, section, min, max, step, default, `requiresProcess`), each layer kind's settable controls (plus `opacity`), the blend mode ids and the `layers.<layerId>.<controlId>` path format. Call once per session before writing. |
|
|
210
212
|
| `get_selection` | Read the active selection: `empty`, `bounds` (natural pixels), `area` in pixels, `percent` of the image and a description. Check it after every select or modify step and before masking: a wand that covered 90% of the image is the wrong selection. |
|
|
213
|
+
| `inspect_mask_edge` | Measure an edge without looking: the selection (default) or a layer's `layer-mask` / `vector-mask` (`layerId`). Returns `haloScore`, `edgeAlignment`, `jaggedness`, `softFraction`, `holes`, `islands`, `worst` regions for `capture_view` and `issues` with fixes. Inspect, fix, inspect again (see `pixerate-image-selection-masking`). |
|
|
211
214
|
| `list_channels` | Read the alpha channels (saved selections): id, name, bounds, percent, overlay and visibility, and whether Quick Mask mode is on. |
|
|
212
215
|
| `select_subject` | Select › Subject. With a host segmentation model, `target` describes what to select ("the dog", "the sky") and the model's outlines are selected (`maxSegments` 1 for one). Without one, the subject that stands out most is found classically; `target` can't choose it (`method: "saliency"`, `note`). Combined by `mode`. Returns the selection, `found` (label, bounds) and `method`; nothing found leaves the selection unchanged. |
|
|
213
216
|
| `list_layers` | Read the layer stack bottom to top as a tree: id, type (`pixel`, `adjustment`, `group`, `shape`, `annotation`, `smart`; smart objects add their `contents`, `smartFilters` and `filterMask`; shapes a `shape` summary of component ops and kinds and paint types, and `annotation` when migrated from one), name, document `bounds` of pixel layers, visibility, opacity, `fillOpacity`, blend, `clipped`, locks, a `mask` summary, an `effects` summary (master switch, scale, effect ids and kinds), pixel-layer `filters` (the Background's `develop` filter is the base adjustments), adjustment `kind` and `params`, group `children`, and which layer is `active`. Call it to understand a document with more than the Background before changing it; adjustment layer ids work with every adjustment-layer tool. |
|
|
@@ -44,10 +44,10 @@ This skill is the method. `pixerate-image-editor` has the full tool reference.
|
|
|
44
44
|
what leaked, and `intersect` to limit it to a region (for example `select_rect` around the
|
|
45
45
|
subject).
|
|
46
46
|
**Check:** `bounds` hug the subject. A bounds as large as the image means a leak.
|
|
47
|
-
3. **
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
47
|
+
3. **Measure the edge, then look.** `inspect_mask_edge` scores it: `haloScore` ≥ 0.35 is old
|
|
48
|
+
background inside the edge, `edgeAlignment` < 0.5 is off the image's edges, `jaggedness` ≥ 15
|
|
49
|
+
zigzags; it counts `holes` and `islands`, `issues` names fixes, `worst` gives `capture_view` rects.
|
|
50
|
+
**Check:** `issues` is empty, or each one is intended (a deliberately soft edge or a second piece).
|
|
51
51
|
4. **Clean it up.**
|
|
52
52
|
- `modify_selection` `smooth` (2–4 px) removes wand speckle.
|
|
53
53
|
- `grow_selection` (tolerance 10–30) picks up the shades a wand click missed;
|
|
@@ -89,7 +89,8 @@ This skill is the method. `pixerate-image-editor` has the full tool reference.
|
|
|
89
89
|
writes the selection, a channel or a layer mask as a greyscale PNG; a matte that comes
|
|
90
90
|
back goes into a channel with `load_channel_from_image` (or straight to the selection with
|
|
91
91
|
`load_selection_from_image`).
|
|
92
|
-
7. **Verify.** `
|
|
92
|
+
7. **Verify.** `inspect_mask_edge` again (`target: "layer-mask"` and `layerId` for a mask),
|
|
93
|
+
`render_preview` at 1024 px or more, and `capture_view` on `worst` regions at 2–8×.
|
|
93
94
|
**Check:** no fringe of the old background colour, no stair-steps, no visible line where an
|
|
94
95
|
adjustment ends. To judge a layer mask itself, `set_mask_view` `gray` (or `overlay`) and
|
|
95
96
|
`capture_view` with `includeOverlays: true`; holes and stray grey show plainly there. Set it
|
|
@@ -220,8 +221,7 @@ This skill is the method. `pixerate-image-editor` has the full tool reference.
|
|
|
220
221
|
- `select_layer_transparency`: a layer's own pixels
|
|
221
222
|
- `grow_selection`, `select_similar`: add similar colour, connected or anywhere
|
|
222
223
|
- `reselect`: undo a deselect
|
|
223
|
-
- `save_selection`, `load_selection_from_channel`, `list_channels`: keep selections as alpha channels
|
|
224
|
-
- `set_channel_properties`, `duplicate_channel`, `delete_channel`: manage channels
|
|
224
|
+
- `save_selection`, `load_selection_from_channel`, `list_channels`, `set_channel_properties`, `duplicate_channel`, `delete_channel`: keep and manage selections as alpha channels
|
|
225
225
|
- `export_mask_image`: a selection, channel or layer mask as a greyscale PNG
|
|
226
226
|
- `load_channel_from_image`: a mask image into a new channel
|
|
227
227
|
- `set_quick_mask`: paint the selection
|
|
@@ -240,8 +240,8 @@ This skill is the method. `pixerate-image-editor` has the full tool reference.
|
|
|
240
240
|
- `modify_selection`: expand, contract, feather, border, smooth
|
|
241
241
|
- `refine_selection`: snap the edge to the image
|
|
242
242
|
- `transform_selection`: move or scale the outline only
|
|
243
|
-
- `
|
|
244
|
-
- `render_preview`: check the result
|
|
243
|
+
- `inspect_mask_edge`: measured halo, alignment, jaggedness, softness, holes and islands of an edge
|
|
244
|
+
- `capture_view`, `render_preview`: look at the edge at pixel level; check the result
|
|
245
245
|
- `apply_selection_as_mask`: a masked adjustment layer in one step
|
|
246
246
|
- `add_layer_mask`, `set_layer_mask_properties`, `apply_layer_mask`, `remove_layer_mask`: layer masks
|
|
247
247
|
- `set_smart_filter_mask`: limit smart filters
|