From 9b6c80da42a5cffc949121b5358bf45f5125e9a2 Mon Sep 17 00:00:00 2001 From: Frank Schwenk Date: Mon, 13 Jul 2026 10:15:28 +0200 Subject: [PATCH] feat: add aichelberg-indians pipeline and rebuild RECIPES.md Replace the verbose recipe catalog with a compact agent reference (modules, ImageMagick ops, G'MIC commands, combines) and add the Indians Aichelberg tone-mapping pipeline with gmic variants and xcf export. Co-authored-by: Cursor --- RECIPES.md | 785 +++++++---------------- pipelines/pipeline_aichelberg_indians.py | 278 ++++++++ 2 files changed, 513 insertions(+), 550 deletions(-) create mode 100644 pipelines/pipeline_aichelberg_indians.py diff --git a/RECIPES.md b/RECIPES.md index 506c9f5..603940b 100644 --- a/RECIPES.md +++ b/RECIPES.md @@ -1,625 +1,310 @@ -# RECIPES.md — Declarative Pipeline Recipes +# RECIPES.md -*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/.py`. Load via `@RECIPES.md` in Cursor when working on pipelines. +Reference for agents building `pipelines/.py`. Not executable — expand to `p.step(...)` calls. --- -## 1. Rules cheat sheet +## Rules -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): +1. **combine** = one output image. Layers **bottom → top** (first line = background). +2. **composite** takes exactly **2** inputs. For 3+ layers: `composite(A,B)` → `composite(mid, C)` → … +3. **rembg** once per pipeline — reuse the same step ref in every combine. +4. **G'MIC in Python:** `command="-filter args"` (leading `-`). Multi-frame output: framework keeps frame `000001`. +5. **Colors:** hex (`#RRGGBB`) for `imagemagick_fill` / `color_to_alpha`. G'MIC color slots: `R,G,B` ints 0–255 from hex bytes. +6. **Placeholders** at pipeline top: `COLOR1`, `COLOR2`, `R1,G1,B1`, `R2,G2,B2`, `ALPHA1` (default 160), `ALPHA2` (default 110), `STYLE`, `MAX_EDGE`, `PROMPT`, `MODEL`, `TEMPLATE_IMAGE`, `YELLOW`. +7. **xcf_stack:** separate step, not a combine layer. `inputs=[...]` bottom → top; include `"input"` for originals. Needs `gimp` on PATH. +8. Lines starting with `#` in user prompts = human notes — ignore. - ```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 - ``` +**Hex → RGB:** `#244a89` → `36,74,137`. Strip `#RRGGBBAA` alpha for G'MIC tuples. --- -## 2. How agents use this file +## Layer lines (example) -- 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 +One combine, bottom → top. Other phrases: `grayscale` / `original as greyscale`, `white`/`black background`, `original with gmic: ID`, `#COLOR to alpha`, `resize max edge N`, `crop square`, `darktable style STYLE`, `openrouter edit …`, `xcf stack layers: a, b, c`. ```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 +demo-combine gradient background COLOR1 COLOR2 radial + original + rembg with gmic: gmic-drop-shadow + make layer 5% bigger then crop to original size + rembg ``` -- **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 +## Modules -**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). +| name | parameters (defaults) | notes | +|------|----------------------|-------| +| `rembg` | `model`=`birefnet-general`, `alpha_matting`=`True` | output `.png` | +| `gmic` | `command` (required) | timeout 300s | +| `gmic_grayscale` | `command`=`-to_gray` | | +| `composite` | `mode`=`over`, `output_ext`=`.png`, `foreground_opacity`=`1.0` | 2 inputs: bg, fg | +| `imagemagick_fill` | `color1`, `color2`=`""`, `gradient`=`False`, `radial`=`False`, `angle`=`None` | sized to input | +| `imagemagick_grayscale` | `colorspace`=`Gray` | | +| `imagemagick_resize` | `max_edge`=`2000` | no upscale | +| `imagemagick_scale_crop` | `scale`=`1.05` | center crop after scale | +| `color_to_alpha` | `color` (required), `fuzz`=`0.0` | output `.png` | +| `crop_square` | — | center square crop | +| `darktable_style` | `style` (required), `style_overwrite`=`True`, `out_ext`=`""`, `config_dir`=`~/.config/darktable` | jpeg/png only | +| `xcf_stack` | `skip_missing`=`False`, `timeout`=`120` | multi-input step | +| `openrouter_edit` | `prompt`, `model`, `strength`=`0.3`, `template_image`=`None`, `api_key_env`=`OPENROUTER_API_KEY` + AI common | needs `.env` | +| `ai_exposure` | `strength`=`1.0` + AI common | `[ai]` extra | +| `ai_tone_map` | `checkpoint`=`""`, `strength`=`1.0`, `net_input_size`=`256` + AI common | `[ai]` extra | +| `comfy_flux_edit` | `prompt`, `denoise`=`0.35`, `seed`=`-1`, `server_url`, `workflow_path`, `poll_interval`=`2.0` + AI common | local ComfyUI | -### colorsplash +**AI common** (`ai_exposure`, `ai_tone_map`, `openrouter_edit`, `comfy_flux_edit`): `skip_existing`=`True`, `max_edge`=`2048`, `device`=`cpu`. + +--- + +## ImageMagick operations + +Used via modules above — not free-form CLI in pipelines. `W×H` = size of reference input image. + +| id | module | operation | +|----|--------|-----------| +| `im-solid` | `imagemagick_fill` | `-size W×H xc:COLOR` | +| `im-gradient-linear` | `imagemagick_fill` | `-size W×H -define gradient:angle=N gradient:COLOR1-COLOR2` | +| `im-gradient-radial` | `imagemagick_fill` | `-size W×H radial-gradient:COLOR1-COLOR2` | +| `im-grayscale` | `imagemagick_grayscale` | `-colorspace Gray` | +| `im-resize-max-edge` | `imagemagick_resize` | `-auto-orient -resize {N}x{N}>` (no upscale) | +| `im-scale-crop` | `imagemagick_scale_crop` | `-resize {w×scale}x{h×scale}! -gravity Center -crop w×h+0+0 +repage` | +| `im-crop-square` | `crop_square` | `-auto-orient -crop S×S+X+Y +repage` (center square) | +| `im-color-to-alpha` | `color_to_alpha` | `-alpha on [-fuzz N%] -transparent COLOR` | +| `im-composite-over` | `composite` | `(bg) (fg) -compose over -composite` | +| `im-composite-multiply` | `composite` | `mode=multiply` | +| `im-composite-screen` | `composite` | `mode=screen` | +| `im-composite-opacity` | `composite` | fg: `-channel A -evaluate multiply {0..1}` then compose | + +**Layer line → id:** `COLOR background` → `im-solid`; `gradient … 45 degree` → `im-gradient-linear`; `gradient … radial` → `im-gradient-radial`; `grayscale` → `im-grayscale`; `resize max edge N` → `im-resize-max-edge`; `make layer 5% bigger …` → `im-scale-crop` (`scale=1.05`); `crop square` → `im-crop-square`; `COLOR to alpha` → `im-color-to-alpha`. + +--- + +## G'MIC commands + +Python string = `-` + filter + args. Placeholders `R1,G1,B1` / `R2,G2,B2` from `COLOR1` / `COLOR2`. + +| id | command | +|----|---------| +| `gmic-tone-mapping` | `fx_map_tones 0.5,0.7,0.1,30,0` | +| `gmic-grayscale` | `to_gray` (via `gmic_grayscale`) | +| `gmic-feltpen` | `fx_feltpen 300,50,1,0.1,20,5` | +| `gmic-edges` | `fx_edges 0,15,0` | +| `gmic-charred-plastic` | `fx_charred_plastic 1,10,40,1,10,0,0,2,6,5,20,0,11` | +| `gmic-neon` | `fx_neon 0,1,1,1,1,0,0.45,40,60,0,1,1.15,2,3,0,3,20,0.4,0.1,1.5,5,0.2,0.1,1,2,1,0,0,1,1,0,50,50` | +| `gmic-cutout` | `fx_cutout 4,0.5,4,1` | +| `gmic-poster` | `fx_poster_hope 0,3` | +| `gmic-color-abstraction` | `fx_color_abstraction 1,10,0.2` | +| `gmic-huffman-glitches` | `fx_huffman_glitches 30,0,25,0,0,0,0,0,50,50` | +| `gmic-queryprimary` | `afre_queryprimary 1,1` | +| `gmic-local-similarity` | `local_similarity_mask 50,50,50,128,4,10` | +| `gmic-luma-invert` | `Lylejk_Luma_Invert 1,0` | +| `gmic-crayongraffiti` | `fx_crayongraffiti2 300,50,1,0.4,12,1,2,2,0` | +| `gmic-anaglyph` | `fx_stereo_to_anaglyph 2,0` | +| `gmic-stereo` | `gcd_stereo_img 0,0,2.028,1,1.714,3.06,4,1,0` | +| `gmic-drop-shadow` | `fx_drop_shadow3d 0,0,0,10,1,1,2,0.5,R2,G2,B2,200,0` | +| `gmic-jpr-smooth` | `jpr_gradient_smooth 0,1.5` | +| `gmic-bokeh` | `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` | +| `gmic-bwrecolor` | `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` | +| `gmic-custom-gradient-a` | `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` | +| `gmic-custom-gradient-b` | `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` | + +**Typical input:** `original with gmic:` = on `"input"`; `rembg with gmic:` = on rembg step. + +--- + +## Combines + +Format: recipe id, then layers bottom → top. `→` = chained composites. + +### 2 layers ```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 +rembg-custom-gradient-a + rembg with gmic: gmic-custom-gradient-a + rembg -### original-stereo-rembg +rembg-custom-gradient-b + rembg with gmic: gmic-custom-gradient-b + 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 +gmic-feltpen-rembg + rembg with gmic: gmic-feltpen + rembg + +gmic-edges-rembg + rembg with gmic: gmic-edges + rembg + +gmic-charred-plastic-rembg + rembg with gmic: gmic-charred-plastic + rembg + +gmic-neon-rembg + rembg with gmic: gmic-neon rembg ``` -- **3 layers:** composite(original, rembg_stereo) → composite(result, rembg) -- **Source:** baxxter, crusaders +### 3 layers -### original-stereo-black-alpha-rembg +```text +original-stereo-rembg + original + rembg with gmic: gmic-stereo + rembg +→ composite(original, rembg_stereo) → composite(_, rembg) + +original-drop-shadow-rembg + original + rembg with gmic: gmic-drop-shadow + rembg + +original-bwrecolor-rembg + original + rembg with gmic: gmic-bwrecolor + rembg +→ first composite: foreground_opacity=0.5 + +original-jpr-smooth-rembg + original + rembg with gmic: gmic-jpr-smooth + make layer 5% bigger then crop to original size + rembg +→ gmic → scale_crop on middle layer, then 3-layer chain + +bokeh-oktagon + original + original with gmic: gmic-bokeh + rembg + +color-bg-drop-shadow-rembg + COLOR1 background + rembg with gmic: gmic-drop-shadow + rembg + +yellow-bg-drop-shadow-rembg + YELLOW background + rembg with gmic: gmic-drop-shadow + rembg + +gmic-cutout-rembg + original + original with gmic: gmic-cutout + rembg + +gmic-poster-rembg + original + original with gmic: gmic-poster + rembg + +gmic-color-abstraction-rembg + original + original with gmic: gmic-color-abstraction + rembg + +gmic-huffman-glitches-rembg + original + original with gmic: gmic-huffman-glitches + rembg + +gmic-queryprimary-rembg + original + original with gmic: gmic-queryprimary + rembg + +gmic-local-similarity-rembg + original + original with gmic: gmic-local-similarity + rembg + +gmic-luma-invert-rembg + original + original with gmic: gmic-luma-invert + rembg + +gmic-crayongraffiti-rembg + original + original with gmic: gmic-crayongraffiti + rembg + +gmic-anaglyph-rembg + original + original with gmic: gmic-anaglyph + rembg +``` + +### 4 layers (middle pipeline) ```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 + rembg with gmic: gmic-stereo #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 0–255 (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 + rembg with gmic: gmic-jpr-smooth #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 0–255. - -| 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. +### Pipeline chain (not a combine) ```text colorsplash-watermark - colorsplash + colorsplash # expand combine first darktable style STYLE ``` -### With GIMP layer export +--- -```text -Create pipeline "foo" with recipes colorsplash, rembg-radial-2colors, and xcf-stack +## Agent example + +Request: *pipeline with colorsplash + rembg-radial-2colors, COLOR1=#244a89, COLOR2=#c22518* + +```python +rembg_out = p.step("rembg", inputs="input") +grayscale = p.step("gmic_grayscale", inputs="input") +gradient_bg = p.step("imagemagick_fill", inputs="input", color1=COLOR1, color2=COLOR2, gradient=True, radial=True) + +p.step("composite", inputs=[grayscale, rembg_out]) # colorsplash +p.step("composite", inputs=[gradient_bg, rembg_out]) # rembg-radial-2colors ``` -Agent: build all composite steps, then `p.step("xcf_stack", inputs=["input", rembg_out, …, composite_final])` listing every layer bottom → top. +3-layer combine (drop shadow on original): -```text -xcf-stack - xcf stack layers: input, rembg, composite_colorsplash, composite_radial, … +```python +rembg_shadow = p.step("gmic", inputs=rembg_out, command=GMIC_DROP_SHADOW) +mid = p.step("composite", inputs=["input", rembg_shadow]) +p.step("composite", inputs=[mid, rembg_out]) ``` -### Full combine list (baxxter style) +xcf export: -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 +```python +p.step("xcf_stack", inputs=["input", rembg_out, grayscale, composite_colorsplash, ...]) ``` - -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. diff --git a/pipelines/pipeline_aichelberg_indians.py b/pipelines/pipeline_aichelberg_indians.py new file mode 100644 index 0000000..c1f5bc2 --- /dev/null +++ b/pipelines/pipeline_aichelberg_indians.py @@ -0,0 +1,278 @@ +#!/usr/bin/env python3 +"""Aichelberg Indians pipeline — tone mapping, gmic variants, xcf export.""" + +from pathlib import Path + +from imagepipeline import Pipeline + +INPUT = Path( + "/home/frank/pics/20260712_Aichelberg Indians - Heidelberg Hedgehogs/darktable_exported" +) +OUTPUT_BASE = Path.home() / "pipeline_output" + +EXISTING_OUTPUTS: dict[str, Path] = {} +CONTINUE_FROM = Path("/home/frank/pipeline_output/aichelberg-indians_260713081414") + +COLOR1 = "#244a89" +COLOR2 = "#c22518" + +# COLOR1 = #244a89 -> 36,74,137; COLOR2 = #c22518 -> 194,37,24 +ALPHA1 = 160 +ALPHA2 = 110 + +GMIC_TONE_MAPPING = "-fx_map_tones 0.5,0.7,0.1,30,0" +GMIC_FELTPEN = "-fx_feltpen 300,50,1,0.1,20,5" +GMIC_EDGES = "-fx_edges 0,15,0" +GMIC_CHARRED_PLASTIC = "-fx_charred_plastic 1,10,40,1,10,0,0,2,6,5,20,0,11" +GMIC_NEON = ( + "-fx_neon 0,1,1,1,1,0,0.45,40,60,0,1,1.15,2,3,0,3,20,0.4,0.1,1.5,5,0.2,0.1," + "1,2,1,0,0,1,1,0,50,50" +) +GMIC_CUTOUT = "-fx_cutout 4,0.5,4,1" +GMIC_HUFFMAN_GLITCHES = "-fx_huffman_glitches 30,0,25,0,0,0,0,0,50,50" +GMIC_QUERYPRIMARY = "-afre_queryprimary 1,1" +GMIC_LOCAL_SIMILARITY = "-local_similarity_mask 50,50,50,128,4,10" +GMIC_LUMA_INVERT = "-Lylejk_Luma_Invert 1,0" +GMIC_CRAYONGRAFFITI = "-fx_crayongraffiti2 300,50,1,0.4,12,1,2,2,0" +GMIC_ANAGLYPH = "-fx_stereo_to_anaglyph 2,0" +GMIC_DROP_SHADOW = "-fx_drop_shadow3d 0,0,0,10,1,1,2,0.5,194,37,24,200,0" +GMIC_BOKEH = ( + f"-fx_bokeh 3,5,0,30,8,4,0.3,0.2,36,74,137,{ALPHA1},0.7,30,20,20,1,2," + f"194,37,24,{ALPHA2},0.15" +) + + +def main() -> None: + with Pipeline( + name="aichelberg-indians", + input_dir=INPUT, + output_base=OUTPUT_BASE, + existing_outputs=EXISTING_OUTPUTS or None, + continue_from=CONTINUE_FROM, + ) as p: + rembg_out = p.step("rembg", inputs="input", step_id="rembg_out") + + # recipe: gmic-tone-mapping + input_tone_map = p.step( + "gmic", inputs="input", command=GMIC_TONE_MAPPING, step_id="input_tone_map" + ) + + # recipe: gmic-tone-mapping-rembg + rembg_tone_map = p.step( + "gmic", inputs=rembg_out, command=GMIC_TONE_MAPPING, step_id="rembg_tone_map" + ) + + # recipe: gmic-feltpen-rembg + rembg_feltpen = p.step( + "gmic", inputs=rembg_out, command=GMIC_FELTPEN, step_id="rembg_feltpen" + ) + composite_feltpen = p.step( + "composite", inputs=[rembg_feltpen, rembg_out], step_id="composite_feltpen" + ) + + # recipe: gmic-edges-rembg + rembg_edges = p.step( + "gmic", inputs=rembg_out, command=GMIC_EDGES, step_id="rembg_edges" + ) + composite_edges = p.step( + "composite", inputs=[rembg_edges, rembg_out], step_id="composite_edges" + ) + + # recipe: gmic-charred-plastic-rembg + rembg_charred_plastic = p.step( + "gmic", + inputs=rembg_out, + command=GMIC_CHARRED_PLASTIC, + step_id="rembg_charred_plastic", + ) + composite_charred_plastic = p.step( + "composite", + inputs=[rembg_charred_plastic, rembg_out], + step_id="composite_charred_plastic", + ) + + # recipe: gmic-neon-rembg + rembg_neon = p.step( + "gmic", inputs=rembg_out, command=GMIC_NEON, step_id="rembg_neon" + ) + composite_neon = p.step( + "composite", inputs=[rembg_neon, rembg_out], step_id="composite_neon" + ) + + # recipe: gmic-cutout-rembg (tone-mapped input as original) + input_cutout = p.step( + "gmic", inputs=input_tone_map, command=GMIC_CUTOUT, step_id="input_cutout" + ) + cutout_mid = p.step( + "composite", inputs=[input_tone_map, input_cutout], step_id="cutout_mid" + ) + composite_cutout = p.step( + "composite", inputs=[cutout_mid, rembg_out], step_id="composite_cutout" + ) + + # recipe: gmic-huffman-glitches-rembg + input_huffman_glitches = p.step( + "gmic", + inputs=input_tone_map, + command=GMIC_HUFFMAN_GLITCHES, + step_id="input_huffman_glitches", + ) + huffman_glitches_mid = p.step( + "composite", + inputs=[input_tone_map, input_huffman_glitches], + step_id="huffman_glitches_mid", + ) + composite_huffman_glitches = p.step( + "composite", + inputs=[huffman_glitches_mid, rembg_out], + step_id="composite_huffman_glitches", + ) + + # recipe: gmic-queryprimary-rembg + input_queryprimary = p.step( + "gmic", + inputs=input_tone_map, + command=GMIC_QUERYPRIMARY, + step_id="input_queryprimary", + ) + queryprimary_mid = p.step( + "composite", + inputs=[input_tone_map, input_queryprimary], + step_id="queryprimary_mid", + ) + composite_queryprimary = p.step( + "composite", + inputs=[queryprimary_mid, rembg_out], + step_id="composite_queryprimary", + ) + + # recipe: gmic-local-similarity-rembg + input_local_similarity = p.step( + "gmic", + inputs=input_tone_map, + command=GMIC_LOCAL_SIMILARITY, + step_id="input_local_similarity", + ) + local_similarity_mid = p.step( + "composite", + inputs=[input_tone_map, input_local_similarity], + step_id="local_similarity_mid", + ) + composite_local_similarity = p.step( + "composite", + inputs=[local_similarity_mid, rembg_out], + step_id="composite_local_similarity", + ) + + # recipe: gmic-luma-invert-rembg + input_luma_invert = p.step( + "gmic", + inputs=input_tone_map, + command=GMIC_LUMA_INVERT, + step_id="input_luma_invert", + ) + luma_invert_mid = p.step( + "composite", + inputs=[input_tone_map, input_luma_invert], + step_id="luma_invert_mid", + ) + composite_luma_invert = p.step( + "composite", + inputs=[luma_invert_mid, rembg_out], + step_id="composite_luma_invert", + ) + + # recipe: gmic-crayongraffiti-rembg + input_crayongraffiti = p.step( + "gmic", + inputs=input_tone_map, + command=GMIC_CRAYONGRAFFITI, + step_id="input_crayongraffiti", + ) + crayongraffiti_mid = p.step( + "composite", + inputs=[input_tone_map, input_crayongraffiti], + step_id="crayongraffiti_mid", + ) + composite_crayongraffiti = p.step( + "composite", + inputs=[crayongraffiti_mid, rembg_out], + step_id="composite_crayongraffiti", + ) + + # recipe: gmic-anaglyph-rembg + input_anaglyph = p.step( + "gmic", inputs=input_tone_map, command=GMIC_ANAGLYPH, step_id="input_anaglyph" + ) + anaglyph_mid = p.step( + "composite", inputs=[input_tone_map, input_anaglyph], step_id="anaglyph_mid" + ) + composite_anaglyph = p.step( + "composite", inputs=[anaglyph_mid, rembg_out], step_id="composite_anaglyph" + ) + + # recipe: xcf-stack — layers up to composites (excludes bokeh + color-bg-drop-shadow) + p.step( + "xcf_stack", + inputs=[ + "input", + rembg_out, + input_tone_map, + rembg_tone_map, + rembg_feltpen, + rembg_edges, + rembg_charred_plastic, + rembg_neon, + input_cutout, + input_huffman_glitches, + input_queryprimary, + input_local_similarity, + input_luma_invert, + input_crayongraffiti, + input_anaglyph, + composite_feltpen, + composite_edges, + composite_charred_plastic, + composite_neon, + composite_cutout, + composite_huffman_glitches, + composite_queryprimary, + composite_local_similarity, + composite_luma_invert, + composite_crayongraffiti, + composite_anaglyph, + ], + ) + + # recipe: bokeh-oktagon (tone-mapped input as original) + input_bokeh = p.step( + "gmic", inputs=input_tone_map, command=GMIC_BOKEH, step_id="input_bokeh" + ) + bokeh_mid = p.step( + "composite", inputs=[input_tone_map, input_bokeh], step_id="bokeh_mid" + ) + p.step("composite", inputs=[bokeh_mid, rembg_out], step_id="composite_bokeh") + + # recipe: color-bg-drop-shadow-rembg + color_bg = p.step( + "imagemagick_fill", inputs="input", color1=COLOR1, step_id="color_bg" + ) + rembg_shadow = p.step( + "gmic", inputs=rembg_out, command=GMIC_DROP_SHADOW, step_id="rembg_shadow" + ) + shadow_color_mid = p.step( + "composite", inputs=[color_bg, rembg_shadow], step_id="shadow_color_mid" + ) + p.step( + "composite", + inputs=[shadow_color_mid, rembg_out], + step_id="composite_color_bg_shadow", + ) + + output_root = p.run() + + print(f"Pipeline finished. Output: {output_root}") + + +if __name__ == "__main__": + main()