Course ES

Chapter 13 of 22 · intermediate

Image generation

What this session covers

Last week the model read images. This week it makes them. You send a text prompt to an image model and get back pixels — a PNG you can save, render, or hand to a user. The API is almost trivial. The part that matters is that image prompting is a real skill, the same one as text prompting pointed at a canvas. Vague in, generic out. Get specific about subject, style, lighting, and composition and you get something you'd actually ship.

The mechanics take five minutes: one call, prompt in, bytes out, decode and write. The rest of the hour goes to the two things that separate a toy from a tool. One is templating a house style so results stay consistent. The other is picking aspect ratio on purpose, because the same idea composed square versus wide is really two different images.

OpenAI

images.generate hands back base64 PNG data. Decode it, write the bytes, and you're done. size sets the aspect ratio.

# One call, prompt in, image out. gpt-image-1 returns base64 PNG data; decode it
# and write the bytes. `size` controls the aspect ratio (square here; use a wide
# size for banners, a tall one for posters).
def generate(prompt: str, path: str) -> str:
    response = client.images.generate(model=_MODEL, prompt=prompt, size="1024x1024")
    image_bytes = base64.b64decode(response.data[0].b64_json)
    with open(path, "wb") as f:
        f.write(image_bytes)
    return path

There's no conversation here. Image generation is a single request, so there's no history to manage. The thing you iterate on is the prompt. Add "soft watercolor," "dramatic rim lighting," "isometric," "shot on 35mm film," and watch the output shift — the vocabulary of art direction is the vocabulary that works. Keep the prompts that land. A good image prompt is reusable in a way a good image isn't.

Gemini

Google's image model runs through the ordinary generate_content call. response_modalities asks for an image, which comes back as an inline data part (the standalone Imagen endpoint is deprecated). Same story: prompt in, bytes out, and the prompt is where the craft lives.

# Ask generate_content for an IMAGE modality; the returned parts include an
# inline_data part carrying the raw image bytes, ready to write.
def generate(prompt: str, path: str) -> str:
    response = client.models.generate_content(
        model=_MODEL,
        contents=prompt,
        config=types.GenerateContentConfig(response_modalities=["TEXT", "IMAGE"]),
    )
    for part in response.candidates[0].content.parts:
        if getattr(part, "inline_data", None) and part.inline_data.data:
            with open(path, "wb") as f:
                f.write(part.inline_data.data)
            return path
    raise RuntimeError("no image part in response")

Two things to keep in mind before you put this in front of users. Generated images can carry provider watermarks and come with usage terms, so read them before you build a business on the output. And image generation is slower and more expensive than a text call by a wide margin. It's a deliberate action a user triggers, not something you run on every page load. Budget for it that way.

Put it to work

Three docker-compose apps under code/showcase/<slug>/. Each one returns the image as a data URI and the page renders it inline, so they're real little apps rather than plain text. Same drill: bash bootstrap-secrets.sh, docker compose up --build, http://localhost:3000.

Showcase 1 — Text to image

The raw capability: a prompt box, a generated image. Use it to feel how much the wording matters. Run the same subject with three different style clauses and compare what comes back.

Showcase 2 — Sticker maker

The user gives a subject and the app wraps it in a fixed die-cut-sticker style. This is the productization move: prompt templating so a non-expert gets on-brand output every time without knowing the incantation. Most real image features are exactly this — a locked style with a variable subject.

Showcase 3 — Banner maker

A wide banner with negative space for a headline. It exists to make one point concrete: aspect ratio is a creative decision you make up front, not an afterthought. The banner recipe composes around a headline; the square one wouldn't.

All three are the same generate call with a different prompt template and size. The engineering is templating and plumbing. The product is the recipe.

Run it

The README in this folder lists the Python version, the install line, the two environment variables, and the exact commands. Keys come from the untracked .env at the course root. The basic scripts write a PNG you open by hand; the showcases render inline.

Takeaways

Image generation is a one-shot call: prompt in, pixels out, no conversation to manage. The entire skill lives in the prompt and the parameters. Two moves turn the raw capability into a feature. Template a house style so the subject is the only variable a user supplies. And treat aspect ratio as a creative choice that reshapes the composition. Mind the practicalities. It's slow, it costs more than a text call, and the output may carry watermarks and terms, so make it a deliberate user action rather than an automatic one. Next week is the last modality: audio, both directions, speech to text and back.