photocraft · git:20261014.bf59830 · 2026-10-14 · sha256 3c633276b3fc9c54
photocraft git:20261014.bf59830A
Immutable. This exact content never changes and is served at /api/v1/blob/3c633276b3fc9c54.
---
name: photocraft
description: Headless raster image editing with PhotoCraft (~900 Photoshop-style commands) via pi-craft - select, crop, resize, colour-grade, mask, content-aware fill, batch-convert. Pair with pi-nano-banana for generative part-replacement (replace-region). Use for "edit/retouch/crop this image", "remove this object from the photo", "replace just this part of an image".
---
# PhotoCraft — headless raster image editing
PhotoCraft is ArtCraft's Photoshop-class raster editor. `pi-craft` provisions the headless `photocraft-cli`
on first use (no desktop app). Pair it with `pi-nano-banana` (Gemini) when an edit needs *generated* pixels:
PhotoCraft does the precise local part; Gemini only ever sees a crop. Choosing between apps, the
discover → execute → save → verify loop and the MCP policy live in `craft-studio`.
## What it is for / formats
Raster images with layers, masks, selections, adjustments and filters. Reads/writes `png jpg webp tif psd`
(PSD keeps layers); `convert` and `batch` change formats in bulk. For RAW development use LightCraft, for
vector art VectorCraft.
## CLI dialect
`pi-craft photocraft exec <args…>` (alias `pi-photocraft exec`) passes the arguments to `photocraft-cli` verbatim.
```bash
photocraft-cli run (<file> | --new <json>) --cmd <id> [--params <json>] [--cmd …] [--out <file>] [--format <ext>] [--quality 1-100]
photocraft-cli info <file> [--compact] # size, mode, layers (JSON)
photocraft-cli convert <in> <out> # .psd .png .jpg .webp .tif
photocraft-cli batch --actions actions.json --in ./originals --out ./exports [--format webp --quality 85]
```
- **One `--out` per run** (last wins). Every `--params` applies to the `--cmd` before it.
- Each command prints one JSON line (`{"command":…,"result":…}`); exit `1` + `error: …` on failure.
- `select.*` bounds are `[x,y,w,h]` (as `x,y,width,height` params for `select.rect`); `file.placeEmbedded` bounds are `[x0,y0,x1,y1]`.
## State between invocations
None: `run` opens the file, applies the commands in order and saves `--out`. **A selection lives only inside
one `run`** — chain the `select.*` command and the command that uses it in the same invocation. To continue
editing later with layers intact, save as `.psd` and open that next time.
## Discovery first
Command ids change between releases — never guess them.
```bash
pi-craft photocraft exec commands --filter feather # ids by keyword
pi-craft photocraft exec commands --json --filter crop # parameter schema of the matches
pi-craft photocraft exec info photo.png --compact
```
## Traps
- `select.object` returns `{"selected":false}` on flat synthetic or low-contrast images, and a following
`select.modify.*` then fails with "no selection". Check the result line, or use `select.rect` for a known box.
- A second `--out` silently replaces the first; split into two runs instead.
- `edit.contentAwareFill` works on the current selection; expand it a few px (`select.modify.expand`) so the fill
covers the object's fringe.
- Don't send a whole 4K image to Gemini for a small edit — that is what `replace-region` avoids.
- Linux needs glibc ≥ 2.35. `.pi/mcp.json` is only written by `pi-craft photocraft mcp --register` (opt-in).
## MCP route (opt-in)
`pi-craft photocraft mcp --register` writes a `photocraft` entry into `.pi/mcp.json` (merge, never clobber).
The server is confined to the project directory (read and write): **every path passed to `doc_open` /
`doc_save` must lie under the project root** (`exec`/`run` have no such confinement). Call tools one at a
time and wait for each result (`doc_open`, then `command_run`, then `doc_save`). Prefer MCP for multi-step edits
on one open document, bash for one-shot edits, batches and anything outside the project.
## Recipes
```bash recipe: resize-grade
ffmpeg -v error -y -f lavfi -i testsrc=size=1200x800 -frames:v 1 photo.png
pi-craft photocraft exec run photo.png \
--cmd image.imageSize --params '{"width":600,"height":400}' \
--cmd image.adjustments.brightnessContrast --params '{"brightness":10,"contrast":15}' \
--out edited.png
pi-craft verify edited.png --size 600x400
```
```bash recipe: remove-region
# Remove whatever is inside a rectangle with content-aware fill (no generation needed).
ffmpeg -v error -y -f lavfi -i testsrc=size=1200x800 -frames:v 1 photo.png
pi-craft photocraft exec run photo.png \
--cmd select.rect --params '{"x":100,"y":100,"width":200,"height":150}' \
--cmd select.modify.expand --params '{"radius":6}' \
--cmd edit.contentAwareFill --params '{}' \
--out clean.png
pi-craft verify clean.png --size 1200x800 --frame-diff 0:photo.png\>0.1
```
```bash recipe: crop-export
ffmpeg -v error -y -f lavfi -i testsrc=size=1200x800 -frames:v 1 photo.png
pi-craft photocraft exec run photo.png \
--cmd image.crop --params '{"x":200,"y":100,"width":800,"height":600}' \
--out cropped.webp --format webp --quality 85
pi-craft verify cropped.webp --size 800x600
```
Not runnable without a Gemini key — the generative route:
```bash
pi-photocraft replace-region photo.jpg \
--prompt "the apple is an orange, same lighting" \
--select object:540,560,210,210 \
--out photo-orange.jpg [--layered photo-orange.psd]
pi-craft verify photo-orange.jpg --size 1200x800 --frame-diff 0:photo.jpg\>0.2
```
## Generative part-replacement: `replace-region`
Pipeline: locate the region → crop it (plus `--pad`) → `pi-nano-banana` edits **only the crop** → resize back
to the exact crop box → place with a feathered mask → flatten. **Pixels outside the crop box are identical to
the input** (lossless in/out); the layered `.psd` keeps original + masked patch for manual fixes. Needs
`GEMINI_API_KEY` (or `--backend pi`). `replace-region` stays a PhotoCraft subcommand of `pi-photocraft`.
- Region: `--rect x,y,w,h` | `--select subject` | `--select sky` | `--select object:x,y,w,h` (a rough hint
rectangle; PhotoCraft finds the object inside it).
- `--mask crop` (default): the patch shows whole inside the crop (inset by `pad − feather`) — right when the
replacement has a **different shape or size**. `--mask selection`: only the original object's outline is
replaced — right for recolour / texture edits that keep the outline.
- `--pad 32 --feather 8 --min-crop 256` (rule: `pad ≥ 3 × feather`). Raise `--pad` when the new object should be
larger than the old one; a small region is grown to `--min-crop` for context.
- The report (one JSON line) lists `region`, `crop`, `backend`, `model`, `timings`. On failure nothing is written
(an existing `--out` is untouched); `--keep-temp` keeps the intermediates.
Insert a generated asset, then verify the result changed where expected:
```bash
pi-nano-banana "a red enamel mug on a plain white background" --output mug.png
pi-craft photocraft exec run scene.jpg \
--cmd file.placeEmbedded --params '{"path":"/abs/mug.png","fit":false,"scale":40,"center":[640,480]}' \
--out scene-mug.psd
pi-craft verify scene-mug.psd --size 1200x800
```