54 KiB
See
image-base.mdfor the common framework. For the web sourcing path, seeimage-searcher.md.
Image_Generator Reference Manual
Role definition for the AI image generation path: convert each Acquire Via: ai row into an optimized prompt, generate the image, and save it to project/images/; also defines the slice derivation path for AI-generated illustration sheets.
Trigger: resource list rows with Acquire Via: ai or slice. The role is loaded only when at least one such row exists.
1. Core Principle — Maximize AI Image Capability in Service of the Deck
AI images exist to serve the deck's communication goal. Pick whatever combination of page_role and text_policy makes the page work best.
Two page roles (orthogonal to type):
page_role |
Use |
|---|---|
local |
Image occupies a region of an SVG page (left half, right column, hero band, accent corner). Composition is the AI's call — fill the region as the page design wants |
hero_page |
Image is the page's main voice — cover, chapter divider, mood transition, single-number hero, closing quote. SVG above may be minimal or empty |
Two text policies (orthogonal to page_role):
text_policy |
Use |
|---|---|
none |
No text inside the image |
embedded |
Image contains text as part of the artwork — decorative lettering, designed title, hand-lettered keywords, infographic labels, anything the page needs |
Hard rule — only what's actually hard:
- Same
deck_rendering+ samedeck_palettefor every image in the deck - HEX codes and color names are rendering guidance — never visible text in the image
- Long body copy / data points / bulleted lists / long quotes stay in SVG (improving them later means regenerating the image, which is expensive)
- In-image text is only for words that will not need editing later — visual keywords, decorative lettering, mood words. Editable text (titles that may be reworded, subtitles, dates, authors, captions, body) belongs in SVG. Changing one in-image word costs an image regeneration; one SVG word costs a keystroke.
- Prompts are one coherent prose paragraph, not tag soup (a model-output reality, not an aesthetic choice)
Everything else is the AI's judgment per page. No mandated padding, no type-locked text_policy, no scenario whitelists for hero_page.
2. Three Dimensions
Every AI image is described by three orthogonal dimensions. Lock them in this order: Rendering (deck-wide) → Palette (deck-wide) → Type (per image).
| Dimension | Decides | When fixed |
|---|---|---|
| Rendering | Visual style family (vector / sketch-notes / 3d-isometric / corporate-photo / …) | Once per deck — every AI image in the deck shares one rendering |
| Palette | How the deck's HEX colors are used (proportion + role + temperament). HEX values come from design_spec.colors, not from the palette |
Once per deck |
| Type | What the image's internal composition skeleton looks like — geometric layout of a local infographic block (infographic / flowchart / framework / matrix / cycle / funnel / pyramid / comparison / timeline / map / scene). Only applies to page_role: local; for page_role: hero_page, describe composition with §4.1 primitives instead of picking a type. |
Per image |
What rendering vs palette means: rendering is how the image is drawn (line quality, texture, depth). Palette is how colors are distributed and behave (which color dominates, which is accent, what proportion). The HEX values come from Strategist; palette is the usage contract for those HEX values.
2.1 Where to find each dimension
| Reference | Loaded |
|---|---|
image-renderings/_index.md — rendering catalog + auto-selection table |
Always (Step 1 below) |
image-palettes/_index.md — palette catalog + auto-selection table |
Always (Step 1 below) |
image-type-templates/_index.md — type catalog + auto-selection table |
Always (Step 1 below) |
image-renderings/<chosen>.md |
After Step 2 picks the rendering — only the chosen one |
image-palettes/<chosen>.md |
After Step 2 picks the palette — only the chosen one |
image-type-templates/<chosen>.md |
After Step 3 picks the type per image — only the types actually used |
Hard rule — on-demand loading:
- Read the three
_index.mdfiles once at role entry. - After locking dimensions, read only the specific rendering / palette / type files you selected.
- Never glob-read an entire subdirectory (
image-renderings/*.mdis forbidden). Token cost balloons and the AI loses focus.
3. Workflow
Step 1 — Load the dimension indices
Read all three index files. They are short (~50 lines each) and contain auto-selection tables that let you map design_spec signals → dimension values without reading every detail file.
read_file references/image-renderings/_index.md
read_file references/image-palettes/_index.md
read_file references/image-type-templates/_index.md
Step 2 — Resolve deck-wide rendering + palette
Primary path — Strategist already locked these in spec_lock.md colors:
image_rendering: vector-illustration
image_palette: cool-corporate
If both fields are present, use them directly — Strategist made the decision in h.5 with full d-e-f-g-h linkage context. Do NOT re-decide.
Hard rule — custom escape hatch: when either value is the literal string custom, do NOT read_file the preset library for that dimension. Read the sibling *_behavior line from spec_lock.md colors and splice that prose into the prompt in place of the preset's fewshot snippet. The behavior prose owns the style paragraph (for custom rendering) or the proportion/role rules (for custom palette). See image-renderings/_index.md §1.5 / image-palettes/_index.md §2 for the invocation rules.
Fallback path — when spec_lock.md lacks both fields (legacy decks or pipelines that skipped h.5):
| Signal | Maps to |
|---|---|
design_spec.md d. Style mode + descriptor |
Rendering (consult renderings _index.md auto-selection table) |
design_spec.md e. Color Scheme (HEX) + content vibe |
Palette (consult palettes _index.md auto-selection table) |
design_spec.md f. Icon library |
Sanity check: chosen rendering should be compatible with the icon library's visual weight |
If the auto-selection table surfaces multiple candidates, pick the first; do not present a choice to the user.
Tell the user: when falling back, print one line "spec_lock.md missing
image_rendering/image_palette— inferring<X>/<Y>from design_spec. For optimal deck consistency, lock these in Strategist h.5." Then proceed.
Then read_file the single resolved rendering file and the single resolved palette file. These two files give you:
- The 80-120 word style paragraph (rendering)
- The proportion / role / temperament rules for the deck's three HEX values (palette)
- Two ready-to-paste prompt snippets per file (fewshot)
Step 3 — Per-image type + assembly
For each Acquire Via: ai row in design_spec.md §VIII:
- Determine type — only when
page_role: local(the image sits as a region block on an SVG page). Match the row'sPurposeagainst the_index.mdauto-selection table (methodology visualization →framework; process steps →flowchart; SWOT/Eisenhower →matrix; PDCA / flywheel →cycle; etc.).Purposeis authoritative for picking among the 11 internal-composition types. Whenpage_role: hero_page, skip type selection and describe composition directly using §4.1 primitives (single-subject / portrait / typographic / atmospheric). - Determine
text_policy— Strategist's value wins when set. Otherwise picknoneorembeddedbased on whether in-image text serves the page. Long body / data / lists stay in SVG. - Determine
page_role— Strategist's value wins when set. Otherwise picklocalorhero_pagebased on whether the image carries the page or sits inside one. read_file references/image-type-templates/<type>.md(only if not already read — types are commonly reused across images in one deck)- Assemble the prompt by combining:
- The rendering's style paragraph (from Step 2)
- The palette's proportion + role rules applied to the deck's HEX values (from Step 2)
- The type's structural layout (from Step 3)
- The image's specific
Referenceintent (fromdesign_spec.md §VIII) - The container sizing guidance from the type file (so the model knows it's painting a local block, not a full canvas)
- The hard rules from §5 below (HEX-not-as-text, simplified figures, text policy)
The assembled prompt is one cohesive paragraph, not a bulleted list of tags. See §4 for the assembly template.
Step 4 — Write the manifest and execute the confirmed path
Write project/images/image_prompts.json per §6. Then follow §7 Path Selection. image_gen.py --manifest is Path A only; confirmed host-native runs the host image tool directly, and confirmed manual renders the Markdown sidecar and hands off without API generation.
4. Prompt Assembly Template
Every assembled prompt follows this paragraph structure. Write prose, not tag soup.
[Rendering style paragraph — 80-120 words from the chosen rendering file].
[Palette behavior — apply the chosen palette's proportion + role rules to the deck's HEX values, e.g. "primary #1E3A5F dominates as the main shape, secondary #F8F9FA provides 60% breathing space, accent #D4AF37 appears in one or two emphasis points only"].
[Type-specific composition — from the chosen type file, e.g. "central hub node with four radiating satellite nodes connected by clean lines"].
[Image-specific subject — translated from the row's Reference intent into concrete visual nouns].
[Container note — "composed as a {W}x{H}px image for {page_role} use"; add composition cues only when the page actually needs them. SVG-overlay-reservation cues ("leave the lower band calm — SVG title overlays it", "keep the right third calmer for SVG text") are valid **only** when `page_role: hero_page` (SVG sits on top of the image). For `page_role: local`, the image sits inside a region block and the SVG layer never overlays its interior — never reserve overlay space in a local prompt].
[Hard rules — see §5].
Word budget: 150-300 words. Embedded-text prompts skew longer; pure background prompts can be shorter.
Forbidden — tag-soup prompts:
❌ "modern, flat design, gradient, vibrant, professional, clean, 4K, high quality"
This produces generic, model-average output. The model is not weighting your tags — write one coherent visual scene instead.
4.1 Hero-page composition primitives
When page_role: hero_page (the image is the page's main voice — cover, chapter divider, mood transition, signature stat, closing quote), the image's internal composition does not need its own structural type (matrix / cycle / framework etc. are for local infographic blocks). Instead, describe the composition directly in the prompt using one of the four primitives below.
Primitive A — single dominant subject (product / object / concept hero)
One dominant subject occupying 60-70% of the canvas, positioned with intent (centered, rule-of-thirds offset, or slight left/right). Supporting context <30% of canvas weight. Generous negative space — at least 15% padding on the subject's "open" side. No second-place subject competing.
Use for: product reveal, concept introduction, chapter title visual, brand statement.
Primitive B — single human subject (portrait)
One person, frontal or three-quarter turn, head + upper body. Subject occupies 50-65% of canvas height, centered or rule-of-thirds offset. Eyes at the upper-third horizontal line. Background neutral, minimal, or softly blurred. No competing foreground objects. At least 15% padding above the crown.
Use for: founder profile, speaker bio, testimonial page, executive intro. Pair with rendering: corporate-photo for photographic realism; otherwise the §5.2 simplified-figures rule applies.
Primitive C — typographic hero (the text is the image)
The image's central content is one large text element — a short headline, big number, or single word — rendered as art, occupying 40-60% of canvas height. Minimal supporting visual (small icon, geometric anchor, accent line) at <25% weight. At least 20% padding around the text.
Use with text_policy: embedded. Must obey the §5.3 rule — text that is part of the artwork and stable can be embedded; copy that must stay exact or editable goes to SVG overlay (switch to Primitive D).
Primitive D — atmospheric backdrop (no subject)
Atmospheric field with no dominant subject — gradients, subtle patterns, or restrained color blocks. Small geometric anchor optional, placed in a corner or along an edge, never centered. The center 60-70% of the canvas must stay calm to receive SVG title/text overlay.
Applies to page_role: hero_page only. The "calm center for SVG overlay" contract is the defining feature of this primitive — and it only holds when SVG actually sits on top of the image. page_role: local images live inside a region block; the SVG layer never overlays their interior, so Primitive D is not a valid choice for local. Local schematic / scene / chart images use the §3 type templates instead.
Use for: cover background, chapter divider background, breathing-page background, any page where the SVG layer carries the words and the image only sets tone.
Primitive E — custom (escape hatch)
When none of A/B/C/D describe the page's intended layout (triptych, asymmetric multi-focal, narrative diorama, etc.), write the composition description directly into the prompt's composition sentence — same paragraph slot A/B/C/D occupy, but in your own words. No new field; the freedom is in the prose.
Hard rule — custom composition prose:
| Rule | Value |
|---|---|
| Length | One paragraph, 2-5 sentences, replacing A/B/C/D's opening paragraph |
| Required content | subject count, layout structure, where breathing room sits, where SVG overlay can claim canvas |
| Forbidden | Naming a competing primitive ("like A but two subjects") |
Example opening for a triptych hero:
Triptych — three equal vertical bands of canvas, each holding one symbolic object centered in its band; objects share a low horizon line; bands separated by 2px hairline rules; collectively reads as a single composed page. [...rest of prompt continues with rendering paragraph + color behavior + container note...]
Fewshot examples per primitive (one each, deck-context placeholders intact):
A — 3d-isometric + tech-neon product reveal, text_policy: none, 600×600
3D isometric illustration in true 30°/30°/30° projection. One dominant product-form subject — a stylized device or sleek tech object — occupies the center of the canvas at roughly 65% of the area. The subject is rendered in primary electric blue
#0EA5E9on its lit faces, with 15% darker tonal shift on shadowed faces. A subtle 8%-opacity outer glow halo surrounds the subject. Small supporting context: three thin connecting lines in accent vivid cyan#06B6D4arcing from the subject toward the canvas edges (suggesting connectivity), and a soft 8% drop shadow grounding the subject. Background is deep secondary navy#0A0E27(about 30% of canvas, including shadowed plane). The subject is clearly the singular focal element. Composed as a 600×600 hero block with 15% padding around the subject. NO text, letters, numbers, or labels anywhere. Color values are rendering guidance only.
B — corporate-photo + cool-corporate executive headshot, text_policy: none, 600×800
Editorial corporate portrait photograph of one professional executive. The person is centered slightly left of canvas center, photographed from chest-up at eye level, looking confidently toward the camera with a relaxed natural expression — not posed-stiff, not over-smiling. Professionally attired in a contemporary business setting (a tailored blazer, neutral palette clothing). Soft natural light from the upper left, gentle shadow on the right side of the face. Diverse, professionally attired subject, photorealistically rendered, contemporary styling. Background is a softly out-of-focus office context — secondary light gray
#F8F9FAwall with a subtle hint of primary deep navy#1E3A5Fin a blurred architectural element. Color grading is cool-corporate — restrained, professional. Shallow depth of field — subject sharp, background gently blurred. Subject's eyes positioned at the upper-third horizontal line. Composed as a 600×800 bio portrait with 10% padding. NO text, name tags, or captions in the image. Color values are rendering guidance only.
C — ink-notes + mono-ink big-number stat, text_policy: embedded, 800×500
Professional hand-drawn visual-note style on pure white background. The image's central content is the hand-lettered number "100x" — rendered in bold confident ink strokes occupying about 50% of the canvas height, centered with deliberate slight wobble characteristic of hand-lettering. Text is in English/Latin characters only. Beneath the number, a thin hand-drawn underline in ink. To the side of the number, one small hand-drawn doodle decoration — a star or upward arrow — adds visual rhythm. Accent coral
#E8655A(from the deck's accent) appears only as a tiny emphasis dot, totaling under 4% of canvas. Background is pure white#FFFFFF. Composed as an 800×500 typographic hero block with 20% padding around the number. No other text or labels in the image — just the "100x" headline and the small doodle.
D — vector-illustration + cool-corporate cover background, text_policy: none, 1280×720
Clean flat vector illustration backdrop. Atmospheric composition with no central subject — bold geometric shapes arranged along the canvas edges to leave the center calm. Primary deep navy
#1E3A5Fforms a confident diagonal block across the lower-left third; secondary light gray#F8F9FAoccupies the upper two-thirds as breathing space; accent gold#D4AF37appears only as one thin geometric line near the lower right corner (under 5% of canvas). Crisp 2px outlines, no gradients, single 8% soft drop shadow under the navy block. The central 60% of the canvas is deliberately calm and unbusy — designed to receive a slide title overlaid in SVG. Composed as a 1280×720 full-bleed PPT background. NO text, letters, numbers, signs, watermarks, or written symbols anywhere in the image. Color values are rendering guidance only — do not display HEX codes or color names as text. Simplified geometric shapes only.
4.2 Prompt depth — expand for subject-domain accuracy
Hard rule: For images whose deck purpose calls for subject-domain accuracy (scientific figures, academic paper figures, engineering schematics, medical / legal / regulated content), expand the prompt without budget ceiling — 500-1000+ words is normal. The §4 word budget (150-300) is the routine-illustration default, not a cap.
Forbidden — pre-emptive shortening: never trim a subject-domain prompt to fit §4's budget. Name the field's visual conventions explicitly in the prompt.
Detail to name in the prompt (illustrative, not an enumeration to match):
| Domain | Conventions to spell out |
|---|---|
| chemistry / materials | IUPAC atom colors, bond conventions, lattice type, Å / ps units, subplot labeling (A / B / C circles), view angle |
| biology | cell compartment colors, scale bars, organelle conventions, staining palette |
| physics | axis labels with proper symbols, signature curve shapes, unit annotations, peak labeling format |
| engineering | schematic notation, dimension callouts, section-cut conventions |
When uncertain about field conventions: read sources/ before drafting the prompt.
4.3 Illustration sheets — one generation, many spot elements
When a deck wants several small spot illustrations scattered as decorative accessories across pages (the illustration counterpart to icons), do not generate them one image per slot — that multiplies generation cost and lets the style drift between calls. Generate one sheet that lays out all the elements in a grid, then slice it. One call buys a set of elements with an identical style, palette, and line quality — the same cross-page consistency the deck-wide deck_rendering / deck_palette lock exists to protect.
When to use: the §VIII image resource plan needs ≥3 small spot illustrations from the same family across the deck. For a single hero/local image, stay with the normal one-row-per-image flow (§4.1). Use sheets only where decorative illustration genuinely lifts the page; an unused element costs nothing, but a deck papered in decoration reads cheap.
Hard rule: a spot sheet is a generation source, not a slide asset. The sheet row is never listed in spec_lock.md images and never referenced from SVG. Only the sliced element rows are placed.
Sheet prompt convention (one manifest item, page_role: local, text_policy: none, image_size chosen from final placement size):
- Choose the sheet
aspect_ratioand--gridfrom the target element shape. Do not default every sheet to1:1+ a symmetric grid. - Lay the elements out in an explicit R×C grid, evenly spaced with clear gutters, each element centered in its own cell and isolated (no element bleeds into a neighbor).
- State the intended cell shape in the prompt: compact square object, tall portrait element, or wide landscape vignette. Do not let the model shrink every subject into a centered square sticker.
- One flat single-color background across the whole sheet, set to the deck's background/secondary HEX — this is what lets the slicer key it out cleanly and lets the cut element sit on the slide without a visible box.
- Shared
deck_rendering+deck_paletteas always. NO text, labels, or numbers anywhere (§5.1, §5.3).
Cell geometry is designed, not assumed. slice_images.py --grid RxC cuts rows first and columns second. The cell ratio is:
cell_ratio = sheet_ratio * rows / cols
Use that deliberately. On a wide sheet (16:9, 21:9, 4:1, 8:1), 1xN makes each cell tall/portrait because the width is divided by N while height is kept; Nx1 makes each cell wide/landscape because height is divided by N while width is kept. A designed MxN grid is also valid when the resulting cell ratio matches the intended placements.
| Target spot shape | Sheet plan | Slice grid |
|---|---|---|
| Compact objects / badges | 1:1 sheet |
2x2, 2x3, or 3x3 |
| Tall side accents / upright objects | wide or square sheet | 1xN, or any MxN whose cells are portrait |
| Wide banners / horizontal vignettes | wide sheet | Nx1, or any MxN whose cells are landscape |
If one deck needs mixed shapes, create separate sheets per shape family unless one carefully designed grid gives every element enough room in its own cell. Keep the visual family consistent through the same deck_rendering and deck_palette, not by forcing all cells into one square sheet.
Resource contract — the sheet and its elements are different row kinds. A sliced element can only be placed if it exists as a resource the Executor is allowed to reference (spec_lock.md images). So §VIII carries two row kinds (full rules: design_spec_reference.md §VIII):
- Sheet row —
Acquire Via: ai,Type: Illustration Sheet, the intent prompt, named as the slice source with its intended cell shape and placement purpose (Reference: landscape footer-vignette spot set). It is generated in Step 5 but never placed on a slide — keep it out ofspec_lock.md images. Image_Generator resolves the exactaspect_ratio, grid, and slice command from this intent. - Element rows — one per used element,
Acquire Via: slice, filename matching a--namesoutput,Referencenaming the parent sheet + cell/element. These are placed — list every one inspec_lock.md images, usually with| no-crop(a tight-trimmed transparent spot should be fit, not cover-cropped). Their dimensions are filled in after slicing (Step 5 re-runsanalyze_images.py). Set each element row's Layout pattern from the decorative-cutout family, never a boxed container — see Placement below.
For traceability, add optional slice_grid and slice_names fields to the sheet item in image_prompts.json after choosing the geometry. image_gen.py ignores unknown item fields but preserves them in the manifest, so these fields document the exact command that must be used for slicing.
Slice with slice_images.py — cells are cut row-major into individual files in images/. With --alpha they are transparent cutout stickers (image-layout-patterns #63), not rectangular content images. Recommended flags: --names (semantic per-cell filenames matching the element rows; the count must equal rows*cols), --trim (tight-crop each cell so imprecise placement inside a cell doesn't leave lopsided margins), --alpha (knock the flat background out to transparency so an element drops onto any slide color):
python3 scripts/slice_images.py <project>/images/illus_sheet.png --grid 2x3 \
--names team,product,customer,growth,risk,vision --trim --alpha
Three constraints that decide whether it looks good:
- Flat background, matched to the slide.
image_gen.pyhas no transparent-background mode, so the cut element carries whatever was behind it. A flat sheet background (= deck background HEX) is what--alphakeys out and what makes non-keyed pieces blend. - Clean grid, or it cuts ugly. The model will not place every element perfectly; force a clear grid with gutters, and generate a few sheets (re-roll the same prompt) to pick the cleanest-laid-out one before slicing. State the exact row/column structure and cell shape so the model does not invent a square matrix.
--trimabsorbs the rest. - Generate only as large as needed. Each cell is a fraction of the sheet. Pick the smallest sheet size that keeps each sliced cell at least 1.5-2x the intended display size.
1Kis usually enough for small 80-160px decorative spots; use2Kfor medium 180-320px placements; reserve4Kfor large, cropped, or potentially enlarged elements.
Placement — these are decorative accessories, not boxed pictures. A transparent spot wasted in a centered rectangle looks cheaper than no spot at all. Each element row's Layout pattern comes from the decorative-cutout family in image-layout-patterns.md: #63 sticker/cutout, #4 bleed off the canvas edge, #58 corner fragment, #66 fade into the background, #69 slight editorial rotation, #49 asymmetric cluster. Push spots to the margins, let them run off-edge or sit behind/beside text, vary size and angle across pages, and overlap the content rather than reserving a tidy tile for them. Anchor most pages on one primary element and let the rest stay small (primary-per-page §h) — scattered same-weight tiles are exactly the generic look to avoid.
Through-line — one family, many roles. A spot sheet pays off more when the same motif family also drives the deck's cover and section dividers, so the deck reads as one designed system rather than a hero plus unrelated doodles. Because the slicer cuts a uniform grid, a large cover / divider anchor is not a giant cell in the spot sheet — generate it as its own page_role: hero_page image (§4.1 primitives) that shares this sheet's deck_rendering, deck_palette, and subject world. In §VIII the hero_page anchor row(s) and the slice spot rows then belong to one visual family (name the shared subject world in each Reference), differing only in scale and role. Plan this only when the deck leans into illustration — never a per-section quota; the planning rule lives in strategist.md (deck illustration motif).
5. Global Hard Rules
These rules apply to every prompt regardless of dimension choices. Append them as a closing sentence to every assembled prompt.
5.1 HEX is rendering guidance, not text
Image generation models occasionally paint color names and HEX values as visible labels in the image (a #1E3A5F swatch literally drawn as the string "#1E3A5F"). This destroys the image.
Append to every prompt:
Color values (HEX codes like #1E3A5F) and color names are rendering guidance only — do NOT display HEX codes, color names, or palette labels as visible text anywhere in the image.
5.2 Simplified human figures, no realistic faces
When the image contains people:
Human figures appear as simplified stylized silhouettes or symbolic representations — no photorealistic faces, no detailed anatomy, no celebrity likeness. Express role/emotion through posture, attire, and simple gestures.
Exception: when the chosen rendering is corporate-photo, photorealism is intentional — replace the above with: Diverse, professionally attired subjects. Editorial photography style, natural composition.
5.3 Text policy — two-layer ownership
Every AI-image page carries text in two layers:
| Layer | Owned by | Examples |
|---|---|---|
| Layer 1 (image-owned) | the prompt — baked into the raster | figure-internal annotations (axis labels, A / B / C markers, units, scale bars, panel labels); architecture / schematic module names, node labels, signal-path identifiers; hero typographic or decorative lettering that is the visual |
| Layer 2 (SVG-owned) | <text> overlay — fully editable |
page-level chrome (title, navigation, footer, body bullets, conclusion callout); readable copy, captions |
text_policy controls only Layer 1. AI judges per image; no global default bias.
When embedded is the right call — positive triggers (any one match flips the row from a none starting point to embedded; the editability rule at the tail of §5.3 still has final say):
| Trigger | Typical Layer 1 text |
|---|---|
| Paper-figure panel comparison (A/B/C, before/after) | Panel labels — A / B / C, or short panel descriptors |
| Textbook math / signal figure | Curve names (sin / cos), axis labels, unit symbols |
| Architecture / schematic following discipline conventions | Module names (Self-Attention, FFN, Add & Norm), node ids, signal-path tags |
| Data figure with stable axes | Axis labels, units, scale bars |
| Typographic hero (§4.1 Primitive C) | The designed word / number that is the image |
Defaulting an entire ai resource list to none because "SVG can always overlay" is the failure mode this table exists to break. When any row matches a trigger, start at embedded and verify the editability filter below still holds.
text_policy |
Prompt cue |
|---|---|
none |
"NO text of any kind anywhere in the image — no letters, numbers, signs, watermarks, labels, or written symbols." |
embedded |
Describe the Layer 1 text directly inside the visual scene: the word(s), how they're rendered, and the artistic treatment. |
Hard rule — cross-cutting: Layer 2 chrome stays SVG regardless of text_policy. Never bake the deck title, navigation, footer, body bullets, or conclusion callout into the image, even when embedded.
Forbidden — text that may be reworded: any word that may later change belongs in Layer 2, not Layer 1. Layer 1 is for stable visual identifiers and designed lettering that is part of the image itself.
Font choice for in-image text — free description, with the deck typography as one optional reference
The font for in-image text is a free natural-language description, not an enum. Pick whatever serves the image: blackletter for a heritage cover, hand-brushed for a manifesto poster, retro chrome 3D for Y2K, art-deco display for a luxury hero, ribbon script for a bookstore zine — any artistic treatment the image earns.
The table below is a reference for the one case where you want the in-image lettering to read as the same typographic family as the SVG body (e.g. a clean editorial deck where the cover title in the image should feel like the body Helvetica, not a surprise blackletter). Use it as a starting point, not a constraint.
spec_lock typography.font_family contains |
Optional descriptor if you want to echo the SVG body |
|---|---|
KaiTi / FangSong / Georgia / serif families |
"elegant serif lettering, refined letterforms" |
Microsoft YaHei / PingFang SC / Arial / sans-serif families |
"clean geometric sans-serif, modern letterforms" |
SimHei / Impact / Arial Black / display families |
"bold display lettering, heavy expressive strokes" |
Consolas / Courier New / monospace families |
"monospace technical lettering, fixed-width" |
| sketch-notes / ink-notes rendering, or no family specified | "hand-lettered organic strokes, natural variation" |
When to ignore the table:
- Decorative / background lettering, posters, large mood words → describe the artistic treatment freely
- Cover hero title that wants its own visual identity (blackletter, retro chrome, art-deco display, brushed script) → describe freely
- Sketch-notes / ink-notes / hand-drawn renderings where the lettering is part of the rendering itself → describe freely
- Any case where rendering + palette already imply a font character (e.g.
vintage-posterrendering implies period display lettering) → trust the rendering, no need to echo SVG body
When to use the table: a designed title (cover main title, chapter heading) on a deck whose visual identity is grounded in the SVG body typography, and where a surprise font choice would feel out of place.
In-image text vs SVG text — decide by editability, not by model capability
Layer 1 text is rasterized into the artwork — once generated it cannot be edited, corrected, searched, restyled, or reflowed. That is the durable reason to choose where text lives, independent of any backend's rendering ability or the script / length involved:
| Text | Layer |
|---|---|
| Part of the artwork and stable — decorative lettering, designed title, hand-lettered keyword, figure-internal identifiers (axis labels, panel letters, units) | Layer 1 (image) OK |
| Page chrome, body copy, captions, data values — anything that must stay exact, searchable, or may be reworded | Layer 2 (SVG) |
Generation is non-deterministic on every backend, but do not pre-judge by script or length — never push text to SVG, shorten a headline, or downgrade embedded to none on the assumption that a particular script or a long string "won't render". Decide where text lives by the editability rule above, not by guessed rendering ability. Name the exact characters to bake literally in the prompt; do not re-read the generated image to verify them.
Prefer in-image: text that is genuinely part of the artwork and will not be edited — a designed word, a stat lettering, a figure-internal label.
Push to SVG overlay instead: page chrome, captions, data values, or any copy that must stay exact or editable. When the headline must remain editable, switch to Primitive D (atmospheric backdrop) and overlay it as SVG text.
5.4 No brand names or trademarks in the subject
The image must not depict identifiable brand logos, trademarks, or product likenesses unless the row's Reference explicitly names a real brand asset the user owns.
6. Manifest Schema
Write project/images/image_prompts.json with this shape:
{
"project": "{project_name}",
"generated_at": "{ISO-8601 date}",
"deck_rendering": "vector-illustration",
"deck_palette": "cool-corporate",
"color_scheme": {
"primary": "#1E3A5F",
"secondary": "#F8F9FA",
"accent": "#D4AF37"
},
"items": [
{
"filename": "cover_bg.png",
"purpose": "Cover background (Slide 01)",
"page_role": "hero_page",
"text_policy": "none",
"aspect_ratio": "16:9",
"image_size": "2K",
"prompt": "{fully assembled paragraph per §4 — use §4.1 Primitive D for atmospheric cover}",
"alt_text": "Modern tech abstract background with deep blue gradient and digital waves",
"status": "Pending"
},
{
"filename": "framework_p05.png",
"purpose": "Methodology framework (Slide 05)",
"type": "framework",
"page_role": "local",
"text_policy": "none",
"aspect_ratio": "4:3",
"image_size": "1K",
"prompt": "{fully assembled paragraph per §4}",
"status": "Pending"
}
]
}
Field reference
| Field | Required | Source | Description |
|---|---|---|---|
deck_rendering |
yes | Step 2 lock | Single rendering name shared by all items in this deck |
deck_palette |
yes | Step 2 lock | Single palette name shared by all items |
color_scheme |
yes | design_spec.md §III |
HEX triplet from Strategist |
items[].filename |
yes | §VIII resource list |
Output filename with extension |
items[].type |
conditional | Step 3 per-image (only when page_role: local) |
One of 11 internal-composition types: infographic, flowchart, framework, matrix, cycle, funnel, pyramid, comparison, timeline, map, scene. Omit type entirely when page_role: hero_page — the composition comes from §4.1 primitives written directly into the prompt, not from a type file. |
items[].page_role |
yes | Step 3 per-image | local (default — region block on SVG page) or hero_page (image is page's main voice; SVG overlay minimal or empty) |
items[].text_policy |
yes | Step 3 per-image | none (image carries no text — explicit visual rule) or embedded (image contains decorative lettering, designed title, hand-lettered keywords, or stable visual identifiers like axis labels / subplot letters / unit symbols). AI judges per image; no global default bias — see §5.3. |
items[].aspect_ratio |
yes | Container sizing | Passed to image_gen.py --aspect_ratio |
items[].prompt |
yes | §4 assembly | The full assembled paragraph |
items[].image_size |
no | Container sizing | 512px / 1K / 2K / 4K |
items[].alt_text |
no | Accessibility | Short caption |
items[].slice_grid |
no | §4.3 sheet geometry | Illustration sheet only; exact RxC grid to pass to slice_images.py --grid |
items[].slice_names |
no | §4.3 sheet geometry | Illustration sheet only; semantic filenames to pass to slice_images.py --names |
items[].status |
yes | CLI manages | Pending initially; CLI updates to Generated / Failed / Needs-Manual |
Back-compat for legacy
typevalues: existing manifests usingbackground/hero/portrait/typography(the four removed pseudo-types) remain readable. Read them as:background→page_role: hero_page+ no type;hero→page_role: hero_page+ no type (use §4.1 Primitive A in prompt);portrait→page_role: local+ no type (use §4.1 Primitive B);typography→page_role: hero_page+text_policy: embedded+ no type (use §4.1 Primitive C). New manifests should follow the rule above (omittypewhenpage_role: hero_page).Existing manifests without
deck_rendering/deck_palette/type/page_role/text_policyremain valid — older items default topage_role: local,text_policy: none. Legacypage_role: full_page(pre-2026-05-15) is read ashero_page.
7. Generation Execution
Prerequisite: §3 Steps 1-3 complete;
images/image_prompts.jsonexists and validates. The manifest is the shared audit/source contract for all modes. It does not imply thatimage_gen.py --manifestshould run; that command is Path A only.
Path Selection (Deterministic)
C (AI-generated) supports three implementation modes sharing one image_prompts.json source:
| Trigger | Mode | Mechanism |
|---|---|---|
Default — IMAGE_BACKEND configured |
Path A: image_gen.py --manifest |
One command runs the whole manifest with concurrency; status writes back per item |
IMAGE_BACKEND not configured (or Path A fails) AND host has a native image tool |
Path B: Host-native tool | Agent invokes the host's image capability; outputs land at project/images/<filename> |
| Both Path A and Path B fail/unavailable | Offline Manual Mode | Manifest stays on disk; user generates externally from items[].prompt and places files at project/images/<filename> |
Selection logic — the confirmed user choice wins; absent one, fall back to the automatic A → B → C chain:
- Confirmed override (wins) — honor the confirmed image source. The chat choice is canonical; the Confirm UI is only a convenience surface that, when used, records the same choice to
<project>/confirm_ui/result.jsonasimage_ai_path(so there is noresult.jsonon the chat path — read the choice from the conversation). From either channel, if the choice is set and notauto, honor it directly, even when it contradictsIMAGE_BACKEND:api→ Path A (image_gen.py --manifest).host-native→ Path B (host's native image tool) — skip A and do not runimage_gen.py --manifest, even ifIMAGE_BACKENDis configured.manual→ Offline Manual (write prompts, render the Markdown sidecar, hand off; do not runimage_gen.py --manifest). ("use Codex's image tool" / "走接口生成" in chat =host-native/api.) If the chosen path turns out unavailable (e.g.host-nativebut the host has no image tool), fall through along the chain below from that point. Only when no source named a path (chat silent, andimage_ai_pathauto/ absent) does the automatic chain decide.
- Try Path A — if
IMAGE_BACKENDis configured (env or.env), runimage_gen.py --manifest. If it fails twice in a row, fall to Path B. - Try Path B — if
IMAGE_BACKENDwas not configured (A skipped), or A failed, and the host has a native image tool (Codex / Antigravity / Claude Code / similar), the agent invokes the host's image capability directly. - Fall to C (Offline Manual) — if B is also unavailable (no host-native tool) or fails, write prompts to
images/image_prompts.jsonand hand off to the user.
Hard rule: Step 4 is execution, not re-decision. Never present an interactive choice between paths here — image strategy was locked in Strategist Step 4 h item.
All three modes share one output contract: file at
project/images/<filename>. Step 6 SVG references are mode-agnostic.
Path A — image_gen.py --manifest (Default)
python3 scripts/image_gen.py \
--manifest project/images/image_prompts.json \
--output project/images
The CLI iterates items[] with adaptive concurrency, writes status back per item, and is idempotent: re-running only re-processes entries whose status is Pending or Failed.
Parameters:
| Parameter | Short | Description | Default |
|---|---|---|---|
--manifest |
- | Path to image_prompts.json |
— |
--concurrency |
- | Max concurrent requests; halves on rate-limit, min 1 | IMAGE_CONCURRENCY env or 3 |
--image_size |
- | Default size (512px/1K/2K/4K); per-item image_size wins |
1K |
--output |
-o |
Output directory | Manifest's parent dir |
--backend |
-b |
Override IMAGE_BACKEND for this run |
env |
--model |
-m |
Default model; per-item model wins |
Backend default |
--list-backends |
- | Print support tiers and exit | — |
The single-image form
image_gen.py "prompt" --filename ...is preserved for ad-hoc one-offs (re-rolling a single image) but is no longer the primary path.
Configuration sources:
- Current process environment variables
- First
.envfound in this order: current working directory, skill directory (e.g.~/.agents/skills/ppt-master/.env), clone repo root,~/.ppt-master/.env
Precedence:
- Current process environment wins
.envfills missing values only
| Variable | Required | Description |
|---|---|---|
IMAGE_BACKEND |
Required | Backend identifier; run image_gen.py --list-backends for the current set |
IMAGE_CONCURRENCY |
Optional | Manifest-mode default concurrency (CLI --concurrency wins) |
{PROVIDER}_API_KEY |
Required | Provider-specific API key, e.g. GEMINI_API_KEY, ZHIPU_API_KEY |
{PROVIDER}_BASE_URL |
Optional | Provider-specific custom endpoint |
{PROVIDER}_MODEL |
Optional | Provider-specific model override |
OPENAI_SIZE_PRESET |
Optional | OpenAI-compatible size mapping: auto, legacy, gpt-image, gpt-image-2, dall-e-2 |
OPENAI_RESPONSE_FORMAT |
Optional | OpenAI-compatible response field: auto, b64_json, url, omit |
OPENAI_QUALITY |
Optional | OpenAI-compatible quality field: auto, omit, low, medium, high, standard, hd |
Use provider-specific names only (e.g.
GEMINI_API_KEY,OPENAI_API_KEY). See.env.examplein clone mode or${SKILL_DIR}/.env.examplein skill-install mode for the full set per backend.
Note: OpenAI-compatible platforms that reject OpenAI-specific fields stay under
IMAGE_BACKEND=openai; configure theOPENAI_*compatibility knobs instead of adding a provider-specific backend.
IMAGE_API_KEY,IMAGE_MODEL, andIMAGE_BASE_URLare intentionally unsupported.
If
.envor the current environment contains multiple provider configs,IMAGE_BACKENDexplicitly selects the active one.
Support tiers (recommended usage): Core / Extended / Experimental. Run image_gen.py --list-backends for the current assignments.
Concurrency (manifest mode):
- Default 3 concurrent requests, halves on the first rate-limit response, minimum 1 (= serial fallback)
- Rate-limited items requeue automatically; per-item failures are recorded with
last_errorand skipped - Interrupting mid-run is safe — completed items keep
status: Generatedand are skipped on re-run - On normal completion the Markdown sidecar is re-rendered automatically; if the run is interrupted, run
--render-mdmanually to refresh the sidecar
Path B — Host-Native Image Tool
Triggered automatically when IMAGE_BACKEND is not configured (or Path A fails) and the host provides a native image generation tool (Codex, Antigravity, Claude Code's image tool, and similar). No user prompting required — the agent detects the host capability and proceeds. The user may also explicitly name this path ("use Codex's image tool") to force it even when IMAGE_BACKEND is configured.
- Agent invokes the host's native image tool directly; prompts come from
items[].prompt - Do not run
image_gen.py --manifestin Path B. That command is Path A and may use configured API/proxy backends even when the user confirmed host-native. - Still run
python3 scripts/image_gen.py --render-md project/images/image_prompts.jsonso the human-readable sidecar exists without touching any backend. - Batch for speed, mind the rate: when the host can run independent tool calls in parallel (e.g. Claude Code issues independent calls concurrently), fire several generations together in modest groups — a few rows at a time (~3–4), not the whole manifest at once — so their latency overlaps without flooding the host's image quota. When the host only runs tools serially, generate one row at a time. This mirrors Path A's default concurrency of 3.
- Outputs must land at
project/images/<filename-from-resource-list>. Match the Image Resource List dimensions when the host supports arbitrary sizes. Hosts with fixed native resolutions (common — e.g. ~1672x941 landscape / ~1086x1448 portrait) generate at the closest native size and backfill the actual pixels into the resource listDimensionscolumn — same convention as formula rows ("actual dimensions from formula manifest") and slice rows ("dimensions filled after slicing"). Do not upscale the file to fake the requested size (interpolation adds no detail); minor display-side upscaling (up to ~1.3x in practice) surfaces as a quality-checker warning — acknowledge and release per the warning policy. - Mark each item's
statusGeneratedin the manifest the moment its file lands — as each completes, not in one pass at the end (so an interrupted batch leaves accurate state) - Executor downstream is path-agnostic — no spec change required between Path A and Path B
Offline Manual Mode (C's third implementation mode)
Trigger: Both Path A and Path B fail or are unavailable.
Workflow (no user prompting; system enters this mode automatically):
- Verify
images/image_prompts.jsonwas written - Set
status: "Needs-Manual"on every affected item perimage-base.md§6 - Continue to Step 6 — SVG references
images/<filename>optimistically; Step 7 entry verifies presence - Print one consolidated handoff to the user:
- Filenames awaiting manual generation
- Pointer to
images/image_prompts.md(paste-ready### Image N:block per item) orimage_prompts.json(items[].prompt) - Target placement:
project/images/<filename>matching the resource list exactly - Resume command: re-run Step 7 once all expected files exist
User-initiated: When Strategist Step 4 captured "user wants manual generation" up front, Path A is skipped from the start; the workflow above runs as a planned mode.
The pipeline tolerates
Needs-Manualrows end-to-end. The user can leave the project, generate offline at their own pace, then resume Step 7.
AI-specific Failure Handling (extends image-base.md §6)
If Path A's backend fails twice in a row:
- Do not halt. Automatically attempt to fall back to Path B (Host-Native Tool).
- If Path B also fails or is unavailable, mark the row
Needs-Manual. - Report to user: filename, prompt used, error message.
- Fall through to Offline Manual Mode above.
If the alternate platform watermarks outputs (e.g. Gemini web), the repository includes
scripts/gemini_watermark_remover.py.
Guardrails (All Modes)
Hard rule:
- Do not claim an image is generated without an actual file at the expected path
Needs-Manualis set after a failed attempt OR on entering Offline Manual Mode — not as a way to skip work that automation could have done- Status transitions are evidence-driven:
Pending→Generated(file exists) orPending→Needs-Manual(no automation, or attempt failed once)
8. Common Issues & Variant Workflow
Reference field is blank — quick examples
When the Resource List row has no Reference, infer a reasonable image from Purpose. Examples (not prescriptions):
| Purpose | A reasonable starting point |
|---|---|
| Cover | page_role: hero_page + §4.1 Primitive A (single-subject) or D (atmospheric); choose text_policy by what the cover should communicate |
| Chapter divider | page_role: hero_page + Primitive D (atmospheric) or A (single-subject); often text_policy: embedded with a designed chapter title |
| Methodology / framework illustration | type: framework, page_role: local |
| Process / workflow illustration | type: flowchart, page_role: local |
| Before/After or two-option page | type: comparison, page_role: local |
| Team / lifestyle photo (group) | type: scene, page_role: local; rendering = corporate-photo or warm-scene |
| Single-person headshot / bio | page_role: local + §4.1 Primitive B (portrait); rendering = corporate-photo for photo realism |
| Big-number / hero quote block | page_role: hero_page + §4.1 Primitive C (typographic); text_policy: embedded |
| Mood transition / atmosphere | page_role: hero_page + Primitive D (atmospheric), or type: scene if narrative |
When Images Are Unsatisfactory
Diagnose the failure category, adjust the one specific dimension responsible, do not rewrite the whole prompt.
| Symptom | Most likely cause | Adjustment |
|---|---|---|
| Image looks generic, model-average | Tag-soup prompt | Rewrite as one coherent paragraph per §4 |
| Wrong style family (looks photorealistic when flat was intended) | Rendering mismatch or rendering paragraph diluted | Reaffirm chosen rendering's style paragraph at the top of the prompt |
| Colors don't match deck | HEX not echoed in prompt, or palette proportion rule omitted | Repeat HEX values 2-3 times in the prompt; restate palette proportion rule |
| Hex code or color name visible as text in image | Missing §5.1 closing sentence | Append the §5.1 hard rule verbatim |
| Garbled letters in supposedly text-free image | text_policy: none rule too weak |
Strengthen with explicit list: "no letters, no numbers, no words, no signs, no labels, no captions, no watermarks" |
| SVG text overlay clashes with busy image area | Page design needs negative space the prompt didn't request | Add a composition cue like "leave the {center / left third / lower band} relatively calm for text overlay" — only when the page actually overlays text on top of the image |
| Subject vague | Reference field too abstract | Rewrite reference with concrete nouns (verbs + objects) |
| Faces too realistic / uncanny | §5.2 rule omitted, or rendering is photo-incompatible | Either append §5.2, or switch rendering to a non-photo family |
Variant workflow:
- Set the unsatisfactory item's
statusback toPendingand update itspromptin place - Re-run the same confirmed path used for the original item: Path A may re-run
image_gen.py --manifest(only that item is re-processed); Path B uses the host-native tool again for that item; Offline Manual re-renders the sidecar and hands off - To try multiple stylistic approaches, append additional items with distinct filenames (e.g.
cover_bg_v2.png) rather than overwriting
9. Forbidden
- Generating prompts for
webrows — those go throughimage-searcher.md - Brand names or HEX codes inside the subject description (degrades output)
- Mixing renderings or palettes across images in the same deck
- Tag-soup prompts (keyword lists separated by commas without a coherent visual scene)
- Globbing
image-renderings/*.mdor any subdirectory — read only the chosen file - Placing an image without updating its
image_prompts.jsonstatusand the resource list status - Switching rendering or palette for a single image —
hero_pageis not an exception to deck-wide coherence - Embedding body copy, data points, bullet lists, or long quotes inside an image — those route to SVG