●FLASH — Gemini 3.5 Flash is now generally available, billed as the most intelligent model for agentic and coding tasks●AGENTS — Managed Agents arrive in public preview, running autonomous agents in Google-hosted isolated Linux sandboxes●WEBHOOK — Event-driven webhooks now replace polling for the Batch API and long-running operations●SEARCH — File Search goes multimodal, embedding and searching images via gemini-embedding-2●SUNSET — gemini-3.1-flash-image-preview and gemini-3-pro-image-preview shut down on June 25●ANTIGRAVITY — The Antigravity Agent managed agent (antigravity-preview-05-2026) is available in public preview●FLASH — Gemini 3.5 Flash is now generally available, billed as the most intelligent model for agentic and coding tasks●AGENTS — Managed Agents arrive in public preview, running autonomous agents in Google-hosted isolated Linux sandboxes●WEBHOOK — Event-driven webhooks now replace polling for the Batch API and long-running operations●SEARCH — File Search goes multimodal, embedding and searching images via gemini-embedding-2●SUNSET — gemini-3.1-flash-image-preview and gemini-3-pro-image-preview shut down on June 25●ANTIGRAVITY — The Antigravity Agent managed agent (antigravity-preview-05-2026) is available in public preview
Gemini's GA Image Models Won't Output Exact Device Resolutions — A Wallpaper Pipeline That Fixes Aspect Ratio and Safe Areas
After switching to the GA image models, your wallpapers no longer fit the screen. Here's how to crop one master image into every device resolution and cut your generation count to a fraction, with full Pillow code.
Right after moving to the GA image models, I noticed the wallpapers I generated no longer fit the iPhone screen cleanly. I was sending the exact same prompts I had tuned during the preview era, yet some came back with bands of empty space top and bottom, and others had the subject clipped at the edges. The reason is simple: image models don't honor a pixel request like "give me 1206×2622 pixels." They return one of a fixed set of aspect ratios.
If you run a wallpaper app as an indie developer for any length of time, this mismatch between "what the model returns" and "what the device demands" is unavoidable. And since gemini-3.1-flash-image-preview and gemini-3-pro-image-preview are scheduled to shut down on 6/25, migrating to the GA models is exactly when this problem hits you head-on. Below is the "crop every device size from a single master" design I use in my own wallpaper generation pipeline, with working code.
Why pixel requests don't work
When you ask an image model for "1206×2622px output" — whether in the prompt or a parameter — what comes back is rounded to one of the aspect ratios the model supports (1:1, 3:4, 4:3, 9:16, 16:9, and so on). This is true on both the GA and the preview models. The model understands ratios, but it will not produce absolute pixel dimensions tailored to your device.
A wallpaper app, on the other hand, needs exact device pixels. Line up the major portrait devices and the required resolutions cluster like this:
iPhone Air: 1260 × 2736 px (aspect ratio ~0.46)
iPhone 17 Pro: 1206 × 2622 px (~0.46)
iPhone 16/17 Pro Max: 1320 × 2868 px (~0.46)
Pixel 9 Pro class Android: 1280 × 2856 px (~0.448)
Common Android xxxhdpi portrait wallpaper: 1440 × 3200 px (~0.45)
Notice that every ratio sits between 0.45 and 0.46. That is taller than 9:16 (= 0.5625). In other words, an image generated at 9:16 is "wider" than what the device wants, so trimming the horizontal edges covers every model. That points to the most robust approach: generate at 9:16, then resolve the exact pixels in post-processing.
The starting decision — crop every device from one master
This is the most important judgment in this article. Rather than calling the generation API once per device, I recommend generating a single, sufficiently large master image and cropping/resizing it down to each device resolution.
There are two reasons. The first is cost. Image generation is billed per image. Say it's $0.04 per image: generating four device variants separately means four charges per design. Cropping from one master means a single charge, cutting the generation count to a fraction of the device count — in this case 1/4. If you're preparing 1,000 wallpaper designs, that's 4,000 calls dropping to 1,000.
The second is consistency. Generate per device and the same prompt drifts slightly in composition and color each time. Derive everything from one master and every device gets exactly the same artwork.
✦
Thank you for reading this far.
Continue Reading
What follows includes implementation code, benchmarks, and practical content we hope you'll find useful. This site runs without ads — server and development costs are supported entirely by members like you. If it's been helpful, we'd be truly grateful for your support.
WHAT YOU'LL LEARN
✦If your GA image-model output never matched device resolutions, you'll walk away with code that crops one master into every device size precisely
✦You'll be able to switch a per-device pipeline that ran up API costs into one that cuts generations to a fraction of the device count
✦With safe-area-aware prompts and cropping, you can ship wallpapers at scale without the subject getting clipped
Secure payment via Stripe · Cancel anytime
✦
Unlock This Article
Get full access to the rest of this article. Buy once, read anytime. This site is ad-free — your support goes directly toward keeping it running.
First, generate the master. Migrating off the preview model starts, at its core, with swapping the model ID. Because the GA models may update response field names or supported aspect ratios, always confirm against the changelog before pushing to production.
from google import genaifrom google.genai import typesclient = genai.Client(api_key="YOUR_GEMINI_API_KEY")# The preview models shut down on 6/25. Switch to the GA model ID.MODEL = "gemini-3.1-pro-image" # was: gemini-3-pro-image-previewprompt = ( "A serene minimalist mountain range at dawn, soft gradient sky, " "centered composition, generous empty space at top and bottom, " "no text, no watermark, high detail")resp = client.models.generate_content( model=MODEL, contents=prompt, config=types.GenerateContentConfig( response_modalities=["IMAGE"], # Generate at 9:16 (wider than the device's 0.46) and trim width later image_config=types.ImageConfig(aspect_ratio="9:16"), ),)# The generated image comes back in inline_datafor part in resp.candidates[0].content.parts: if part.inline_data: with open("master.png", "wb") as f: f.write(part.inline_data.data) break
I put generous empty space at top and bottom in the prompt so the subject survives later cropping. Whether or not that single line is present made a clear difference to my yield at scale.
Fitting to exact device resolution with fit_to_device
Once the master exists, bring it down to each device resolution precisely. The key is the "cover" approach: to avoid letterboxing, scale up until the target is fully covered, then crop from the center.
from PIL import Imagedef fit_to_device(src_path: str, target_w: int, target_h: int, out_path: str) -> str: img = Image.open(src_path).convert("RGB") sw, sh = img.size # cover scale: pick the smallest factor that covers both dimensions (max is the trick) scale = max(target_w / sw, target_h / sh) nw, nh = round(sw * scale), round(sh * scale) img = img.resize((nw, nh), Image.LANCZOS) # LANCZOS keeps downscaling clean # center crop to the target resolution left = (nw - target_w) // 2 top = (nh - target_h) // 2 img = img.crop((left, top, left + target_w, top + target_h)) img.save(out_path, "PNG") return out_path
Using max() is the point. With min() the whole image fits but you get empty borders (the "contain" approach). Wallpapers can't have borders, so you cover first and trim the excess with max().
There's one more prerequisite: generate the master larger than every device. If scale exceeds 1 (upscaling is needed), enlargement makes it blurry. To reliably cover the largest 1440×3200, I aim for a 9:16 master with a long edge of at least 3,000px. That keeps the scale at or below 1 for every device, so it's always a downscale.
Writing out every device at once
After that, you just loop over a device table. Generation happens once; cropping happens per device.
With this structure, when a new device ships you add one line to DEVICES. You never touch the generation API.
Keeping the subject from getting clipped via safe areas
Once you crop, the edges are always trimmed. The real question is whether you control which edges get cut. I treat the central 80% as the subject's safe area, and I steer the prompt so the subject avoids the top and bottom where the lock-screen clock and widgets sit.
Checking whether output respects the safe area is faster by overlay than by eye. Keeping a small verification overlay handy makes pre-production checks much easier.
from PIL import Image, ImageDrawdef draw_safe_area(path: str, out_path: str, safe: float = 0.8) -> None: img = Image.open(path).convert("RGB") w, h = img.size draw = ImageDraw.Draw(img) mx, my = w * (1 - safe) / 2, h * (1 - safe) / 2 # Draw the central 80% box in red; confirm the subject sits inside it draw.rectangle([mx, my, w - mx, h - my], outline=(255, 0, 0), width=6) img.save(out_path, "PNG")draw_safe_area("out/iphone-pro-max.png", "check/iphone-pro-max.png")
If the subject spills outside that box, strengthen centered composition in the prompt, or regenerate the master.
What I actually verified migrating from preview to GA
Heading into the 6/25 shutdown, here's the order I checked things against my own pipeline:
Swap the model ID from preview to GA (gemini-3.1-pro-image, etc.)
Generate just one image and confirm the inline_data extraction hasn't changed
Verify the aspect_ratio value I was passing still works on GA
Add a fallback path to the most recent master when generation fails
Which GA model I use for the master depends on the use case. For me, simple gradient or abstract-pattern wallpapers are fine on gemini-3.1-flash-image — cheap, fast, and the throughput matters at scale. For detailed landscapes or pieces where texture is the star, I reach for gemini-3.1-pro-image, because even with the same prompt, the detail that survives after downscaling to device resolution is noticeably better.
The rule of thumb is simple: "does the detail collapse when downscaled to a 3,000px long edge?" Send only the subjects that collapse to pro and run everything else on flash, and the cost/quality balance gets much easier to manage. Building everything on pro looks great but sends your bill soaring.
A word on output format too. I receive PNG right after generation for post-processing, but the final files I bundle into the app are converted to WebP. For the same visual result the file size drops by roughly 30%, which helps app size and download time. For wallpapers, where losslessness isn't required, WebP around quality 90 turned out to be the practical sweet spot.
Five pitfalls that trip people up
Here are the spots where I got stuck, or where I've been asked for help.
Specifying a pixel count in the prompt. Writing "make it 1206×2622px" gets ignored; the model returns its fixed aspect ratio. Accept that sizing is the post-processing layer's job.
Generating small and upscaling to device resolution. It will always blur. Generate the master larger than every device and only ever downscale.
Cropping without considering the safe area. Unless the subject is centered, the edges get clipped. Make margins and centering explicit in the prompt.
Generating per device and paying 4x the cost. Crop from one master and generation happens once — a fraction of the device count.
Forgetting Android density buckets. I avoid the "resources drop out on low-density devices" failure by placing one high-resolution copy in drawable-nodpi/. For real photographic assets like wallpapers, consolidating into nodpi rather than splitting by density is the safe move.
When shipping the same artwork to both the App Store and Google Play, just keeping these five in mind cut my rejections noticeably. Being able to deliver a piece I made as an artist across every device without breakage is unglamorous, but it pays off in daily operations.
Your next step
Start by running fit_to_device on a single image you have on hand, then use draw_safe_area to overlay the box across every device size and check whether the subject is clipped. Once that's stable, the rest is just raising the quality of your generation prompts to take it to scale. Thank you for reading.
Share
Thank You for Reading
Gemini Lab is ad-free, supported entirely by members like you. We publish practical guides daily with implementation code, benchmarks, and production-ready patterns. If you've found it useful, we'd love to have you on board.