AI Image Inpaint Changes Pixels Outside the Mask

You masked one region for inpaint and pixels outside the mask shifted too. Force a paste-back composite (apply_overlay), use a hard mask, and verify with a pixel diff.

You inpainted a small area — say, swapping a coffee mug for a wine glass — and the patch inside the mask looks great, but everything outside it has subtly shifted. The skin tone reads a touch warmer. The wood grain on the table moved by a pixel or two. The wall paint hue jumped. This is “inpaint bleed,” and it is one of the nastiest gotchas in diffusion editing because the result looks like it worked until you run a pixel diff and discover the whole image moved.

Fastest fix: composite the result back over the original so only mask pixels can change. In Diffusers that is one line — pipeline.image_processor.apply_overlay(mask, init_image, result). In AUTOMATIC1111/Forge it is Inpaint area: Only masked. In ComfyUI it is a paste-by-mask node at the end of the graph. Then prove it with a pixel diff (Step 6). Everything below is for when that is not enough or you need to know why it happened.

Why this happens (and which one is yours)

The most common surprise, confirmed in the official Diffusers inpainting docs (as of June 2026): inpaint-specific checkpoints are intentionally trained to make a natural transition between masked and unmasked areas, which means they are “more likely to change your unmasked area” than a plain checkpoint. The model is doing its job; the pipeline just is not protecting the outside region. Use this table to find your bucket.

Symptom on a pixel diffLikely causeFix
Large, structured changes everywhereWhole-image regeneration, no paste-backStep 1
A soft band of change hugging the mask edgeFeathered / blurred maskStep 2
Changes 8-64 px outside the mask, sharp bandInpaint padding / crop marginStep 3
Whole image looks “redrawn”strength / denoise too highStep 4
Outside is stable until a second pass runsSDXL refiner not mask-awareStep 5
Tiny random noise everywhere (within +/- 2 of 0-255)VAE round-trip (harmless)accept, or Step 1
Everything shifted after exportRe-saved as JPEG, not bleedStep 6, save PNG

Common causes

Ordered by frequency.

1. Pipeline runs whole-image inpaint, not masked-only paste-back

Many pipelines have two behaviors:

  • Masked-only (paste-back): only pixels inside the mask change; outside is copied byte-for-byte from the original.
  • Whole-image with mask conditioning: the model regenerates the entire image and uses the mask only as a guidance hint.

The second one bleeds, and it is the effective default in many SDXL inpaint workflows because inpaint checkpoints smooth the seam by touching the surroundings.

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

2. Mask has feathered / soft edges

A feathered mask (gaussian blur) tells the model to blend gradually from inside to outside. That blend band is partially regenerated, so pixels you thought were “outside” are quietly changed. In Diffusers this is the blur_factor on mask_processor.blur(); in AUTOMATIC1111/Forge it is the Mask blur slider (default 4).

How to spot it: open the mask file. Gradient-grey edges mean feathering; sharp black/white edges mean none.

3. Inpaint padding / crop margin extends the worked region

Most pipelines expand the mask by N pixels and process that margin to blend the seam. That margin is “outside the mask” to you but “inside” to the pipeline. The parameter names differ by tool: Diffusers padding_mask_crop, AUTOMATIC1111/Forge Only masked padding, pixels, ComfyUI grow_mask_by on the VAE Encode (for Inpainting) node.

How to spot it: check that value. If it is 32 or 64, expect 32-64 pixels of “outside” to be touched.

4. strength / denoising strength too high

In img2img-based inpaint, strength (called Denoising strength in AUTOMATIC1111/Forge) near 1.0 is effectively text-to-image with weak conditioning from the original, so the model regenerates everything. A value of 0 returns the original unchanged; 1 ignores the source image entirely.

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

5. SDXL refiner pass touches the whole image

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

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

6. VAE decode is lossy

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

How to spot it: diff outside pixels. If differences are tiny (within +/- 2 on a 0-255 scale) and look like random noise, this is VAE round-trip loss, not bleed. A paste-back (Step 1) eliminates it entirely.

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

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

How to spot it: save the output as PNG and 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 which inpaint mode your pipeline uses (masked-only vs whole-image).
  • Decide your tolerance: pixel-perfect preservation outside the mask, or visually-indistinguishable (a few units of VAE noise are fine).

Information to collect

  • Inpaint mode / area flag (Inpaint area in AUTOMATIC1111, apply_overlay use in Diffusers, paste-by-mask presence in ComfyUI).
  • Mask file (open it; verify edges are crisp).
  • Mask feathering / blur value (blur_factor, Mask blur).
  • Inpaint padding / crop value (padding_mask_crop, Only masked padding, pixels, grow_mask_by).
  • strength / Denoising strength.
  • Whether an SDXL refiner is enabled.
  • Output file format and any quality setting.

Step-by-step fix

Step 1: Force a masked-only paste-back

This is the single most reliable fix and it works regardless of which checkpoint you use.

Diffusers (official method). The library ships VaeImageProcessor.apply_overlay(), which forces the unmasked area to stay identical to the original. The docs note the trade-off: a slightly harder transition at the seam, which Step 2 handles.

from diffusers import AutoPipelineForInpainting

repainted = pipe(prompt=prompt, image=init_image, mask_image=mask).images[0]

# Force unmasked region to remain byte-identical to the original
final = pipe.image_processor.apply_overlay(mask, init_image, repainted)
final.save("out.png")

If you prefer an explicit composite (e.g. you are outside Diffusers), do the alpha blend by hand — bit-identical outside the mask:

