Files
imagepipeline/RECIPES.md
T
Frank Schwenk 0daf3e2315 refactor(xcf_stack): use explicit inputs for layer list and scheduling
Replace prior_steps and runs_last with inputs=[...] step refs so GIMP
export waits only on listed layers. Add rezepttest pipeline and
bokeh-oktagon recipe.

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-07-12 12:51:35 +02:00

626 lines
19 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# RECIPES.md — Declarative Pipeline Recipes
*Open this file when you forget how recipes work — rules first, catalog below.*
Not executable code. Named building blocks for creating or editing pipelines in `pipelines/<name>.py`. Load via `@RECIPES.md` in Cursor when working on pipelines.
---
## 1. Rules cheat sheet
1. **What a recipe is** — a named list of declarative lines. Agents turn it into Python using modules from `imagepipeline/modules/`.
2. **Layer order** — in every `combine` / composite recipe, lines are listed **bottom layer → top layer** (first line = background, last line = foreground on top).
3. **Indentation** — recipe id on its own line; indented lines below are the layers/steps of that recipe.
4. **`combine` = composite** — a multi-line recipe produces one output image by stacking those layers.
5. **Two-layer vs three-layer** — the `composite` module accepts exactly 2 inputs. Three or more layers need chained composites (bottom pair first, then add the next layer on top):
```text
# 3 layers: A (bottom), B (middle), C (top)
combine
A
B
C
# → composite(A, B) then composite(result, C)
```
6. **Shared expensive steps** — `rembg` runs once per pipeline and is reused. Recipe lines say `rembg`, but the agent must not re-run it for every composite.
7. **`xcf_stack` (GIMP export)** — stacks **listed step outputs** into one `.xcf` per image. Pass step refs in `inputs=[...]` (bottom layer → top). The runner waits until all listed steps finish. Requires `gimp` on PATH. Not a layer inside a `combine` block — a separate recipe.
8. **Placeholders** — `COLOR1`, `COLOR2`, `COLOR`, `STYLE`, `MAX_EDGE`, `PROMPT`, `MODEL`, `TEMPLATE_IMAGE`, etc. are filled in at pipeline creation time (from your prompt or constants at the top of the script).
9. **Colors** — hex for backgrounds (`#RRGGBB` or `#RRGGBBAA`). G'MIC filters need `R,G,B` tuples (see [GMIC color derivation](#6-gmic-color-derivation) below).
10. **Single-step vs composite vs pipeline** — one declarative line = one module step. Multiple indented lines in a **composite** recipe = layers (bottom → top). Multiple indented lines in a **pipeline** recipe = sequential steps (first step first, output feeds the next).
11. **Recipe inclusion** — an indented line that matches a **recipe id** from this file expands that recipe instead of inlining its steps:
- In a **pipeline** recipe (e.g. `colorsplash-watermark`): resolve the referenced recipe, then chain the next line on its output.
- In a **composite** recipe: layers stay declarative (layer descriptions, not recipe ids). Do not nest composite recipes as layers.
- Expand recursively; share expensive steps (`rembg`, etc.) once across all expanded recipes in the same pipeline.
12. **Notes (human only)** — freeform reminders for you. **Agents must ignore them entirely** when building pipelines:
- **Full-line note:** line starts with `#` (optional leading spaces) — not a layer, not a recipe id.
- **Inline note:** `# …` after a recipe id on the same line (everything from `#` onward is ignored).
- Notes may be German or English. They never become code, constants, or prompts.
13. **Quick read examples:**
```text
original-stereo-rembg # wie horseland 3d effekt
original
rembg with gmic: gcd_stereo_img …
rembg
```
```text
colorsplash
# team gallery default look
original as greyscale
rembg
```
```text
colorsplash-watermark
colorsplash
darktable style STYLE
```
---
## 2. How agents use this file
- Recipes are **names + declarative steps**, not Python.
- When creating or editing a pipeline: read `@RECIPES.md`, resolve named recipes, substitute placeholders, emit real `Pipeline` code.
- **Recipe inclusion:** if an indented line is a known recipe id (e.g. `colorsplash` inside `colorsplash-watermark`), look up that recipe, expand it, and use its output as the input for the next step. Expand recursively. Do not duplicate shared steps — one `rembg` (etc.) per pipeline when multiple included recipes need it.
- **Pipeline vs composite:** multiple indented lines that are **sequential steps or recipe refs** = pipeline recipe (chain). Multiple indented **layer** lines (original, rembg, backgrounds, gmic-on-rembg, …) = composite recipe (combine). When unsure: if any line is another recipe id, treat the parent as a pipeline recipe.
- **Ignore notes:** skip any line that is only a `#` comment (after indent trim). Strip inline `# …` suffixes on recipe-id lines. Do not copy note text into Python comments unless Fränky asks.
- **`xcf_stack`:** list every layer source in `inputs=[...]` (bottom → top). Include `"input"` for the original. The runner schedules `xcf_stack` after all listed steps complete. See `imagepipeline/modules/xcf_stack.py` and `pipelines/pipeline_rezepttest.py`.
- **Shared steps:** define `rembg` (and other expensive steps) **once** per pipeline and reference the step in composites — mirror `pipelines/pipeline_baxxter.py`.
- Follow the **Rules cheat sheet** above — especially layer order and chained composites.
- **3+ layers:** chain `composite` steps (see baxxter, crusaders, orange).
---
## 3. Vocabulary
| Recipe term | Maps to |
|-------------|---------|
| `original` | `inputs="input"` |
| `original as greyscale` | `gmic_grayscale` on input |
| `grayscale` | `imagemagick_grayscale` on input |
| `rembg` | `rembg` on input (outputs `.png`) |
| `white background` / `black background` | `imagemagick_fill` solid `#ffffff` / `#000000` |
| `gradient background COLOR1 COLOR2 45 degree` | `imagemagick_fill` linear, `angle=45` |
| `gradient background COLOR1 COLOR2 radial` | `imagemagick_fill` radial |
| `COLOR background` | `imagemagick_fill` solid `color1=COLOR` |
| `rembg with gmic: FILTER` | `gmic` on rembg output; command = `-FILTER` |
| `original with gmic: FILTER` | `gmic` on input; command = `-FILTER` |
| `layer opacity N%` | `composite` `foreground_opacity=N/100` on that layer |
| `make layer 5% bigger then crop to original size` | `imagemagick_scale_crop` `scale=1.05` |
| `COLOR to alpha` | `color_to_alpha` `color=COLOR` |
| `resize max edge MAX_EDGE` | `imagemagick_resize` `max_edge=MAX_EDGE` |
| `crop square` | `crop_square` |
| `darktable style STYLE` | `darktable_style` `style=STYLE` |
| `xcf stack layers: LAYER1, LAYER2, …` | `xcf_stack` with `inputs=[...]` in that order |
| `xcf stack skip missing` | `xcf_stack` `skip_missing=true` |
| `openrouter edit PROMPT MODEL` | `openrouter_edit` |
| `openrouter gallery match TEMPLATE_IMAGE` | `openrouter_edit` with `template_image` |
**Placeholders:** `COLOR1`, `COLOR2`, `COLOR`, `STYLE`, `MAX_EDGE`, `PROMPT`, `MODEL`, `TEMPLATE_IMAGE`, `YELLOW` — substituted from the user prompt or pipeline constants.
---
## 4. Single-step recipes
One entry per built-in module. Format: recipe id, declarative line(s), Python mapping.
### rembg
```text
rembg
rembg
```
- **Module:** `rembg` — `p.step("rembg", inputs="input")`
- **Note:** outputs `.png`; downstream steps match by stem.
### rembg-alpha-matting-off
```text
rembg-alpha-matting-off
rembg
```
- **Module:** `rembg` — `alpha_matting=False`
### grayscale-gmic
```text
grayscale-gmic
original as greyscale
```
- **Module:** `gmic_grayscale` — default command `-to_gray`
### grayscale-imagemagick
```text
grayscale-imagemagick
grayscale
```
- **Module:** `imagemagick_grayscale`
### resize-max-edge
```text
resize-max-edge
resize max edge MAX_EDGE
```
- **Module:** `imagemagick_resize` — e.g. `max_edge=2000`
- **Source:** `pipelines/pipeline_2000px.py`
### scale-crop-5pct
```text
scale-crop-5pct
make layer 5% bigger then crop to original size
```
- **Module:** `imagemagick_scale_crop` — `scale=1.05`
### solid-fill
```text
solid-fill
COLOR background
```
- **Module:** `imagemagick_fill` — `color1=COLOR`
### gradient-linear-45
```text
gradient-linear-45
gradient background COLOR1 COLOR2 45 degree
```
- **Module:** `imagemagick_fill` — `gradient=True`, `angle=45`
### gradient-radial
```text
gradient-radial
gradient background COLOR1 COLOR2 radial
```
- **Module:** `imagemagick_fill` — `gradient=True`, `radial=True`
### gmic
```text
gmic
gmic: COMMAND
```
- **Module:** `gmic` — `command="-COMMAND"` (leading `-` as in existing pipelines)
### color-to-alpha
```text
color-to-alpha
COLOR to alpha
```
- **Module:** `color_to_alpha` — outputs `.png`
### darktable-style
```text
darktable-style
darktable style STYLE
```
- **Module:** `darktable_style` — style must exist in `~/.config/darktable/styles/`
### crop-square
```text
crop-square
crop square
```
- **Module:** `crop_square` — center-crop to largest square
### xcf-stack
```text
xcf-stack # GIMP layer export — one .xcf per input image
xcf stack layers: input, rembg, composite_01, composite_02, …
```
- **Module:** `xcf_stack` — `p.step("xcf_stack", inputs=["input", step_a, step_b, …])`
- **Layer order in XCF:** same as `inputs` list (bottom → top); GIMP layer names = step ids (`input` for originals)
- **Matching:** layers matched by filename stem (extension may differ, e.g. rembg `.png` on `.jpg` input)
- **Output:** `{stem}.xcf` per input image
- **Requires:** `gimp` on PATH
- **Source:** `pipelines/pipeline_rezepttest.py`
### xcf-stack-skip-missing
```text
xcf-stack-skip-missing
xcf stack skip missing
```
- **Module:** `xcf_stack` — `skip_missing=True`
### openrouter-edit
```text
openrouter-edit
openrouter edit PROMPT MODEL
```
- **Module:** `openrouter_edit` — needs `OPENROUTER_API_KEY` in `.env`
- **Source:** `pipelines/example_ai.py`
### openrouter-gallery-match
```text
openrouter-gallery-match
openrouter gallery match TEMPLATE_IMAGE
```
- **Module:** `openrouter_edit` with `template_image=TEMPLATE_IMAGE` and gallery-match prompt
- **Source:** `pipelines/pipeline_team_gallery_match.py`
### ai-exposure
```text
ai-exposure
ai exposure strength STRENGTH
```
- **Module:** `ai_exposure` — optional `[ai]` extra; `max_edge`, `strength`
### ai-tone-map
```text
ai-tone-map
ai tone map strength STRENGTH
```
- **Module:** `ai_tone_map` — optional `[ai]` extra
### comfy-flux-edit
```text
comfy-flux-edit
comfy flux edit PROMPT
```
- **Module:** `comfy_flux_edit` — local ComfyUI; very slow on CPU
---
## 5. Composite and pipeline recipes
**Composite** recipes produce one output image. Layers listed bottom → top.
**Pipeline** recipes chain steps or other recipes in order (output of step *n* → input of step *n+1*).
**Parameterized** recipes use `COLOR1` / `COLOR2` (hex) — derive G'MIC RGB via [section 6](#6-gmic-color-derivation).
### colorsplash
```text
colorsplash
original as greyscale
rembg
```
- **Layers:** grayscale background, rembg cutout on top
- **Source:** baxxter, orange, crusaders, `pipeline_colorsplash_watermark_f12.py`
### colorsplash-watermark
```text
colorsplash-watermark
colorsplash
darktable style STYLE
```
- **Type:** pipeline recipe (includes `colorsplash`, then chains `darktable_style`)
- **Python:** expand `colorsplash` → composite step ref → `p.step("darktable_style", inputs=combined, style=STYLE)`
- **Source:** `pipelines/pipeline_colorsplash_watermark_f12.py` (`STYLE = "Watermark F12.rocks"`)
### rembg-white-bg
```text
rembg-white-bg
white background
rembg
```
- **Source:** baxxter, crusaders
### rembg-black-bg
```text
rembg-black-bg
black background
rembg
```
- **Source:** baxxter, crusaders
### rembg-gradient-45
```text
rembg-gradient-45
gradient background COLOR1 COLOR2 45 degree
rembg
```
- **Source:** baxxter, crusaders
### rembg-radial-2colors
```text
rembg-radial-2colors
gradient background COLOR1 COLOR2 radial
rembg
```
- **Source:** baxxter, crusaders
### original-stereo-rembg
```text
original-stereo-rembg # wie horseland 3d effekt
original
rembg with gmic: gcd_stereo_img 0,0,2.028,1,1.714,3.06,4,1,0
rembg
```
- **3 layers:** composite(original, rembg_stereo) → composite(result, rembg)
- **Source:** baxxter, crusaders
### original-stereo-black-alpha-rembg
```text
original-stereo-black-alpha-rembg
original
rembg with gmic: gcd_stereo_img 0,0,2.028,1,1.714,3.06,4,1,0
#000000 to alpha
rembg
```
- **Middle layer:** stereo gmic, then `#000000 to alpha` on that output
- **Source:** crusaders, baxxter_2
### original-drop-shadow-rembg
```text
original-drop-shadow-rembg
original
rembg with gmic: fx_drop_shadow3d 0,0,0,10,1,1,2,0.5,R2,G2,B2,200,0
rembg
```
- **Shadow color:** RGB from `COLOR2` (see [GMIC color derivation](#6-gmic-color-derivation))
- **Source:** baxxter, orange, crusaders
### original-bwrecolor-rembg
```text
original-bwrecolor-rembg
original
rembg with gmic: fx_bwrecolorize 0,0,0,0,0,1,0,2,R2,G2,B2,255,R1,G1,B1,255,158,137,189,255,224,191,228,255,R1,G1,B1,0,255,255,255,255,255,255,255,255,255,R1,G1,B1,0,255
rembg
```
- **Middle layer opacity:** 50% (`foreground_opacity=0.5` on first composite)
- **Palette:** `R1,G1,B1` from `COLOR1`, `R2,G2,B2` from `COLOR2`
- **Source:** baxxter, orange, crusaders
### rembg-custom-gradient-a
```text
rembg-custom-gradient-a
rembg with gmic: fx_custom_gradient 0,0,0,1,2,1,0,128,100,100,2,0,1,0,"",1,0,R1,G1,B1,0,255,R2,G2,B2,255,255,255,0,255,255,255,255,255,0,255,255,255,0,255,0,255,0,0,255,255,128,128,128,255,255,0,255,255,0,0,0,0
rembg
```
- **Gradient direction:** COLOR1 → COLOR2 on the rembg cutout
- **Source:** baxxter, orange, crusaders
### rembg-custom-gradient-b
```text
rembg-custom-gradient-b
rembg with gmic: fx_custom_gradient 0,0,0,1,2,1,0,128,100,100,2,0,1,0,"",1,0,R2,G2,B2,255,R1,G1,B1,0,255,255,255,0,255,255,255,255,255,0,255,255,255,0,255,0,255,0,0,255,255,128,128,128,255,255,0,255,255,0,0,0,0
rembg
```
- **Gradient direction:** COLOR2 → COLOR1 on the rembg cutout
- **Source:** baxxter, orange, crusaders
### original-jpr-smooth-rembg
```text
original-jpr-smooth-rembg
original
rembg with gmic: jpr_gradient_smooth 0,1.5
make layer 5% bigger then crop to original size
rembg
```
- **Middle layer:** jpr smooth on rembg, then scale 5% + center crop
- **Source:** baxxter, crusaders
### bokeh-oktagon
```text
bokeh-oktagon
original
original with gmic: fx_bokeh 3,5,0,30,8,4,0.3,0.2,R1,G1,B1,ALPHA1,0.7,30,20,20,1,2,R2,G2,B2,ALPHA2,0.15
rembg
```
- **3 layers:** composite(original, input_bokeh) → composite(result, rembg)
- **Colors:** `R1,G1,B1` from **COLOR1**; `R2,G2,B2` from **COLOR2**; `ALPHA1` / `ALPHA2` are 0255 (defaults 160 / 110)
- **G'MIC:** octagonal bokeh discs — first color pair `R1,G1,B1,ALPHA1`, second `R2,G2,B2,ALPHA2`
### original-jpr-smooth-grey-alpha-rembg
```text
original-jpr-smooth-grey-alpha-rembg
original
rembg with gmic: jpr_gradient_smooth 0,1.5
#7f7f7f to alpha
make layer 5% bigger then crop to original size
rembg
```
- **Middle layer:** jpr smooth → `#7f7f7f to alpha` → scale 5% + crop
- **Source:** crusaders, baxxter_2
### color-bg-drop-shadow-rembg
```text
color-bg-drop-shadow-rembg
COLOR1 background
rembg with gmic: fx_drop_shadow3d 0,0,0,10,1,1,2,0.5,R2,G2,B2,200,0
rembg
```
- **3 layers:** solid COLOR1 bg, drop-shadow rembg in middle, rembg on top
- **Source:** orange (`COLOR1 = #AA4E00`), crusaders
### yellow-bg-drop-shadow-rembg
```text
yellow-bg-drop-shadow-rembg
YELLOW background
rembg with gmic: fx_drop_shadow3d 0,0,0,10,1,1,2,0.5,R2,G2,B2,200,0
rembg
```
- **Source:** `pipelines/pipeline_baxxter_2.py` (`YELLOW = #d7fd00`, shadow RGB from baxxter COLOR2)
### resize-2000px
```text
resize-2000px
resize max edge 2000
```
- **Single-step preset** — not a composite
- **Source:** `pipelines/pipeline_2000px.py`
### openrouter-enhance
```text
openrouter-enhance
openrouter edit PROMPT MODEL
```
- **Single-step** — subtle enhancement prompt from `pipelines/example_ai.py`
- **Typical:** `max_edge=2048`
### team-gallery-match
```text
team-gallery-match
openrouter gallery match TEMPLATE_IMAGE
```
- **Single-step** — style-match new photos to existing gallery reference
- **Source:** `pipelines/pipeline_team_gallery_match.py`
---
## 6. GMIC color derivation
G'MIC commands in parameterized recipes use **comma-separated RGB integers**, not hex.
### Hex → RGB
1. Take `#RRGGBB` or strip alpha from `#RRGGBBAA` (last two hex digits = alpha, ignored for GMIC tuples).
2. Split into three byte pairs → decimal 0255.
| Hex | R,G,B |
|-----|-------|
| `#AA4E00` | 170, 78, 0 |
| `#EDDD93` | 237, 221, 147 |
| `#d7fd00` | 215, 253, 0 |
| `#fc0ade` | 252, 10, 222 |
| `#0064b0` | 0, 100, 176 |
| `#00badf` | 0, 186, 223 |
### Where colors go
| Recipe | Color usage |
|--------|-------------|
| `original-drop-shadow-rembg` | shadow tint: `R2,G2,B2` from **COLOR2** in `fx_drop_shadow3d …,R2,G2,B2,200,0` |
| `original-bwrecolor-rembg` | highlight `R2,G2,B2`, accent `R1,G1,B1` in `fx_bwrecolorize` |
| `rembg-custom-gradient-a` | starts with `R1,G1,B1`, transitions via `R2,G2,B2` |
| `rembg-custom-gradient-b` | starts with `R2,G2,B2`, transitions via `R1,G1,B1` |
| `bokeh-oktagon` | bokeh tints `R1,G1,B1,ALPHA1` and `R2,G2,B2,ALPHA2` in `fx_bokeh` |
| `imagemagick_fill` backgrounds | use full hex including alpha if needed (`#d7fd00ff`) |
### Agent workflow
1. Define `COLOR1` and `COLOR2` as constants at the top of the pipeline script.
2. Add a short comment with derived RGB tuples (as in `pipeline_orange.py`).
3. Build GMIC command strings using those integers.
---
## 7. Usage examples
### Compose by recipe name
```text
Create pipeline "foo" with recipes colorsplash and rembg-radial-2colors, COLOR1=#123456, COLOR2=#654321
```
Agent: shared `rembg` step, `gmic_grayscale`, two `imagemagick_fill` gradients, two `composite` outputs (separate variants, not chained).
### Pipeline recipe with inclusion
```text
Create pipeline "watermark" with recipe colorsplash-watermark, STYLE="Watermark F12.rocks"
```
Agent: expand `colorsplash` inside `colorsplash-watermark`, then chain `darktable_style` on the composite output.
```text
colorsplash-watermark
colorsplash
darktable style STYLE
```
### With GIMP layer export
```text
Create pipeline "foo" with recipes colorsplash, rembg-radial-2colors, and xcf-stack
```
Agent: build all composite steps, then `p.step("xcf_stack", inputs=["input", rembg_out, …, composite_final])` listing every layer bottom → top.
```text
xcf-stack
xcf stack layers: input, rembg, composite_colorsplash, composite_radial, …
```
### Full combine list (baxxter style)
Paste a raw list of `combine` blocks (bottom → top). Agent maps each block to a recipe id from section 5 or expands inline steps. Reuse one `rembg` step for the whole pipeline.
### Extend existing pipeline
```text
Add recipe color-bg-drop-shadow-rembg to pipeline_orange.py with current COLOR1
```
Agent: read existing constants, append new composite steps using the same `rembg_out` and `rembg_shadow` pattern.
### Human notes in prompts
You may add `# notizen` in recipe blocks in this file anytime — agents ignore them per section 1 rule 11.