fix(heal): restore inpaint-web MI-GAN neural model to resolve grey blob bug

This commit is contained in:
2026-10-16 07:36:25 +07:00
parent 5650fe8888
commit 64e6ff2608
6 changed files with 463 additions and 100 deletions
+19 -20
View File
@@ -53,9 +53,10 @@ import {
HEAL_FILL_BLUR,
HEAL_FILL_MAX_LEVELS,
HEAL_MASK_STOPS,
HEAL_PATCH_STOPS,
readHeal,
} from '../../shared/utils/heal';
import { fastLocalInpaint, inpaintCropSize, INPAINT_REF_LONG } from './inpaint';
import { inpaintCrop, inpaintCropSize, INPAINT_REF_LONG } from './inpaint';
import { mosaicSkSL, mosaicUniforms, readMosaic } from '../../shared/utils/mosaic';
import { gradientMaskSkSL, maskUniforms, readMasks, masksHaveSpatial } from '../../shared/utils/gradientMask';
import {
@@ -1619,13 +1620,9 @@ function clusterHealSpots(
fillCanvas.drawImageRect(cutImage, box, onto, own(Skia.Paint()));
}
// The other half of the fill, and the better one: the pixels under the
// brush invented from the pixels around them (engine/inpaint.ts) rather
// than averaged from the rim — the rim's own light, diffused inward, with
// the rim's own grain over it. It is synchronous. It used to be MI-GAN
// over an ONNX runtime here, awaited; that was the lag and the worse
// repair both (heal.ts and inpaint.ts carry the numbers), so the model is
// gone and this is the rung it used to fall back on.
// The other half of the fill, and the better one where it can be had: the
// pixels under the brush invented by MI-GAN from the pixels around them
// (engine/inpaint.ts) rather than averaged from the rim.
//
// One drag is one repair, and a drag that runs the length of the frame is
// solved in crops of its own extent rather than in one downscaled box that
@@ -1654,12 +1651,13 @@ function clusterHealSpots(
const srcH = srcBottom - srcTop;
if (srcW >= 8 && srcH >= 8) {
// The crop's grid comes from its share of the FRAME and not from the
// pixels the frame is rendered at, so the same repair is the same
// repair at every zoom and at the export's own size — see
// The model's grid comes from the crop's share of the FRAME and not
// from the pixels the frame is rendered at, so the same repair is the
// same repair at every zoom and at the export's own size — see
// INPAINT_REF_LONG. It is also finer than it used to be wherever the
// stage is small: a crop a tenth of a 768px preview is 77 pixels, and
// 77 pixels of context invent a coarser repair than a bigger one.
// 77 pixels of context is what the model used to be asked to invent
// from.
const { w: mw, h: mh } = inpaintCropSize(
(srcW / width) * INPAINT_REF_LONG,
(srcH / height) * INPAINT_REF_LONG
@@ -1711,18 +1709,20 @@ function clusterHealSpots(
}
}
}
const patch = fastLocalInpaint(cropPixels, mw, mh, hole);
const patch = await inpaintCrop(cropPixels, mw, mh, hole).catch(() => null);
if (patch) {
const patchSurface = own(createSurface(mw, mh));
if (patchSurface && patchSurface.getCanvas().writePixels(patch, mw, mh, 0, 0)) {
flush(patchSurface);
const patchImage = own(patchSurface.makeImageSnapshot());
if (patchImage) {
// The invented pixels under the spot, and nowhere else at
// all. They are laid through the mask's own stops: they are
// the repair wherever they exist, so they own the whole spot
// and give way only in the sliver the mask's edge fades over,
// exactly as the blur under them does.
// The model's pixels under the spot, and nowhere else at
// all. What shape they are laid through is the join between
// the model's invention and the photo's real texture, and it
// is not the mask's shape: the model is the repair wherever
// it has one, so it owns the whole spot and gives way only in
// the sliver the mask itself fades over (HEAL_PATCH_STOPS,
// and the numbers that put it there are in heal.ts).
const patchSurface = own(createSurface(srcW, srcH));
if (patchSurface) {
const patchCanvas = patchSurface.getCanvas();
@@ -1745,7 +1745,7 @@ function clusterHealSpots(
[cx, cy],
rad,
new Float32Array([1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0]),
HEAL_MASK_STOPS,
HEAL_PATCH_STOPS,
Skia.TileMode.Clamp
)
);
@@ -1781,7 +1781,6 @@ function clusterHealSpots(
}
}
}
flush(fillSurface);
const fillImage = own(fillSurface.makeImageSnapshot());