# Guide: Assets and presets

> Product shots, avatars, heroes, textures and pixel sprites: presets and their checks.

Generate one master at the model's native size, then derive every export
deterministically (resize, pad, crop) `[community: usausa/mcp-image-generator,
appicons]`. Presets in `presets/assets.yaml` encode these recipes.

## What a preset promises (vincevangen presets)

Presets earn their keep on what code cannot draw: product-shot, avatar, hero, story,
social-square, blog-header, YouTube thumbnail, texture-tile and pixel-sprite. For icons,
favicons, logos, designed OG cards, UI illustration and sprite sheets, write SVG/Pillow and use a
render as a reference at most.

- **Concept only, redraw in code:** app-icon, favicon, logo-concept, og-image (as a designed
  card), ui-illustration, empty-state, sprite-sheet, background-pattern. Agent-written
  SVG/Pillow beats them today; use the render as a reference. sprite-sheet keeps the label after
  V6 and V8: its frames are cut, counted and aligned, but one image call draws poses, not a cycle
  (frames repeat or drift), and each frame sits on its own pixel grid, so `pixelate` on the whole
  sheet does not find one. A consistent cycle comes from image-to-video of the idle sprite (then
  cut the clip's frames), or from code; one clean sprite comes from pixel-sprite.
- **Sizes it names are sizes it delivers.** A preset whose `post` names WxH ends with
  `cover:WxH` (exact, centre crop) or `contain:WxH` (no crop) and checks it with `size:WxH`:
  hero 1920×1080, og-image 1200×630, blog-header 1800×1200, avatar 1024², social-square
  1080², story 1080×1920, product-shot 1600×1200, background-pattern 1024², YouTube
  thumbnail 1280×720. The renders are smaller (16:9 = 1344×768), so these are Lanczos
  upscales of up to 1.43×: fine behind copy, soft for print. A `size:` check alone never
  drops a variant (n>1 still returns every one); taste checks and the judge do.
- **It fills what you left open, never overrides you.** `lighting` (hero, product-shot,
  avatar; MCP/HTTP/spec `lighting`, CLI `--lighting`) replaces the preset's lighting sentence
  and the style's lighting clause: `"from the right"` becomes "Lit from the right."; a
  phrase that names its light ("golden-hour backlight") is kept as written. A subject that
  already states its lighting ("lit by a single candle") drops the default, with a warning.
  Other presets have no lighting slot and refuse `lighting` before any spend: describe the
  light in the subject there.
- **empty-state** draws line art by default (`line-icon` style + "thin consistent lines, one
  accent colour"); any `style` replaces both. With n>1 its rubric adds "the image clearly
  shows {subject}", so an "empty coffee cup" drawn full is rejected.
- **sprite-sheet** has slots `frames` (2-16, default 8), `layout` (`row`, or `CxR` such as `4x2`:
  the count becomes C×R and the aspect the nearest to C:R), `action` (default "a simple walk cycle")
  and `cell` (`auto`, or the frame size `WxH` / `N`): `params={"frames": 4, "action": "a hop"}`
  (MCP/HTTP/spec `params`, CLI `-p frames=4 -p action="a hop"`). The template states the count
  once. Post is `key:auto` then `slice:{grid}:{cell}`: the sheet stays as drawn, and beside it
  go `<stem>/frames/<stem>-NN.png` (equal, transparent, standing on one baseline) and
  `<stem>/<stem>.gif` (result fields `frames`, `gif`; the MCP preview shows the cut frames).
  The `frames:{frames}` check counts the figures for free and rejects a sheet with the wrong count.
- **texture-tile** delivers a seamless 1024x1024 tile: post `cover:1024x1024` then `tileable`
  (offset by half, minimum-error cut through the old seam, histogram match; free), checks
  `size:1024x1024` and `seam:1.3`. The FLUX-2 render measures 1.7-2.2 (a visible seam); after
  `tileable` 0.85-0.9. Organic materials (cobbles, moss, bark, sand) come out clean. On regular rows
  (bricks, planks, pavers in courses) the cut has to cross rows that do not line up and shows as a
  stepped line: draw those in code. It fixes the wrap, not large-scale unevenness (a lighting
  gradient or one odd stone still repeats visibly), so look at a 2×2 tiling. `tileable:repair`
  (paid, one `edit:fill` call, ledger tag `post:repair`) is the alternative to `tileable`: same
  offset, then Bria gen_fill repaints a 12% band over the old seam cross (mask white = repaint) and only
  the band is blended back. No cut line; the band's stones can come out a little more regular
  (evaluation tile 2.20 → 0.88, `[measured 2026-10-12]`). Not offered where `edit:fill` is missing (hosted, for now).
- **pixel-sprite** delivers one game sprite as real pixel art: rendered on flat magenta, post
  `key:auto`, `trim`, `pixelate:{grid}:{colors}:outline`, `trim`, checks `palette:{colors}` and
  `edge_halo:0`, exports `sprite-2x/4x/8x.png` (whole multiples, nearest-neighbour). Slots `grid`
  (`auto` = the art's own grid, or a height in px) and `colors` (default 24). Models draw "pixel art"
  as soft blocks 4-10 px wide, rarely a whole number (the evaluation knight: 6.82 px); `pixelate`
  finds that pitch and gives each block one colour, so the 759 px knight becomes a 112×111 sprite
  with 24 colours and alpha 0/255. The art's own grid is usually 60-120 px tall; `grid=48` or less
  reduces by block mode with the outline kept, but detail turns to mush there: for sprites of 32-48
  px, draw them in code.

## Aspect ratio by use `[community: jezweb, Qwen rewriter]`

| Use | Ratio | Master |
|---|---|---|
| Icon, logo, avatar, sticker, badge | 1:1 | 1024² |
| Hero | 16:9 or 21:9 | 1920×1080 |
| OG image | 1.91:1 → generate 16:9, cover-crop | 1200×630 |
| Blog header | 3:2 or 16:9 | 1800×1200 |
| Card | 4:3 | 1600×1200 |
| Instagram feed | 1:1 or 4:5 | 1080×1080 / 1080×1350 |
| Story, phone wallpaper, mobile hero | 9:16 | 1080×1920 |
| Poster | 2:3 | |
| YouTube thumbnail | 16:9 | 1280×720 |
| Default horizontal / vertical | 3:2 / 2:3 | |

Snap sizes to multiples of 16 for FLUX (32 for FLUX-2-pro) `[vendor: schema]`;
Qwen-Image native sizes: 1328², 1664×928, 928×1664, 1472×1104, 1104×1472,
1584×1056, 1056×1584 `[vendor]`.

## Export size tables `[community: platform guides]`

- **Favicon / web:** 16, 32, 48; apple-touch-icon 180; PWA 192 and 512
  (maskable: keep content inside the central 80%).
- **iOS:** App Store icon 1024×1024 PNG, no alpha; device sizes 20-180 pt at
  1x/2x/3x. Dark and tinted variants optional.
- **Android:** adaptive icon 108 dp with content inside the central 72 dp
  (66%) safe zone; density buckets mdpi 48 → xxxhdpi 192; monochrome layer.
- **OG:** 1200×630. Twitter/X summary_large_image: same.

## Recipes (prompt clauses + post-processing)

- **App icon (concept only, redraw in SVG):** "single centred [symbol], flat vector style, bold simple
  silhouette, two colours, a simple uncluttered background, generous padding,
  content within the central two-thirds". 1:1. Post: square, resize set.
  Reads at 32 px or it fails. Keep the background clause compatible with a
  gradient the subject asks for — don't force "solid single-colour".
- **Favicon (concept only, redraw in SVG):** as above but simpler; at most two colours, thick strokes.
  Post: rmbg if needed, trim, pad, export 16/32/48.
- **Logo concept (redraw in SVG):** "minimal vector logo mark, centred, strong silhouette,
  flat colours, plain white background, generous margin", quoted wordmark on
  a text-capable model. Treat as a concept; redraw in SVG `[vendor: OpenAI]`.
- **Hero / OG:** subject on one side, a calm/uninterrupted area of plain
  colour on the [side] for copy added later in HTML/CSS or post — describe
  that area visually ("an uninterrupted plain area of soft colour across the
  left half"), one lighting clause. Never write "title", "headline", "text"
  or "copy" in the visual prompt itself: FLUX/Gemini read that as a cue and
  paint fake letters. `[vendor: OpenAI, Google minimalist template]`
- **Cutout / product / sticker:** FLUX, Qwen and Gemini emit no alpha. Ask for
  "isolated on a seamless pure white background, soft even studio lighting,
  crisp clean edges, faint contact shadow only if wanted", then
  `Bria/remove_background`, check halos, trim to bbox `[community]`. Seedream
  5 Pro has `background: transparent` `[vendor: schema]`.
- **Sprite sheet:** "sprite sheet, N frames in a single horizontal row (or
  4×2 grid), evenly spaced, identical character and scale, plain flat
  background". Condition on a reference of the character and fix the seed;
  slice in post (`slice:CxR`). Pixel art: generate large, then `pixelate` (snap to the render's
  own grid, never average) and scale nearest-neighbour `[community: scenario.com; measured 2026-10-12]`. Animation that must loop cleanly: image-to-video of the
  idle sprite, not a one-shot sheet `[evaluation 2026-10-12]`.
- **Texture tile:** "seamless tileable [material], top-down orthographic, flat
  even lighting, uniform detail, no focal object". The model's edges do not wrap; post op
  `tileable` repairs the seam for free (offset + minimum-error cut), `seam:<max>` measures it.
  Verify by tiling 2×2 `[measured 2026-10-12]`.
- **Product shot:** softbox key, 3/4 angle, "seamless light-grey sweep",
  sharp label, generous padding `[vendor: Google product template]`.
- **UI / empty-state illustration:** flat or isometric, restrained palette
  with hex, plain light background, open margins; transparent via rmbg.
- **Avatar:** head and shoulders, face centred in the middle half so a circle
  crop keeps the head; plain background.

## Post-processing ops (vincevangen `post:` list)

`trim` (crop to alpha bbox), `pad:<fraction>` (transparent margin),
`square` (pad to square), `fit:WxH` (shrink to fit inside; never enlarges, and says so in
`warnings` when it left a smaller image unchanged), `contain:WxH` (scale up or down to fit
inside, no crop), `cover:WxH` (scale and crop to exactly WxH),
`webp` (convert, q85 `[community: jezweb]`). Exports are resized from the
processed master with Lanczos; a master that went through `pixelate` exports nearest-neighbour, and an
export entry `{name, scale: k}` is a whole multiple of the master.

More ops, same list (presets, `--post`, MCP/HTTP `post`): `resize:<w>[x<h>]`
(`resize:512` keeps aspect, `resize:64x64` stretches), `rmbg` (paid background
removal, ledger tag `post:rmbg`), `key:<hex>[:tol]` (pixels within tol, default 24,
of a colour become transparent, then a 1px defringe; for leftover grey or magenta
after a cutout; `key:auto[:tol]` keys the dominant border colour; a key that reaches under 0.1% of
the pixels warns and names the border colour), `defringe[:px]` (contract the alpha edge and recolour the fringe
from the solid pixels), `match_color:<hex>[:window_deg]` (move the dominant colour
cluster's hue and brightness to the target, other hues and greys untouched),
`slice:<cols>x<rows>[:<w>x<h>]` (cut a sheet into equal frames: cells are equal divisions of the
subject's box with each cut moved into the nearest background gap, every frame trimmed to one
shared box, centred, feet on one baseline; `:WxH` scales them into that frame size, nearest-neighbour;
writes `frames/` + a looping GIF beside the file and leaves the image itself unchanged; warns when the
sheet shows another number of figures than the grid has cells), `tileable` (make a texture wrap: offset by about
half, a minimum-error cut in a 15% band along each border hides the old seam, histogram matched to the source; the
seam before and after goes to `params.post_log`; `tileable:repair` = the same offset plus a paid `edit:fill` inpaint
of a 12% band over the old seam, blended back inside the band only, ledger tag `post:repair`), `pixelate[:<height>|auto][:<colors>][:outline]` (fake pixel
art to real: edge-energy autocorrelation finds the render's pixel pitch, fractional and per axis; each cell
becomes one pixel of its most common colour, opaque when half of it is; `<height>` then reduces by block mode;
median-cut palette of `<colors>` (default 32), alpha 0/255; `outline` keeps a dark 1 px silhouette edge
through the reduction. No grid found: the size is kept and a warning says so. Pitch, size and colours go to
`params.post_log`). Check `palette:<n>` counts the opaque colours.
Typical sprite chain: `rmbg`, `key:808080:20`, `defringe`, `trim`, `pad:0.05`,
`match_color:#ff6a00`.
