AI Image Inpaint Bleeds Pixels Outside the Mask

You masked a region for inpaint and the model changed pixels outside the mask too — usually a feathering, padding, or full-image-conditioning issue. Tighten mask handling.

You inpainted a small area — say, replacing a coffee mug with a wine glass — and the inpainted result looks great inside the mask but everything outside the mask has subtly shifted. The skin tone is slightly warmer. The wood grain on the table has shifted by 2 pixels. The wall paint hue jumped. This is “inpaint bleed” and it is one of the most frustrating gotchas in diffusion editing because it looks like the model “worked” until you do a pixel-diff and discover the entire image moved. There are several mechanisms that cause this, and only one of them is fixable in the prompt; the rest require pipeline changes.

Common causes

Ordered by frequency.

1. Pipeline is running full-image inpaint, not masked-only inpaint

Many diffusion pipelines have two inpaint modes:

  • Masked-only: only pixels inside the mask are changed; outside is copied byte-for-byte.
  • Whole-image with mask conditioning: the model regenerates the entire image, using the mask as a guidance hint.

The second mode bleeds. It is the default in many SDXL inpainting workflows.

How to spot it: Diff the output and original outside the mask. If any pixel differs, you are in whole-image mode.

2. Mask has feathered / soft edges

A mask with a feathered edge (gaussian blur) tells the model “blend gradually from inside to outside”. The blended region is partially regenerated — which means even pixels you thought were “outside” are subtly changed.

How to spot it: Open the mask file. If the edges are gradient grey, you have feathering. Sharp black/white edges mean no feathering.

3. VAE decode is lossy

Even if the model only touches inside the mask, the encode-decode through the VAE is not bit-identical. The outside region gets a round-trip through the VAE and comes back with quantization noise.

How to spot it: Diff outside pixels. If differences are tiny (within ±2 in 0-255 range) and look like random noise, this is VAE round-trip loss.

4. Inpaint padding extends the mask region

Most inpaint pipelines pad the mask by N pixels and process the padded region for blending. That padding region is “outside the mask” from your perspective but “inside” from the pipeline’s perspective.

How to spot it: Check the inpaint padding parameter (often inpaint_full_res_padding or pad_pixels). If it is 32 or 64, expect 32-64 pixels of “outside” to be touched.

5. CFG-rescale or img2img strength too high

In img2img-based inpaint, denoising_strength near 1.0 effectively does text-to-image with weak conditioning from the original. The model regenerates everything.

How to spot it: The whole output looks “different”, not just the area inside the mask. Drop strength to 0.7 and re-test.

6. SDXL refiner pass touches the whole image

After base inpaint, an SDXL refiner second pass may run on the whole image. The refiner is not mask-aware in some implementations.

How to spot it: Run base-only (refiner disabled). If outside pixels are stable, the refiner is the culprit.

7. Output is re-encoded as JPEG after PNG inpaint

If you exported to JPEG, the JPEG quantization shifts every pixel, not just inside-mask pixels. This is not inpaint bleed; it is JPEG.

How to spot it: Save output as PNG. Re-diff. If outside pixels are now identical, JPEG was the cause.

Before you start

  • Save the original image at full resolution.
  • Save the mask file at the same resolution.
  • Note the inpaint mode your pipeline is using (masked-only vs whole-image).
  • Decide your tolerance: pixel-perfect preservation outside the mask, or visually-indistinguishable (a few units of VAE noise is fine).

Information to collect

  • Inpaint mode flag (inpaint_full_res, mask_mode, whole_image, etc.).
  • Mask file (open it; verify edges are crisp).
  • Mask feathering / blur value.
  • Inpaint padding value.
  • Denoising strength.
  • Whether refiner is enabled.
  • Output file format and any quality setting.

Step-by-step fix

Step 1: Switch to masked-only paste-back mode

In AUTOMATIC1111: enable “Only masked” + “Inpaint at full resolution”.

In ComfyUI: use the “Set Latent Noise Mask” node + a “Paste By Mask” node at the end that restores outside pixels from the original.

In Diffusers:

from PIL import Image
import numpy as np

generated = pipe(prompt, image=img, mask_image=mask).images[0]

# Composite: keep original where mask is black, use generated where white
mask_arr = np.array(mask) / 255.0
orig_arr = np.array(img).astype(np.float32)
gen_arr = np.array(generated).astype(np.float32)
final = (1 - mask_arr[:, :, None]) * orig_arr + mask_arr[:, :, None] * gen_arr
Image.fromarray(final.astype(np.uint8)).save("out.png")

