◉GEMINI LABJP
●ARGON — gemini-4-argon is generally available as of 10/13, aimed at complex coding, agents, math and science●DEEP RES — deep-research-pro-preview-12-2025 shuts down 10/23, 6 days left. Move to deep-research-preview-04-2026●NANO — gemini-nano-banana-2.1 is GA (10/06). gemini-3.1-flash-image is deprecated with no shutdown date yet●MCP — One write-up cut an 85,000-token tool result to 310 tokens by wrapping it in an ADK FunctionTool●FLASH — gemini-3.7-flash and 3.5-flash now route automatically to newer Flash models (10/08)●NEW — Stop Re-Reading Every Crash Report: Gre…●ARGON — gemini-4-argon is generally available as of 10/13, aimed at complex coding, agents, math and science●DEEP RES — deep-research-pro-preview-12-2025 shuts down 10/23, 6 days left. Move to deep-research-preview-04-2026●NANO — gemini-nano-banana-2.1 is GA (10/06). gemini-3.1-flash-image is deprecated with no shutdown date yet●MCP — One write-up cut an 85,000-token tool result to 310 tokens by wrapping it in an ADK FunctionTool●FLASH — gemini-3.7-flash and 3.5-flash now route automatically to newer Flash models (10/08)●NEW — Stop Re-Reading Every Crash Report: Gre…
Articles/API / SDK
◈ API / SDK/2026-06-26Advanced

Reliable Text-in-Image with Gemini 3.1 Flash Image — an OCR-Verified Pipeline

After the preview shutdown, the GA gemini-3.1-flash-image still occasionally garbles text baked into images. Here is a generate -> read-back-verify -> regenerate/composite pipeline, with working code and an unattended retry budget.

gemini119gemini-api286image-generation9nano-banana3ocrindie-dev48

✦ Premium Article

When you automate image generation as an indie developer — banners for a wallpaper app, OGP thumbnails for a blog — you eventually hit a quietly maddening wall. The composition looks great, but the Japanese line you wanted baked into the image, something like "Free this week," comes out subtly wrong. One character swapped for a similar one, a diacritic dropped, the second line illegible. You don't notice at a glance; you notice when you zoom in.

On June 25, 2026 the preview models gemini-3.1-flash-image-preview and gemini-3-pro-image-preview were shut down, and the GA versions gemini-3.1-flash-image (Flash Image) and gemini-3-pro-image (Pro Image) took their place. Text rendering keeps getting better with each generation. Even so, in an unattended setup — spin it up, publish whatever comes out — the occasional garble is guaranteed to become an incident. I spent a while eyeballing that one bad frame by hand, which defeated half the point of automating.

What I landed on is a two-stage approach: don't trust the output, read the image back with a model to confirm the characters, and if it fails, either regenerate or switch to compositing. This article records that verification-gated pipeline, thresholds and retry design included.

After the shutdown: which model should draw the text

First, pick the model that draws. The two GA options trade text accuracy against speed and cost differently. Here is my rough feel for my use case (a few short Japanese characters on wallpaper-app announcement banners and blog OGP images).

Aspectgemini-3.1-flash-image (Flash Image)gemini-3-pro-image (Pro Image)
Accuracy of short Japanese textProduction-usable; stable up to a few linesHigher; holds up on multi-line, smaller text
Speed per imageFast (seconds)Somewhat slower
Approx. cost per imageLowA few times Flash
Video-to-imageSupported (gemini-3.1-flash-image only)Not supported
Workhorse for unattended bulk runsMake this the defaultReserve for regeneration on failure

Personally, defaulting to Flash Image and only promoting an image to Pro Image after it fails verification twice in a row gave me the best cost-effectiveness. Running every image through Pro Image from the start reduces garbling but inflates cost to several times Flash, which undercuts the point of unattended runs. I think the realistic way to win on text accuracy is "promote only the suspicious ones," not "use the premium model for everything."

How to prompt the model to actually draw the text

The first lever for text-in-image is how you prompt. Three rules I keep:

Pass the target string as text to copy verbatim

Don't bury it inside a description; hand over the exact characters as an unchangeable quote. A vague "a title that feels like ~" leaves room for paraphrasing and mis-conversion.

Pin character count, line count, and placement with numbers

Instead of "large in the center," constrain layout numerically: "one line at 25% from the top, max 8 characters." Long or multi-line text raises the failure rate on its own, so keep the text payload short.

from google import genai
from google.genai import types
 
client = genai.Client(api_key="YOUR_GEMINI_API_KEY")
 
MODEL_FAST = "gemini-3.1-flash-image"
MODEL_STRONG = "gemini-3-pro-image"
 
def build_prompt(headline: str) -> str:
    # Pass the desired characters as a verbatim, do-not-alter constraint
    return (
        "Generate a simple vertical (9:16) announcement banner image.\n"
        "Calm indigo background with a soft washi-paper texture.\n"
        f"At 25% from the top, draw EXACTLY these characters, one horizontal line, large: '{headline}'\n"
        "- Max 10 characters. Avoid decorative fonts; high-legibility gothic.\n"
        "- Draw NO other characters, no alphanumerics, no logo, no signature.\n"
        "- Do not omit diacritics or small kana."
    )
 
def generate_image(model: str, headline: str) -> bytes:
    resp = client.models.generate_content(
        model=model,
        contents=build_prompt(headline),
        config=types.GenerateContentConfig(response_modalities=["Image"]),
    )
    for part in resp.candidates[0].content.parts:
        if part.inline_data and part.inline_data.mime_type.startswith("image/"):
            return part.inline_data.data  # PNG bytes
    raise RuntimeError("No image part returned")

The negative instruction "draw no other characters" helps suppress stray English signatures and random logos that creep in. With Japanese in particular, unrequested alphabet tends to sneak in as decoration, so I include this line almost every time.

✦

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 garbled text in generated images keeps biting you, you can drop in an OCR match-ratio gate today and copy-paste the verification code
✦You'll get the full two-stage pattern for gemini-3.1-flash-image: generate, read back with a vision model, and fall back to Pillow compositing, thresholds included
✦You can size a monthly cost ceiling for unattended runs by reasoning from per-image cost and a capped retry budget instead of guessing
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.

or
Unlock all articles with Membership →
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.

  • ✦Copy-paste ready implementation code
  • ✦New advanced guides published daily
  • ✦$5/mo or $10 for lifetime access
View Membership →

Related Articles

◈ API / SDK2026-08-23
Moving app AI work from runtime calls to a pre-ship batch pass
Where you put a Gemini call decides whether your request count scales with users or with assets. Here is the decision rule I used to move classification into a pre-ship batch pass, plus a resumable implementation.
◈ API / SDK2026-08-22
Once You Pass Twenty Mediation Groups, How Do You Find the Setting That Went Missing?
As ad mediation groups multiply, missing sources and type drift accumulate quietly. Here is the split I settled on: normalize the settings into one matrix, let code confirm the gaps, and send Gemini only the cells that need judgment.
◈ API / SDK2026-05-18
Rebuilding My Wallpaper App's Color Variations on Nano Banana 2 — Life After the Imagen 4 Shutdown, and Why Resolution Decides the Margin
How I moved my wallpaper app's color-variation pipeline onto gemini-3.1-flash-image (Nano Banana 2): where Imagen 4 users have to go now, the current Interactions API call shape, a two-stage design derived from published per-resolution pricing, and a script that finds retired model IDs still sitting in your code.