# Generation parameters

Reference for every parameter accepted by the three image-producing
subcommands — `generate`, `stylize`, `upscale` — with what each does, how
it affects output, and the ranges that actually work in practice.

For batch generation via `scenario`, the same parameters are accepted as
top-level fields in the HJSON file (with `kebab-case` instead of CLI
`kebab-case` — the names match). The scenario-only fields (`scene`,
`weather`, `tasks`, prompt-assembly headers/footers, `upscale` block) are
documented inline in the annotated example
[`examples/scenario.hjson`](../examples/scenario.hjson). A runnable
batch demo with artefact placement also lives under
[`examples/tutorials/ZONES/scenario.hjson`](../examples/tutorials/ZONES/scenario.hjson).

---

## `plakat generate`

Single text-to-image inference.

### Required

#### `<PROMPT>` (positional)

What you want generated. Free text — no token limit on the input, but
CLIP/T5 tokenizers truncate to their max context (77 tokens for SD, 256
for Flux schnell, 512 for Flux dev), so the *end* of a very long prompt
is dropped.

**Style**: Stable Diffusion responds well to **comma-separated tags**:
`subject, environment, lighting, medium, style, mood`. Flux handles
**natural language** better — `"a knight standing in a moonlit forest"`
works as well as a tag list.

### Model + dimensions

#### `--model <ALIAS|REPO>` (default `sd15`)

Which base model to use. Accepts a short alias or any HuggingFace repo id.

