Edit existing images (inpainting, outpainting)
On this page
Updated November 2, 2026 — Image models.
gpt-image-1, the model this course was originally written for, was deprecated on October 23, 2026. Additionally,gpt-image-1.5,gpt-image-1-mini, andchatgpt-image-latestwill be retired on December 1, 2026. Examples now usegpt-image-2.5-sunburst, which offers the highest precision for editing;gpt-image-2.5-flareis faster for general generation. Both models also supportxhighandmaxquality settings. GPT Image models always return images in base64: theresponse_formatparameter used by older DALL·E models is no longer accepted and has been removed from the code. Source: Image generation (OpenAI).
Edit existing images
The client.images.edit() method modifies an existing image using a mask to define the area to regenerate. This leads to two distinct use cases, which are worth naming precisely because they require different masks. Inpainting fills or replaces an interior area—you might change the sky in a landscape, the screen on a computer, or the color of a wall. Outpainting, by contrast, extends the image beyond its original borders, such as turning a square portrait into a panoramic banner.
How the mask works
The mask is a PNG image with the same dimensions as the source image. Transparent areas (alpha = 0) indicate where the model should generate new content; opaque areas remain untouched. This convention is counterintuitive at first: you naturally want to “paint” what you want to change, but you actually need to cut a hole in the mask instead. The API call itself remains simple, requiring only the two files and a description of what should appear.
import base64
from openai import OpenAI
client = OpenAI()
response = client.images.edit(
model="gpt-image-2.5-sunburst",
image=open("desk_photo.png", "rb"),
mask=open("desk_mask.png", "rb"),
prompt="A computer screen displaying a modern analytics dashboard"
)
with open("edited_desk.png", "wb") as f:
f.write(base64.b64decode(response.data[0].b64_json))
You don’t need a graphics editor to create this mask. Pillow can generate one in just a few lines: start with a fully opaque layer matching the source dimensions, then draw the shape you want to make transparent. The script below creates a rectangular window in the center of the image.
from PIL import Image, ImageDraw
# Load the source image to get its dimensions
source = Image.open("desk_photo.png")
width, height = source.size
# Create a fully opaque mask (black)
mask = Image.new("RGBA", (width, height), (0, 0, 0, 255))
draw = ImageDraw.Draw(mask)
# Draw a transparent area (the area to edit)
# Rectangle in the center of the image
x1, y1 = width // 4, height // 4
x2, y2 = 3 * width // 4, 3 * height // 4
draw.rectangle([x1, y1, x2, y2], fill=(0, 0, 0, 0))
mask.save("desk_mask.png")
print(f"Mask created: {width}x{height}, editable area in center")
Inpainting: Replace an element
Once you repeat this operation, it makes sense to encapsulate mask creation and the API call into a single function, parameterized by the region to modify. The prompt should then describe only the content expected within the masked area, never the rest of the image. The example below applies this function to a landscape photograph, modifying only the top 400 pixels—the sky—to give it a stormy atmosphere.
from openai import OpenAI
from PIL import Image, ImageDraw
import base64
client = OpenAI()
def inpaint_region(image_path, region, prompt, output_path):
"""
Replace a rectangular region in an image.
region: tuple (x1, y1, x2, y2) in pixels
"""
# Create the mask
source = Image.open(image_path)
mask = Image.new("RGBA", source.size, (0, 0, 0, 255))
draw = ImageDraw.Draw(mask)
draw.rectangle(region, fill=(0, 0, 0, 0))
mask.save("temp_mask.png")
# Call the API
response = client.images.edit(
model="gpt-image-2.5-sunburst",
image=open(image_path, "rb"),
mask=open("temp_mask.png", "rb"),
prompt=prompt
)
# Save the result
with open(output_path, "wb") as f:
f.write(base64.b64decode(response.data[0].b64_json))
print(f"Edited image saved: {output_path}")
# Example: Replace the sky in a landscape photo
inpaint_region(
image_path="landscape.png",
region=(0, 0, 1024, 400), # Top part = the sky
prompt="Dramatic sky with purple storm clouds at sunset",
output_path="landscape_sky_edited.png"
)
Outpainting: Extend the image
Enlarging an image requires an extra step because the API cannot generate content “next to” a file directly. You must first create a larger canvas, paste the original onto it, and provide a mask that protects exactly the area occupied by that original. The entire logic hinges on calculating the new dimensions and the paste position based on the requested direction.
from openai import OpenAI
from PIL import Image
import base64
from io import BytesIO
client = OpenAI()
def outpaint(image_path, direction, extra_pixels, prompt, output_path):
"""
Extend an image in a given direction.
direction: 'right', 'bottom', 'left', 'top'
extra_pixels: number of pixels to add
"""
source = Image.open(image_path).convert("RGBA")
w, h = source.size
# Calculate new dimensions
if direction == "right":
new_size = (w + extra_pixels, h)
paste_pos = (0, 0)
elif direction == "bottom":
new_size = (w, h + extra_pixels)
paste_pos = (0, 0)
elif direction == "left":
new_size = (w + extra_pixels, h)
paste_pos = (extra_pixels, 0)
elif direction == "top":
new_size = (w, h + extra_pixels)
paste_pos = (0, extra_pixels)
# Create the extended canvas (transparent = area to generate)
extended = Image.new("RGBA", new_size, (0, 0, 0, 0))
extended.paste(source, paste_pos)
# The mask: transparent everywhere except where the original image is placed
mask = Image.new("RGBA", new_size, (0, 0, 0, 0))
opaque = Image.new("RGBA", (w, h), (0, 0, 0, 255))
mask.paste(opaque, paste_pos)
# Save temporarily
extended.save("temp_extended.png")
mask.save("temp_mask_outpaint.png")
response = client.images.edit(
model="gpt-image-2.5-sunburst",
image=open("temp_extended.png", "rb"),
mask=open("temp_mask_outpaint.png", "rb"),
prompt=prompt
)
with open(output_path, "wb") as f:
f.write(base64.b64decode(response.data[0].b64_json))
print(f"Extended image saved: {output_path}")
# Extend a portrait to the right
outpaint(
image_path="portrait.png",
direction="right",
extra_pixels=512,
prompt="Natural continuation of the scene, same lighting and background",
output_path="portrait_extended.png"
)
Note the wording of the extension prompt: “natural continuation of the scene, same lighting and background.” In outpainting, you aren’t describing a new subject; you’re asking the model to extend what it already sees. An overly creative prompt would produce a visible seam between the original and the added area.
Iterative editing
Nothing stops you from chaining multiple edits on the same image, using each output as the input for the next step. This is the most reliable way to build a complex composition: handle the background first, then the foreground, with a dedicated mask for each pass, rather than hoping a single prompt will fix everything at once.
import base64
from openai import OpenAI
client = OpenAI()
def edit_step(image_path, mask_path, prompt, output_path):
response = client.images.edit(
model="gpt-image-2.5-sunburst",
image=open(image_path, "rb"),
mask=open(mask_path, "rb"),
prompt=prompt
)
with open(output_path, "wb") as f:
f.write(base64.b64decode(response.data[0].b64_json))
return output_path
# Step 1: Modify the background
step1 = edit_step(
"scene.png", "mask_background.png",
"Lush tropical forest background",
"scene_step1.png"
)
# Step 2: Add an element to the foreground
step2 = edit_step(
step1, "mask_foreground.png",
"A colorful macaw parrot perched on a branch",
"scene_step2.png"
)
print(f"Final result: {step2}")
Key constraints
Four technical rules govern the success of the call. Violating any of them results in an immediate error rather than a degraded result.
- The source image and mask must have the same dimensions
- Both files must be in PNG format
- The maximum size per file is 4 MB
- The mask uses the alpha channel to define editable areas
One empirical observation adds to these constraints: results are more consistent when the masked area represents less than 50% of the image. Beyond that, the model has too little context to blend correctly, and you will often get better results by splitting your modification into two successive edits.
Put this into practice with a photo of your desk or any royalty-free image: write a script that automatically generates a mask over the top quarter of the image, replaces that area with a starry sky, and saves the result locally.