examples.md
# Ideogram 4 — Worked Examples
Real caption examples for the toolkit's bread-and-butter use cases. Each shows the user's casual
ask and the JSON caption Claude should produce (this *is* the magic-prompt expansion). JSON is
shown pretty-printed for readability — serialize compact (`separators=(",",":")`) when passing.
---
## 1. Title card with headline text (the flagship use case)
**User:** "Title card for the AI Engineering Review video — big bold 'AI ENGINEERING REVIEW' on a
dark techy background."
```json
{
"high_level_description": "A dark modern tech title card with a large bold headline reading 'AI ENGINEERING REVIEW' centered in the upper-middle.",
"style_description": {
"aesthetics": "bold, modern, high-contrast corporate tech, generous negative space",
"lighting": "soft cyan rim glow from below, deep shadows",
"medium": "flat vector graphic design with subtle gradient backdrop",
"art_style": "minimalist tech poster, clean geometric",
"color_palette": ["#0B0B1A", "#00E0C6", "#1B2A4A", "#FFFFFF"]
},
"compositional_deconstruction": {
"background": "Deep near-black navy gradient with a faint geometric grid and a soft cyan glow rising from the lower edge.",
"elements": [
{
"type": "text",
"bbox": [340, 120, 540, 880],
"text": "AI ENGINEERING REVIEW",
"desc": "Bold heavy sans-serif headline, all caps, crisp white, tight letter spacing, centered.",
"color_palette": ["#FFFFFF"]
},
{
"type": "obj",
"bbox": [580, 380, 620, 620],
"desc": "A thin glowing cyan horizontal accent rule beneath the headline.",
"color_palette": ["#00E0C6"]
}
]
}
}
```
Why it works: the headline is a `text` element with the literal string (legible-text strength),
the brand cyan is pinned via per-element palette on the accent rule, and the bbox keeps the title
in the upper-middle band.
---
## 2. Thumbnail with a punchy phrase
**User:** "YouTube thumbnail, the words 'SHIP FASTER' huge on the left, a rocket on the right."
```json
{
"high_level_description": "A high-energy thumbnail with the huge two-line phrase 'SHIP FASTER' on the left and a stylized rocket launching on the right.",
"style_description": {
"aesthetics": "loud, saturated, high-energy, big-text thumbnail style",
"lighting": "bright punchy lighting with strong contrast",
"medium": "bold flat illustration",
"art_style": "modern poster illustration, thick outlines",
"color_palette": ["#FF3B30", "#FFD60A", "#0B0B1A", "#FFFFFF"]
},
"compositional_deconstruction": {
"background": "Bold diagonal split background, deep navy on the right, warm yellow burst on the left.",
"elements": [
{
"type": "text",
"bbox": [250, 40, 750, 520],
"text": "SHIP FASTER",
"desc": "Massive heavy condensed sans-serif, two lines, all caps, white with a thick dark outline, left-aligned.",
"color_palette": ["#FFFFFF", "#0B0B1A"]
},
{
"type": "obj",
"bbox": [200, 560, 850, 960],
"desc": "A stylized cartoon rocket launching upward with a bright flame trail, slight tilt.",
"color_palette": ["#FF3B30", "#FFD60A", "#FFFFFF"]
}
]
}
}
```
Keep thumbnail phrases to 1–3 words — that's where the text rendering is strongest.
---
## 3. Brand CTA with exact colors
**User:** "End-card CTA for Digital Samba: 'Start your free trial' button, on-brand colors."
> When the user names a brand, read `brands/<brand>/brand.json` for the real hex values and place
> them in the palettes. The placeholders below stand in for those.
```json
{
"high_level_description": "A clean end-card with the headline 'Start your free trial' and a rounded call-to-action button, in Digital Samba brand colors.",
"style_description": {
"aesthetics": "clean, trustworthy, modern SaaS",
"lighting": "even soft lighting, airy",
"medium": "flat UI-style vector graphic",
"art_style": "minimal product marketing card, lots of whitespace",
"color_palette": ["#FFFFFF", "#1456F0", "#0B1B3A"]
},
"compositional_deconstruction": {
"background": "Soft white-to-pale-blue gradient, very clean.",
"elements": [
{
"type": "text",
"bbox": [300, 150, 460, 850],
"text": "Start your free trial",
"desc": "Friendly bold sans-serif headline, dark navy, centered.",
"color_palette": ["#0B1B3A"]
},
{
"type": "obj",
"bbox": [560, 340, 680, 660],
"desc": "A rounded rectangular button with subtle shadow.",
"color_palette": ["#1456F0"]
},
{
"type": "text",
"bbox": [585, 360, 655, 640],
"text": "Get started free",
"desc": "Button label, medium-weight white sans-serif, centered on the button.",
"color_palette": ["#FFFFFF"]
}
]
}
}
```
Note the layered text-over-object: the button is an `obj`, its label is a separate `text` element
with a tighter bbox sitting inside the button's bbox.
---
## 4. Quote / stat card (multi-line text + layout)
**User:** "Stat card: big '94%' with 'of teams shipped faster' underneath."
```json
{
"high_level_description": "A minimal stat card with a giant '94%' figure and the supporting line 'of teams shipped faster' beneath it.",
"style_description": {
"aesthetics": "confident, minimal, data-forward",
"lighting": "flat even studio light",
"medium": "flat vector graphic design",
"art_style": "clean infographic card",
"color_palette": ["#0B0B1A", "#00E0C6", "#FFFFFF"]
},
"compositional_deconstruction": {
"background": "Solid deep navy with a faint radial glow behind the figure.",
"elements": [
{
"type": "text",
"bbox": [220, 250, 560, 750],
"text": "94%",
"desc": "Enormous bold sans-serif figure, bright cyan, centered.",
"color_palette": ["#00E0C6"]
},
{
"type": "text",
"bbox": [600, 200, 700, 800],
"text": "of teams shipped faster",
"desc": "Medium-weight white sans-serif subline, centered beneath the figure.",
"color_palette": ["#FFFFFF"]
}
]
}
}
```
---
## 5. Photographic scene (when to use `photo`)
**User:** "A photo of a modern office at golden hour for a background, no text."
```json
{
"high_level_description": "A warm, modern open-plan office bathed in golden-hour light, no text.",
"style_description": {
"aesthetics": "warm, aspirational, editorial",
"lighting": "golden hour sunlight streaming through floor-to-ceiling windows, long soft shadows",
"photo": "35mm, f/2.8, shallow depth of field, eye-level",
"medium": "photograph",
"color_palette": ["#F4C77E", "#3A2E25", "#D9C8B4"]
},
"compositional_deconstruction": {
"background": "Open-plan office with wood desks and plants, large windows with sun flare at the far end.",
"elements": [
{
"type": "obj",
"bbox": [350, 100, 800, 500],
"desc": "A wooden desk with an open laptop and a steaming coffee mug, foreground left.",
"color_palette": ["#6B4E2E", "#1B1B1B"]
},
{
"type": "obj",
"bbox": [300, 600, 750, 780],
"desc": "A leafy potted plant catching warm backlight.",
"color_palette": ["#3E5C2E"]
}
]
}
}
```
Note the key order flips for photos: `aesthetics → lighting → photo → medium → color_palette`.
---
## Good vs. bad at a glance
| Bad (plain text) | Good (JSON caption) |
|------------------|---------------------|
| `"Title card saying AI Engineering Review"` | Example 1 — headline as a `text` element, bbox, brand palette |
| Text described inside `desc` | Literal string in the `text` field of a `text` element |
| `color: blue` | `"color_palette": ["#1456F0"]` (uppercase hex, per element) |
| Paragraph of body copy in one text element | 1–3 word headline + short subline, each its own element |
| Lowercase hex `#1456f0` | Uppercase `#1456F0` |
prompting.md
# Ideogram 4 — JSON Caption Prompting
Ideogram 4 is trained **exclusively** on structured JSON captions where every element is named
explicitly. This file is the reference for building those captions. The whole prompt-adherence and
text-rendering advantage flows from following this schema precisely — including its quirks (strict
key ordering, an unusual bbox axis order).
## Top-Level Structure
A caption is a JSON object with three top-level fields:
| Field | Required | Purpose |
|-------|----------|---------|
| `high_level_description` | Optional but **strongly recommended** | 1–2 sentence summary of the whole image |
| `style_description` | Optional object | Visual aesthetics, medium, global color palette |
| `compositional_deconstruction` | **Required** object | Spatial layout: background + every element |
## `style_description`
Must contain **exactly one** of `photo` or `art_style`:
- `photo` — for photographic images
- `art_style` — for illustrations, paintings, 3D renders, vector/graphic design
Required keys when the block is present: `aesthetics`, `lighting`, `medium`. Optional: `color_palette`.
### Strict key ordering (this matters)
> "The model was trained on JSON with a consistent key order, so maintaining it improves
> generation quality." Deviating is *allowed* but samples outside the training distribution.
- **Photos:** `aesthetics` → `lighting` → `photo` → `medium` → `color_palette`
- **Non-photos:** `aesthetics` → `lighting` → `medium` → `art_style` → `color_palette`
```json
"style_description": {
"aesthetics": "bold, modern, high-contrast corporate",
"lighting": "flat even studio lighting, no harsh shadows",
"medium": "flat vector graphic design, clean geometric",
"art_style": "minimalist tech poster, generous negative space",
"color_palette": ["#0B0B1A", "#00E0C6", "#FFFFFF"]
}
```
## `compositional_deconstruction`
Two required fields:
- `background` — string describing the environment / backdrop
- `elements` — array of objects and text, each with a **fixed key order**
### Element: object (`type: "obj"`)
Key order: `type` → `bbox` → `desc` → `color_palette`
```json
{ "type": "obj", "bbox": [200, 100, 800, 450], "desc": "A sleek silver laptop, three-quarter view, screen glowing.", "color_palette": ["#C0C0C0", "#1B1B2F"] }
```
### Element: text (`type: "text"`)
Key order: `type` → `bbox` → `text` → `desc` → `color_palette`
The `text` field holds the **literal string to render**; `desc` describes how it looks
(font weight, style, placement feel).
```json
{ "type": "text", "bbox": [120, 80, 300, 920], "text": "AI ENGINEERING REVIEW", "desc": "Bold heavy sans-serif headline, all caps, crisp white, centered.", "color_palette": ["#FFFFFF"] }
```
## Bounding Boxes (`bbox`)
- Format: **`[y_min, x_min, y_max, x_max]`** — note the **y-first** axis order (row before column).
- Coordinates are **normalized 0–1000**, where `0` is top/left and `1000` is bottom/right.
- Optional per element. Omit when you don't care where something lands; include for precise layout.
Quick mental map (full frame is 0–1000 on each axis):
```
x: 0 ............... 500 ............... 1000
y=0 ┌───────────────────────────────────┐
│ top band [0,0, 250,1000] │ ← headline / title text here
│ │
y=500 │ center [350,150, 650,850] │ ← hero subject
│ │
y=1000│ bottom [800,0, 1000,1000] │ ← CTA / footer text
└───────────────────────────────────┘
```
So a centered headline across the top third is `[80, 100, 280, 900]`
(`y_min=80, x_min=100, y_max=280, x_max=900`).
## Color Palette Rules
- Up to **16** colors in `style_description.color_palette`; up to **5** per element.
- Must be **uppercase hex**: `#RRGGBB` (e.g. `#1B1B2F`, not `#1b1b2f` or `rgb(...)`).
- Include both background and contrast/accent colors for controlled lighting.
- Per-element palettes override/steer that element specifically — ideal for forcing exact brand
colors on a logo or headline.
## Serialization Quirks
When emitting the JSON string the model consumes:
- Use compact separators: `separators=(",", ":")` (no spaces) — the `--json` path in the tool
handles this; if hand-passing, compact JSON is safest.
- `ensure_ascii=False` so non-ASCII / multilingual text passes through literally.
- Preserve key order exactly as above.
## Magic Prompt vs. Claude
Ideogram ships an LLM "magic prompt" that expands plain text → JSON. Three configs exist:
`ideogram-4-v1` (free hosted default), `claude-opus-v1`, `claude-sonnet-v1` (both via OpenRouter).
Their docs warn the shipped expander "is **not** the same used in production — results will differ."
**Claude Code runs on Opus.** So the highest-quality path is: Claude builds the caption directly
using this skill, rather than round-tripping through the weaker free expander or adding an
OpenRouter dependency. Treat *yourself* as the magic-prompt LLM.
## Prompting Principles
1. **Name every element explicitly.** The model rewards exhaustive captions. Don't write "a busy
office" — list the desk, the monitor, the plant, the window light.
2. **Put literal on-image text in a `text` element**, never bury it in a description. This is what
unlocks the legible-text advantage.
3. **Keep rendered strings reasonable.** A headline + subhead + CTA is fine (its strength); a
paragraph of body copy still degrades.
4. **Use bboxes for layout-critical images** (title cards, thumbnails). Skip them for loose scenes.
5. **Use per-element palettes to pin brand colors** on logos/headlines; global palette for mood.
6. **Match the style block to intent** — `photo` for realism, `art_style` for design/graphic work.
Most title cards and thumbnails are `art_style`.
## Common Pitfalls
| Pitfall | Fix |
|---------|-----|
| Plain-text prompt, generic result | Build the JSON caption — that's the whole point |
| Text rendered garbled/misspelled | Put it in a `text` element with the literal string in `text`, keep it short |
| Wrong colors | Uppercase hex, per-element `color_palette` on the headline/logo |
| Text in wrong spot | Add a `bbox` (remember y-first: `[y_min,x_min,y_max,x_max]`, 0–1000) |
| Mediocre adherence | Add `high_level_description`; name more elements; keep key order |
| `lowercase hex` ignored | Always `#RRGGBB` uppercase |
SKILL.md
---
name: ideogram4
description: Prompting patterns for Ideogram 4 text-to-image — best-in-class in-image text rendering and exact color/layout control via structured JSON captions. Use when generating images that need legible on-image text (title cards, thumbnails, logos, signage, CTAs), precise brand colors, or controlled spatial layout. Triggers include title slide image, thumbnail with text, on-image text, legible text in image, brand color palette image, bounding-box layout, Ideogram.
---
# Ideogram 4 Skill
Text-to-image generation with **Ideogram 4** (9.3B, open-weight, released June 2026). Its
superpower is **best-in-class in-image text rendering** — it beats much larger models
(FLUX.2 dev 32B, Qwen-Image 20B, Hunyuan 80B) at rendering legible signage, logos, captions,
and multi-line text — plus **exact color-palette and bounding-box control**.
That advantage is **locked behind a structured JSON caption format**. A plain-text prompt gets
you FLUX-level results and misses the entire point of using this model. This skill teaches
Claude to act as the "magic prompt" expander — turning a user's casual request into the JSON
caption Ideogram 4 was trained on.
> **Backend:** The toolkit uses Ideogram's **hosted v4 API** (not self-hosted weights). The API
> accepts a structured `json_prompt`, so everything this skill teaches applies directly — Claude
> builds the caption, the tool posts it as `json_prompt`. Paid API plans include a **commercial
> license**, which the self-hostable weights (non-commercial) do not — that's why we use the API.
> Cost is ~$0.03/image (turbo) to ~$0.10/image (quality).
## When to Use This Skill
Reach for Ideogram 4 (over FLUX.2) when the image needs:
- **Legible on-image text** — title cards, thumbnails, lower-thirds backgrounds, signage, logos,
quote cards, CTAs with a headline baked in
- **Exact brand colors** — hex color-palette conditioning, per-element
- **Controlled layout** — bounding boxes place text/objects in specific regions
- **Multilingual text** in the image
Use **FLUX.2** instead when: the image has no critical text, you need commercial-licensed output,
or you just want a fast atmospheric background. FLUX takes plain natural-language prompts; Ideogram
wants JSON. See `tools/flux2.py`.
## The One Thing to Get Right
**Always emit a structured JSON caption, not a plain sentence.** The model is trained
*exclusively* on JSON captions that name every element explicitly. Claude is a better expander
than Ideogram's free hosted magic-prompt (their own docs note the shipped one "is not the same
used in production"), so build the caption yourself using this skill rather than passing raw text.
Minimal valid caption:
```json
{"high_level_description":"A sailboat at sunset on calm water.","style_description":{"aesthetics":"serene, warm, golden hour","lighting":"golden hour backlighting","photo":"wide angle, f/8","medium":"photograph","color_palette":["#FF6B35","#F7C59F","#004E89"]},"compositional_deconstruction":{"background":"Calm ocean at low horizon with orange-pink sky.","elements":[{"type":"obj","desc":"White triangular sail silhouetted against the setting sun."}]}}
```
Full schema, strict key-ordering rules, and the bbox coordinate system are in **`prompting.md`**.
Worked title-card / thumbnail / quote-card examples are in **`examples.md`**.
## Quick Reference — `tools/ideogram4.py`
> Thin wrapper over Ideogram's hosted v4 API. Needs `IDEOGRAM_API_KEY` in `.env`
> (key from developer.ideogram.ai). `--json` posts the caption as the API's `json_prompt`
> field (no server-side magic prompt — Claude is the expander); `--prompt` posts `text_prompt`.
```bash
# Hand-authored JSON caption (the recommended path for text/layout) — Claude writes caption.json
uv run tools/ideogram4.py --json caption.json --output title.png
# Caption from stdin (Claude can pipe it directly)
cat caption.json | uv run tools/ideogram4.py --json - --output title.png
# Plain prompt — Ideogram's server-side magic prompt expands it (weaker; prefer --json)
uv run tools/ideogram4.py --prompt "Title card: 'AI ENGINEERING REVIEW' bold white on dark" --output title.png
# Inject brand hex colors into the caption's palette (JSON mode)
uv run tools/ideogram4.py --json caption.json --brand digital-samba --output cta.png
# Quality tier + resolution
uv run tools/ideogram4.py --json caption.json --speed QUALITY --resolution 2048x2048 --output slide.png
```
## Ideogram 4.5 (opt-in)
`--model 4.5` sends the same flags to Ideogram 4.5 on the v2 API
(`POST /v2/image/generate/ideogram-4-5`, multipart like the v1 call). It has been run live:
**JSON captions render near-perfect text; plain prompts add garbled filler text**, so always
send a JSON caption to 4.5. Ideogram 4 stays the default because 4.5 sizes must be multiples
of 32, which rules out exact YouTube 1280x720 (`/thumbnail` uses 4.0 for that).
```bash
uv run tools/ideogram4.py --model 4.5 --json caption.json --speed QUALITY --resolution 1440x2560 --output card.png
```
- **Prompt:** both modes go in 4.5's single `prompt` field. `--json` sends the caption as
compact JSON. 4.5 reads the Ideogram 4 caption format, and a valid caption skips
server-side magic prompt. `--prompt` text is expanded by magic prompt, as on v1.
- **`--speed` maps to `quality`**: TURBO, DEFAULT and QUALITY at the same price as on
Ideogram 4, and FLASH, which has no Ideogram 4 price, to low (Ideogram's list prices,
checked 2026-10-12; 1K and 2K sizes cost the same):
| `--speed` | 4.5 `quality` | Per image |
|-----------|---------------|-----------|
| FLASH, TURBO | `low` | $0.03 |
| DEFAULT (the default) | `medium` | $0.06 |
| QUALITY | `high` | $0.10 |
4.5's `very_low` tier needs source images, so the tool never sends it. On `--model 4`,
FLASH is still sent as is, but Ideogram's docs say it currently returns HTTP 400.
- **`--resolution` is sent as `size`:** `auto`, or WIDTHxHEIGHT with both sides multiples of
32 and at least 256px, at most 2048x2048 pixels in total, and an aspect ratio of at most
6:1. Omit it and Ideogram picks a size from the prompt. The tool checks these rules before
calling the API and exits 2 when a size breaks them. `1440x2560` (the 9:16 card size) and
`2048x2048` pass. Six Ideogram 4 presets don't, and the error suggests a replacement:
720x1280 (1440x2560), 1280x720 (2560x1440), 720x1440 (1440x2880), 1440x720 (2880x1440),
1296x3168 (1280x3072), 3168x1296 (3072x1280). Without source images Ideogram also needs
one of its supported 1K/2K presets, a list it doesn't publish, so a size that passes the
checks can still get an HTTP 400.
## Key Files
- `prompting.md` — full JSON schema, strict key ordering, bbox coordinate system, palette rules
- `examples.md` — worked captions for title cards, thumbnails, quote cards, brand CTAs
## Video Production Fit
Ideogram 4's niche in the toolkit is **slides and thumbnails with baked-in text**, where FLUX and
LTX-2 fail (both render garbled text). Natural pairings:
| Use case | Why Ideogram 4 |
|----------|----------------|
| Title-card / CTA background **with headline text** | Legible text + exact brand hex colors in one pass |
| YouTube/social **thumbnail with a punchy phrase** | Big readable text is its strongest suit |
| Quote card / stat card | Multi-line text + layout control via bboxes |
| Signage/logos inside a product-demo scene | In-image text other models can't render |
Then feed the still into Remotion (`<OffthreadVideo>`/`Img`) or animate it with `tools/ltx2.py --input`.