| Alias | Resolves to | Notes |
|---|---|---|
| `sd15` | `stable-diffusion-v1-5/stable-diffusion-v1-5` | 512² native. ~2 GB download. |
| `sd21` | `stabilityai/stable-diffusion-2-1` | 768² native. |
| `sdxl` | `stabilityai/stable-diffusion-xl-base-1.0` | 1024² native. ~7 GB. |
| `sdxl-turbo` | `stabilityai/sdxl-turbo` | 512–1024. Requires `--steps 4 --guidance 0`. |
| `flux-schnell` | `black-forest-labs/FLUX.1-schnell` | 1024² typical. 4 steps. ~31 GB. |
| `flux-dev` | `black-forest-labs/FLUX.1-dev` | Gated — needs `HF_TOKEN`. 20–50 steps. |
| `flux-fill-dev` | `black-forest-labs/FLUX.1-Fill-dev` | BFL's dedicated Flux inpaint checkpoint. Driven via `plakat img2img --mask`. |
| `flux-canny-dev` | `black-forest-labs/FLUX.1-Canny-dev` | BFL "concept" Flux with canny conditioning baked into `img_in` (128 channels = 64 noise + 64 canny latent). Pass the canny map via `--concept-image PATH`. Recommended guidance ~30. Gated. |
| `flux-depth-dev` | `black-forest-labs/FLUX.1-Depth-dev` | BFL "concept" Flux with depth-map conditioning. Same shape as Canny-dev. Pass the depth map via `--concept-image PATH`. Gated. |
| `flux-kontext-dev` | `black-forest-labs/FLUX.1-Kontext-dev` | **v0.18**. BFL image-editing Flux. Reference VAE-encoded + sequence-concatenated onto the noise tokens (`img_in` stays at 64ch, unlike Canny/Depth which widen it). Pass the reference via `--concept-image PATH` on `plakat generate`, or `plakat img2img input.png --model flux-kontext-dev --prompt "..."`. Recommended guidance 3.5 (same as Dev — not 30 like Canny/Depth, since the conditioning flows through cross-attention not channel-concat). Optional `--kontext-bucket` snaps `--size` to one of 17 BFL-recommended resolutions. Gated. |
| `flux-dev-gguf` | `city96/FLUX.1-dev-gguf` | 4-bit quantized FLUX.1-dev. ~7 GB transformer (vs ~24 GB BF16). Pair with `--flux-quant-level` to pick precision. |
| `flux-schnell-gguf` | `city96/FLUX.1-schnell-gguf` | 4-bit quantized FLUX.1-schnell. |
| `flux-fill-dev-gguf` | `city96/FLUX.1-Fill-dev-gguf` | 4-bit quantized Flux Fill. |
| `flux-kontext-dev-gguf` | `unsloth/FLUX.1-Kontext-dev-GGUF` | **v0.18**. GGUF Kontext via the unsloth mirror. Same `--flux-quant-level` options as `flux-dev-gguf`. Composes with `--lora` (Kontext shares Dev's transformer layer names). NF4 isn't supported (no upstream pack). |
| `flux-dev-nf4` | `lllyasviel/flux1-dev-bnb-nf4-v2` | NF4 (bitsandbytes 4-bit) quantization. ~6 GB transformer. Composes with `--lora`. |
| `sd35-medium` | `stabilityai/stable-diffusion-3.5-medium` | Stable Diffusion 3.5 Medium. 2.5B-param MMDiT. Gated. |
| `sd35-large` | `stabilityai/stable-diffusion-3.5-large` | SD3.5 Large flagship. 8B-param MMDiT. Gated. |
| `sd35-large-turbo` | `stabilityai/stable-diffusion-3.5-large-turbo` | 4-step distillation of SD3.5 Large. `--steps 4 --guidance 0`. Gated. |
| `sd3-medium` | `stabilityai/stable-diffusion-3-medium` | original SD3 Medium (June 2024). Superseded by 3.5. Gated. |

Custom HF repos: pass the full `org/name`. For SD-family the repo must
have the diffusers layout (`unet/`, `vae/`, `text_encoder[_2]/`,
`tokenizer[_2]/`). For Flux variants we expect the BFL-native single-file
layout (`flux1-{schnell,dev}.safetensors` + `ae.safetensors`). GGUF
repos ship a single `flux1-{variant}-{LEVEL}.gguf` file; the matching
`ae.safetensors` + text encoders come from the original BFL repo
("donor") on first run.

#### `--flux-quant-level <LEVEL>` (default `Q4_K_S`)

GGUF quant level for the Flux transformer when running a `flux-*-gguf`
model. city96 publishes Q2_K through Q8_0 plus F16. Common picks:

| Level | Approx footprint | Notes |
|---|---|---|
| `Q3_K_S` | ~5.5 GB | Tightest VRAM; noticeable quality drop. |
| `Q4_K_S` | ~7 GB | Default; balanced. |
| `Q5_K_M` | ~8.5 GB | Sweet spot for quality/memory tradeoff. |
| `Q8_0` | ~13 GB | Near-BF16 quality at half memory. |
| `F16` | ~24 GB | Equivalent to BF16. |

Ignored on BF16 Flux and SD-family models.

#### `--quantize-t5` (default off)

Load T5-XXL via city96's GGUF mirror instead of the BF16 sharded
safetensors. Drops T5 from ~10 GB to ~3 GB (Q4_K_M default).
`--t5-quant-level Q5_K_M` picks a higher level. Requires `--model
flux-*-gguf` (bails loud otherwise — pairing quantized T5 with a BF16
transformer wastes T5 quality without unlocking the memory budget
that needs it).

#### `--size <WxH>`

Output dimensions. Must be multiples of 8 (16 for Flux). Examples:
`512x512`, `768x512`, `1024x1024`.

**Effect on quality**: each model was trained at a specific resolution.
Generating outside the trained band introduces specific artifacts:

- Too small → blurry, low detail, sometimes simplified subjects.
- Too large → duplication (two heads, two faces, repeated structure), or
 "ribboning" at boundaries.

Stay near each model's native resolution unless you have a reason not to.

#### `--aspect <N:M>` + `--base <SIDE>` (alternative to `--size`)

Specify an aspect ratio (`16:9`, `2:3`, `1:1`) and a base resolution
(default `768`). The shorter side becomes `--base`, the longer side scales
to match the ratio (rounded down to a multiple of 8).

Use `--aspect 16:9 --base 1024` for cinematic SDXL, etc. Exclusive with
`--size`.

#### `--count <N>` / `-n <N>` (default `1`)

How many images to generate from this prompt. Each gets a unique seed
(`seed`, `seed+1`, …). Within one invocation, the model is loaded once
and shared across all `N` images — cheap to bump.

### Sampling

#### `--steps <N>` (default `28`)

Denoising steps. Each step refines the latent further toward the prompt.

| Model | Sweet spot |
|---|---|
| SD 1.5 / 2.1 | 25–35 |
| SDXL | 25–40 |
| SDXL-Turbo | **4** (hard requirement of the trained schedule) |
| Flux schnell | **4** (rectified flow, very few steps suffice) |
| Flux dev | 20–50 |

Diminishing returns past the sweet spot. 50 steps rarely beat 30. With
LCM-LoRA on SD 1.5, 4–8 steps is enough.

#### `--guidance <FLOAT>` (default `7.5`)

Classifier-free guidance scale. Controls prompt adherence vs.
naturalness. Higher = follows prompt more strictly but with more "burn"
(oversaturated, plastic-looking, ringing artifacts).

| Model | Sweet spot |
|---|---|
| SD 1.5 / 2.1 | 7.0–9.0 |
| SDXL | 5.0–7.5 (lower than 1.5) |
| SDXL-Turbo | **0.0** (model was trained without CFG; non-zero breaks output) |
| Flux schnell | **1.0** (no CFG) |
| Flux dev | 3.0–4.5 |

If output looks oversaturated or melted, drop guidance by 1.0. If
composition is vague and ignoring details in the prompt, raise it.

#### `--negative <TEXT>` (default empty)

Negative prompt — what you *don't* want. The model's CFG pushes the
output away from this. A baseline that fixes 50% of bad outputs:

```
blurry, low quality, deformed, ugly, watermark, text, signature, jpeg artifacts, oversaturated, bad anatomy, extra limbs
```

**SDXL-Turbo / Flux ignore the negative prompt** because they don't use
CFG.

#### `--seed <U64>` (default random)

Random seed. Same seed + same prompt + same model = same image. Use this
to:
- Iterate prompts deterministically (find one composition you like, vary
 prompt at the same seed).
- Reproduce a result you already generated.
- Sweep over seeds (`--seed 0 -n 4` tries 0, 1, 2, 3).

On Metal, the seed is masked to `u32` because candle's Metal RNG accepts
only `u32`. CPU device doesn't support seeding at all — generations are
non-deterministic there (warned via debug log).

#### `--scheduler <KIND>` (default `default`)

Numerical solver for the reverse diffusion ODE.

| Choice | When |
|---|---|
| `default` | Use the variant's built-in (DDIM for SD 1.5/2.1/SDXL, Euler-A for SDXL-Turbo). |
| `ddim` | Deterministic baseline. Reproduces exactly given a seed. |
| `euler-a` | Euler-Ancestral. **Often higher quality at the same step count** for SD 1.5 / SDXL. Mildly stochastic (varies even with the same seed across runs). |
| `unipc` | UniPC corrector with Karras sigmas — DPM-Solver++ family. Predictor-corrector tends to be smooth at low step counts. **CUDA / CPU only** (Metal-guarded). |
| `dpmpp-2m` | DPM-Solver++ 2M Karras — same UniPC class with corrector disabled. Crisper edges than `unipc` at the same step count; widely considered an A1111/ComfyUI "safe default". **CUDA / CPU only**. |
| `unipc-exp` | UniPC with exponential sigma schedule (vs Karras). Different noise-step distribution; sometimes better at very low step counts. **CUDA / CPU only**. |
| `lcm` | LCM consistency-function scheduler. **Pair with an LCM-LoRA** (e.g. `latent-consistency/lcm-lora-sdv1-5`) at 4–8 steps with `--guidance 1.0–2.0`. Pure F32 math — works on Metal/CUDA/CPU. `--steps` must be ≤ 50. |
| `euler` | Deterministic Euler. Same algorithm as `euler-a` minus the per-step noise injection. Reproducible across runs given a seed. Pure F32 — works on Metal/CUDA/CPU. |
| `heun` | Heun second-order predictor-corrector. **2× the UNet evaluations** per `--steps` value (interleaved predictor + corrector). Quality typically beats Euler at the same number of model calls. Pure F32 — works on Metal/CUDA/CPU. |
| `ddpm` | The original DDPM. Slow (best at high step counts, since each step is a single Markov transition). Mainly useful as a reference. Pure F32 — works on Metal/CUDA/CPU. |

If unsure on Metal: `euler-a` (stochastic) or `euler` (deterministic).
On CUDA/CPU: `dpmpp-2m` is a strong all-purpose default. With LCM-LoRA
at low step counts: `lcm`. For maximum quality at moderate step count
where 2× UNet evaluations is fine: `heun`.

> Naming note: `--scheduler euler` means **deterministic** Euler, matching
> A1111/ComfyUI convention. The aliases `euler-a`, `eulera`, and
> `euler-ancestral` all hit Euler-Ancestral.

#### `--lcm` (v0.30, default off)

Force LCM-LoRA mode: flip `--scheduler` to `lcm`, drop `--steps` to
`4`, drop `--guidance` to `1.5`. The 10× speedup that LCM-LoRAs deliver
on SD 1.5 / SDXL t2i.

Two ways to trigger:

1. **Auto-detect** (no flag) — plakat scans your `--lora` stack for
   anything matching `lcm` (case-insensitive) in the source. Canonical
   Hub repos like `latent-consistency/lcm-lora-sdv1-5` and
   `latent-consistency/lcm-lora-sdxl` flip the switch automatically.
2. **Explicit `--lcm`** — for LCM-LoRAs you've stored under names that
   don't contain `lcm` (some Civitai uploads), or when you want
   diagnostic certainty.

User-supplied `--steps` / `--guidance` / `--scheduler` take precedence
over the override — they only fire when those args are still at their
clap defaults. So `--lcm --steps 8` runs at 8 steps for higher quality
at 2× cost; `--lcm --scheduler euler-a` keeps Euler-A.

Example:

```
plakat generate "a misty forest, dawn light" \
    --lora latent-consistency/lcm-lora-sdv1-5
# auto-detects: --scheduler lcm --steps 4 --guidance 1.5
```

Composes with `--look` / `--genre` / `--fast` presets — detection runs
*after* preset application, so preset-added LCM-LoRAs trigger the
override too.

#### `--refine <N>` + `--refine-strength <FLOAT>` (defaults: off, 0.3)

After the main denoise loop completes, run `N` extra steps of img2img on
the produced latents using the **same base model**. Sharpens details and
removes some artifacts.

`--refine-strength` controls how much fresh noise gets re-added before
the polish loop:

- `0.0` → no effect.
- `0.2–0.4` → subtle sharpening, recommended.
- `0.5+` → significant re-rendering, can change subject features.

This is the **same-model polish pass** — distinct from the real refiner
below. Useful for SD 1.5 / 2.1 (no separate refiner exists for those) or
when you want extra detail without the 6 GB refiner download.

#### `--refiner` + `--refiner-frac <FLOAT>` (defaults: off, 0.8)

Use the **official SDXL refiner** (`stabilityai/stable-diffusion-xl-refiner-1.0`)
for the last fraction of the schedule. The base SDXL UNet handles the
first `frac×N` steps; the refiner UNet handles the remaining
`(1−frac)×N` on the same latents.

- **`--refiner-frac 0.8`** (default) — last 20% of steps run on refiner.
 Diffusers reference uses this.
- Lower values (0.6) give the refiner more responsibility — different
 trade-off, sometimes better for portraits / fine textures.
- Higher values (0.9) keep more of the base's composition.

SDXL / SDXL-Turbo only — errors on SD 1.5/2.1 or Flux. Adds **~6 GB
download** for the refiner UNet on first run.

**Refiner micro-conditioning** — the refiner's
`addition_embed_type: text_time` (pooled CLIP-G + 5-id time_ids:
orig_size, crops_coords_top_left, aesthetic_score) is fully
wired via plakat's vendored `SdxlUNet2DConditionModel`. CFG
splits the aesthetic_score across the [cond, uncond] branches
(pos = 6.0, neg = 2.5 by default) so the refiner pulls toward
higher-aesthetic outputs — matches diffusers' reference
recipe.

`--refiner` and `--refine` are independent — you can stack both (refiner
for the last 20%, then a polish pass on top).

### LoRA

#### `--lora <SPEC>` (repeatable)

Apply a LoRA. Supported on SD-family (SD 1.5 / 2.1 / SDXL / SDXL-Turbo),
Flux (BF16 / GGUF / NF4), and **SD3 / SD3.5**.

The `<SPEC>` accepts three forms:

| Form | Meaning |
|---|---|
| `./local/file.safetensors` | Local path. |
| `org/repo` | HF repo. plakat picks the `.safetensors` (canonical names first, then largest). |
| `org/repo#sub/path.safetensors` | HF repo, explicit file. |

Append `:0.7` for per-LoRA scale: `--lora foo.safetensors:0.7`. Multiple
`--lora` flags stack.

**Quality dependencies**:
- The LoRA must target the same base model as `--model`. An SDXL LoRA on
 SD 1.5 will mismatch every cross-attention layer; plakat detects this,
 prints the right "use `--model sdxl`" message, and either errors (if
 zero targets match) or warns (if some match).
- Most LoRA trigger tokens need to appear in the prompt to activate. Read
 the LoRA's README on HF.

**Memory**: peak ~3.4 GB on SD 1.5 during merge, ~10 GB on SDXL. Merge
happens once per `generate` call.

**Targets merged**: plakat runs the merge against each base-model
component the LoRA touches:

- **UNet** (always)
- **CLIP-L text encoder** (when the file has `lora_te_*` or `lora_te1_*` keys, or PEFT `text_encoder.*` keys)
- **CLIP-G text encoder** (SDXL only, when the file has `lora_te2_*` or PEFT `text_encoder_2.*` keys)

Each pass reports a separate `merged X/Y targets` line — so a civitai
LoRA with both UNet and text-encoder targets shows two lines,
e.g.:

```
LoRA … → UNet: 192/192 targets merged
LoRA … → text encoder: 72/72 targets merged
```

**Formats recognized**:

| Format | Detection key(s) | Math |
|---|---|---|
| Standard LoRA / LoCon / DyLoRA | `lora_down` + `lora_up` (kohya), `lora_A` + `lora_B` (PEFT) | `W ← W + (α/rank) · (B · A) · scale` |
| DoRA | LoRA keys + `dora_scale` (per-row magnitude vector) | `W ← scale · (W + ΔW) / rowwise_L2(W + ΔW)` |
| LyCORIS LoHa | `hada_w1_a/b` + `hada_w2_a/b` | `W ← W + (W1_b·W1_a) ⊙ (W2_b·W2_a) · α/rank` |
| LyCORIS LoHa (Tucker) | LoHa keys + `hada_t1` + `hada_t2` | `W ← W + tucker(t1,a1,b1) ⊙ tucker(t2,a2,b2) · α/rank` |
| LyCORIS LoKr | `lokr_w1` (or `_a`+`_b`) + `lokr_w2` (or `_a`+`_b`) | `W ← W + kron(W1, W2) · α/dim` |

DyLoRA stores as standard LoRA at inference time (its "dynamic rank" is a
training-time feature), so it's covered by the standard arm with no
extra detection.

Conv weight shapes handled automatically: 2D Linear, 1×1 conv, and 3×3
conv (LCM-LoRA exercises all three). LoKr w2 conv weights are flattened
along trailing dims for the Kronecker reshape. `base_model.model.` /
`diffusion_model.` prefixes are stripped before matching.

Tucker LoHa uses two matmul contractions to evaluate the einsum
`"r1 r2 kh kw, r2 in, out r1 -> out in kh kw"` (the two ranks `r1`/`r2`
are both `lora_dim` in practice; the spatial dims pass through). Only
emitted by LyCORIS when training on conv layers with non-1 kernels — 2D
Linear targets don't ship a Tucker form.

#### `--lora-scale <FLOAT>` (default `1.0`)

Multiplier applied to every LoRA's per-file scale. So
`--lora foo:0.8 --lora-scale 0.5` runs `foo` at effective 0.4.

### Prompt enhancement

#### `--enhance <PROVIDER>`

Pass the prompt through an LLM that rewrites it with concrete visual
detail (composition, lighting, medium, style) before generation.

```bash
plakat generate "knight" --enhance deepseek
plakat generate "knight" --enhance local        # v0.18 — no API key
plakat generate "knight" --enhance auto         # v0.18 — pick what's available
```

| Provider | Backend | Cost |
|---|---|---|
| `deepseek` | DeepSeek API (env `DEEPSEEK_API_KEY`) | metered API |
| `gemini` | Gemini API (env `GEMINI_API_KEY`) | metered API |
| `local` | quantized LLM in-process (default: Qwen2.5-1.5B-Instruct) | one-time ~1 GB download |
| `local:<alias>` | local with explicit model — `qwen2.5-1.5b` (default) or `smollm2-360m` (CPU-budget fallback) | varies |
| `auto` | DeepSeek if `DEEPSEEK_API_KEY` set → Gemini if `GEMINI_API_KEY` set → `local` | depends on what fires |

The local enhancer runs entirely in-process via candle's quantized
LLM backends — no API key, no network after the one-time GGUF
download. Greedy decoding by default (reproducible: same prompt =
same enhancement). Refusals / empty output fall back silently to
the un-enhanced prompt rather than poisoning the diffusion encoder.

The enhancer is the cheap-but-effective way to compensate for a model
whose prompt comprehension is weaker than you'd like (SD 1.5 / 2.1 in
particular).

