fix(heal): eliminate border seams with smoothstep feather, 95% solid mask, and matched photo grain texture
This commit is contained in:
@@ -284,7 +284,7 @@ export function fastLocalInpaint(
|
||||
}
|
||||
|
||||
// 3. Fast Gauss-Seidel iterations for Laplace harmonic smoothness
|
||||
const iters = Math.min(24, Math.max(12, Math.round(Math.sqrt(holeCount) * 1.2)));
|
||||
const iters = Math.min(48, Math.max(20, Math.round(Math.sqrt(holeCount) * 1.5)));
|
||||
for (let iter = 0; iter < iters; iter++) {
|
||||
for (let y = 0; y < h; y++) {
|
||||
const row = y * w;
|
||||
@@ -304,18 +304,32 @@ export function fastLocalInpaint(
|
||||
}
|
||||
}
|
||||
|
||||
// 4. Sample high-frequency texture noise from boundary to avoid plastic flatness
|
||||
// 4. Sample high-frequency texture noise from surrounding photo to match grain and avoid plastic flatness
|
||||
let noiseVariance = 0;
|
||||
let count = 0;
|
||||
for (let i = 0; i < bLen; i++) {
|
||||
const bp = boundary[i];
|
||||
const idx = bp.y * w + bp.x;
|
||||
const diffR = bp.r - fR[idx];
|
||||
const diffG = bp.g - fG[idx];
|
||||
const diffB = bp.b - fB[idx];
|
||||
noiseVariance += (diffR * diffR + diffG * diffG + diffB * diffB) / 3;
|
||||
const p = (bp.y * w + bp.x) * 4;
|
||||
if (bp.x + 1 < w && !hole[bp.y * w + bp.x + 1]) {
|
||||
const p2 = (bp.y * w + bp.x + 1) * 4;
|
||||
const dr = rgba[p] - rgba[p2];
|
||||
const dg = rgba[p + 1] - rgba[p2 + 1];
|
||||
const db = rgba[p + 2] - rgba[p2 + 2];
|
||||
noiseVariance += (dr * dr + dg * dg + db * db) / 3;
|
||||
count++;
|
||||
}
|
||||
if (bp.y + 1 < h && !hole[(bp.y + 1) * w + bp.x]) {
|
||||
const p2 = ((bp.y + 1) * w + bp.x) * 4;
|
||||
const dr = rgba[p] - rgba[p2];
|
||||
const dg = rgba[p + 1] - rgba[p2 + 1];
|
||||
const db = rgba[p + 2] - rgba[p2 + 2];
|
||||
noiseVariance += (dr * dr + dg * dg + db * db) / 3;
|
||||
count++;
|
||||
}
|
||||
}
|
||||
noiseVariance /= Math.max(1, bLen);
|
||||
const noiseAmp = Math.min(8, Math.sqrt(noiseVariance));
|
||||
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)));
|
||||
|
||||
let seed = (w * 31 + h * 17 + holeCount) | 0;
|
||||
function prng(): number {
|
||||
@@ -323,17 +337,20 @@ export function fastLocalInpaint(
|
||||
return (seed / 0x7fffffff) * 2 - 1;
|
||||
}
|
||||
|
||||
// 5. Output
|
||||
// 5. Output: natural photographic grain matching the surrounding photo
|
||||
for (let y = 0; y < h; y++) {
|
||||
const row = y * w;
|
||||
for (let x = 0; x < w; x++) {
|
||||
const idx = row + x;
|
||||
if (hole[idx]) {
|
||||
const p = idx * 4;
|
||||
const grain = noiseAmp > 0.5 ? prng() * noiseAmp : 0;
|
||||
out[p] = Math.max(0, Math.min(255, Math.round(fR[idx] + grain)));
|
||||
out[p + 1] = Math.max(0, Math.min(255, Math.round(fG[idx] + grain)));
|
||||
out[p + 2] = Math.max(0, Math.min(255, Math.round(fB[idx] + grain)));
|
||||
const lum = prng() * noiseAmp;
|
||||
const gr = lum + prng() * (noiseAmp * 0.2);
|
||||
const gg = lum + prng() * (noiseAmp * 0.2);
|
||||
const gb = lum + prng() * (noiseAmp * 0.2);
|
||||
out[p] = Math.max(0, Math.min(255, Math.round(fR[idx] + gr)));
|
||||
out[p + 1] = Math.max(0, Math.min(255, Math.round(fG[idx] + gg)));
|
||||
out[p + 2] = Math.max(0, Math.min(255, Math.round(fB[idx] + gb)));
|
||||
out[p + 3] = 255;
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user