Edit one region of an AI image and keep every other pixel
A mask edit is guidance, not a guarantee. Paste the original back outside the mask with Pillow so untouched pixels stay identical. Python with Sume's mask_url.
To change one part of an AI image and keep the rest identical, do not rely on the model to leave the rest alone. Run the masked edit, then paste the original image back everywhere outside the mask with an image library, so those pixels are the same by construction. OpenAI's guide says GPT Image treats a mask as guidance and "may not follow its exact shape with complete precision", which is why a model-only edit can drift outside the box.
The recipe below works on Sume's ChatGPT Image 2.5 rows, the ones with a mask_url. It needs the image and mask at public HTTPS URLs and about twenty lines of Python with Pillow.
Why does the rest of the picture change at all?
Image models regenerate the whole canvas. A mask tells the model where to concentrate, and OpenAI's wording is that masking is entirely prompt-based and that the mask is used as guidance. Press coverage of FLUX 3 Image says the new model's multi-step edits leave other parts of the image alone (The Decoder, reported); that is a model property you could test on a FLUX 3 host, and it is not something Sume's catalog offers today.
Compositing is the portable fix. It does not care which model made the edit, and it gives you an exact statement you can check: outside the mask, the output file equals the input file.
What does the mask need to look like?
OpenAI's guide says the mask must contain an alpha channel, and that the image and mask must be the same format and size and under 50 MB. If you send several images, the mask applies to the first one. Sume's docs list mask_url as a public HTTPS URL for ChatGPT Image 2.5 edits and say nothing more about its format. The guide excerpt I read does not say which polarity marks the edit area; the common convention is that fully transparent pixels mark the area to change. Run one cheap quality: low test and flip the script's EDIT_ALPHA constant if the wrong area changes.
| Rule | Source | Consequence for the script |
|---|---|---|
| Mask has an alpha channel | OpenAI image guide | Save the mask as RGBA PNG |
| Image and mask same size and format | OpenAI image guide | Build the mask from the image's own size |
| Mask applies to the first image | OpenAI image guide | Put the edited image first in input_references |
| mask_url is a public HTTPS URL | Sume Image API docs | Host the mask before the call |
| Up to 16 reference images | fal edit page, Sume catalog | Plenty of room for extra references |
The script: edit, then paste the original back
The code downloads the original and the mask, asks Sume for the edit, resizes the result to the original's size if the model returned a different one, and composites with the mask's own alpha as the blend weight. A two-pixel blur softens the seam; set it to zero for a hard edge. If Sume answers with a 202 job, the script stops and prints the status URL, because the result is not ready yet.
import io, os, requests
from PIL import Image, ImageFilter
ORIG = "https://example.com/photo.png"
MASK = "https://example.com/mask.png"
EDIT_ALPHA = 0 # alpha value that marks the area to change
load = lambda u: Image.open(io.BytesIO(requests.get(u, timeout=60).content))
r = requests.post(
"https://api.sume.com/v1/images",
headers={"Authorization": f"Bearer {os.environ['SUME_API_KEY']}"},
json={
"model": "openai/gpt-image-2.5",
"prompt": "replace the mug with a blue enamel mug, same lighting",
"input_references": [{"type": "image_url", "image_url": {"url": ORIG}}],
"mask_url": MASK,
"aspect_ratio": "auto",
},
timeout=90,
)
r.raise_for_status()
if r.status_code == 202:
raise SystemExit("job running: " + r.json()["data"]["status_url"])
orig = load(ORIG).convert("RGBA")
edited = load(r.json()["data"][0]["url"]).convert("RGBA").resize(orig.size)
alpha = load(MASK).convert("RGBA").getchannel("A")
region = alpha.point(lambda a: 255 if a == EDIT_ALPHA else 0).filter(ImageFilter.GaussianBlur(2))
Image.composite(edited, orig, region).save("final.png")
Why not just ask for a tighter mask?
A tighter mask helps, and a perfect one still does not guarantee identical pixels, because the model returns a freshly generated image rather than a patch. The returned file may also come back at a different size from your input, which is why the script resizes it before blending. If you let an auto aspect ratio follow the reference, Sume's docs say the output matches the reference shape, but pixel dimensions are not promised, so the resize guard stays.
The practical rule is to treat the model output as raw material for the masked area only. Everything outside it is yours: the original file, untouched, plus the seam you chose.
How do I check that nothing else moved?
Subtract the final from the original and look only outside the mask. With the blur set to zero, every pixel where the mask was not marked should be exactly equal. With a two-pixel blur, expect a thin band at the seam to differ; that band is by design.
Compositing has a cost: if the edit changed global light, such as a new lamp that should cast a glow across the desk, the pasted-back original will not show it, and you will see a hard boundary. In that case widen the mask to include the area the change should affect, or accept the model's drift outside the box.
When is this the wrong tool?
Do not composite when the edit is meant to change the whole frame, such as relighting or a style change. For object removal the mask_url object remover post shows the same call with a removal prompt, and GPT Image mask leaks covers what OpenAI says about edits that spill. The mask preflight script checks size and alpha before you spend a call.
Sources
Related posts
More in Use cases
- Face swap on someone else's Short: does YouTube call it original?
YouTube said Oct 1 that Shorts re-uploading others' videos without significant changes will get less reach. Why a face swap on borrowed footage is a risk.
- GPT Image 2.5 icon set: one anchor icon, then reference it
Make a consistent icon set with GPT Image 2.5 on Sume: approve one anchor icon, pass it as input_references, change only the subject, and keep a style block.
- GPT Image 2.5 pixel art sprite sheet: prompt a grid, slice it
Ask GPT Image 2.5 for a 4x2 sprite sheet at 2048x1024 on Sume, then slice frames and snap to a true pixel grid with Pillow. Prompt and code included.
- GPT Image 2.5 menu board prompt: quote every line, then proofread
Make a restaurant menu board image with GPT Image 2.5 on Sume: a prompt that quotes each item and price, a size that fits, and a proofreading pass before print.
Written by Sume