#### `--enhance-system PATH` (v0.19)

Override the built-in `"You rewrite text-to-image prompts..."`
system prompt with one loaded from disk. Applies to all four
providers (DeepSeek / Gemini / local / auto).

```bash
plakat generate "knight" --enhance local \
    --enhance-system ./my-style-prompt.txt
```

#### `--enhance-temp F` / `--enhance-max-tokens N` (v0.19)

Local-LLM only. `--enhance-temp 0.0` (default) is greedy decoding
— reproducible: same prompt = same enhancement. Bump to `0.5`-
`1.0` for variety at the cost of repeatability.
`--enhance-max-tokens 96` (default) caps the rewrite length;
raise for longer prompts at the cost of decode-loop latency.
Ignored on DeepSeek / Gemini (they have server-side defaults).

#### `--enhance-cache` (v0.19)

Opt-in disk cache for the local enhancer. SHA-256 of `(alias,
system, user, temperature, max_new_tokens)` keys an on-disk
lookup at `~/.cache/plakat/enhance/`; cache hits skip the LLM
forward entirely. Misses run the model and write the result on
success (refusals + empty outputs never cache).

```bash
# First run — cache miss, runs the LLM, writes to cache.
plakat generate "knight" --enhance local --enhance-cache

# Second run with the same inputs — cache hit, no LLM forward.
# ~3-5s faster on CPU; instantaneous compared to a model load.
plakat generate "knight" --enhance local --enhance-cache
```

Opt-in (rather than default-on) so iterating on the system prompt
or model alias doesn't surface stale hits. Scenarios that
re-enhance the same prompts across multiple runs are the prime
use case.

#### `--enhance-keep-original` (v0.20)

Keep the original prompt alongside the enhancer's rewrite by
joining them with the SD-family `BREAK` keyword. Each chunk gets
its own 77-token CLIP slot, so the original terms keep full
attention weight instead of being diluted by the enhancer's added
detail. Available on `plakat generate` and `plakat portrait`.

```bash
# Original: "a knight on a hill"
# Enhanced: "a knight in burnished plate armor stands on a
#            windswept hill at dawn, cinematic lighting, ..."
# Final:    "<enhanced> BREAK a knight on a hill"
plakat generate "a knight on a hill" --model sd15 \
    --enhance local --enhance-keep-original
```

SD 1.5 / 2.1 / SDXL only. On Flux and SD3 the flag warns once
and is a no-op: their T5 text encoder ignores `BREAK` and has
the token budget to carry both phrasings without it, so the
original terms aren't at risk of being clipped in the first
place. Without `--enhance` the flag is silently ignored.

### Output

#### `--format png | webp` (v0.19)

Output image container. PNG (default) carries the v0.17
Auto1111-compatible `parameters` tEXt chunk that A1111 / Civitai
/ ComfyUI auto-read on drag-and-drop. WebP ships ~30% smaller
files at perceptually-equivalent quality but can't carry the
embedded tEXt chunk (WebP's EXIF / XMP slots aren't part of the
diffusion-tools metadata convention).

The JSON sidecar (`<stem>.json`) is written for both formats —
`plakat metadata` and `plakat clone` recover the recipe from
WebP outputs via the sidecar. Drag-and-drop interop is the only
real cost.

```bash
plakat generate "..." --model sd15 --format webp
# → ./out/plakat-42.webp + ./out/plakat-42.json
```

**Scope (v0.20)**: every backbone honours `--format webp` —
SD 1.5 / 2.1 / SDXL / SDXL-Turbo, Flux (BF16 / GGUF / NF4 / Fill
/ Canny / Depth / Kontext), and SD3 / SD3.5. The `--grid` PNG
itself stays `.png` regardless (combining N WebP cells into one
shareable grid file is the common workflow). The v0.19 warn-and-
fallback on Flux / SD3 is no longer needed.

#### `--out <DIR>` (default `./out`)

Directory for generated images. Created if absent. Files are named
`plakat-<seed>.png` (or `plakat-flux-<seed>.png` for Flux).

#### `--no-metadata` (default off)

Skip the Auto1111-compatible PNG `parameters` tEXt chunk + the
sibling `<filename>.json` sidecar. By default every output ships
with the recipe (prompt, negative, seed, sampler, CFG, model,
LoRAs, ControlNet stack, refiner config) embedded so the standard
viewers (A1111 Web UI, Civitai upload, ComfyUI drag-to-load,
sd-prompt-reader) surface it. Pass `--no-metadata` when you want
anonymous PNGs.

#### `--grid` / `--grid-cols <N>` / `--grid-padding <PX>` (default off / sqrt / 0)

When `--count N > 1`, also write a single
`plakat-grid-<base-seed>.png` combining all N outputs in a
near-square grid. Per-image PNGs are still written individually.
`--grid-cols` overrides the default `ceil(sqrt(count))`;
`--grid-padding` inserts a white separator between cells. The
grid is composed AFTER artefact composite, ADetailer, and
Hires-fix passes, so the cells reflect the final post-processed
images.

#### `--preview-every <N>` / `--preview-size <PX>` (default 0 / 384)

Write a low-cost latent-projection preview PNG every N denoise
steps so long runs aren't a black box. Output goes to
`<out>/plakat-<seed>-preview.png` and is overwritten each
update. `0` (default) disables the previews. The preview path
uses the community 4-channel → RGB approximation
(microseconds — no VAE decode), so previews add no meaningful
runtime cost. SD 1.5 / 2.1 / SDXL / SDXL-Turbo only — Flux /
SD3 use 16-channel latents with a different projection matrix
not wired in this release. The final saved PNG always uses the
full VAE decode regardless of `--preview-every`.

### Tiled hi-res generation

For outputs above the model's trained working resolution (4K SDXL,
2K–4K Flux) without OOM, use MultiDiffusion-style tiled denoise:

| Flag | Default | Description |
|---|---|---|
| `--tiled` | off | Enable tiled denoise. The transformer/UNet only ever sees `--tile-size` worth of tokens per call; per-step noise predictions are blended via a 2D Hann window. |
| `--tile-size <PX>` | `1024` | Tile side length in pixels. Default matches SDXL's native and Flux's working scale. Must be a multiple of 8 (SD) or 16 (Flux + SD3). |
| `--tile-stride <PX>` | `768` | Stride between tile origins. Smaller = more overlap = smoother seams + more compute. |

Composes with: GGUF + LoRA + img2img on Flux; ControlNet on
SDXL and Flux (each tile gets its CN conditioning cropped to its
region); **Flux.1-Fill-dev** (per-tile masked-latent + mask
packing); **Flux.1-Kontext-dev** (per-tile reference-image
slicing — see "Kontext + tiled" below); **SD3 / SD3.5 img2img
+ inpaint** (the rectified-flow init lerp + RePaint mask blend
compose with the per-tile velocity blend). Does **not** compose
with the SDXL refiner, Flux concept variants (Canny-dev /
Depth-dev), SD3 ControlNet, or `--hires-fix`.

SD3 / SD3.5 join the tiled lineup. MMDiT's `pos_embed_max_size`
caps the patched tile dim at 192 (SD3 / SD3.5-Large) or 384
(SD3.5-Medium); the default 1024-px tile patches to 64×64, well
within either cap. Each per-step prediction is the post-CFG
velocity per tile, Hann-blended into a full-canvas update applied
via the Euler step.

Inpaint + tiled note: tile seams can become visible near sharp
mask boundaries because the Hann blend doesn't know about the
mask. Pass `--mask-feather PX` to smooth the transition.

```bash
# 4K SDXL
plakat generate "ultra-detailed architectural diagram" \
 --model sdxl --size 3072x2048 --tiled

# 2K Flux with depth-guided structure across every tile
plakat generate ".." --model flux-dev --size 2048x2048 \
 --tiled --control-spec 'depth:from=ref.jpg'

# 2K SD3.5-Medium
plakat generate ".." --model sd35-medium --size 2048x2048 \
 --tiled --tile-size 1024 --tile-stride 768
```

#### Kontext + tiled (v0.20)

Flux.1-Kontext-dev composes with `--tiled` for hi-res
reference-image edits. Each tile slices the matching region of
the reference latent, packs it, and seq-concats onto the tile's
noise tokens — so a 2K edit of a 2K reference image stays
spatially aligned without OOM.

```bash
plakat generate "fold the dress into a flowing cape" \
 --model flux-kontext-dev --concept-image portrait.jpg \
 --size 2048x2048 --tiled --tile-size 512 --tile-stride 384
```

**Tile-size constraint**: each tile attends over
`t5 + noise + reference` tokens, and the noise + ref halves are
the same size — so Kontext + tiled hits Flux's RoPE budget
(4096 tokens) much faster than tiled alone. At `--tile-size 512`
the per-tile attention seq is ~2560 (safe); at `--tile-size 1024`
it would be ~8700 (rejected). The pipeline bails up front with
the largest safe `--tile-size` for the current model in the
error message — typically `≤ 608` for Kontext-dev.

Reference must be passed via `--concept-image`. Composes with
`--control-spec` (CN residuals zero-pad the reference half) and
`--redux-image` (subject to the same combined RoPE budget).

### Artefact compositing

`plakat generate` (and `plakat portrait`) accept three related flags
for placing named PNG cutouts into the generated image:

