fix(heal): the mask owns the whole spot, and the fill is the diffusion, not the model

This commit is contained in:
2026-10-15 23:24:01 +07:00
parent 5830812862
commit 49c5c9c5b9
6 changed files with 100 additions and 472 deletions
+15 -22
View File
@@ -25,9 +25,20 @@ 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 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 mask is solid out to here and gives way in the sliver that is left, which
// is the edge's own anti-aliasing and nothing else.
//
// It was 0.75 — solid to three quarters, faded over the last quarter — and that
// fade is what the brush painted wrong. A spot aimed at something bigger than
// itself (the crown of a tree against flat sky) faded out ON the thing, so the
// thing came back at partial weight: a grey cloud the shape of the brush, and
// the fill showing through it where the mask had already given way. Measured
// (heal-repro2.mjs, one stroke of four spots over the crown, |after - truth|
// over the mask): 34.02 with the fade against 3.29 without; the outer band,
// 0.85r to r, 154.4 against 10.2; and the seam outside the brush 0 either way,
// because the fill is made of the rim's own light and already agrees with the
// photo along the edge — it needs no fade to hide behind.
export const HEAL_FEATHER = 0.995;
// 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
@@ -143,22 +154,4 @@ half4 main(float2 pos) {
// the same fraction of themselves.
export const HEAL_MASK_STOPS: number[] = [0, HEAL_FEATHER, 1];
// The same stops for the model's own pixels, and they are not the mask's. The
// model is the repair wherever it has one, so its shape is the whole spot and
// the last fraction of a radius is the only place it gives way. At
// HEAL_MASK_STOPS instead — the model solid out to 0.75 of the radius and faded
// out by the rim — the band between was the FILL's, and the fill is built from
// the frame's own light over the whole of its box (a stroke a third of the frame
// long is filled by blurs up to sixteen brush radii wide, so what it brings into
// that band is the box's average and not the rim's).
//
// Measured (probe-seam.mjs, one stroke of seven spots across the sky, `d` over
// the spots' own circles between the frame before the repair and after it): the
// band came back 8 to 11 levels below the photo it was made in — a ring, on the
// one line a repair is looked at along. With the model owning the band: 0.71
// levels of mean change over the whole repair, 8.5 at its worst pixel, and the
// edge strength at the rim after the repair 0.84 where the photo's own edges
// around it are 0.5 (1.52 before). Past 0.92 the numbers stop moving (0.73 /
// 8.7 / 0.88 at 0.995), so the model does own the band and what is left there is
// the shape of the spot, not the fill under it.
export const HEAL_PATCH_STOPS: number[] = [0, 0.92, 1];