fix(heal): eliminate boundary seams by windowing inpaint clusters to 1:1 scale and smoothing feather transition

This commit is contained in:
2026-10-14 17:35:27 +07:00
parent 8df0a03414
commit 4b5e55bc50
4 changed files with 35 additions and 11 deletions
+28 -5
View File
@@ -1326,20 +1326,43 @@ function clusterHealSpots(
}
}
}
// Maximum dimension of a cluster's bounding box so that the inpaint crop
// runs at ~1:1 native resolution (avoiding heavy downscale/upscale blur & edge seams)
const isMob = typeof navigator !== 'undefined' && /iPad|iPhone|iPod|Android|Mobile/i.test(navigator.userAgent || '');
const maxSpan = isMob ? 240 : 380;
const visited = new Uint8Array(n);
const clusters: { x: number; y: number; r: number }[][] = [];
for (let i = 0; i < n; i++) {
if (visited[i]) continue;
visited[i] = 1;
const cluster: { x: number; y: number; r: number }[] = [spots[i]];
let minX = spots[i].x * width;
let maxX = minX;
let minY = spots[i].y * height;
let maxY = minY;
const queue = [i];
while (queue.length > 0) {
const curr = queue.pop()!;
const curr = queue.shift()!;
for (const neighbor of adj[curr]) {
if (!visited[neighbor]) {
visited[neighbor] = 1;
cluster.push(spots[neighbor]);
queue.push(neighbor);
const nx = spots[neighbor].x * width;
const ny = spots[neighbor].y * height;
const newMinX = Math.min(minX, nx);
const newMaxX = Math.max(maxX, nx);
const newMinY = Math.min(minY, ny);
const newMaxY = Math.max(maxY, ny);
if (newMaxX - newMinX <= maxSpan && newMaxY - newMinY <= maxSpan) {
visited[neighbor] = 1;
cluster.push(spots[neighbor]);
minX = newMinX;
maxX = newMaxX;
minY = newMinY;
maxY = newMaxY;
queue.push(neighbor);
}
}
}
}
@@ -1503,7 +1526,7 @@ function clusterHealSpots(
const my1 = Math.max(...cYs) + cWidest;
const margin = Math.max(
16,
Math.min(64, Math.round(Math.max(cWidest * 1.5, 0.25 * Math.max(mx1 - mx0, my1 - my0))))
Math.min(64, Math.round(cWidest * 1.5))
);
const srcLeft = Math.max(boxLeft, Math.floor(mx0 - margin));
const srcTop = Math.max(boxTop, Math.floor(my0 - margin));