image-slots · diff
git:20260822.3775b27 to git:20260916.2f4b343
3 added, 4 removed. Audit A to A.
---
name: image-slots
description: >-
- Fill the empty image slots of an existing Google Slides deck with AI-generated
- pictures: find the frames the template reserves for a picture, generate one
- composed for each frame's shape, and place it so it fills the frame. Runs on
- any accessible deck URL, including decks slide-forge did not generate.
+ Fill the empty image slots of an existing Google Slides deck with
+ AI-generated pictures composed for each frame's shape. Runs on any
+ accessible deck URL, including decks slide-forge did not generate.
Use for: 表紙に絵を入れて, 章扉の画像枠を埋めて, 空いている画像枠に画像を生成,
fill the image placeholders.
Not: decks still being generated (put aiImage in the spec instead);
shape-drawn diagrams; replacing pictures already in place; verification
(slide-qa).
---
*[日本語](SKILL.ja.md)*
# Fill Image Slots on an Existing Deck
Working directory: the slide-forge root — `${CLAUDE_PLUGIN_ROOT}` when running
from an installed plugin, `/path/to/slide-forge` on a local clone.
## Important
- **Prefer the spec path when the deck is still being generated.** If the deck
comes from a slide-forge deck spec, add an `aiImage` figure with `x`/`y`/`w`/`h`
omitted and regenerate — `build_deck.py` puts it in the slot. That keeps the
spec the source of truth, which is the repo's standing rule (see slide-qa:
*fixes happen in the source, not the artifact*). **This skill is for decks
that have no spec behind them**, or for adding imagery to a deck that already
exists and must keep its URL.
- **This writes to a live deck.** Take a version snapshot first and report the
revision ID:
`.venv/bin/python scripts/snapshot_version.py <URL>`
- **Always `--dry-run` first** and show the user which slides and frames will be
filled, with the prompt each one will use, before generating anything.
- **Slots that already hold a picture are never touched.** Replacing an existing
picture is out of scope — delete it by hand first if that is the intent.
- **`GEMINI_API_KEY` is required** and the image model has **zero free-tier
quota**; the key must belong to a billing-enabled project
(`references/images.md`).
- **Image generation can be switched off for the whole toolkit**
(`imageGeneration: false` in `config/settings.json`). Check it before
proposing this skill — `.venv/bin/python scripts/settings.py --show`. When it
is off, `fill_image_slots.py` stops before reading the deck; either the user
turns it on or the frames stay empty (`references/settings.md`).
- **Verify with thumbnails afterwards.** A clean API response cannot show a
picture whose subject got cropped. Hand off to the `slide-qa` skill.
## Quick Reference
| Task | Use |
|------|-----|
| List the fillable frames (no API image calls, no changes) | `.venv/bin/python scripts/fill_image_slots.py <URL> --dry-run` |
| Fill every empty frame | `.venv/bin/python scripts/fill_image_slots.py <URL>` |
| One slide, explicit subject | `… <URL> --slide 3 --prompt "a data center at night"` |
| Pick a frame when a slide has several | `… --slot 1` (0-based, in the order the survey lists them) |
| Also use frames only *inferred* from the deck's own usage | `--include-inferred` (off by default — see below) |
| Change the illustration style | `--style isometric` (`flat_vector` / `line_art` / `blueprint` / `paper` / `photo`) |
| Snapshot before writing | `.venv/bin/python scripts/snapshot_version.py <URL>` |
| What a slot is, and how images are composed for it | `references/images.md` |
## How a frame is found
Same three sources as template registration (`references/template-schema.md`):
1. a **PICTURE-family placeholder** on the slide (`PICTURE` / `CLIP_ART` /
`DIAGRAM` / `MEDIA` / `OBJECT` / `SLIDE_IMAGE`)
2. an **empty image element** on the slide — it renders as nothing, so it is a
slot, not a decoration
3. otherwise the **layout's** `imageSlots`, largest frame first
Sources 1 and 2 are **declarations** — the template says a picture belongs there.
The third kind of frame `imageSlots` can hold, one *inferred* from other slides
placing pictures in the same spot, is **excluded by default**: on a real deck
that inference matches the body area of ordinary content slides, and filling it
everywhere would bury the deck in pictures. Measured on a 78-slide template,
the default finds 0 fillable frames where the inference offers 39. Opt in per
run with `--include-inferred` when you actually want that.
Frames on the slide itself win over the layout's, because a slide that carries
its own placeholder is more specific than the layout it was made from. The
survey lists layout frames largest-first; frames found on the slide come in the
order the slide stores them, so read the `--dry-run` output rather than assuming
an order when picking `--slot`.
**A picture is layered over the frame, not poured into it.** An empty PICTURE
placeholder is not consumed — the image is created at the frame's coordinates
and the placeholder stays underneath. It renders as nothing, but it is still
there in the editor. The spec path (`build_deck.py`) behaves the same way.
Pass `--template templates/<id>.json` when the deck was generated from a
registered template — its verified roles and palette are then used. Without it
the deck is analyzed on the fly, so the skill works on **any** deck.
## What gets drawn
The prompt defaults to the slide's **title** — its TITLE placeholder, or the
topmost text if it has none — so a divider titled "Chapter 1: Rebuilding the
Data Platform" asks for a picture about that. Body text is deliberately
excluded: a slide's bullets read as "① Verify ② Distribute ③ Reflect" and make
a poor drawing instruction. Override per run with `--prompt`. A slide with
**no text at all** is skipped with a message rather than guessed at — pass
`--prompt` for those.
The picture is generated for the frame's shape: the closest aspect ratio the
model supports, plus a prompt instruction naming the edges and percentage the
fill will crop, so the subject survives. It is then placed with `fit="cover"`,
exactly filling the frame. Details in `references/images.md`.
Generation is cached by (model, style, aspect, full prompt), so re-running does
not redraw or re-bill; `--force` overrides.
## Flow
1. **Snapshot** — `snapshot_version.py <URL>`; keep the revision ID for the report.
2. **Survey** — `fill_image_slots.py <URL> --dry-run`. Show the user the slide
numbers, frames, and prompts. If everything is "already has a picture" or no
frames exist, say so and stop — do not invent frames.
3. **Confirm subjects.** The auto-derived prompts come from slide text and are
often literal. Offer to set `--prompt` per slide when the subject reads badly
as a picture (long body text, numbers, product names).
4. **Fill** — run without `--dry-run`. Each placement prints the chosen aspect
ratio and the composition note.
5. **QA** — invoke the `slide-qa` skill on the deck URL and check the pictures
are not cropped through their subject and do not fight the layout's text.
6. **Report** — slides filled, the style used, the snapshot revision ID, and the
deck URL.
## Troubleshooting
| Symptom | Cause / fix |
|---|---|
| `Nothing to fill` | Every frame already holds a picture, or the layouts reserve none. Check with `inspect_template.py <URL>`; place the image by coordinates via a deck spec instead |
| `skipped: no text on this slide to build a prompt from` | Title-less slide — pass `--prompt` |
| The picture's subject is cut off | The frame's ratio is far from anything the model can produce. Re-run that slide with `--prompt` describing a centred, simple subject, or `--force` for a different draw |
| `HTTP 429 / limit: 0` | The API key's project has no image quota — billing must be enabled |
| Picture looks stretched or letterboxed | Report it: frames are always filled with `cover`, so this means the fit-up pass failed (the run prints a warning when it cannot read the created image's size) |