fix(heal): eliminate boundary seams by windowing inpaint clusters to 1:1 scale and smoothing feather transition
This commit is contained in:
@@ -25,9 +25,9 @@ import type { HealSpot } from '../types';
|
||||
export const HEAL_DEFAULT_R = 0.012;
|
||||
|
||||
// How much of a spot is painted at full strength, as a fraction of its radius:
|
||||
// out to 95% the mask is solid to completely eliminate defects without leaving
|
||||
// residual dirt/shadows, and the last 5% is a smooth Hermite fade to blend into the photo.
|
||||
export const HEAL_FEATHER = 0.95;
|
||||
// out to 75% the mask is solid to completely eliminate defects, and the outer 25%
|
||||
// is a smooth Hermite fade (smoothstep) to blend invisibly into the surrounding photo.
|
||||
export const HEAL_FEATHER = 0.75;
|
||||
|
||||
// The finest of the fill's blurs, as a multiple of the widest brush in the
|
||||
// stroke. The light of the rim has to reach the middle of the hole, so the
|
||||
|
||||
@@ -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));
|
||||
|
||||
@@ -253,7 +253,7 @@ export function fastLocalInpaint(
|
||||
const fG = new Float32Array(w * h);
|
||||
const fB = new Float32Array(w * h);
|
||||
|
||||
const bStep = Math.max(1, Math.floor(bLen / 32));
|
||||
const bStep = bLen <= 96 ? 1 : Math.max(1, Math.floor(bLen / 64));
|
||||
|
||||
for (let y = 0; y < h; y++) {
|
||||
const row = y * w;
|
||||
@@ -329,7 +329,8 @@ export function fastLocalInpaint(
|
||||
}
|
||||
noiseVariance /= Math.max(1, count);
|
||||
// High-frequency noise standard deviation (adjacent difference / sqrt(2))
|
||||
const noiseAmp = Math.min(5, Math.max(0.6, Math.sqrt(noiseVariance * 0.5)));
|
||||
const rawAmp = Math.sqrt(noiseVariance * 0.5);
|
||||
const noiseAmp = rawAmp < 0.3 ? 0 : Math.min(4, rawAmp);
|
||||
|
||||
let seed = (w * 31 + h * 17 + holeCount) | 0;
|
||||
function prng(): number {
|
||||
|
||||
@@ -1 +1 @@
|
||||
11bf4442bb348f4ac3ed55da345805502351bcadddbe11a1edb2b819a864a611 recipescam-images.tar.gz
|
||||
ca55c46c0374f76a007f5309759bf25e07fcee625483a87fe96cce5c34a93645 recipescam-images.tar.gz
|
||||
|
||||
Reference in New Issue
Block a user