This is a literal paste-back: bit-identical outside the mask.

Step 2: Use a hard-edged mask

If you need soft transitions, paint a hard mask first, then composite with a separate alpha gradient at the very end. Do not let the diffusion pipeline see a soft mask:

# Convert grey mask to hard mask (threshold)
convert mask_soft.png -threshold 50% mask_hard.png

Step 3: Reduce inpaint padding

If padding is bleeding too far:

pipe(
    prompt=prompt,
    image=img,
    mask_image=mask,
    padding_mask_crop=8,  # smaller
)

Or set to 0 for zero blending. A 4-8 pixel padding gives a clean seam without much bleed.

Step 4: Drop denoising strength

For inpaint of a small change (color swap, object swap):

strength = 0.7  # not 0.95

Below 0.5 the inpainted region barely changes. Above 0.85 the bleed risk goes up. 0.7 is the sweet spot for most edits.

Step 5: Disable refiner pass for inpaint

In SDXL workflows:

inpaint_pipe(
    prompt=prompt,
    image=img,
    mask_image=mask,
    use_refiner=False,
)

If you need refinement, run refiner on the inpainted region only, with the mask preserved.

Step 6: Output as PNG and verify pixel-stability

# Diff outside-mask pixels
python -c "
from PIL import Image
import numpy as np
a = np.array(Image.open('orig.png'))
b = np.array(Image.open('out.png'))
m = np.array(Image.open('mask.png'))[:,:,0] > 128
outside_diff = np.abs(a[~m].astype(int) - b[~m].astype(int)).max()
print('Max outside-mask pixel diff:', outside_diff)
"

Expect 0 if paste-back is working. Expect ≤2 with VAE round-trip (acceptable). Anything higher means bleed is still happening.

Step 7: For tiny edits, crop-edit-paste manually

For very small changes (under 256x256), crop the region, inpaint inside the crop, paste back. This sidesteps all whole-image bleed:

crop = img.crop((x, y, x + 256, y + 256))
edited_crop = pipe(prompt, image=crop, mask_image=mask_crop).images[0]
img.paste(edited_crop, (x, y))

For related quality control, see AI image not matching prompt.

Verify

  • Pixel-diff outside the mask should be 0 (paste-back) or ≤2 (VAE round-trip).
  • Visual inspection of the seam: no visible color step at the mask boundary.
  • Run on three different test images. Bleed-free should be reproducible, not coincidence.

Long-term prevention

  • Always use paste-back composition for inpaint. Treat full-image regeneration as a separate mode for stylistic transforms only.
  • Save inpaint workflows as named presets: “tight-edit” (paste-back, hard mask, low padding) vs “blend-edit” (soft mask, large padding) vs “full-redo” (whole-image regenerate).
  • Validate outside-mask pixel diff in CI for production pipelines.
  • Document the mask format you use (8-bit grayscale, alpha, RGBA mask channel) and stick to it.
  • Avoid JPEG output for any image that has been inpainted; always PNG.
  • When edits include lighting changes that genuinely should affect the whole image, opt into whole-image mode explicitly rather than discovering it accidentally.

Common pitfalls

  • Using gaussian-blurred masks because they “look softer in the UI” and not realizing soft edges bleed.
  • Trusting that the model “respects the mask” without ever doing a pixel diff.
  • Cranking denoising strength to 1.0 for inpaint and expecting outside pixels to be preserved.
  • Letting the SDXL refiner run after inpaint without disabling it.
  • Exporting to JPEG and blaming the model for color shifts that are JPEG quantization.
  • Using a default pipeline that has inpaint_full_res_padding=64 and being surprised by 64-pixel bleed.

FAQ

Q: Why does my UI claim “masked only” but I still see bleed?

Check whether the UI is composing with the original image after generation or just visualizing the mask region. Some UIs use “masked only” as a visual hint but still regenerate the whole image. Test with a hard pixel diff.

Q: Is there a way to do mask-aware refining?

Yes. Run the base inpaint, then run the refiner on only the masked crop, then paste back. Sequence: base → crop → refine → paste.

Q: What about face restoration after inpaint? Will it bleed?

Yes. Face restorers (CodeFormer, GFPGAN) ignore masks. Run face restore before inpaint, or skip it for inpaint workflows.

Q: Can I inpaint at a higher resolution than the source image?

You can run inpaint at higher resolution by upscaling the crop, editing, downscaling, and pasting back. The paste-back must downscale to original resolution to avoid an alignment shift at the seam.

Tags: #Troubleshooting #ai-image #inpaint #mask #diffusion