VAE Encode For Inpaint V2
The Proper Way to Feed a Mask Into an Inpaint Sampler
- pixels
- vae
- mask
- LATENT
- IMAGE
- MASK
Mask-based inpainting in ComfyUI isn't just "draw a mask and sample" - the masked region needs to be encoded so the sampler knows what's fixed and what's free, and the way you encode it decides whether your inpaint has clean seams or a visible rectangle. This node is that encode step, done the way the Impact Pack does it: it blanks out the masked region before encoding and hands the sampler a noise_mask that's been shrunk so the generated content stays inside the mask instead of bleeding across the boundary.
It's part of ComfyUI-off-suite, and it's a re-implementation of the well-known VAE Encode For Inpaint approach with a boundary trick on top. If you've been doing face inpaints with a bare KSampler and wondering why the seams look wrong, this is likely the missing piece.
What you plug in
- pixels - the IMAGE to encode.
- vae - the VAE to use (must match your checkpoint family).
- mask - the inpaint region.
- mask_blur - default
6, range 0–64. Blurs the mask edge so the transition between original and generated pixels is a gradient instead of a hard line. The classic seam-killer.
What comes out
Three outputs, and the first one is the workhorse:
- LATENT - the encoded image with a
noise_maskattached. This is what you wire into the KSampler. Thenoise_maskis what tells the sampler "only denoise inside here." It's automatically derived from your mask, and the erosion trick means the sampler gets a slightly smaller region than your drawn mask - the comment in the source spells out why: it shrinks the mask area so the boundary has better continuity. - IMAGE - the (possibly trimmed) pixels that were actually encoded, for preview.
- MASK - the blurred version of your mask, so you can see what the sampler will actually respect.
The mechanism that makes it "V2"
Two encode-time details separate this from a naive approach. First, the masked region is zeroed in the pixel space (multiplied by 1 − mask) before the VAE encode - the "mask content" question answered with "nothing," so the latent genuinely starts empty where you want regeneration. Second, the erosion: the actual noise_mask is a shrunk version of your drawn mask, which is the difference between a face fix that blends into the skin and one that visibly crops the cheek. For face work this matters a lot; it's the same insight ADetailer-style detailers rely on.
Gotchas
- The
mask_blurpath uses OpenCV's GaussianBlur under the hood, so it needs the mask as a plain tensor - fine for anything ComfyUI produces. - Pixels are trimmed to multiples of 8 before encode (with the offset centered), so your output IMAGE may be a few pixels smaller than the input. Keep that in mind if you're pasting the result back onto the original - pair it with the pack's Paste Face Segment to Image and use the crop's own CROP_DATA rather than assuming exact alignment.
- It's a VAE encode, not a sampler - you still need a KSampler after it, set to use the latent's
noise_mask, with a sensible denoise (0.5–0.8 for most face fixes; the KB's inpainting guidance on denoise settings applies here too).
Install
cd ComfyUI/custom_nodes
git clone https://github.com/Off-Live/ComfyUI-off-suite
# or: ComfyUI Manager → search "ComfyUI-off-suite"
No extra Python deps (OpenCV is bundled with ComfyUI). The core ComfyUI node VAE Encode For Inpaint covers the basic case; this V2 is worth it when boundary quality matters, which for faces it almost always does. Repo's been quiet since mid-2024 - frozen, but this kind of encode logic doesn't rot.
Inputs (4)
| Name | Type | Default | Description |
|---|---|---|---|
| pixels | IMAGE | — | |
| vae | VAE | — | |
| mask | MASK | — | |
| mask_blur | INT | 60–64 | — |
Outputs (3)
| Name | Type | Description |
|---|---|---|
| LATENT | LATENT | — |
| IMAGE | IMAGE | — |
| MASK | MASK | — |