Hide the seam in an AI image edit with a feathered composite in Pillow

A hard-edged paste of an AI edit leaves a visible line. Blur the mask a few pixels so the edit fades into the original. Short Pillow script and settings.

5 min readSume
All posts

When you paste an AI-edited region back over your original, blur the mask edge by a few pixels. A hard rectangle leaves a visible line wherever the edited color or exposure differs even slightly from the original, and a feathered edge fades the two together. A Gaussian blur of 8 to 16 pixels on the mask, applied before compositing, is usually enough for a 1500 to 2500 pixel image.

FLUX 3 Image advertises edits that leave the rest of the picture unchanged. On a model that does not promise it, compositing the original back is how you make that guarantee yourself, and feathering is what keeps the guarantee from showing.

The script

composite_feathered loads the original and the edit, resizes the edit to the original's size if the API returned a different one, draws a white box on a black mask, blurs it, and composites with Pillow. The demo prints three pixel values: one far outside the box (original), one in the middle (edit) and one on the boundary, which lands in between.

from PIL import Image, ImageFilter

def composite_feathered(original_path, edited_path, box_px, out_path, feather=12):
    """Keep the original everywhere except box_px, blending the seam over `feather` pixels."""
    orig = Image.open(original_path).convert("RGB")
    edit = Image.open(edited_path).convert("RGB")
    if orig.size != edit.size:
        edit = edit.resize(orig.size, Image.LANCZOS)
    mask = Image.new("L", orig.size, 0)
    mask.paste(255, box_px)
    mask = mask.filter(ImageFilter.GaussianBlur(feather))
    Image.composite(edit, orig, mask).save(out_path)

if __name__ == "__main__":
    Image.new("RGB", (400, 300), (200, 180, 160)).save("orig.png")
    Image.new("RGB", (400, 300), (30, 30, 30)).save("edited.png")
    composite_feathered("orig.png", "edited.png", (100, 100, 250, 200), "blend.png")
    im = Image.open("blend.png")
    print(im.getpixel((50, 50)), im.getpixel((175, 150)), im.getpixel((100, 150)))

Choosing the feather

Feather scales with image size and with how different the two sides are. Too little and you see an edge; too much and the edit bleeds into areas you wanted to protect. Start from the table, then check on a real result at 100 percent.

rules of thumb, not measurements; read 2026-10-03
Image widthStarting feather (px)Notes
10246 to 8Small images need less
204810 to 14Typical product still
384016 to 24Large; verify the interior is untouched

Where it breaks

Feathering softens a seam; it cannot fix a mismatch. If the edit changed the lighting direction or white balance across the whole frame, the blend will show a gradient of two color casts. In that case, either mask a larger area or ask for the edit with the original lighting named explicitly in the prompt. Size matters too: if the edit comes back at a different size, resizing it can shift the content by a pixel or two, so request the same aspect ratio as the source.

The shape of the mask need not be a rectangle. A hand-drawn or detected outline blurred the same way works better for round objects, since a rectangle crosses background the model may have subtly altered.

A quick visual check

Look at the result with a high-contrast overlay: subtract the composite from the original and amplify the difference. The difference should be zero outside the box, fade smoothly across the feather and show the edit inside. If you see a bright outline right at the feather's outer limit, the blur was too small for the color gap. If you see differences far from the box, the edit changed the whole frame, and you should return to the original lighting or mask more of the scene.

A note on alpha and bit depth

The script converts both images to RGB, which discards any alpha channel. If your edit must keep transparency, composite in RGBA instead and blur the mask the same way. Likewise, keep the master in PNG, not JPEG, so that the original side of the blend is not a re-compressed copy. Save the final blend as PNG as well, and convert to JPEG only for delivery.

Related steps

To confirm that nothing outside the box moved, diff the result against the original as in the composite-back post. For layered work from transparent PNGs, see editable layers with Pillow. Masks are accepted by ChatGPT Image 2.5 on Sume; see the Image API docs for mask_url.

Sources

Related posts

More in Developers

All Developers posts

Written by Sume