@pixerate/editor 0.219.1 → 0.220.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-3X244IZ5.js → chunk-A7GJ6BXI.js} +43 -26
- package/dist/{chunk-SRMDF2VN.cjs → chunk-JBBJE76O.cjs} +43 -26
- package/dist/{chunk-S55JFWDA.cjs → chunk-R35YOUP2.cjs} +407 -161
- package/dist/{chunk-F7Z2MKY2.js → chunk-S6KKLFZO.js} +261 -15
- package/dist/image-editor/controller.d.cts +25 -1
- package/dist/image-editor/controller.d.ts +25 -1
- package/dist/image-editor/index.cjs +1257 -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 +148 -16
- package/dist/image-editor/layers/hybrid-mask.d.cts +31 -0
- package/dist/image-editor/layers/hybrid-mask.d.ts +31 -0
- package/dist/image-editor/layers/model.d.cts +17 -0
- package/dist/image-editor/layers/model.d.ts +17 -0
- package/dist/image-editor/layers/serialize.d.cts +2 -0
- package/dist/image-editor/layers/serialize.d.ts +2 -0
- package/dist/image-editor/layers/vector-mask.d.cts +10 -5
- package/dist/image-editor/layers/vector-mask.d.ts +10 -5
- package/dist/image-editor/path-edit.d.cts +4 -0
- package/dist/image-editor/path-edit.d.ts +4 -0
- package/dist/image-editor/selection-worker.cjs +3 -3
- package/dist/image-editor/selection-worker.js +1 -1
- package/dist/paint-editor/index.cjs +6 -6
- package/dist/paint-editor/index.js +1 -1
- package/package.json +1 -1
- package/skills/pixerate-image-editor/SKILL.md +2 -1
- package/skills/pixerate-image-editor/references/paths.md +6 -0
- package/skills/pixerate-image-selection-masking/SKILL.md +4 -4
|
@@ -7,6 +7,7 @@ import {
|
|
|
7
7
|
decodeSelectionRle,
|
|
8
8
|
decontaminateColors,
|
|
9
9
|
encodeSelectionRle,
|
|
10
|
+
guidedFilterColor,
|
|
10
11
|
hashMaskContent,
|
|
11
12
|
resizeSelection
|
|
12
13
|
} from "./chunk-ZNA3IQZF.js";
|
|
@@ -94,6 +95,8 @@ import {
|
|
|
94
95
|
spliceRuns,
|
|
95
96
|
starSubpath,
|
|
96
97
|
styleAt,
|
|
98
|
+
subpathSegment,
|
|
99
|
+
subpathSegmentCount,
|
|
97
100
|
subpathsToBezier,
|
|
98
101
|
textDecorations,
|
|
99
102
|
textFragmentOutline,
|
|
@@ -106,6 +109,7 @@ import {
|
|
|
106
109
|
bezierPathBounds,
|
|
107
110
|
outlineStroke,
|
|
108
111
|
pathBoolean,
|
|
112
|
+
segmentPoint,
|
|
109
113
|
strokeOutlineRings,
|
|
110
114
|
transformBezierPath
|
|
111
115
|
} from "./chunk-WEIK3UYB.js";
|
|
@@ -1669,9 +1673,9 @@ function getProcess2Plan(adjustments) {
|
|
|
1669
1673
|
const luts = [];
|
|
1670
1674
|
const scale = [0, 0, 0];
|
|
1671
1675
|
for (let c = 0; c < 3; c++) {
|
|
1672
|
-
let
|
|
1673
|
-
for (let k = 0; k < 3; k++)
|
|
1674
|
-
const headroom = Math.max(1,
|
|
1676
|
+
let reach2 = 0;
|
|
1677
|
+
for (let k = 0; k < 3; k++) reach2 += Math.max(0, m[c * 3 + k]);
|
|
1678
|
+
const headroom = Math.max(1, reach2);
|
|
1675
1679
|
luts[c] = buildToneLut(contrast, brightness, headroom);
|
|
1676
1680
|
scale[c] = TONE_LUT_SIZE / headroom;
|
|
1677
1681
|
}
|
|
@@ -2946,12 +2950,224 @@ function annotationOfShape(layer) {
|
|
|
2946
2950
|
return out;
|
|
2947
2951
|
}
|
|
2948
2952
|
|
|
2953
|
+
// src/image-editor/layers/hybrid-mask.ts
|
|
2954
|
+
var VECTOR_MASK_EDGE_DEFAULTS = { radius: 4, inner: 4, outer: 8 };
|
|
2955
|
+
var JOIN_SIGMA = 2;
|
|
2956
|
+
function vectorMaskSegments(components) {
|
|
2957
|
+
const out = [];
|
|
2958
|
+
components.forEach((c, ci) => c.subpaths.forEach((sp, si) => {
|
|
2959
|
+
for (let k = 0; k < subpathSegmentCount(sp); k++) out.push({ component: ci, subpath: si, segment: k });
|
|
2960
|
+
}));
|
|
2961
|
+
return out;
|
|
2962
|
+
}
|
|
2963
|
+
function validVectorMaskEdges(edges, components) {
|
|
2964
|
+
if (!edges?.length) return [];
|
|
2965
|
+
return edges.filter((e) => {
|
|
2966
|
+
const sp = components[e.component]?.subpaths[e.subpath];
|
|
2967
|
+
return Boolean(sp) && e.segment >= 0 && e.segment < subpathSegmentCount(sp);
|
|
2968
|
+
});
|
|
2969
|
+
}
|
|
2970
|
+
function reach(e) {
|
|
2971
|
+
return e.mode === "feather" ? 3 * (e.radius ?? VECTOR_MASK_EDGE_DEFAULTS.radius) : Math.max(e.inner ?? VECTOR_MASK_EDGE_DEFAULTS.inner, e.outer ?? VECTOR_MASK_EDGE_DEFAULTS.outer);
|
|
2972
|
+
}
|
|
2973
|
+
function hybridVectorMaskCoverage(vm, hard, width, height, guide) {
|
|
2974
|
+
const edges = validVectorMaskEdges(vm.edges, vm.components).filter((e) => e.mode !== "hard");
|
|
2975
|
+
if (!edges.length) return hard;
|
|
2976
|
+
const classKey = (e) => e.mode === "feather" ? `f:${e.radius ?? VECTOR_MASK_EDGE_DEFAULTS.radius}` : `m:${e.inner ?? VECTOR_MASK_EDGE_DEFAULTS.inner}:${e.outer ?? VECTOR_MASK_EDGE_DEFAULTS.outer}`;
|
|
2977
|
+
const classes = [{ key: "hard", edge: null }];
|
|
2978
|
+
const classOfEdge = /* @__PURE__ */ new Map();
|
|
2979
|
+
for (const e of edges) {
|
|
2980
|
+
const k = classKey(e);
|
|
2981
|
+
if (!classes.some((c) => c.key === k)) classes.push({ key: k, edge: e });
|
|
2982
|
+
classOfEdge.set(`${e.component}:${e.subpath}:${e.segment}`, classes.findIndex((c) => c.key === k));
|
|
2983
|
+
}
|
|
2984
|
+
const maxReach = Math.max(...edges.map(reach)) + 3 * JOIN_SIGMA + 2;
|
|
2985
|
+
const seedX = [];
|
|
2986
|
+
const seedY = [];
|
|
2987
|
+
const seedClass = [];
|
|
2988
|
+
vm.components.forEach(
|
|
2989
|
+
(c, ci) => c.subpaths.forEach((sp, si) => {
|
|
2990
|
+
for (let k = 0; k < subpathSegmentCount(sp); k++) {
|
|
2991
|
+
const seg = subpathSegment(sp, k);
|
|
2992
|
+
let len = 0;
|
|
2993
|
+
for (let i = 1; i < seg.length; i++) len += Math.hypot(seg[i][0] - seg[i - 1][0], seg[i][1] - seg[i - 1][1]);
|
|
2994
|
+
const steps = Math.max(2, Math.ceil(len * 2));
|
|
2995
|
+
const cls2 = classOfEdge.get(`${ci}:${si}:${k}`) ?? 0;
|
|
2996
|
+
for (let s = 0; s <= steps; s++) {
|
|
2997
|
+
const [x, y] = segmentPoint(seg, s / steps);
|
|
2998
|
+
seedX.push(x);
|
|
2999
|
+
seedY.push(y);
|
|
3000
|
+
seedClass.push(cls2);
|
|
3001
|
+
}
|
|
3002
|
+
}
|
|
3003
|
+
})
|
|
3004
|
+
);
|
|
3005
|
+
if (!seedX.length) return hard;
|
|
3006
|
+
let minX = Infinity;
|
|
3007
|
+
let minY = Infinity;
|
|
3008
|
+
let maxX = -Infinity;
|
|
3009
|
+
let maxY = -Infinity;
|
|
3010
|
+
for (let i = 0; i < seedX.length; i++) {
|
|
3011
|
+
if (seedX[i] < minX) minX = seedX[i];
|
|
3012
|
+
if (seedX[i] > maxX) maxX = seedX[i];
|
|
3013
|
+
if (seedY[i] < minY) minY = seedY[i];
|
|
3014
|
+
if (seedY[i] > maxY) maxY = seedY[i];
|
|
3015
|
+
}
|
|
3016
|
+
const x0 = Math.max(0, Math.floor(minX - maxReach));
|
|
3017
|
+
const y0 = Math.max(0, Math.floor(minY - maxReach));
|
|
3018
|
+
const x1 = Math.min(width, Math.ceil(maxX + maxReach) + 1);
|
|
3019
|
+
const y1 = Math.min(height, Math.ceil(maxY + maxReach) + 1);
|
|
3020
|
+
const w = x1 - x0;
|
|
3021
|
+
const h = y1 - y0;
|
|
3022
|
+
if (w < 1 || h < 1) return hard;
|
|
3023
|
+
const n = w * h;
|
|
3024
|
+
const dist = new Float32Array(n).fill(Infinity);
|
|
3025
|
+
const cls = new Int16Array(n);
|
|
3026
|
+
for (let i = 0; i < seedX.length; i++) {
|
|
3027
|
+
const x = Math.round(seedX[i]) - x0;
|
|
3028
|
+
const y = Math.round(seedY[i]) - y0;
|
|
3029
|
+
if (x < 0 || y < 0 || x >= w || y >= h) continue;
|
|
3030
|
+
const k = y * w + x;
|
|
3031
|
+
const d = Math.hypot(seedX[i] - (x + x0), seedY[i] - (y + y0));
|
|
3032
|
+
if (d < dist[k]) {
|
|
3033
|
+
dist[k] = d;
|
|
3034
|
+
cls[k] = seedClass[i];
|
|
3035
|
+
}
|
|
3036
|
+
}
|
|
3037
|
+
const D = Math.SQRT2;
|
|
3038
|
+
const relax = (k, j, step) => {
|
|
3039
|
+
const d = dist[j] + step;
|
|
3040
|
+
if (d < dist[k]) {
|
|
3041
|
+
dist[k] = d;
|
|
3042
|
+
cls[k] = cls[j];
|
|
3043
|
+
}
|
|
3044
|
+
};
|
|
3045
|
+
for (let y = 0; y < h; y++) {
|
|
3046
|
+
for (let x = 0; x < w; x++) {
|
|
3047
|
+
const k = y * w + x;
|
|
3048
|
+
if (x > 0) relax(k, k - 1, 1);
|
|
3049
|
+
if (y > 0) {
|
|
3050
|
+
relax(k, k - w, 1);
|
|
3051
|
+
if (x > 0) relax(k, k - w - 1, D);
|
|
3052
|
+
if (x < w - 1) relax(k, k - w + 1, D);
|
|
3053
|
+
}
|
|
3054
|
+
}
|
|
3055
|
+
}
|
|
3056
|
+
for (let y = h - 1; y >= 0; y--) {
|
|
3057
|
+
for (let x = w - 1; x >= 0; x--) {
|
|
3058
|
+
const k = y * w + x;
|
|
3059
|
+
if (x < w - 1) relax(k, k + 1, 1);
|
|
3060
|
+
if (y < h - 1) {
|
|
3061
|
+
relax(k, k + w, 1);
|
|
3062
|
+
if (x < w - 1) relax(k, k + w + 1, D);
|
|
3063
|
+
if (x > 0) relax(k, k + w - 1, D);
|
|
3064
|
+
}
|
|
3065
|
+
}
|
|
3066
|
+
}
|
|
3067
|
+
const out = Float32Array.from(hard);
|
|
3068
|
+
const pad = maxReach + 3 * JOIN_SIGMA + 2;
|
|
3069
|
+
const T = 128;
|
|
3070
|
+
for (let ty = 0; ty < h; ty += T) {
|
|
3071
|
+
for (let tx = 0; tx < w; tx += T) {
|
|
3072
|
+
const tw = Math.min(T, w - tx);
|
|
3073
|
+
const th = Math.min(T, h - ty);
|
|
3074
|
+
let active = false;
|
|
3075
|
+
for (let y = ty; y < ty + th && !active; y++) for (let x = tx; x < tx + tw; x++) if (dist[y * w + x] <= maxReach) {
|
|
3076
|
+
active = true;
|
|
3077
|
+
break;
|
|
3078
|
+
}
|
|
3079
|
+
if (!active) continue;
|
|
3080
|
+
const px0 = Math.max(0, tx - pad);
|
|
3081
|
+
const py0 = Math.max(0, ty - pad);
|
|
3082
|
+
const px1 = Math.min(w, tx + tw + pad);
|
|
3083
|
+
const py1 = Math.min(h, ty + th + pad);
|
|
3084
|
+
const result = solveTile(px0, py0, px1 - px0, py1 - py0);
|
|
3085
|
+
for (let y = ty; y < ty + th; y++) {
|
|
3086
|
+
for (let x = tx; x < tx + tw; x++) {
|
|
3087
|
+
const k = y * w + x;
|
|
3088
|
+
if (dist[k] > maxReach + 3 * JOIN_SIGMA) continue;
|
|
3089
|
+
const v = result[(y - py0) * (px1 - px0) + (x - px0)];
|
|
3090
|
+
if (!Number.isNaN(v)) out[(y + y0) * width + x + x0] = v;
|
|
3091
|
+
}
|
|
3092
|
+
}
|
|
3093
|
+
}
|
|
3094
|
+
}
|
|
3095
|
+
return out;
|
|
3096
|
+
function solveTile(bx, by, bw, bh) {
|
|
3097
|
+
const m = bw * bh;
|
|
3098
|
+
const at = (x, y) => (by + y) * w + bx + x;
|
|
3099
|
+
const Ht = new Float32Array(m);
|
|
3100
|
+
for (let y = 0; y < bh; y++) for (let x = 0; x < bw; x++) Ht[y * bw + x] = hard[(by + y + y0) * width + bx + x + x0];
|
|
3101
|
+
const total = new Float32Array(m);
|
|
3102
|
+
const blended = new Float32Array(m);
|
|
3103
|
+
let guidePlanes = null;
|
|
3104
|
+
const planes = () => {
|
|
3105
|
+
if (guidePlanes) return guidePlanes;
|
|
3106
|
+
const p = [new Float32Array(m), new Float32Array(m), new Float32Array(m)];
|
|
3107
|
+
for (let y = 0; y < bh; y++) {
|
|
3108
|
+
for (let x = 0; x < bw; x++) {
|
|
3109
|
+
const s = ((by + y + y0) * width + bx + x + x0) * 4;
|
|
3110
|
+
const a = guide[s + 3] / 255;
|
|
3111
|
+
for (let c = 0; c < 3; c++) p[c][y * bw + x] = guide[s + c] / 255 * a;
|
|
3112
|
+
}
|
|
3113
|
+
}
|
|
3114
|
+
return guidePlanes = p;
|
|
3115
|
+
};
|
|
3116
|
+
for (let c = 0; c < classes.length; c++) {
|
|
3117
|
+
let weight = new Float32Array(m);
|
|
3118
|
+
let any = false;
|
|
3119
|
+
for (let y = 0; y < bh; y++) {
|
|
3120
|
+
for (let x = 0; x < bw; x++) {
|
|
3121
|
+
const k = at(x, y);
|
|
3122
|
+
const mine = dist[k] <= maxReach ? cls[k] === c : c === 0;
|
|
3123
|
+
if (mine) {
|
|
3124
|
+
weight[y * bw + x] = 1;
|
|
3125
|
+
any = true;
|
|
3126
|
+
}
|
|
3127
|
+
}
|
|
3128
|
+
}
|
|
3129
|
+
if (!any) continue;
|
|
3130
|
+
weight = gaussianBlur(weight, bw, bh, JOIN_SIGMA, "clamp");
|
|
3131
|
+
const e = classes[c].edge;
|
|
3132
|
+
let cov;
|
|
3133
|
+
if (!e) cov = Ht;
|
|
3134
|
+
else if (e.mode === "feather" || !guide) {
|
|
3135
|
+
const sigma = e.mode === "feather" ? e.radius ?? VECTOR_MASK_EDGE_DEFAULTS.radius : ((e.inner ?? VECTOR_MASK_EDGE_DEFAULTS.inner) + (e.outer ?? VECTOR_MASK_EDGE_DEFAULTS.outer)) / 3;
|
|
3136
|
+
cov = gaussianBlur(Ht, bw, bh, sigma, "clamp");
|
|
3137
|
+
} else {
|
|
3138
|
+
const inner = e.inner ?? VECTOR_MASK_EDGE_DEFAULTS.inner;
|
|
3139
|
+
const outer = e.outer ?? VECTOR_MASK_EDGE_DEFAULTS.outer;
|
|
3140
|
+
const r = Math.max(2, Math.round((inner + outer) / 2));
|
|
3141
|
+
const q2 = guidedFilterColor(planes(), Ht, bw, bh, r);
|
|
3142
|
+
cov = new Float32Array(m);
|
|
3143
|
+
for (let y = 0; y < bh; y++) {
|
|
3144
|
+
for (let x = 0; x < bw; x++) {
|
|
3145
|
+
const i = y * bw + x;
|
|
3146
|
+
const inside = Ht[i] >= 0.5;
|
|
3147
|
+
const d = dist[at(x, y)];
|
|
3148
|
+
const unknown = inside ? d <= inner : d <= outer;
|
|
3149
|
+
cov[i] = unknown ? Math.min(1, Math.max(0, q2[i])) : inside ? 1 : 0;
|
|
3150
|
+
}
|
|
3151
|
+
}
|
|
3152
|
+
}
|
|
3153
|
+
for (let i = 0; i < m; i++) {
|
|
3154
|
+
total[i] += weight[i];
|
|
3155
|
+
blended[i] += weight[i] * cov[i];
|
|
3156
|
+
}
|
|
3157
|
+
}
|
|
3158
|
+
const res = new Float32Array(m);
|
|
3159
|
+
for (let i = 0; i < m; i++) res[i] = total[i] > 0 ? blended[i] / total[i] : NaN;
|
|
3160
|
+
return res;
|
|
3161
|
+
}
|
|
3162
|
+
}
|
|
3163
|
+
|
|
2949
3164
|
// src/image-editor/layers/vector-mask.ts
|
|
2950
3165
|
function isActiveVectorMask(vm) {
|
|
2951
3166
|
return Boolean(vm && vm.enabled !== false);
|
|
2952
3167
|
}
|
|
2953
|
-
function vectorMaskCoverage(vm, width, height) {
|
|
3168
|
+
function vectorMaskCoverage(vm, width, height, guide = null) {
|
|
2954
3169
|
let v = componentsCoverage(vm.components, { x: 0, y: 0, width, height }, vm.fillRule ?? "nonzero", vm.inverted === true);
|
|
3170
|
+
if (vm.edges?.length) v = hybridVectorMaskCoverage(vm, v, width, height, guide);
|
|
2955
3171
|
if ((vm.feather ?? 0) > 0) v = gaussianBlur(v, width, height, vm.feather, "clamp");
|
|
2956
3172
|
const density = vm.density ?? 1;
|
|
2957
3173
|
if (density !== 1) for (let i = 0; i < v.length; i++) v[i] = 1 - density * (1 - v[i]);
|
|
@@ -2970,12 +3186,18 @@ function rasterCoverage(mask, ox, oy, width, height) {
|
|
|
2970
3186
|
return v;
|
|
2971
3187
|
}
|
|
2972
3188
|
var layerOffset = (layer) => layer.type === "pixel" ? layer.offset : { x: 0, y: 0 };
|
|
2973
|
-
function
|
|
3189
|
+
function layerGuide(layer, width, height, surfaceOf) {
|
|
3190
|
+
const surface = surfaceOf?.(layer) ?? layer.surface;
|
|
3191
|
+
if (!surface || surface.channels !== 4) return null;
|
|
3192
|
+
const { x: ox, y: oy } = layerOffset(layer);
|
|
3193
|
+
return readRegion(surface, { x: -ox, y: -oy, width, height });
|
|
3194
|
+
}
|
|
3195
|
+
function effectiveLayerMask(layer, width, height, surfaceOf) {
|
|
2974
3196
|
const vm = layer.vectorMask;
|
|
2975
3197
|
const raster = layer.mask;
|
|
2976
3198
|
if (!isActiveVectorMask(vm) || raster?.source || width < 1 || height < 1) return raster;
|
|
2977
3199
|
const { x: ox, y: oy } = layerOffset(layer);
|
|
2978
|
-
const v = vectorMaskCoverage(vm, width, height);
|
|
3200
|
+
const v = vectorMaskCoverage(vm, width, height, vm.edges?.length ? layerGuide(layer, width, height, surfaceOf) : null);
|
|
2979
3201
|
let outside = outsideValue(vm);
|
|
2980
3202
|
if (raster && raster.enabled !== false) {
|
|
2981
3203
|
const r = rasterCoverage(raster, ox, oy, width, height);
|
|
@@ -2995,17 +3217,17 @@ function hasVectorMask(layers) {
|
|
|
2995
3217
|
}
|
|
2996
3218
|
return false;
|
|
2997
3219
|
}
|
|
2998
|
-
function resolveVectorMasks(layers, width, height) {
|
|
3220
|
+
function resolveVectorMasks(layers, width, height, surfaceOf) {
|
|
2999
3221
|
if (!hasVectorMask(layers)) return layers;
|
|
3000
|
-
return layers.map((l) => resolveLayer(l, width, height));
|
|
3222
|
+
return layers.map((l) => resolveLayer(l, width, height, surfaceOf));
|
|
3001
3223
|
}
|
|
3002
|
-
function resolveLayer(layer, width, height) {
|
|
3224
|
+
function resolveLayer(layer, width, height, surfaceOf) {
|
|
3003
3225
|
let next = layer;
|
|
3004
3226
|
if (layer.type === "group" && hasVectorMask(layer.children)) {
|
|
3005
3227
|
const key2 = `${width}x${height}`;
|
|
3006
3228
|
const hit2 = resolved.get(layer);
|
|
3007
3229
|
if (hit2?.key === key2) return hit2.layer;
|
|
3008
|
-
next = { ...layer, children: layer.children.map((c) => resolveLayer(c, width, height)) };
|
|
3230
|
+
next = { ...layer, children: layer.children.map((c) => resolveLayer(c, width, height, surfaceOf)) };
|
|
3009
3231
|
if (!isActiveVectorMask(layer.vectorMask)) {
|
|
3010
3232
|
resolved.set(layer, { key: key2, layer: next });
|
|
3011
3233
|
return next;
|
|
@@ -3015,7 +3237,7 @@ function resolveLayer(layer, width, height) {
|
|
|
3015
3237
|
const key = `${width}x${height}:v`;
|
|
3016
3238
|
const hit = resolved.get(layer);
|
|
3017
3239
|
if (hit?.key === key) return hit.layer;
|
|
3018
|
-
const mask = effectiveLayerMask(layer, width, height);
|
|
3240
|
+
const mask = effectiveLayerMask(layer, width, height, surfaceOf);
|
|
3019
3241
|
const out = { ...next, mask };
|
|
3020
3242
|
delete out.vectorMask;
|
|
3021
3243
|
if (!mask) delete out.mask;
|
|
@@ -3560,6 +3782,11 @@ function filterReach(filters) {
|
|
|
3560
3782
|
return r;
|
|
3561
3783
|
}
|
|
3562
3784
|
var filterSpecCache = /* @__PURE__ */ new WeakMap();
|
|
3785
|
+
function guideSurfaces(sources) {
|
|
3786
|
+
const of = sources.surfaceOf;
|
|
3787
|
+
if (!of) return void 0;
|
|
3788
|
+
return (l) => l.type === "pixel" || l.type === "smart" ? of(l) : void 0;
|
|
3789
|
+
}
|
|
3563
3790
|
var maskIds = /* @__PURE__ */ new WeakMap();
|
|
3564
3791
|
var nextMaskId = 1;
|
|
3565
3792
|
var edgeSpecCache = /* @__PURE__ */ new WeakMap();
|
|
@@ -3633,13 +3860,13 @@ var LayerCompositor = class extends RasterCompositor {
|
|
|
3633
3860
|
}
|
|
3634
3861
|
// Vector masks reach the engine as part of each layer's raster mask (see `resolveVectorMasks`).
|
|
3635
3862
|
composite(layers, rect, sources, options) {
|
|
3636
|
-
return super.composite(resolveVectorMasks(layers, sources.width, sources.height), rect, sources, options);
|
|
3863
|
+
return super.composite(resolveVectorMasks(layers, sources.width, sources.height, guideSurfaces(sources)), rect, sources, options);
|
|
3637
3864
|
}
|
|
3638
3865
|
compositeTiles(layers, rect, sources, onTile, options) {
|
|
3639
|
-
super.compositeTiles(resolveVectorMasks(layers, sources.width, sources.height), rect, sources, onTile, options);
|
|
3866
|
+
super.compositeTiles(resolveVectorMasks(layers, sources.width, sources.height, guideSurfaces(sources)), rect, sources, onTile, options);
|
|
3640
3867
|
}
|
|
3641
3868
|
tile(layers, tx, ty, sources) {
|
|
3642
|
-
return super.tile(resolveVectorMasks(layers, sources.width, sources.height), tx, ty, sources);
|
|
3869
|
+
return super.tile(resolveVectorMasks(layers, sources.width, sources.height, guideSurfaces(sources)), tx, ty, sources);
|
|
3643
3870
|
}
|
|
3644
3871
|
};
|
|
3645
3872
|
function compositeDeep(layers, rect, sources, depth) {
|
|
@@ -3652,7 +3879,7 @@ function compositeDeep(layers, rect, sources, depth) {
|
|
|
3652
3879
|
return { reach: filterReach(filters), apply: (data, width, height, linear) => runLayerFiltersFloat(data, width, height, filters, linear) };
|
|
3653
3880
|
}
|
|
3654
3881
|
}, depth);
|
|
3655
|
-
return deep.composite(resolveVectorMasks(layers, sources.width, sources.height), rect, sources);
|
|
3882
|
+
return deep.composite(resolveVectorMasks(layers, sources.width, sources.height, guideSurfaces(sources)), rect, sources);
|
|
3656
3883
|
}
|
|
3657
3884
|
|
|
3658
3885
|
// src/image-editor/layers/text.ts
|
|
@@ -5268,6 +5495,7 @@ function serializeVectorMask(vm) {
|
|
|
5268
5495
|
if (vm.linked === false) out.linked = false;
|
|
5269
5496
|
if (vm.density !== void 0 && vm.density !== 1) out.density = vm.density;
|
|
5270
5497
|
if (vm.feather) out.feather = vm.feather;
|
|
5498
|
+
if (vm.edges?.length) out.edges = vm.edges.map((e) => ({ ...e }));
|
|
5271
5499
|
return out;
|
|
5272
5500
|
}
|
|
5273
5501
|
function readShapeComponents(stored) {
|
|
@@ -5290,6 +5518,19 @@ function readVectorMask(r) {
|
|
|
5290
5518
|
if (r.linked === false) vm.linked = false;
|
|
5291
5519
|
if (typeof r.density === "number" && r.density !== 1) vm.density = num2(r.density, 0, 1, 1);
|
|
5292
5520
|
if (typeof r.feather === "number" && r.feather > 0) vm.feather = num2(r.feather, 0, 1e3, 0);
|
|
5521
|
+
if (Array.isArray(r.edges)) {
|
|
5522
|
+
const edges = [];
|
|
5523
|
+
for (const raw of r.edges) {
|
|
5524
|
+
const e = raw;
|
|
5525
|
+
const int = (v) => typeof v === "number" && Number.isInteger(v) && v >= 0 ? v : -1;
|
|
5526
|
+
const at = { component: int(e?.component), subpath: int(e?.subpath), segment: int(e?.segment) };
|
|
5527
|
+
if (at.component < 0 || at.subpath < 0 || at.segment < 0) continue;
|
|
5528
|
+
if (e.mode === "feather") edges.push({ ...at, mode: "feather", radius: num2(e.radius, 0.5, 250, 4) });
|
|
5529
|
+
else if (e.mode === "matte") edges.push({ ...at, mode: "matte", inner: num2(e.inner, 0.5, 200, 4), outer: num2(e.outer, 0.5, 200, 8) });
|
|
5530
|
+
}
|
|
5531
|
+
const valid = validVectorMaskEdges(edges, vm.components);
|
|
5532
|
+
if (valid.length) vm.edges = valid;
|
|
5533
|
+
}
|
|
5293
5534
|
return vm;
|
|
5294
5535
|
}
|
|
5295
5536
|
function serializeMask(mask, hint, ctx) {
|
|
@@ -11646,8 +11887,13 @@ export {
|
|
|
11646
11887
|
shapeFromAnnotation,
|
|
11647
11888
|
annotationBlend,
|
|
11648
11889
|
annotationOfShape,
|
|
11890
|
+
VECTOR_MASK_EDGE_DEFAULTS,
|
|
11891
|
+
vectorMaskSegments,
|
|
11892
|
+
validVectorMaskEdges,
|
|
11893
|
+
hybridVectorMaskCoverage,
|
|
11649
11894
|
isActiveVectorMask,
|
|
11650
11895
|
vectorMaskCoverage,
|
|
11896
|
+
layerGuide,
|
|
11651
11897
|
effectiveLayerMask,
|
|
11652
11898
|
resolveVectorMasks,
|
|
11653
11899
|
offsetVectorMask,
|
|
@@ -10,6 +10,7 @@ import { type SelectionJob, type SelectionJobResult, type SelectionWorkerFactory
|
|
|
10
10
|
import type { SelectionProgress } from './selection/segment/work.cjs';
|
|
11
11
|
import type { FocusAreaOptions } from './selection/segment/focus.cjs';
|
|
12
12
|
import type { SkyOptions } from './selection/segment/sky.cjs';
|
|
13
|
+
import { type VectorMaskEdgeMode } from './layers/hybrid-mask.cjs';
|
|
13
14
|
import { RenderOptions } from './renderer.cjs';
|
|
14
15
|
import { type BlendMode } from './blend.cjs';
|
|
15
16
|
import type { ImageHistogram } from './histogram.cjs';
|
|
@@ -23,7 +24,7 @@ import { type RefineEdit } from './selection/refine-edits.cjs';
|
|
|
23
24
|
import { type LayerMaskProps } from './selection/layer-mask.cjs';
|
|
24
25
|
import { type AlphaChannel } from './selection/channels.cjs';
|
|
25
26
|
import type { AdjustmentLayerMask } from './types.cjs';
|
|
26
|
-
import { type LayerBlendMode, type ImageLayer, type LayerFilter, type LayerTarget, type PixelLayer, type RasterMask, type SmartContents, type SmartFilter, type SmartPlacement, type LiveShape, type ShapeComponent, type ShapeFillRule, type VectorMask, type ShapeOperation, type ShapePaint, type ShapeStroke, type ParagraphStyle, type TextStyle, type TextOrientation, type TypeLayer, type ImageEffectAnimation } from './layers/model.cjs';
|
|
27
|
+
import { type LayerBlendMode, type ImageLayer, type LayerFilter, type LayerTarget, type PixelLayer, type RasterMask, type SmartContents, type SmartFilter, type SmartPlacement, type LiveShape, type ShapeComponent, type ShapeFillRule, type VectorMask, type VectorMaskEdge, type ShapeOperation, type ShapePaint, type ShapeStroke, type ParagraphStyle, type TextStyle, type TextOrientation, type TypeLayer, type ImageEffectAnimation } from './layers/model.cjs';
|
|
27
28
|
import { type TextLayout, type TextShaper } from './layers/text.cjs';
|
|
28
29
|
import type { VectorSubpath } from '../vector/types.cjs';
|
|
29
30
|
import { EffectsSlot } from '../effects/helpers.cjs';
|
|
@@ -345,6 +346,10 @@ export interface VectorMaskInfo {
|
|
|
345
346
|
live: LiveShape | null;
|
|
346
347
|
subpaths: VectorSubpath[];
|
|
347
348
|
}>;
|
|
349
|
+
/** Hybrid mask: the segments with a soft edge mode (the rest are hard). */
|
|
350
|
+
edges: VectorMaskEdge[];
|
|
351
|
+
/** How many segments the path has (`{ component, subpath, segment }` address each). */
|
|
352
|
+
segmentCount: number;
|
|
348
353
|
}
|
|
349
354
|
export interface ShapeGeometryInput {
|
|
350
355
|
shape?: LiveShape | Record<string, unknown>;
|
|
@@ -1028,6 +1033,8 @@ export declare class ImageEditorController {
|
|
|
1028
1033
|
/** Changes whenever the Background's source pixels can have changed. */
|
|
1029
1034
|
private sourceVersion;
|
|
1030
1035
|
/** The pixels a pixel layer, smart object or shape composites (the decoded source for the Background). */
|
|
1036
|
+
/** A layer's real pixels for hybrid vector masks' matte guide (the Background's from the source). */
|
|
1037
|
+
private readonly guideSurfaceOf;
|
|
1031
1038
|
private surfaceOf;
|
|
1032
1039
|
/**
|
|
1033
1040
|
* The source image as a surface (cached): host-supplied sample pixels first, then the decoded
|
|
@@ -1237,6 +1244,23 @@ export declare class ImageEditorController {
|
|
|
1237
1244
|
selectionFromVectorMask(id: string, mode?: SelectionMode, invert?: boolean): boolean;
|
|
1238
1245
|
/** A layer's vector mask with its paths (rounded), or `null` without one. */
|
|
1239
1246
|
getVectorMask(id: string): VectorMaskInfo | null;
|
|
1247
|
+
/**
|
|
1248
|
+
* Hybrid mask: a vector mask segment's edge mode. `hard` (the default) is the path's own sharp
|
|
1249
|
+
* edge; `feather` blurs across it by `radius` px (default 4); `matte` solves a corridor from
|
|
1250
|
+
* `inner` px inside to `outer` px outside (defaults 4 and 8) from the layer's colours, for hair
|
|
1251
|
+
* and fur. Modes blend where they meet. One undo step. Returns the vector mask's info.
|
|
1252
|
+
*/
|
|
1253
|
+
setVectorMaskEdge(id: string, at: {
|
|
1254
|
+
component: number;
|
|
1255
|
+
subpath: number;
|
|
1256
|
+
segment: number;
|
|
1257
|
+
}, mode: VectorMaskEdgeMode, options?: {
|
|
1258
|
+
radius?: number;
|
|
1259
|
+
inner?: number;
|
|
1260
|
+
outer?: number;
|
|
1261
|
+
}, commitToHistory?: boolean): VectorMaskInfo;
|
|
1262
|
+
/** A layer's vector mask over the canvas as a selection mask, hybrid edges, feather and density included. */
|
|
1263
|
+
private vectorMaskSelection;
|
|
1240
1264
|
/** Changes any layer mask's density, feather, enabled or linked flag (pixels kept). Returns the mask. */
|
|
1241
1265
|
setLayerMaskProps(id: string, props: LayerMaskProps, commitToHistory?: boolean): RasterMask;
|
|
1242
1266
|
/**
|
|
@@ -10,6 +10,7 @@ import { type SelectionJob, type SelectionJobResult, type SelectionWorkerFactory
|
|
|
10
10
|
import type { SelectionProgress } from './selection/segment/work.js';
|
|
11
11
|
import type { FocusAreaOptions } from './selection/segment/focus.js';
|
|
12
12
|
import type { SkyOptions } from './selection/segment/sky.js';
|
|
13
|
+
import { type VectorMaskEdgeMode } from './layers/hybrid-mask.js';
|
|
13
14
|
import { RenderOptions } from './renderer.js';
|
|
14
15
|
import { type BlendMode } from './blend.js';
|
|
15
16
|
import type { ImageHistogram } from './histogram.js';
|
|
@@ -23,7 +24,7 @@ import { type RefineEdit } from './selection/refine-edits.js';
|
|
|
23
24
|
import { type LayerMaskProps } from './selection/layer-mask.js';
|
|
24
25
|
import { type AlphaChannel } from './selection/channels.js';
|
|
25
26
|
import type { AdjustmentLayerMask } from './types.js';
|
|
26
|
-
import { type LayerBlendMode, type ImageLayer, type LayerFilter, type LayerTarget, type PixelLayer, type RasterMask, type SmartContents, type SmartFilter, type SmartPlacement, type LiveShape, type ShapeComponent, type ShapeFillRule, type VectorMask, type ShapeOperation, type ShapePaint, type ShapeStroke, type ParagraphStyle, type TextStyle, type TextOrientation, type TypeLayer, type ImageEffectAnimation } from './layers/model.js';
|
|
27
|
+
import { type LayerBlendMode, type ImageLayer, type LayerFilter, type LayerTarget, type PixelLayer, type RasterMask, type SmartContents, type SmartFilter, type SmartPlacement, type LiveShape, type ShapeComponent, type ShapeFillRule, type VectorMask, type VectorMaskEdge, type ShapeOperation, type ShapePaint, type ShapeStroke, type ParagraphStyle, type TextStyle, type TextOrientation, type TypeLayer, type ImageEffectAnimation } from './layers/model.js';
|
|
27
28
|
import { type TextLayout, type TextShaper } from './layers/text.js';
|
|
28
29
|
import type { VectorSubpath } from '../vector/types.js';
|
|
29
30
|
import { EffectsSlot } from '../effects/helpers.js';
|
|
@@ -345,6 +346,10 @@ export interface VectorMaskInfo {
|
|
|
345
346
|
live: LiveShape | null;
|
|
346
347
|
subpaths: VectorSubpath[];
|
|
347
348
|
}>;
|
|
349
|
+
/** Hybrid mask: the segments with a soft edge mode (the rest are hard). */
|
|
350
|
+
edges: VectorMaskEdge[];
|
|
351
|
+
/** How many segments the path has (`{ component, subpath, segment }` address each). */
|
|
352
|
+
segmentCount: number;
|
|
348
353
|
}
|
|
349
354
|
export interface ShapeGeometryInput {
|
|
350
355
|
shape?: LiveShape | Record<string, unknown>;
|
|
@@ -1028,6 +1033,8 @@ export declare class ImageEditorController {
|
|
|
1028
1033
|
/** Changes whenever the Background's source pixels can have changed. */
|
|
1029
1034
|
private sourceVersion;
|
|
1030
1035
|
/** The pixels a pixel layer, smart object or shape composites (the decoded source for the Background). */
|
|
1036
|
+
/** A layer's real pixels for hybrid vector masks' matte guide (the Background's from the source). */
|
|
1037
|
+
private readonly guideSurfaceOf;
|
|
1031
1038
|
private surfaceOf;
|
|
1032
1039
|
/**
|
|
1033
1040
|
* The source image as a surface (cached): host-supplied sample pixels first, then the decoded
|
|
@@ -1237,6 +1244,23 @@ export declare class ImageEditorController {
|
|
|
1237
1244
|
selectionFromVectorMask(id: string, mode?: SelectionMode, invert?: boolean): boolean;
|
|
1238
1245
|
/** A layer's vector mask with its paths (rounded), or `null` without one. */
|
|
1239
1246
|
getVectorMask(id: string): VectorMaskInfo | null;
|
|
1247
|
+
/**
|
|
1248
|
+
* Hybrid mask: a vector mask segment's edge mode. `hard` (the default) is the path's own sharp
|
|
1249
|
+
* edge; `feather` blurs across it by `radius` px (default 4); `matte` solves a corridor from
|
|
1250
|
+
* `inner` px inside to `outer` px outside (defaults 4 and 8) from the layer's colours, for hair
|
|
1251
|
+
* and fur. Modes blend where they meet. One undo step. Returns the vector mask's info.
|
|
1252
|
+
*/
|
|
1253
|
+
setVectorMaskEdge(id: string, at: {
|
|
1254
|
+
component: number;
|
|
1255
|
+
subpath: number;
|
|
1256
|
+
segment: number;
|
|
1257
|
+
}, mode: VectorMaskEdgeMode, options?: {
|
|
1258
|
+
radius?: number;
|
|
1259
|
+
inner?: number;
|
|
1260
|
+
outer?: number;
|
|
1261
|
+
}, commitToHistory?: boolean): VectorMaskInfo;
|
|
1262
|
+
/** A layer's vector mask over the canvas as a selection mask, hybrid edges, feather and density included. */
|
|
1263
|
+
private vectorMaskSelection;
|
|
1240
1264
|
/** Changes any layer mask's density, feather, enabled or linked flag (pixels kept). Returns the mask. */
|
|
1241
1265
|
setLayerMaskProps(id: string, props: LayerMaskProps, commitToHistory?: boolean): RasterMask;
|
|
1242
1266
|
/**
|