| Flag | Purpose |
|---|---|
| `--artefact NAME[@ZONE[:SCALE]]` (repeatable) | Alpha-composite a named cutout from the library. |
| `--artefact-library <DIR>` | Override the bundled library path. |
| `--artefact-blend` / `--artefact-blend-strength F` | v2 — masked low-strength img2img pass to soften edges (~2–5 s on GPU). |
| `--smart-zones` | v3 — derive zones from each image's depth + luminance instead of the rigid 4×3 grid. Requires Depth-Anything-V2 (~99 MB, downloaded on first use). |

Full reference: [`ARTEFACTS.md`](ARTEFACTS.md). Runnable end-to-end
walkthrough: [`examples/tutorials/ZONES/`](../examples/tutorials/ZONES/).

---

### `--redux-image <SPEC>`

Repeatable image-conditioning input for Flux Redux. Each spec is a
path (weight = 1.0) or `path:weight=F.F` (custom weight; 0.0 turns
the image off, ≤2.0 typical range). Up to 4 references; the
attention cost grows quadratically with the seq length so 1–2 is
typical.

```bash
# Single ref
plakat generate "in this style" --model flux-dev \
 --redux-image style.png

# Multi-ref with weights
plakat generate ".." --model flux-dev \
 --redux-image style.png:weight=0.8 \
 --redux-image subject.png:weight=0.4
```

Composes with: BF16 / GGUF / NF4 Flux, LoRA, ControlNet, img2img,
tiled denoise. **Does not** compose with `flux-fill-dev` (Fill's
384ch `img_in` is incompatible).

### `--concept-image <PATH>` / `--concept-from <PATH>`

Conditioning map for Flux.1-Canny-dev / Flux.1-Depth-dev. Pass a
canny edge map (with `--model flux-canny-dev`) or a depth map (with
`--model flux-depth-dev`) at the target output resolution. The image
is VAE-encoded and packed alongside the noise tokens — the "concept"
checkpoint's `img_in` Linear is 128 channels wide (64 noise + 64
conditioning).

Two ways to supply the map:

* **`--concept-image PATH`** — a pre-rendered canny / depth map you
 already have on disk.
* **`--concept-from PATH`** — a source photo to auto-annotate. Plakat
 runs Canny edge detection (for `flux-canny-dev`) or
 Depth-Anything-V2 (for `flux-depth-dev`) on the source, writes the
 result to a temporary PNG, and feeds it to the model the same way
 `--concept-image` would.

```bash
# Auto-annotate a reference photo with the matching annotator
plakat generate "a Victorian mansion, gothic, twilight" \
 --model flux-canny-dev --concept-from photo.jpg --guidance 30

plakat generate "a polished marble statue of an angel" \
 --model flux-depth-dev --concept-from photo.jpg --guidance 30

# Or supply a pre-rendered map
plakat generate ".." --model flux-canny-dev \
 --concept-image edges.png --guidance 30
```

The two flags are mutually exclusive. `--concept-from` is only valid
with the two concept variants — passing it with `flux-dev` or any
other model raises an explicit error.

Caveats:
* Doesn't compose with `--tiled`, `--init-image` / `--mask`,
 `--redux-image`, or `--control-spec` — pairing raises an explicit
 error.
* `--guidance 30` is BFL's recommendation per their model cards.
 Lower values (3-7) underrespect the conditioning; higher
 oversharpen.

### `--fast <PRESET>`

Bundles a published distillation LoRA + recommended step + guidance
+ (where applicable) scheduler in one flag. Presets:

| Preset | Target | LoRA | Steps | Guidance | Scheduler |
|---|---|---|---|---|---|
| `hyper-8` | Flux | ByteDance Hyper-FLUX 8-step (CFG-free) | 8 | 1.0 | (default) |
| `hyper-16` | Flux | ByteDance Hyper-FLUX 16-step (CFG-free) | 16 | 1.0 | (default) |
| `turbo-alpha` | Flux | alimama-creative FLUX.1-Turbo-Alpha | 8 | 3.5 | (default) |
| `lcm-sdxl` | SDXL | Latent Consistency LoRA for SDXL | 4 | 1.5 | `lcm` |
| `lcm-sd15` | SD 1.5 | Latent Consistency LoRA for SD 1.5 | 4 | 1.5 | `lcm` |

```bash
# Flux distillation — Hyper-FLUX 8-step
plakat generate "..." --model flux-dev --fast hyper-8

# SDXL Latent Consistency — 4-step inference, ~5x speedup over base SDXL
plakat generate "..." --model sdxl --fast lcm-sdxl

# SD 1.5 Latent Consistency — 4-step inference, same recipe for the
# smaller backbone
plakat generate "..." --model sd15 --fast lcm-sd15
```

The preset LoRA gets prepended to `--lora`; `--steps`,
`--guidance`, and (for the `lcm-*` presets) `--scheduler` are
overridden **only** when you didn't pass them explicitly. Flux
presets require a non-Fill Flux model; `lcm-sdxl` requires an
SDXL / SDXL-Turbo model and bails if `--refiner` is also set (the
refiner's late-step non-LCM scheduler conflicts with the 4-step
LCM schedule); `lcm-sd15` requires an SD 1.5 model and bails on
the inpaint variant (mask + 4-step distillation interact poorly).

### Wildcards

Inline alternation + file-backed random picks in the prompt.
Auto1111 / NovelAI / ComfyUI grammar.

| Flag | Default | Description |
|---|---|---|
| `--wildcard-dir <DIR>` | (none) | Directory holding `<name>.txt` wildcard files for `__name__` expansion. |

* **Inline alternation**: `{red|blue|green}` picks one of the three
 at random. Nestable: `{a {b|c}|d}` → `a b`, `a c`, or `d`. Works
 without `--wildcard-dir`.
* **Weighted alternation (v0.31)**: any option may be prefixed with
 `WEIGHT::` to bias the random pick. Weights are relative —
 normalized over the group:
   * `{2::common|rare}` — "common" is **twice as likely** as "rare"
     (omitted weight defaults to `1.0`).
   * `{0.7::a|0.3::b}` — explicit probabilities.
   * `{4::landscape|1::portrait|0::wide}` — `0::` excludes
     "wide" entirely.

  Malformed weight prefixes (negative, NaN, non-numeric) fall back
  to treating the option as literal text — `{foo::bar|baz}` is two
  options, `foo::bar` and `baz`, each at weight 1.0. Nestable with
  unweighted groups: `{9::{a|b}|1::c}` picks an inner-uniform
  `a`/`b` 90% of the time, `c` 10%.
* **File wildcards**: `__name__` reads
 `<wildcard-dir>/<name>.txt` and picks a uniformly-random
 non-empty, non-comment (`#`) line. Names accept letters, digits,
 `-`, and `_` (so `__warm-colors__` and `__warm_colors__` both
 work).

```bash
plakat generate "a {red|blue|green} {fox|cat|owl}" \
 --model sd15 --count 4 --seed 42

# v0.31 weighted — landscape twice as likely as portrait
plakat generate "a {2::landscape|portrait} of a runner" \
 --model sd15 --count 4 --seed 42

mkdir -p wildcards
echo -e "ruby\ncrimson\namber" > wildcards/warm-colors.txt
plakat generate "a __warm-colors__ fox" \
 --wildcard-dir ./wildcards --model sd15
```

The wildcard RNG is seeded from `--seed` when set (reproducible
expansion) and from OS entropy otherwise. Expansion runs **before**
`--enhance` so the enhancer sees a concrete prompt.

### Inline LoRA tags (v0.18)

A1111 / Civitai-style `<lora:NAME[:weight]>` syntax in the prompt.
The tag is extracted at the CLI boundary, the LoRA is prepended to
the user's `--lora` stack, and the cleaned prompt (tag removed)
flows on to the encoder. Available on `plakat generate`,
`plakat img2img`, and `plakat portrait`.

```bash
# Inline a single LoRA — no separate --lora needed
plakat generate "a watercolor fox <lora:civitai:12345:0.7>" \
    --model sd15

# Multiple LoRAs in the same prompt
plakat generate "<lora:style1:0.5> a fox <lora:style2:0.3>" \
    --model sd15

# Mix inline + explicit --lora; --lora wins on key collision
plakat generate "a fox <lora:style:0.7>" \
    --model sd15 \
    --lora civitai:99999:0.5
```

Grammar (everything inside `<lora:>` is whatever
`LoraSpec::from_str` accepts — same as the `--lora` flag):

| Form | Meaning |
|---|---|
| `<lora:NAME>` | weight = 1.0 (A1111 default) |
| `<lora:NAME:0.7>` | explicit weight |
| `<lora:myfile.safetensors[:weight]>` | local file |
| `<lora:author/repo[#file.safetensors][:weight]>` | HF repo |
| `<lora:civitai:NNNNNN[#file][:weight]>` | Civitai by model id |
| `<lora:civitai-version:NNNNNN[:weight]>` | Civitai by version id |

Ordering: wildcards → enhance → `<lora:>` extraction → attention
syntax → encode. So `<lora:{styleA|styleB}>` resolves a single name
via the wildcard pick BEFORE this stage sees it.

Negative-prompt `<lora:>` tags are stripped silently (A1111
convention; LoRAs apply to both the cond and uncond CLIP forwards
regardless of which branch they appeared in).

Unbalanced `<lora:` with no closing `>` is treated as a literal —
no error, just no extraction (same robustness contract as the
attention parser and wildcards).

### `--negative-preset` (v0.19)

Bundled negative-prompt presets — saves users from copy-pasting
the same `blurry, low quality, watermark, ...` line into every
invocation. Four entries shipped:

| Preset | Curated for |
|---|---|
| `photo` | realistic photography — suppresses blur / jpeg artifacts / watermarks / deformities |
| `painting` | oil / watercolor / acrylic — quality + overlay suppressors; allows anatomical liberty |
| `anime` | anime / illustration — bad-hands + missing-fingers + lowres specifically |
| `cinematic` | film / poster — moody compositions; preserves colour saturation |

```bash
# Preset alone
plakat generate "a sunlit forest path" --model sd15 --negative-preset photo

# Combine with explicit --negative (preset first, user appended)
plakat generate "anime girl, masterpiece" --model sd15 \
    --negative-preset anime --negative "purple hair"
```

