fix(heal): restore inpaint-web MI-GAN neural model to resolve grey blob bug
This commit is contained in:
@@ -9,10 +9,12 @@
|
||||
// measured here, on synthetic photos whose answer is known, with CanvasKit
|
||||
// running the same two passes exportEngine runs.
|
||||
//
|
||||
// The blur is the fill's only author. MI-GAN used to be a second one, over the
|
||||
// mask, and it was measured (heal-repro2.mjs) as both the slower half and the
|
||||
// worse one on every case it was given, so it is off the render path and out of
|
||||
// the tree; what is asserted about it here is that it stays gone.
|
||||
// The fill now has a second, better author where one can be had — MI-GAN, over
|
||||
// the mask (engine/inpaint.ts) — and this check deliberately measures the other
|
||||
// one: the blur that stands in when the model cannot be had, which is the case
|
||||
// on a machine without the runtime and the case this file can run offline. What
|
||||
// is asserted about the model is only that the pass is still wired in, and with
|
||||
// which polarity.
|
||||
//
|
||||
// node scripts/heal-check.mjs
|
||||
import assert from 'node:assert/strict';
|
||||
@@ -53,13 +55,21 @@ assert.match(engine, /imageShaderChildAt\(fillImage, boxLeft, boxTop\)/, 'the fi
|
||||
// from up and to the left of the spot (the case at the foot of this file).
|
||||
assert.match(engine, /drawImageRect\(cutImage, box, onto, /, 'the fill is drawn at the box\'s frame coordinates, not at the surface\'s own corner');
|
||||
|
||||
// And the model stays off the pass: an `await` here is a drag that stalls for
|
||||
// seconds on every move of the brush, and the crop it is asked for is a ride out
|
||||
// to the CPU and back that the numbers below never needed.
|
||||
assert.doesNotMatch(engine, /inpaintCrop\(/, 'exportEngine is asking the model for the crop again');
|
||||
// The repair is the diffusion fallback itself, called in-line: no model, no
|
||||
// promise, no cache to fill.
|
||||
assert.match(engine, /fastLocalInpaint\(cropPixels, mw, mh, hole\)/, 'the repair is the local diffusion, not a model round-trip');
|
||||
// The model half is wired into the same pass: the masked crop is asked for, the
|
||||
// answer is written into the fill surface over the box, and the mask it is
|
||||
// handed is built from the brush's own circles. What it is NOT is measured
|
||||
// here — a working model is invisible to the numbers below, which are the
|
||||
// fallback's. The polarity is MI-GAN's, 255 KEEP and 0 HOLE, and it is the one
|
||||
// thing that, got backwards, repaints the frame and leaves the hole (measured in
|
||||
// engine/inpaint.ts; the probe is gone).
|
||||
const inpaint = readFileSync(new URL('../src/engine/inpaint.ts', import.meta.url), 'utf8');
|
||||
assert.match(engine, /await inpaintCrop\(cropPixels, mw, mh, hole\)/, 'exportEngine does not ask the model for the masked crop');
|
||||
assert.match(engine, /patchSurface\.getCanvas\(\)\.writePixels\(patch, mw, mh, 0, 0\)/, 'the model answer is never written into the fill surface');
|
||||
assert.match(engine, /new Uint8Array\(mw \* mh\)/, 'the mask handed to the model is not built at the crop\'s own resolution');
|
||||
assert.match(engine, /hole\[row \+ x\] = 1/, 'the mask handed to the model is not the brush\'s own circles');
|
||||
assert.match(inpaint, /fill\(255\)/, 'the model mask no longer starts at KEEP');
|
||||
assert.match(inpaint, /INPAINT_MAX_SIDE/, 'the crop handed to the model is no longer bounded in size');
|
||||
assert.match(inpaint, /inpaintCropSize/, 'nothing tells the renderer the resolution the model will run at');
|
||||
|
||||
// --- the stored spots --------------------------------------------------------
|
||||
// A recipe written by the older brush carries sx/sy. It still loads: those two
|
||||
@@ -299,16 +309,7 @@ for (const p of painted) {
|
||||
const i = p * 4;
|
||||
for (let c = 0; c < 3; c++) worstIn = Math.max(worstIn, Math.abs(made.pixels[i + c] - GREY));
|
||||
}
|
||||
// The bound is 6 and not 4 because the mask now owns the WHOLE spot (heal.ts,
|
||||
// HEAL_FEATHER): the pixels it used to fade over — the outer quarter of a radius
|
||||
// — are painted at full weight now, and this is the one place the fill's own
|
||||
// maths shows. A blur of the cut reads a neighbourhood, so a rim pixel next to
|
||||
// the card (3.2 radii out, and the trap this assertion exists for) comes back
|
||||
// with a share of it: 5 levels of GREY 118. Before the mask reached the rim the
|
||||
// same pixels measured 4, and the same card, on the pixels the fade had already
|
||||
// left out. Either number catches what this is for — a borrowed patch is off by
|
||||
// a hundred, and a spot left unhealed by the dust's own 80.
|
||||
assert.ok(worstIn <= 6, `a painted pixel is off the ground it sits in by ${worstIn} levels (dust left, or a patch borrowed)`);
|
||||
assert.ok(worstIn <= 4, `a painted pixel is off the ground it sits in by ${worstIn} levels (dust left, or a patch borrowed)`);
|
||||
|
||||
// 3. Nothing survives INSIDE the stroke either: neighbouring painted pixels
|
||||
// differ by a level or two, the way the ground does, where a spot solved on
|
||||
|
||||
Reference in New Issue
Block a user