Outpaint Config
Expansion geometry and the prompt, in one bundle
- outpaint_config
What it's for
Outpainting is two separate jobs that people insist on doing in one node: deciding how much canvas to add and where, and telling the model what should appear in it. PortraitUtils splits them. This config node stores both, and emits one PORTRAIT_OUTPAINT_CONFIG_V2 wire that the geometry node and the prompt reader each pull their half from.
The reason to bother: your expansion policy and your prompt describe the same intention, and they should change together. When you decide the photo needs a wider crop on the right, that's a geometry change and usually a prompt tweak. One node, one edit.
What's in it
mode is the fork: Percent or Pixels.
In Percent mode, horizontal_percent (default 20) and vertical_percent (default 10) are the total expansion as a fraction of the source width and height - expressed as a percentage, not a fraction, so 20 means 20% and typing 0.2 gets you a 0.2% sliver. gravity decides how that total is distributed across the nine positions: center splits it evenly, left puts all the horizontal expansion on the right, top puts the vertical expansion below, and the corner options combine both anchors. Which is worth re-reading once, because "gravity: left" reads like "grow leftward" and means the exact opposite.
In Pixels mode, left_px, right_px, top_px and bottom_px are used literally and gravity is ignored silently. Fine, but don't leave a stale gravity value and expect it to matter.
outpaint_prompt is the multiline text describing what goes in the new area - read out by Outpaint Prompt from Config and handed to your editor model.
Wiring it
Two consumers, each reading only what it needs:
Outpaint Config ─┬─> Outpaint Padding Compute ─> left/top/right/bottom ─> pad node
└─> (reader) Outpaint Prompt from Config ─ STRING ─> editor text encode
The geometry node does the arithmetic - percent-to-pixels, gravity split, and a one-pixel nudge on the right/bottom so the final canvas is even. This node just holds the policy.
Choosing an approach, briefly
There are two ways to fill the canvas you're about to add, and they aren't equivalent. The masked route pads the canvas, masks the new border, and denoises it back in - the extension looks continuous, and the original pixels survive untouched. Its known failure is seams, and the standard mitigations are generous mask overlap (20–30% of the original edge), adequate blur, and multiple passes at decreasing denoise strength.
The 2026 route needs no mask at all: pad each side with a flat colour and ask an instruction editor to replace it. It's a two-node trick, it works on any instruction model, and it's why outpainting is the branch of masking that instruction editing took most cleanly - there's nothing outside the extension area to protect. The prompt field in this config is what drives that route too, so both paths share a config.
Where the masked path still wins: when the original must be byte-identical afterward, or when you're extending repeatedly and can't afford drift accumulating across passes.
Where people get burned
Two semantic traps - Percent meaning total rather than per side, and the percentage-vs-fraction thing above - plus an upgrade note. The first two produce a canvas that's dramatically wrong rather than subtly wrong, so they tend to be obvious, which is at least kind.
Third, an upgrade note: the standalone Outpaint Config from 1.x was removed and the unconfigured Padding Compute ID is gone too. Old workflows will show missing nodes; rebuild them rather than trying to patch node IDs, because widget values and socket order don't migrate.
Install
Part of PortraitUtils. ComfyUI Manager → search PortraitUtils (publisher heyburns, GPLv3), or:
cd ComfyUI/custom_nodes
git clone https://github.com/heyburns/PortraitUtils
/path/to/ComfyUI/venv/bin/python -m pip install -r PortraitUtils/requirements.txt
That installs numpy, Pillow>=9.1, opencv-python-headless>=4.10.0.84 and tifffile into ComfyUI's Python environment - one OpenCV wheel variant only. Restart the ComfyUI server after installing; a browser refresh won't reload Python nodes. The padding and outpainting nodes themselves come from other packs, and so do the editor models.
Inputs (9)
| Name | Type | Default | Description |
|---|---|---|---|
| mode | COMBO | Percent | 2 options: Percent, Pixels |
| gravity | COMBO | center | 9 options: center, left, right, top, bottom, top left, +3 |
| horizontal_percent | FLOAT | 20.00–10000 | — |
| vertical_percent | FLOAT | 10.00–10000 | — |
| left_px | INT | 00–1000000 | — |
| right_px | INT | 00–1000000 | — |
| top_px | INT | 00–1000000 | — |
| bottom_px | INT | 00–1000000 | — |
| outpaint_prompt | STRING | — |
Outputs (1)
| Name | Type | Description |
|---|---|---|
| outpaint_config | PORTRAIT_OUTPAINT_CONFIG_V2 | — |