Works on `plakat generate`, `plakat img2img`, and `plakat portrait`.
The portrait subcommand normally falls back to its built-in
DEFAULT_NEGATIVE (face / hand suppressors); `--negative-preset`
replaces that fallback. A typo bails up front with the list of
supported names.

#### User-defined presets (v0.20)

Drop a plain-text file into the plakat config directory and the
filename (without `.txt`) becomes a `--negative-preset` name:

| Platform | Location |
|---|---|
| Linux   | `~/.config/plakat/negative-presets/<name>.txt` |
| macOS   | `~/Library/Application Support/ai.plakat.plakat/negative-presets/<name>.txt` |
| Windows | `%APPDATA%\plakat\plakat\config\negative-presets\<name>.txt` |

```bash
mkdir -p ~/.config/plakat/negative-presets
cat > ~/.config/plakat/negative-presets/anatomy.txt <<'EOF'
extra fingers, fused fingers, mutated hands, asymmetric eyes,
missing limbs, deformed face
EOF

plakat generate "portrait of an alchemist" --model sd15 \
    --negative-preset anatomy
```

File contents become the negative prompt verbatim (whitespace
trimmed). Names must be `[a-zA-Z0-9_-]+` — slashes / dots / spaces
are skipped. **User files override built-ins of the same name**,
so saving an `anime.txt` replaces the bundled `anime` preset (a
typo'd `--negative-preset anime` lists it as `anime (user
override)` in the error output so the source is unambiguous).

### BREAK keyword (SD 1.5 / 2.1 / SDXL — v0.18)

CLIP's 77-token cap silently truncates long prompts. Split a long
prompt into chunks with the literal word `BREAK` (case-sensitive,
word-bounded); each chunk gets its own 77-token CLIP encoding, and
the per-chunk hidden states are sequence-concatenated before the
UNet's cross-attention consumes them. No max sequence length on
attention — chunks just stack.

```bash
# Two-chunk prompt — 154 tokens of conditioning instead of 77
plakat generate \
    "a brutalist whale poster, watercolor on rough paper, \
     dramatic lighting, cinematic composition \
     BREAK \
     soft pastels, hand-painted feel, no digital artifacts, \
     1970s editorial illustration aesthetic" \
    --model sd15
```

| Backbone | BREAK support |
|---|---|
| SD 1.5 / SD 2.1 | ✓ — chunks the single CLIP-L encoder |
| SDXL / SDXL-Turbo | ✓ — chunks both CLIP-L and CLIP-G; pooled `add_text_embeds` comes from chunk 0 (A1111 convention) |
| Flux (BF16 / GGUF / NF4) | strips + warns (T5 already has a 256/512-token budget) |
| SD3 / SD3.5 | strips + warns (T5 budget + pooled `y` assumes single-chunk) |

CFG: both cond (`--prompt`) and uncond (`--negative`) are chunked
independently. Whichever has fewer chunks is padded with empty
chunks so the resulting `(2 × num_chunks × 77)` cat shape lines up.

Adjacent BREAKs (e.g. `BREAK BREAK`) drop empty chunks; pathological
`BREAK BREAK` alone falls back to a single empty chunk. The keyword
is case-sensitive — `Break` / `break` / `breakfast` are passed
through as ordinary words. Word-boundary check is ASCII (matches
A1111's regex behaviour) — `BREAKING`, `BREAKDOWN`, `BREAKERS_v1`
all stay intact as words.

### Attention emphasis (all backbones)

A1111 / NovelAI-style prompt grammar — used by virtually every
Civitai LoRA card. plakat parses these inline and applies the
per-token weight to the encoder's hidden state (per-row scale on
the penultimate output, before cross-attention).

| Syntax | Weight |
|---|---|
| `(token)` | `× 1.1` (default emphasis). |
| `((token))` | `× 1.21` (nested). |
| `(token:1.5)` | `× 1.5` (explicit). |
| `[token]` | `× 1/1.1 ≈ 0.909` (default de-emphasis). |
| `[token:0.6]` | `× 0.6` (explicit). |
| `\(`, `\)`, `\[`, `\]` | escaped — literal punctuation. |

```bash
plakat generate \
    "masterpiece, best quality, (1girl:1.2), (red hair:1.3), [low quality]" \
    --model sd15

# Works on Flux too — Civitai Flux LoRA cards rely on it.
plakat generate \
    "a (cyberpunk:1.4) street market, [muted colors]" \
    --model flux-dev

# And SD3 / SD3.5 — broadcasts on CLIP-L, CLIP-G, and T5 hidden states.
plakat generate \
    "a (cinematic:1.3) portrait, [shallow depth of field]" \
    --model sd35-medium
```

**Supported**: SD 1.5 / SD 2.1 / SDXL (CLIP-L + CLIP-G penultimate),
Flux (T5-XXL hidden states — CLIP-L on Flux is pooled-only, so per-
token weighting is a no-op there), SD3 / SD3.5 (CLIP-L penult,
CLIP-G penult, and T5 hidden — pooled CLIP-{L,G} stays unweighted).

**Negative prompts**: `--negative` accepts the same syntax. The
uncond branch of CFG goes through the same encoder entry points as
the positive branch, so `(blurry:1.5), [low quality]` weights the
"blurry" cluster more than other negative tokens.

```bash
plakat generate "portrait, soft light" \
    --negative "(blurry:1.6), (low quality:1.4), [oversharp]" \
    --model sdxl
```

**Sentencepiece caveat (Flux + SD3)**: T5's sentencepiece tokenizer
may produce a slightly different subtoken split for a segment in
isolation vs the same text inside a longer string (the leading
word-boundary marker depends on the previous character). The weight-
per-resulting-subtoken contract is preserved either way — the visual
effect of `(token:1.5)` matches A1111 even when the subtoken count
drifts by one. Unbalanced parens are treated as literal characters —
no error, just no emphasis.

### CLIP-skip (SD 1.5 / SD 2.1)

| Flag | Default | Description |
|---|---|---|
| `--clip-skip <N>` | `1` | Use the N-th-from-last CLIP-L hidden state. `1` = last (diffusers default; byte-identical to). `2` = penultimate (community default for SD 1.5 anime checkpoints). |

SDXL ignores `--clip-skip > 1` with a warning (the dual-encoder
path already uses penultimate by training default). Flux / SD3
ignore the flag entirely.

### ADetailer face refinement (SD-family)

After the t2i pass, runs SCRFD on each output to detect faces,
crops + img2img-refines each face, and feather-composites the
refined crop back. Reuses the t2i SdCore — no second model load.

| Flag | Default | Description |
|---|---|---|
| `--adetailer` | off | Enable the post-t2i face refinement pass. |
| `--adetailer-strength <F>` | `0.4` | img2img strength on each face crop. Lower preserves identity + colour; `0.6+` re-imagines. |
| `--adetailer-padding <F>` | `0.25` | Bbox expansion per side. More = better blending, less res per face. |
| `--adetailer-feather <F>` | `0.25` | Outer fraction of the bbox that fades to 0 opacity at the edge. |
| `--adetailer-confidence <F>` | `0.5` | SCRFD score threshold. Faces below are skipped. |
| `--adetailer-size <PX>` | `512` | Working resolution for the face img2img (square, snapped to /8). |
| `--adetailer-prompt <STR>` | (generic) | Override the face-pass prompt. Default: "detailed face, sharp focus, high quality". |

**Required setup**: SCRFD weights via `PLAKAT_SCRFD_WEIGHTS` (local
path) or `PLAKAT_SCRFD_HF` (HF spec). Same env vars the FaceID
portrait flow uses.

**Restrictions**: SD 1.5 / SD 2.1 / SDXL / SDXL-Turbo only. Flux /
SD3 bail loud. Composes with `--lora`, `--seed`, and `--hires-fix`
(ADetailer runs after hires fix so face refinement operates on the
upscaled image).

```bash
plakat generate "a woman walking through a forest, full body shot" \
 --model sd15 --size 768x1024 --adetailer
```

### Hires fix (SD-family)

Mitigates the "multi-head problem" SD/SDXL produce when sampled
above their trained resolution. Generate at the trained res, then
upscale + img2img-refine.

| Flag | Default | Description |
|---|---|---|
| `--hires-fix` | off | Enable the post-t2i upscale + refine pass. |
| `--hires-scale <F>` | `2.0` | Upscale factor for classical upscalers. ML upscalers (Real-ESRGAN) use native scale and ignore this. |
| `--hires-strength <F>` | `0.5` | img2img strength on the upscaled image. Lower preserves composition; `0.7+` allows more reinterpretation. |
| `--hires-upscaler <MODE>` | `lanczos` | `lanczos / bicubic / bilinear / nearest / real-esrgan-x2 / real-esrgan-x4 / real-esrgan-anime-x4`. |
| `--hires-steps <N>` | (main `--steps`) | Step count for the refine pass. |

**Restrictions**: SD 1.5 / SD 2.1 / SDXL / SDXL-Turbo only. Flux /
SD3 already have `--tiled` for high-res output and bail loud. Does
**not** compose with `--artefact*` (upscale changes dims; the
artefact compositor reads the original t2i dims).

```bash
# 1.5k SD 1.5 via 2x Lanczos + img2img
plakat generate "an astronaut on a beach" \
 --model sd15 --size 768x768 --hires-fix

# 4K poster: hires-fix + ADetailer
plakat generate "a vintage travel poster of Tokyo at night" \
 --model sd15 --size 768x768 --steps 30 \
 --hires-fix --hires-upscaler real-esrgan-x2 --hires-strength 0.45 \
 --adetailer
