| 1 | # Image Tools |
| 2 | |
| 3 | > **Design boundary**: keep provider credentials explicit, keep in-pipeline |
| 4 | > acquisition manifest-driven, and treat external image references as authoring |
| 5 | > inputs while delivery writes self-contained SVG previews and native PPTX |
| 6 | > media. |
| 7 | |
| 8 | Image tools cover prompt-based AI generation, web image search, image inspection, |
| 9 | and Gemini watermark removal. Native formula authoring belongs to the SVG |
| 10 | pipeline, not the image pipeline. |
| 11 | |
| 12 | ## Legacy standalone `latex_render.py` |
| 13 | |
| 14 | This retained standalone utility renders a user-authored |
| 15 | `images/formula_manifest.json` to PNG. Neither Default nor Quick Generate calls |
| 16 | it, and new projects do not create formula manifests or formula images. The |
| 17 | supported generated-deck path authors a native formula marker in SVG and lets |
| 18 | `svg_to_pptx.py` compile its LaTeX payload to editable PowerPoint OMML. |
| 19 | |
| 20 | ```bash |
| 21 | python3 scripts/latex_render.py <project_path> |
| 22 | python3 scripts/latex_render.py <project_path> --dry-run |
| 23 | ``` |
| 24 | |
| 25 | Use it only for an explicitly requested external raster workflow. It is not a |
| 26 | compatibility fallback for Keynote, WPS, LibreOffice, or another client. |
| 27 | |
| 28 | ## `image_gen.py` |
| 29 | |
| 30 | Unified image generation entry point. |
| 31 | |
| 32 | This script is the **Path A** API/proxy executor for generated images. Default |
| 33 | Generate checks `design_spec.md §I / AI Image Acquisition Path` before manifest |
| 34 | mode: only `api` / `auto` permits Path A; a missing or unknown value fails |
| 35 | closed and returns to Step 4 recovery. Quick Generate has no Design Spec: use |
| 36 | the explicit active-context path when supplied, otherwise `auto` selects the |
| 37 | A → B chain defined in |
| 38 | [`image-generator.md`](../../references/image-generator.md) §7 without asking; |
| 39 | exhausted automation triggers Quick's no-AI replan rather than Offline Manual. |
| 40 | In either profile, `host-native` uses the host image tool directly and an |
| 41 | explicit `manual` choice uses the read-only Markdown sidecar. |
| 42 | |
| 43 | ```bash |
| 44 | python3 scripts/image_gen.py "A modern futuristic workspace" |
| 45 | python3 scripts/image_gen.py "Abstract tech background" --aspect_ratio 16:9 --image_size 4K |
| 46 | python3 scripts/image_gen.py "Concept car" -o projects/demo/images |
| 47 | python3 scripts/image_gen.py --list-backends |
| 48 | ``` |
| 49 | |
| 50 | Backends are grouped into Core / Extended / Experimental tiers. Run `python3 scripts/image_gen.py --list-backends` for the current list. |
| 51 | |
| 52 | Backend selection: |
| 53 | |
| 54 | ```bash |
| 55 | python3 scripts/image_gen.py "A cat" --backend openai |
| 56 | python3 scripts/image_gen.py "A cinematic portrait" --backend minimax |
| 57 | python3 scripts/image_gen.py "A product launch hero image" --backend qwen |
| 58 | python3 scripts/image_gen.py "科技感背景图" --backend zhipu |
| 59 | python3 scripts/image_gen.py "A product KV in cinematic style" --backend volcengine |
| 60 | ``` |
| 61 | |
| 62 | Configuration sources: |
| 63 | |
| 64 | 1. Current process environment variables |
| 65 | 2. First `.env` found in this order: |
| 66 | - Current working directory |
| 67 | - Skill directory (e.g. `~/.agents/skills/ppt-master/.env`) |
| 68 | - Clone repo root |
| 69 | - `~/.ppt-master/.env` |
| 70 | |
| 71 | The active backend must always be selected explicitly via `IMAGE_BACKEND`. |
| 72 | |
| 73 | Example `.env`: |
| 74 | |
| 75 | ```env |
| 76 | IMAGE_BACKEND=openai |
| 77 | OPENAI_API_KEY=sk-xxx |
| 78 | OPENAI_MODEL=gpt-image-2 |
| 79 | # Optional proxy |
| 80 | # OPENAI_BASE_URL=http://127.0.0.1:3000/v1 |
| 81 | # OpenAI-compatible provider knobs: |
| 82 | # OPENAI_SIZE_PRESET=auto |
| 83 | # OPENAI_RESPONSE_FORMAT=auto |
| 84 | # OPENAI_QUALITY=auto |
| 85 | # Allowed values: png / jpeg / webp |
| 86 | # OPENAI_OUTPUT_FORMAT=png |
| 87 | # jpeg/webp only, 0-100 |
| 88 | # OPENAI_OUTPUT_COMPRESSION=80 |
| 89 | # gpt-image-2: auto / opaque |
| 90 | # OPENAI_BACKGROUND=auto |
| 91 | # auto / low |
| 92 | # OPENAI_MODERATION=auto |
| 93 | ``` |
| 94 | |
| 95 | Example process environment: |
| 96 | |
| 97 | ```bash |
| 98 | export IMAGE_BACKEND=openai |
| 99 | export OPENAI_API_KEY=sk-xxx |
| 100 | export OPENAI_MODEL=gpt-image-2 |
| 101 | export OPENAI_OUTPUT_FORMAT=png |
| 102 | ``` |
| 103 | |
| 104 | Current process environment wins over `.env`. |
| 105 | |
| 106 | OpenAI backend notes: |
| 107 | - `gpt-image-2` is the default OpenAI model. |
| 108 | - Requests are sent with plain `requests.post()` to improve compatibility with |
| 109 | OpenAI-compatible proxies that block the OpenAI SDK's `httpx` transport. |
| 110 | - For `gpt-image-2`, `image_size=512px` means a low-quality draft preset, not a literal 512px edge. The model requires both edges to be multiples of 16px, a long:short ratio no greater than 3:1, and total pixels between 655,360 and 8,294,400. |
| 111 | - `OPENAI_BACKGROUND=transparent` is not supported by `gpt-image-2`; use `auto` or `opaque`. |
| 112 | - If `OPENAI_OUTPUT_FORMAT=jpeg` or `webp`, generated files use `.jpg` or `.webp` extensions instead of `.png`. |
| 113 | - OpenAI-compatible providers that reject OpenAI-specific fields can use `OPENAI_RESPONSE_FORMAT=omit`, `OPENAI_QUALITY=omit`, and `OPENAI_SIZE_PRESET=<preset>`. Valid response formats are `auto`, `b64_json`, `url`, and `omit`; valid size presets are `auto`, `legacy`, `gpt-image`, `gpt-image-2`, and `dall-e-2`. |
| 114 | |
| 115 | Example `.env` for Agnes AI through the OpenAI-compatible backend: |
| 116 | |
| 117 | ```env |
| 118 | IMAGE_BACKEND=openai |
| 119 | OPENAI_API_KEY=your-agnes-key |
| 120 | OPENAI_MODEL=agnes-image-2.1-flash |
| 121 | OPENAI_BASE_URL=https://apihub.agnes-ai.com/v1 |
| 122 | OPENAI_SIZE_PRESET=gpt-image-2 |
| 123 | OPENAI_RESPONSE_FORMAT=omit |
| 124 | OPENAI_QUALITY=omit |
| 125 | ``` |
| 126 | |
| 127 | Use provider-specific keys only (e.g. `GEMINI_API_KEY`, `OPENAI_API_KEY`). See `.env.example` in clone mode or `${SKILL_DIR}/.env.example` in skill-install mode for the full list per backend. |
| 128 | |
| 129 | `IMAGE_API_KEY`, `IMAGE_MODEL`, and `IMAGE_BASE_URL` are intentionally unsupported. |
| 130 | |
| 131 | If you keep multiple providers in one `.env` or environment, `IMAGE_BACKEND` must explicitly select the active provider. |
| 132 | |
| 133 | Recommendation: |
| 134 | - Default to the Core tier for routine PPT work |
| 135 | - Use Extended only when you need a specific model style |
| 136 | - Treat Experimental backends as opt-in |
| 137 | |
| 138 | Example `.env` for MiniMax image backend: |
| 139 | |
| 140 | ```env |
| 141 | IMAGE_BACKEND=minimax |
| 142 | MINIMAX_API_KEY=your-api-key |
| 143 | # Optional: override base URL (defaults to https://api.minimaxi.com, domestic China endpoint) |
| 144 | # Use https://api.minimax.io for overseas access |
| 145 | # MINIMAX_BASE_URL=https://api.minimax.io |
| 146 | # MINIMAX_MODEL=image-01 |
| 147 | ``` |
| 148 | |
| 149 | ## `image_treat.py` |
| 150 | |
| 151 | Create a non-destructive PNG derivative from one bitmap already prepared under |
| 152 | `<project_path>/images/`. Use this only when a slide needs a baked bitmap effect; |
| 153 | crop, mask, rotation, mirror, opacity, shadow, scrim, outline, and overlap remain |
| 154 | native SVG/PPT treatments. This tool does not perform semantic background |
| 155 | removal: use `slice_images.py --alpha --bg <key> --strict-alpha` for flat-color |
| 156 | keys (a pure red/green/blue key also recovers soft alpha and removes spill), an |
| 157 | already prepared RGBA asset or the active host image editor for a standalone cutout, and |
| 158 | [`image-generator.md`](../../references/image-generator.md) §4.4 only for |
| 159 | registered subject/base layers. |
| 160 | |
| 161 | ```bash |
| 162 | python3 scripts/image_treat.py projects/demo hero.jpg \ |
| 163 | --output hero_soft.png --brightness 0.9 --contrast 1.1 --blur 12 |
| 164 | |
| 165 | python3 scripts/image_treat.py projects/demo hero.jpg \ |
| 166 | --output hero_duotone.png --duotone "#14213D" "#FCA311" |
| 167 | ``` |
| 168 | |
| 169 | Supported operations are brightness, contrast, desaturation/grayscale, |
| 170 | duotone, and Gaussian blur. They compose in a fixed order: brightness → |
| 171 | contrast → tone treatment → blur. Desaturation, grayscale, and duotone are |
| 172 | mutually exclusive. At least one option must produce a real change; animated |
| 173 | or multi-frame sources are rejected rather than reduced to one frame. |
| 174 | |
| 175 | Both input and output are bare filenames directly under `images/`; output must |
| 176 | be a new `.png` file. The tool keeps the EXIF-corrected display dimensions, |
| 177 | leaves any alpha mask unchanged, and never overwrites the source or an existing |
| 178 | derivative. If `images/image_sources.json` contains the source filename, the |
| 179 | new record inherits that legal provenance and records `derived_from` plus the |
| 180 | ordered `treatments`. Run `analyze_images.py` after all planned derivatives are |
| 181 | ready so the inventory reflects the files that SVG authoring will consume. |
| 182 | |
| 183 | ## `analyze_images.py` |
| 184 | |
| 185 | Analyze objective image-file facts in a project directory before writing the |
| 186 | design spec or authoring SVG. |
| 187 | |
| 188 | ```bash |
| 189 | python3 scripts/analyze_images.py <project_path>/images |
| 190 | ``` |
| 191 | |
| 192 | The tool does not resolve a canvas or recommend a left/right, top/bottom, or |
| 193 | other slide layout. Its atomic CSV records EXIF-corrected native dimensions and |
| 194 | `AspectRatio`, the objective aspect-ratio category, optional source |
| 195 | `SourceDisplayRatio`, format, actual transparent-pixel presence, usage count, |
| 196 | and bitmap/vector capability facts. An empty folder rewrites a header-only |
| 197 | report; unreadable supported files still refresh the report and produce a |
| 198 | non-zero exit. |
| 199 | |
| 200 | Use this as the default factual inventory; it does not perform semantic image |
| 201 | understanding or choose composition. Generate planning follows the Strategist's |
| 202 | context-first boundary: source context, captions / alt text / titles, filenames, |
| 203 | user notes, and existing resource records come first. Only an already-selected |
| 204 | provided/web asset whose focal-safe crop, overlay contrast, or quiet region |
| 205 | remains materially ambiguous may be inspected for that placement; this never |
| 206 | reopens selection or provenance, never bulk-opens the image folder, and never |
| 207 | restores routine readback of AI-generated images. |
| 208 | |
| 209 | ## `image_search.py` |
| 210 | |
| 211 | Zero-config web image search across openly-licensed providers. Sister tool to `image_gen.py` — used when the resource list row has `Acquire Via: web`. |
| 212 | |
| 213 | ```bash |
| 214 | python3 scripts/image_search.py "offshore wind farm" \ |
| 215 | --filename cover_bg.jpg --slide 01_cover \ |
| 216 | --orientation landscape -o projects/demo/images |
| 217 | ``` |
| 218 | |
| 219 | For multiple web rows, `--batch images/image_queries.json` searches them concurrently (modest default, `--concurrency N` / `IMAGE_SEARCH_CONCURRENCY` to tune) instead of one call per row — the web sister of `image_gen.py --manifest`. Schema and status semantics: [`image-searcher.md`](../../references/image-searcher.md) §5. |
| 220 | |
| 221 | Providers (Pexels / Pixabay are tried first when keyed; Openverse and Wikimedia are zero-config fallbacks): |
| 222 | |
| 223 | | Provider | Config | Strength | |
| 224 | |---|---|---| |
| 225 | | `pexels` | recommended: `PEXELS_API_KEY` | modern stock photography, people, workplace, lifestyle | |
| 226 | | `pixabay` | recommended: `PIXABAY_API_KEY` | broad type coverage including photos and illustrations | |
| 227 | | `openverse` | zero-config | fallback aggregator: Wikimedia + Flickr + museums + rawpixel | |
| 228 | | `wikimedia` | zero-config | educational, scientific, geographic, historical | |
| 229 | |
| 230 | Default search chain (when `--provider` is unset): configured Pexels, configured Pixabay, Openverse, then Wikimedia. Missing keyed credentials are silently skipped. Keyed providers broaden stock-photo coverage but are optional; zero-config providers remain valid. |
| 231 | |
| 232 | `image_search.py` uses the same `.env` lookup order as `image_gen.py`, so skill installs can keep `PEXELS_API_KEY` / `PIXABAY_API_KEY` in `~/.ppt-master/.env`. |
| 233 | |
| 234 | Query guidance: |
| 235 | |
| 236 | Keep the Design Spec §VIII `Reference` as the full visual/crop intent; write a separate concise provider query for this CLI. Start with the shortest phrase that preserves identity, but retain exact multi-word names and necessary disambiguators beyond four words. |
| 237 | |
| 238 | For exact entities with multiple common names, add repeatable `--query-variant` |
| 239 | values (batch: `query_variants`) for materially different official |
| 240 | translations, spellings, aliases, or Chinese names. Results are aggregated and |
| 241 | deduplicated before ranking. |
| 242 | |
| 243 | | Case | Pattern | |
| 244 | |---|---| |
| 245 | | Generic stock concept | `boardroom meeting` | |
| 246 | | China-specific landmark | Precise official place/identity name plus necessary geography | |
| 247 | | Avoid | Negative prompt wording such as `not tourist snapshot` | |
| 248 | |
| 249 | License filter: |
| 250 | |
| 251 | - **Default**: search all providers with `cc0,pdm,pexels,pixabay,cc by,cc by-sa` allowed together. The chosen image may be `no-attribution` or `attribution-required`; Executor adds an inline credit only when needed. |
| 252 | - `--strict-no-attribution` restricts the search to `cc0,pdm,pexels,pixabay` — useful for full-bleed hero images or templates that cannot host a credit element. |
| 253 | |
| 254 | Pin a provider, refuse attribution, or override the manifest path: |
| 255 | |
| 256 | ```bash |
| 257 | # Pin Wikimedia |
| 258 | python3 scripts/image_search.py "Olympics opening ceremony" \ |
| 259 | --filename event.jpg --provider wikimedia \ |
| 260 | --orientation landscape -o projects/demo/images |
| 261 | |
| 262 | # Strict mode — refuse CC BY / CC BY-SA |
| 263 | python3 scripts/image_search.py "abstract gradient" \ |
| 264 | --filename hero.jpg --strict-no-attribution \ |
| 265 | -o projects/demo/images |
| 266 | ``` |
| 267 | |
| 268 | Suitability & manual replacement (a web top hit is metadata-relevant, not guaranteed visually right): |
| 269 | |
| 270 | - By default only the best match is downloaded, plus a downscaled review copy at `images/.review/<stem>.jpg` (the placed asset stays full-resolution). |
| 271 | - For exact subjects (landmarks, people, companies, products), use `--require-terms` or batch `required_terms` so visually plausible but wrong metadata is rejected before ranking. Example: `--require-terms Chongqing --require-terms "Jiefangbei|Liberation Monument"`. Keep proper-name / geography anchors; do not broaden to generic terms like `canyon`, `stone pillar`, or `ancient town` just to improve coverage. |
| 272 | - When the current Generate agent can inspect images, use `--save-candidates`. The tool saves only the first ranked page of review-eligible provider previews (**8 by default**), writes `candidates/<stem>/review_sheet.jpg`, and leaves the target image and `image_sources.json` untouched. Standalone CLI use remains best-only unless this flag is explicit. |
| 273 | - Compare the thumbnail set against the active Reference/Crop Policy. Only after one passes, run `--promote candidate_03.jpg --filename <name>.jpg`; this downloads and validates exactly that original. In batch mode, pass the same `--batch images/image_queries.json` so `Needs-Selection` becomes `Sourced`. |
| 274 | - If no thumbnail passes and `has_more_candidates` is true, fetch `--candidate-page 2` (or set the batch row's `candidate_page` to `next_candidate_page` and reset it to `Pending`). Candidate numbering continues at 9; no original is downloaded. Only after the pool is exhausted should you materially change the identity wording, viewpoint, translation, alias, or disambiguator and generate a fresh pool. |
| 275 | - Without multimodal inspection, omit `--save-candidates`. Best-only mode rejects visual-verification-required near matches, accepts only a strict metadata candidate, downloads one original, and records `selection_method: metadata-ranked`; if metadata cannot prove the entity or the active visual requirement, use `Needs-Manual` rather than claiming visual confirmation. |
| 276 | - `--from-url <url> --filename <name>.jpg` downloads a user-chosen image URL and replaces the target (recorded `license_tier: manual`) — the model-agnostic manual path; works even without a multimodal model. |
| 277 | |
| 278 | Full review / escalation flow: [`image-searcher.md`](../../references/image-searcher.md) §5. |
| 279 | |
| 280 | Output: |
| 281 | |
| 282 | - `--save-candidates`: thumbnail-only `candidates/<stem>/candidates.json`, at most 8 provider previews by default, and `review_sheet.jpg`; no target image or provenance entry. `--candidate-page N` advances through the ranked pool; `--max-candidates 0` explicitly dumps all candidates for exceptional debugging |
| 283 | - Best-only / `--promote`: one original saved to the specified output directory (auto-converts webp → jpg via Pillow when the filename extension demands) |
| 284 | - Best-only / `--promote`: `image_sources.json` manifest with full provenance (provider, license, license_tier, author, source URL, dimensions, attribution_text) |
| 285 | - Manifest is idempotent on `filename` and written atomically; damaged existing provenance blocks replacement |
| 286 | |
| 287 | Allowed licenses (default): CC0, Public Domain, Pexels License, Pixabay Content License, CC BY, CC BY-SA. Auto-rejected: CC BY-NC, CC BY-ND, CC BY-NC-SA, CC BY-NC-ND, all rights reserved, unknown. |
| 288 | |
| 289 | The full role-level reference (intent → query translation, on-slide attribution contract) is in [`references/image-searcher.md`](../../references/image-searcher.md). |
| 290 | |
| 291 | ## `gemini_watermark_remover.py` |
| 292 | |
| 293 | Remove Gemini watermark assets after manual download. |
| 294 | |
| 295 | ```bash |
| 296 | python3 scripts/gemini_watermark_remover.py <image_path> |
| 297 | python3 scripts/gemini_watermark_remover.py <image_path> -o output_path.png |
| 298 | python3 scripts/gemini_watermark_remover.py <image_path> -q |
| 299 | ``` |
| 300 | |
| 301 | Notes: |
| 302 | - Requires `scripts/assets/bg_48.png` and `scripts/assets/bg_96.png` |
| 303 | - Best used after downloading “full size” Gemini images |
| 304 | |
| 305 | Dependencies: |
| 306 | |
| 307 | ```bash |
| 308 | pip install Pillow numpy |
| 309 | ``` |
| 310 |