from PIL import Image
import numpy as np

mask_arr = np.array(mask.convert("L"))[:, :, None] / 255.0
orig_arr = np.array(init_image).astype(np.float32)
gen_arr = np.array(repainted).astype(np.float32)
final = (1 - mask_arr) * orig_arr + mask_arr * gen_arr
Image.fromarray(final.astype(np.uint8)).save("out.png")

AUTOMATIC1111 / Forge: in the Inpaint tab set Inpaint area: Only masked and Masked content: original. “Only masked” crops to the mask, edits, and pastes back, so the rest of the canvas is untouched.

ComfyUI: drive the sampler with a Set Latent Noise Mask (or InpaintModelConditioning) node so only the masked area is denoised, then end the graph with a paste-by-mask / image-composite node that restores the outside pixels from the original image.

Step 2: Use a hard-edged mask

Soft edges bleed. If you need a soft transition, paint a hard mask, then apply an alpha gradient at the very end — do not let the diffusion pipeline see a soft mask.

# Convert a grey/feathered mask into a hard black-and-white mask (ImageMagick)
convert mask_soft.png -threshold 50% mask_hard.png

In AUTOMATIC1111/Forge, lower Mask blur from the default 4 toward 0-2 for tight edits. In Diffusers, skip mask_processor.blur() or use a small blur_factor.

Step 3: Reduce inpaint padding / crop margin

If the worked region reaches too far past the mask:

pipe(
    prompt=prompt,
    image=init_image,
    mask_image=mask,
    strength=0.75,
    padding_mask_crop=8,   # smaller margin; the docs use 32 as a quality default
)

A 4-8 pixel margin gives a clean seam without much bleed. In AUTOMATIC1111/Forge, set Only masked padding, pixels to a low value (e.g. 8); raising it widens how far the surroundings influence the fill.

Step 4: Drop strength

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

strength = 0.7  # not 0.95

Below 0.5 the masked region barely changes; above 0.85 the bleed risk climbs. 0.7 is a good starting point for most edits. In AUTOMATIC1111/Forge this is the Denoising strength slider.

Step 5: Disable the refiner for inpaint

In SDXL workflows, turn the refiner off for masked edits:

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

If you need refinement, run the refiner only on the masked crop, then paste back (sequence: base, then crop, then refine, then paste).

Step 6: Output PNG and verify pixel-stability

Always export the edited image as PNG (never JPEG), then prove the fix:

python -c "
from PIL import Image
import numpy as np
a = np.array(Image.open('orig.png').convert('RGB'))
b = np.array(Image.open('out.png').convert('RGB'))
m = np.array(Image.open('mask.png').convert('L')) > 128
outside_diff = int(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, <= 2 with a VAE round-trip (acceptable). Anything higher means bleed is still happening — go back to Step 1.

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

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

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

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

How to confirm it’s fixed

  • The Step 6 pixel diff prints 0 (paste-back) or <= 2 (VAE round-trip).
  • Visual check at the mask boundary: no visible color step or seam line.
  • Re-run on three different test images. Bleed-free should be reproducible, not a coincidence of one seed.

Long-term prevention

  • Always paste back. Treat whole-image regeneration as a separate mode reserved for stylistic transforms.
  • Save inpaint workflows as named presets: tight-edit (paste-back, hard mask, low padding) vs blend-edit (soft mask, larger padding) vs full-redo (whole-image regenerate).
  • Validate the outside-mask pixel diff in CI for any production pipeline.
  • Document your mask format (8-bit grayscale, alpha, or RGBA mask channel) and stick to it.
  • Never export an inpainted image as JPEG; always PNG.
  • When an edit involves lighting that genuinely should affect the whole image, opt into whole-image mode explicitly instead of discovering it by accident.

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 running a pixel diff.
  • Cranking strength to 1.0 for inpaint and still expecting outside pixels to survive.
  • Letting the SDXL refiner run after inpaint without disabling it.
  • Exporting to JPEG and blaming the model for color shifts that are just JPEG quantization.
  • Running a default workflow with padding_mask_crop=64 (or Only masked padding, pixels: 64) and being surprised by 64-pixel bleed.

FAQ

Q: My UI says “Only masked” but I still see bleed. Why?

Check whether the UI actually composites with the original after generation or merely visualizes the mask region. Some UIs treat “only masked” as a visual hint while still regenerating the whole canvas. Confirm with a hard pixel diff (Step 6), and make sure Masked content is original, not fill.

Q: Is there a built-in way to keep the unmasked area, or do I have to composite by hand?

In Diffusers, yes — pipeline.image_processor.apply_overlay(mask, init_image, result) forces the unmasked region to stay identical. The official docs flag one trade-off: a slightly harder seam transition, which a small mask blur or feather-at-the-end fixes.

Q: Why does an inpaint-specific checkpoint bleed more than a plain one?

By design. Inpaint checkpoints are trained to blend the masked and unmasked regions for a natural seam, so they touch the surroundings on purpose. A plain checkpoint preserves the outside better but often leaves a visible mask outline. Either way, paste-back (Step 1) gives you the clean outside and the inpaint quality.

Q: Will face restoration after inpaint also bleed?

Yes. Face restorers (CodeFormer, GFPGAN) ignore the mask and rework the whole face. Run face restore before the inpaint, or skip it in inpaint workflows.

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

Yes — upscale the crop, edit, downscale, and paste back. The paste-back must downscale to the original resolution or you will get an alignment shift at the seam.

External references: Diffusers inpainting guide, ComfyUI VAE Encode (for Inpainting) node

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