```

### Textual Inversion

| Flag | Description |
|---|---|
| `--embedding <SPEC>` (repeatable) | Textual Inversion `.safetensors`. Format: `PATH_OR_REPO[:trigger][:scale]`. |

**Status (v0.31):** runtime injection works for SD 1.5 / SD 2.1 /
SDXL — both **CLIP-L-only** TIs (single tensor) and **SDXL dual-
encoder** TIs (files carrying both `clip_l` and `clip_g` tensors
in the same safetensors). At load time plakat appends the TI's
vectors to the matching `token_embedding.weight` matrix — CLIP-L
for single-encoder TIs and SD 1.5 / SD 2.1; both CLIP-L and
CLIP-G when the file carries the dual format on SDXL. The
trigger tokens are registered with the matching tokenizer(s) so
prompts referencing the trigger word resolve to the new vocab IDs.

Multiple `--embedding` flags stack: each one adds its rows
sequentially. Multi-vector TIs (e.g. `(N, 768)` with N > 1) are
rendered as `N` consecutive tokens in the prompt — each new vector
becomes one new vocab entry (`trigger`, `trigger_1`, ...). Dual-
encoder TIs must agree on N across both halves (the parser bails
if `clip_l` has 2 vectors but `clip_g` has 3).

A stack can mix single- and dual-encoder TIs on SDXL: single-
encoder TIs only extend CLIP-L; dual-encoder TIs extend both.

Use `plakat embedding info PATH` to inspect a TI file before
applying it.

---

## `plakat outpaint`

Extend an input image past its borders. A thin wrapper around the
inpaint pipeline: expand the canvas, replicate-fill the new region
from the input's edge pixels, build a mask covering the new region
(white = inpaint, black = preserve), then hand off to `plakat
img2img --mask`.

```bash
# 256 px on every side, default sdxl-inpaint
plakat outpaint photo.png --prompt ".." --expand 256

# Panoramic: extend only horizontally
plakat outpaint photo.png --prompt "wide mountain valley" \
 --left 512 --right 512

# Flux Fill outpaint
plakat outpaint photo.png --prompt ".." --expand 256 \
 --model flux-fill-dev
```

| Flag | Default | Description |
|---|---|---|
| `<INPUT>` | required | Path to the input image. |
| `--prompt` | required | Describes the whole output (including the new region). |
| `--left / --right / --top / --bottom <PX>` | `0` | Per-side padding. At least one must be > 0. |
| `--expand <PX>` | (off) | Shorthand for all four sides equally. Conflicts with per-side flags. |
| `--model` | `sdxl-inpaint` | Inpaint-capable model. `sd15-inpaint` and `flux-fill-dev` also work, as do non-inpaint models in RePaint mode. |
| `--mask-feather <PX>` | `16` | Softens the seam between original + outpainted regions. Higher than `img2img`'s default 8. |

Padding is snapped up to the model's VAE / patch granularity (8 for
SD, 16 for Flux). Edge replication gives the inpaint UNet a smooth
low-frequency hint at the seam — beats flat gray, which often biases
the denoise toward "wall" content.

---

## `plakat portrait`

Portrait generation, optionally guided by a reference photo. The default
strategy is **IP-Adapter-Plus-Face** on SD 1.5 — the photo flows through
CLIP-H's penultimate hidden state, a 4-layer Perceiver resampler emits
16 image tokens, those concat onto the text token sequence, and the
standard SD denoise loop runs from pure noise. Other strategies
(`plus-face-sdxl`, `faceid`, `faceid-sdxl`) follow the same pattern with
different model variants and identity encoders. Without `--photo`, the
command degrades to a portrait-tuned text-only generate (3:4 aspect,
face/anatomy negatives baked in) with no extra download.

```bash
plakat portrait "cinematic close-up, soft Rembrandt lighting" \
 --photo face.jpg --face-strength 0.8
```

#### `<PROMPT>` (positional, required)

Describes the portrait: pose, lighting, framing, style. The photo seeds
identity; the prompt shapes everything else.

#### `--photo <PATH>` (optional)

Reference photo. Provide a tight head-and-shoulders crop, or use
`--face-bbox` to mark the face region. Optional SCRFD auto-detection
(`PLAKAT_SCRFD_*` env vars) fills landmarks automatically — see
PERSONA.md. With no `--photo`, the identity branch is skipped entirely
and no Plus-Face / CLIP-H weights are downloaded.

#### `--identity <KIND>` (default `plus-face`)

Identity strategy. Must match the `--model` you pass (the validator
refuses cross-attn-dim mismatches at load):

- `plus-face` — IP-Adapter-Plus-Face, SD 1.5. Use with `--model sd15`
 (or any HF SD 1.5 repo).
- `plus-face-sdxl` (aliases: `plusface-sdxl`, `plus-face-xl`,
 `sdxl-plus-face`) — IP-Adapter-Plus-Face SDXL via the `vit-h` variant
 (reuses the SD 1.5 CLIP-H image encoder). Use with `--model sdxl`.
- `faceid` — IP-Adapter-FaceID, SD 1.5. Uses the InsightFace ArcFace
 IR-ResNet50 embedding for stronger identity preservation than
 CLIP-H-based strategies. Requires ArcFace weights (set via
 `PLAKAT_ARCFACE_WEIGHTS` or `PLAKAT_ARCFACE_HF`). See PERSONA.md
 "FaceID setup".
- `faceid-sdxl` — IP-Adapter-FaceID on SDXL. Same ArcFace backbone;
 SDXL UNet target. Use with `--model sdxl`.
- `instantid` — roadmap; not yet implemented.

#### `--face-strength <FLOAT>` (default `0.8`)

Scales the image-token contribution before concatenation. Standard
IP-Adapter `scale` parameter equivalent.

- `0.0` — image tokens vanish; equivalent to running without `--photo`.
- `0.5` — light influence, prompt dominates.
- `0.8` — default; strong likeness while keeping prompt steering.
- `1.0+` — over-amplifies the face; useful for tough photos but the prompt
 can start losing control.

Ignored without `--photo`.

#### `--model <ALIAS|REPO>` (default `sd15`)

Either `sd15` (Stable Diffusion 1.5, default) or `sdxl` (SDXL base 1.0).
Any HF SD-1.5/SDXL repo id also works. Flux is not supported for
portraits. Pair `--model sdxl` with `--identity plus-face-sdxl`.

#### `--size <WxH>` / `--aspect <N:M>` + `--base <SIDE>`

Same semantics as `generate`. Defaults to `--aspect 3:4 --base 768`
(`768×1024`).

#### `--count <N>` / `-n <N>` (default `1`)

Number of portraits per invocation. Each gets `seed + i` if `--seed` is set.

#### `--steps <N>` (default `30`)

Slightly higher than `generate`'s `28` because faces benefit from a few
extra refinement steps. With `--scheduler lcm` + an LCM-LoRA you can drop
to 4–8.

#### `--guidance <FLOAT>` (default `7.0`)

Tuned a touch below `generate`'s `7.5` — IP-Adapter conditioning already
pulls strongly toward the reference, so very high CFG tends to over-saturate.

#### `--negative <TEXT>` (default: face-and-anatomy fixers)

The baseline negative covers `deformed face, asymmetric eyes, extra fingers,
cross-eyed, low quality, blurry, watermark, jpeg artifacts, bad anatomy,
cropped head, disfigured, extra limbs, low resolution`. Pass an explicit
`--negative ""` to disable, or a custom string to fully replace it.

#### `--scheduler <KIND>` (default `euler-a`)

Defaults to Euler-Ancestral — its stochasticity helps skin-tone gradients
look less plasticky than deterministic samplers on SD 1.5. All schedulers
from `generate` are available.

#### `--lora <SPEC>` / `--lora-scale <FLOAT>`

Same syntax as `generate`. A realistic-portrait LoRA stacks cleanly on top
of the Plus-Face conditioning — the LoRA controls aesthetic, the photo
controls identity.

#### `--refine <N>` / `--refine-strength <FLOAT>` (defaults: off, `0.3`)

Same-model polish pass on the final latents. Identity conditioning persists
through the polish loop, so refining usually sharpens without losing the
likeness.

#### `--seed <U64>` / `--enhance <PROVIDER>` / `--out <DIR>`

Identical to `generate`. Output files are named `plakat-portrait-<seed>.png`
to distinguish them from `generate`'s `plakat-<seed>.png`.

### What portrait is and isn't

For `plus-face` / `plus-face-sdxl` strategies: candle 0.8's UNet exposes
no cross-attention hooks, so the *decoupled* IP-Adapter path — separate
`to_k_ip` / `to_v_ip` projections in every block — is not wired up.
Identity tokens travel through the same cross-attention as text. Result:
identity is recognisable but not pixel-perfect, ~50–70% of the diffusers
reference. The `faceid` / `faceid-sdxl` strategies bypass this ceiling
using InsightFace ArcFace embeddings + an automatically-applied UNet
LoRA, landing closer to ~80–90% of reference — at the cost of needing
ArcFace weights set up (see PERSONA.md).

Plus-Face strategies have no face detector — pass a head-and-shoulders
crop, or use `--face-bbox`. FaceID strategies can optionally use SCRFD
for automatic face detection (`PLAKAT_SCRFD_*` env vars).

First-run downloads (`plus-face` / SD 1.5):

- Plus-Face safetensors (~50 MB).
- CLIP-H image encoder (~2.5 GB) — shared with `stylize`, cached once.
- SD 1.5 base (~4 GB) — shared with `generate` / `stylize`.

---

## `plakat stylize`

IP-Adapter style transfer: take an input image and a reference image,
produce an output that keeps the input's content with the reference's
style. SD 1.5 base only.

#### `--in <IN>` (required)

Input image (the thing whose content you want to keep). Any common
format (PNG, JPEG, WebP). Aspect ratio is preserved; dims are rounded to
multiples of 8 and capped at 768.

#### `--ref <REF>` (required)

Style reference image. plakat resizes it to 224×224 and CLIP-normalizes
internally — high resolution isn't required.

#### `--out <OUT>` (required)

Output PNG path. Created (with parent dirs) if absent.

#### `--strength <FLOAT>` (default `0.7`)

How much to redraw IN. Higher = more like REF, less like IN. This is the
single most important knob — pick it based on **what IN is**, not what
you want REF to do.

| Strength | Face input | Non-face input |
|---|---|---|
| `0.2` | reference barely visible, face exact | barely visible, photo unchanged |
| `0.3 – 0.4` | subtle restyling, face preserved | mild palette shift |
| `0.5` | clear style shift, mild face drift | balanced restyling |
| `0.6` | strong style shift, face starts to drift | recognisable IN, distinctive REF |
| `0.7` (default) | heavy restyle, identity wobble — **too much for faces** | balanced→heavy restyle |
| `0.8 +` | IN is essentially a composition hint; identity gone | heavy redraw |

The default `0.7` is tuned for scenes/landscapes. For face inputs use
`0.35` (or `--preset portrait`, below).

#### `--preset <PRESET>` (default off)

Strength preset shortcut — picks a documented `--strength` for a use
case. Explicit `--strength` always wins if both are passed.

| Preset | Strength | Use for |
|---|---|---|
| `portrait` (aliases: `face`, `person`) | `0.35` | Face inputs — preserves identity while picking up REF's palette/brushwork. |
| `scene` (aliases: `landscape`, `balanced`) | `0.55` | Landscapes, architecture, objects. Clear style shift, structure preserved. |
| `grading` (aliases: `grade`, `tonal`, `color`) | `0.25` | Tonal/colour grading — adds REF's character without redrawing anything. Safest preset for photos you want to keep recognisable. |

Examples:

```bash
# Face-preserving style transfer.
plakat stylize --in face.jpg --ref painting.jpg --out styled.png --preset portrait

# Landscape restyling.
plakat stylize --in landscape.jpg --ref ukiyoe.jpg --out styled.png --preset scene

# Just borrow the reference's colour palette.
plakat stylize --in photo.jpg --ref gradient.jpg --out graded.png --preset grading

# Preset + explicit override → explicit wins (warns about the conflict).
plakat stylize .. --preset portrait --strength 0.45 # uses 0.45
```

#### `--steps <N>` (default `30`)

Denoising steps for the img2img pass. 20–40 is the useful range.

#### `--seed <U64>` (default random)

Same semantics as `generate`. Reproducible given the same IN + REF +
strength + seed.

#### `--model <ALIAS|REPO>` (default `sd15`)

Base diffusion model for the denoise loop. **Currently SD 1.5 only.** The
IN image's original generator can be anything (SDXL, Flux, a photo) —
stylize operates on image bytes, not on latents from another model.

### What stylize is and isn't

Stylize uses IP-Adapter's image projection (the `image_proj.*` weights
from `h94/IP-Adapter`) to map the CLIP-H image embedding of REF into the
text-token space, then concats those tokens onto the (empty) text prompt
for the UNet.

The **reference IP-Adapter** also adds decoupled cross-attention
(separate `to_k_ip`/`to_v_ip` projections per UNet layer) — plakat
doesn't implement that path because candle's UNet has no attention
hook. Quality is roughly 50–70% of the reference implementation.

For consistent results: REF should have a clear visual style (painting,
sketch, distinctive photo). REFs that are themselves stylistically neutral
won't push IN very far.

---

## `plakat upscale`

Resize an image. Two families: classical filters (pure CPU resize, no
weights) and Real-ESRGAN (RRDBNet ported to candle, ML).

#### `--in <IN>` (required)

Source image. Any common format (PNG, JPEG, WebP).

#### `--out <OUT>` (required)

Output path. Extension determines format (`.png`, `.jpg`, `.webp`).

#### `--scale <FLOAT>` (default `2.0`)

Scale factor for **classical** methods. Non-integer values OK:
`--scale 1.5`, `--scale 2.5`. New dimensions are `round(orig × scale)`.
**Ignored for ML methods** — those have a fixed factor baked into the model.

#### `--method <METHOD>` (default `lanczos`)

**Classical** (fast, predictable, no weights):

| Filter | Quality | Best for |
|---|---|---|
| `nearest` | Lowest — pixelated | Pixel art, retro look |
| `bilinear` | Blurry edges | Thumbnails |
| `bicubic` | Decent, slight softening | General-purpose |
| `lanczos` | Sharpest, slight ringing on hard edges | Photographic / detailed images |

**ML** — Real-ESRGAN (downloads ~64 MB on first use):

| Method | Scale | Variant | Use case |
|---|---|---|---|
| `real-esrgan-x2` | ×2 | xinntao x2plus | Subtle resolution boost, photographic |
| `real-esrgan-x4` | ×4 | xinntao x4plus | Standard 4× upscale, recovers fine detail |
| `real-esrgan-anime-x4` | ×4 | xinntao x4plus_anime_6B | Line art / anime / illustration |

ML methods run on the device chosen by `--device`. Memory scales with the
**output** size — a 4× upscale of 1024² → 4096² requires room for that
64 MB output tensor in F32 plus intermediates.

For 8× or more, chain two calls (`real-esrgan-x4` → `real-esrgan-x2`).
ESRGAN models weren't trained for cascaded use past 4–8×, so quality
degrades.

---

## `plakat transparent`

Make every pixel whose colour matches the **upper-left corner** transparent.
Useful for stripping solid-colour backgrounds from generated images, logos,
or screenshots.

#### `--in <IN>` (required)

Source image.

#### `--out <OUT>` (required)

Output. Must be a format that supports alpha (`.png` or `.webp`); `.jpg` is
rejected with a clear error.

#### `--tolerance <N>` (default `0`)

Per-channel tolerance for the colour match (0–255). `0` is an exact match;
~`10` absorbs JPEG noise on solid backgrounds; ~`30+` extends to anti-aliased
edges and similar shades.

```bash
plakat transparent --in logo.png --out logo-alpha.png --tolerance 12
# → ✓ key #ffffff • 512×512 • 238921/262144 pixels transparent (91.1%)
```

---

## `plakat models`

Browse HuggingFace and manage the local cache.

| Subcommand | Purpose |
|---|---|
| `models search <QUERY>` | Free-text HF search. |
| `models recommend [--query Q] [--sort downloads\|likes\|trending\|recent] [--limit N]` | T2I-filtered recommendations. |
| `models size <REPO>` | Total Hub size + the subset plakat would actually download. |
| `models pull <REPO>` | Pre-download SD/Flux weight files for a repo. |
| `models ls` | List cached models with disk usage. |
| `models rm <REPO>.. [--yes]` | Delete cached models (with size + confirmation by default). |
| `models aliases [--family F] [--repo] [--gated]` | List every `--model` alias plakat recognises, grouped by family. **v0.20.** |

### `plakat models aliases`

Enumerates the static alias table — the same lookup `--model` runs
against — so you can see what short names plakat accepts before
typing one. Three modes:

```bash
# Default: human-readable, grouped by family
plakat models aliases

# Filter to one family (substring match, case-insensitive)
plakat models aliases --family flux
plakat models aliases --family "sd 3"

# Bare repo ids, one per line — pipes cleanly into models pull
plakat models aliases --family flux --repo | xargs -L1 plakat models pull

# Only HF_TOKEN-gated repos
plakat models aliases --gated
```

Output rows show every alias spelling, the canonical HF repo, the
sub-kind (`base`, `inpaint`, `GGUF`, `Kontext`, …), a `[gated]`
marker when HF_TOKEN is required, and a one-line note. Adding a
new alias in `src/hf/mod.rs` updates both the resolver and this
listing.

---

## `plakat civitai`

Browse + download Civitai assets — community LoRAs, checkpoints,
embeddings, ControlNet variants.

| Subcommand | Purpose |
|---|---|
| `civitai search <QUERY> [--asset-type lora\|checkpoint\|ti\|controlnet\|vae] [--limit N] [--page P] [--include-nsfw]` | Free-text search. Filters NSFW by default; pages 1-indexed. |
| `civitai info <REF>` | Show model/version details — trigger words, base model, files. |
| `civitai download <REF> [--file NAME]` | Fetch the asset into the local cache. Prints the absolute path. |

`<REF>` accepts: bare integer model ID (`123456`), `civitai:`
shorthand (`civitai:123456`), or any of the `https://civitai.com/`
URL shapes (`/models/<id>`, `/models/<id>/<slug>`,
`?modelVersionId=..`, and the `/api/download/models/<vid>`
direct-download form).

Downloads stream into
`<plakat-cache>/civitai/model-<id>/version-<id>/`. Cache-hit
short-circuits on matching size. Authenticated downloads use
`CIVITAI_API_KEY` from the env. Drop the printed path into
`--lora` or `--model`.

```bash
plakat civitai search "watercolor" --asset-type lora --limit 10
plakat civitai info 12345
plakat civitai download "https://civitai.com/models/12345?modelVersionId=789"
```

---

## `plakat embedding`

Inspect Textual Inversion (`.safetensors`) files + Flux IP-Adapter
weights. Runtime injection lands when candle exposes the seam —
parsers + inspectors ship today.

| Subcommand | Purpose |
|---|---|
| `embedding info <PATH_OR_REPO> [--trigger NAME]` | Inspect a TI file: trigger word, vector count × dim, matching SD variant. |
| `embedding flux-ip-adapter-info <PATH_OR_REPO>` | Inspect an XLabs Flux IP-Adapter: SigLIP feature dim, Flux hidden dim, per-block attention count. |

```bash
plakat embedding info ./my-style.safetensors
plakat embedding info sd-concepts-library/cat-toy
plakat embedding flux-ip-adapter-info XLabs-AI/flux-ip-adapter
```

---

## `plakat animate`

Frame-by-frame prompt-morph animation. Encodes two prompts
through the relevant text encoders **once**, then runs the
denoise loop N times with linearly-lerped hidden states. The
shared seed keeps the initial noise constant so the morph is
smooth rather than flickery.

| Flag | Default | Description |
|---|---|---|
| `--from <STR>` | (required) | Frame 0's prompt. |
| `--to <STR>` | (required) | Frame N-1's prompt. |
| `--frames <N>` | `16` | Frame count (≥ 2). |
| `--seed <U64>` | (random) | Shared seed for every frame. Locking it produces smooth morphs. |
| `--model <ALIAS>` | `sd15` | SD 1.5 / 2.1 / SDXL or Flux Dev / Schnell (v0.20). SD3 / 3.5 + Flux Kontext / Fill / Canny / Depth bail loud. |
| `--size <WxH>` | `512x512` | Output dims; must be /8 on SD, /16 on Flux. |
| `--steps <N>` | `20` | Steps per frame. Lower OK for animations. |
| `--guidance <F>` | `7.5` | CFG, shared across frames. **Flux only**: drop to `3.5` (Dev) or `0` (Schnell) — Flux is guidance-distilled, the scalar is fed directly to the model. |
| `--negative <STR>` | `""` | Negative prompt, shared. **Ignored on Flux** (no CFG batching to steer); animate warns if you pass one. |
| `--scheduler <KIND>` | `default` | SD-family only. Flux animate uses BFL's flow-match schedule unconditionally. |
| `--out <DIR>` | `./out` | Frames land as `frame-NNNN.png`. |
| `--gif` | off | Bundle frames into `<out>/animation.gif`. |
| `--gif-delay-ms <N>` | `100` | GIF frame delay. 100=10fps, 41≈24fps, 33≈30fps. |

```bash
# SD 1.5
plakat animate \
    --from "a photo of a fox in a meadow" \
    --to "a photo of a cat in a meadow" \
    --frames 24 --seed 42 \
    --model sd15 --out ./fox_to_cat --gif

# Flux Dev — pre-encodes both endpoints' CLIP-L pooled + T5
# states once, lerps them per frame. T5 encode dominates the
# load cost so amortising it across frames is the whole point.
plakat animate \
    --from "an oil painting of a fox in a meadow" \
    --to   "an oil painting of a cat in a meadow" \
    --frames 24 --seed 42 --steps 20 --guidance 3.5 \
    --model flux-dev --size 1024x1024 --out ./flux_morph --gif
```

Full walkthrough + composition tips:
[`Documentation/Tutorials/ANIMATE_TUTORIAL.md`](Tutorials/ANIMATE_TUTORIAL.md).

---

## `plakat init` (v0.20)

Bootstrap a starter project directory — `scenario.hjson`,
`wildcards/` (with three example files), and a focused
`.gitignore`. Targets `sd15` and `enhancer: local` so the
generated scenario runs end-to-end with no API key, no HF token,
and no model selection up front.

| Flag | Default | Description |
|---|---|---|
| `DIR` (positional) | `.` | Target directory. Created if missing. |
| `--minimal` | off | Write only `scenario.hjson`. Skip `wildcards/` and `.gitignore`. |
| `--force` | off | Overwrite existing files. Default errors on conflict. |

```bash
# Fresh project in a new directory
plakat init ./my-poster-project
plakat scenario ./my-poster-project/scenario.hjson --dry-run
plakat scenario ./my-poster-project/scenario.hjson

# Minimal — just the scenario file (e.g. adding to an existing repo)
plakat init . --minimal

# Re-init after editing things you regret
plakat init . --force
```

The generated `wildcards/` directory holds `subject.txt`,
`style.txt`, and `lighting.txt` — three options each — for use
with `plakat generate "__subject__ in a __lighting__" --wildcard-dir
./wildcards`. The scenario file itself uses the scenario engine's
own scene/weather catalog mechanism rather than wildcards (the two
mechanisms are independent).

---

## Global flags (every subcommand)

#### `-v` / `-vv` (default off)

Increase log verbosity. `-v` enables debug logs from plakat; `-vv` adds
trace.

#### `--device <SPEC>` (default `auto`)

| Spec | Meaning |
|---|---|
| `auto` | Try CUDA, then Metal, then fall back to CPU. |
| `cuda` / `cuda:N` | NVIDIA GPU N. Requires building with `--features cuda`. |
| `metal` | Apple Silicon GPU. Requires `--features metal`. |
| `cpu` | CPU. Works everywhere. Slow on full-size generations. |

For Apple hardware (chip tiers, expected speeds, memory headroom),
see [`APPLE_REQUIREMENTS.md`](APPLE_REQUIREMENTS.md).

#### `--cache-dir <PATH>` (env: `PLAKAT_CACHE_DIR`)

Where HF model weights are cached. Resolution order:
1. `--cache-dir` flag
2. `PLAKAT_CACHE_DIR` env var
3. `HUGGINGFACE_HUB_CACHE` env var
4. `HF_HOME` env var (with `/hub` appended)
5. Default `~/.cache/huggingface/hub`

---

## Common workflows

### Iterate on a prompt at a fixed composition

Set `--seed` to anything, vary the prompt — same composition, different
details.

```bash
plakat generate "a tranquil koi pond, soft light" \
 --model sdxl --size 1024x1024 --steps 30 --scheduler euler-a \
 --seed 42

# Now tweak just the prompt:
plakat generate "a tranquil koi pond, soft light, autumn leaves" \
 --model sdxl --size 1024x1024 --steps 30 --scheduler euler-a \
 --seed 42
```

### Sample many seeds, then refine the best

```bash
# Step 1 — sample 4 candidates
plakat generate "a tranquil koi pond, soft light" \
 --model sdxl --size 1024x1024 --seed 0 -n 4

# Step 2 — pick seed=2 (say), refine with extra polish
plakat generate "a tranquil koi pond, soft light" \
 --model sdxl --size 1024x1024 \
 --steps 35 --scheduler euler-a \
 --refine 8 --refine-strength 0.25 \
 --seed 2
```

### Fast generation with LCM-LoRA

LCM-LoRA + the matching LCM scheduler render usable output in 4 steps on
any device.

```bash
plakat generate "a serene mountain landscape at sunset" \
 --model sd15 --size 512x512 \
 --steps 4 --guidance 1.5 \
 --scheduler lcm \
 --lora latent-consistency/lcm-lora-sdv1-5
```

### Style transfer that mostly preserves IN

Lower strength keeps more of the input photo's content while picking up
REF's style.

```bash
plakat stylize \
 --in photo.jpg \
 --ref reference_painting.jpg \
 --out styled.png \
 --strength 0.4 --steps 30
```

### Img2img / inpaint / outpaint

For prompt-driven transforms of an existing image (with or without a
region mask), use the dedicated `plakat img2img` subcommand. SD
1.5 / 2.1 / SDXL, Flux (`flux-dev` for img2img, `flux-fill-dev`
for inpaint, both available as GGUF variants), and SD3 / SD3.5
all work. To extend a canvas past its borders, use `plakat outpaint`. Full
reference: [`IMG2IMG.md`](IMG2IMG.md). Runnable walkthrough:
[`examples/tutorials/IMG2IMG/`](../examples/tutorials/IMG2IMG/).

### ControlNet (layout conditioning)

For structural guidance from a depth map or canny edge map,
every SD-family subcommand accepts:

```bash
--control <depth|canny>
--control-image PATH # pre-rendered conditioning
--control-from PATH # OR auto-annotate any image
--control-strength F # default 1.0
```

Works on SD 1.5, SD 2.1, SDXL, **Flux** (BF16 / GGUF / NF4, via
Shakker-Labs Union Pro v2), and **SD3 / SD3.5** (via the
InstantX adapter family). The architecture is
auto-detected from `--model` and the resolver picks the matching
adapter repo per variant. Full reference:
[`CONTROLNET.md`](CONTROLNET.md). Runnable walkthrough:
[`examples/tutorials/CONTROL/`](../examples/tutorials/CONTROL/).

### Generate → 4× ML upscale

```bash
plakat generate "an isometric tiny diorama of a forest cabin" \
 --model sd15 --size 512x512 --seed 7 --out ./out

plakat upscale --in ./out/plakat-7.png --out ./out/plakat-7-4x.png \
 --method real-esrgan-x4 --device metal
# 512×512 → 2048×2048
```

### Generate → stylize → upscale (one pipeline via scenario)

For batch runs that chain all three steps with shared pipelines, see
the annotated example at
[`examples/scenario.hjson`](../examples/scenario.hjson). The `scenario`
subcommand assembles per-task prompts from a catalog of scenes and
weather, runs the per-image pipeline, and reuses loaded weights across
every task.

#### Scenario fieldsAll are
optional; existing scenarios keep working unchanged.

**Scenario-level (top of the HJSON):**

| Field | Type | Description |
|---|---|---|
| `quantize-t5:` | `bool` | Load T5-XXL as a quantized GGUF. Requires a GGUF Flux model. |
| `quant-level:` | string | Flux GGUF quant level (e.g. `Q5_K_M`). Defaults to `Q4_K_S`. |
| `t5-quant-level:` | string | T5 GGUF quant level. Defaults to `Q4_K_M`. |
| `tiled:` | `{ size, stride }` | MultiDiffusion-style tiled denoise for every task. Defaults: `1024` / `768`. |

**Per-task (inside each `tasks: [..]` entry):**

| Field | Type | Description |
|---|---|---|
| `controls:` | `Vec<ControlSpec>` | Multi-ControlNet — residuals sum per step. Mutually exclusive with the singular `control:`. |
| `init-image:` | path | Source image for img2img / inpaint / outpaint. |
| `mask:` | path | Inpaint mask. Requires `init-image:`. |
| `strength:` | float | img2img strength `[0, 1]`. Ignored for Fill. |
| `mask-feather:` | int | SD inpaint mask feather radius (px). |
| `mask-invert:` | `bool` | Flip mask polarity (black = inpaint). |
| `outpaint:` | `{ expand | left/right/top/bottom }` | Canvas expansion. Synthesises mask from padding amounts. Requires `init-image:`. |

```hjson
{
 model: flux-dev-gguf,
 quant-level: Q5_K_M,
 quantize-t5: true,
 tiled: { size: 1024, stride: 768 },
 tasks: [
 // Multi-CN, depth + canny stacked
 { name: gen1, prompt: "..", controls: [
 { kind: depth, image: ./d.png, strength: 0.8 },
 { kind: canny, auto-from: ./ref.jpg, strength: 0.5, end: 0.5 },
 ]},
 // Outpaint via Flux Fill
 { name: panorama, prompt: "wide landscape", model: flux-fill-dev,
 init-image: ./photo.png, outpaint: { left: 512, right: 512 } },
 // SD inpaint
 { name: fix-sky, model: sdxl-inpaint, prompt: "blue sky",
 init-image: ./in.png, mask: ./sky-mask.png,
 mask-feather: 16, strength: 1.0 },
 ]
}
```

Notes on scenario pipeline caching:

* **SD 1.5 / 2.1 / SDXL** — t2i tasks share a single `Arc<SdCore>`;
  img2img/inpaint tasks within the same scenario still reload the
  SD pipeline per task (the img2img dispatcher doesn't yet share
  the t2i SdCore).
* **Flux** (BF16 / GGUF / NF4) — one pipeline shared across every
  task. Per-task `loras:` swap at runtime via the LoraLinear stack.
* **SD3 / SD3.5** — one MMDiT pipeline shared across every task.
  Per-task `loras:` swap at runtime via the MMDiT LoraLinear stack.

For **SD-family per-task LoRA** (different LoRA stacks per task),
plakat runs a preflight at scenario start and either prints a hint
to fold uniform stacks to scenario-level `loras:` or bails loud
with three concrete workarounds (switch to Flux/SD3.5, split
scenarios, or fold). The SD UNet runtime LoraLinear vendor is
deferred — candle 0.8's UNet internals are private.
