Nodes/Outpaint Mask Editor/Outpaint Mask Editor
ComfyUI Node

Outpaint Mask Editor

Drag a frame, get a mask that composites itself back

By zaum·Created 7 days ago·Updated a day ago· 1
Outpaint Mask Editor
  • image_opt
  • cropped_image
  • cropped_mask
  • original_image
  • original_mask
  • crop_x
  • crop_y
image
outpaint_state{}

Outpainting in ComfyUI has a geometry problem before it has a model problem. Bigger canvas, original sitting inside it at a whole-pixel offset, a mask that's white exactly where the new pixels go, and then a way to put the generated tile back without letting the VAE chew on the untouched part of your image. Four nodes and a pile of arithmetic you redo every time you nudge the frame. Outpaint Mask Editor collapses that: drag a frame in a fullscreen editor and the node hands you the canvas, the mask, and the coordinates to paste the result back.

What the frame actually is

Here's the part worth internalising, because it isn't what you'd assume: the frame you drag is the render tile, not just the outpaint region. You set its size and how much of the original it swallows, by hand. Negative padding on a side makes the tile cut into the source - which is what makes this useful for more than extension. Frame a face, render that region at the model's native resolution, paste it back over the original. Crop-and-stitch, by hand.

How the frame becomes a mask

The editor state is JSON stored in the outpaint_state widget, so your frame geometry travels inside the workflow file. On execute the node reads the four paddings and snaps the frame outward to a 16 px grid - floor on the origin, ceil on the far edge, so no selected pixel is dropped. That alignment isn't cosmetic: Flux 2's VAE works on a 16 px grid, and an 8-aligned tile gets centre-cropped and shifted by 4 px, which shows up as drifting content once you composite.

Then it builds two canvases. The empty outpaint area is filled with mid-gray (128), not black, so the model reads it as neutral context rather than a hard black object. The mask is 1.0 where generation should happen, 0.0 over your original. The full-canvas pair uses the same convention, and negative paddings never shrink the full canvas - only positive expansion grows it.

Inputs and outputs that matter

Three inputs, and only one is interesting. image is a dropdown of files in ComfyUI's input folder with the usual upload and paste buttons, exactly like LoadImage. outpaint_state is the JSON the editor writes - you never touch it. The optional image_opt is an IMAGE input that overrides the dropdown when you connect something, which is how you feed a freshly generated image straight in.

Six outputs, and the naming is the whole API:

  • cropped_image and cropped_mask - the render tile and its mask. These go to the sampler.
  • original_image and original_mask - the full canvas, source on mid-gray, same size as each other.
  • crop_x and crop_y - where the tile sits on the full canvas.

It's an output node, so it previews inline: the original on a checkerboard, frame border drawn on.

Installing it

Manager first - search ComfyUI-OutpaintMask (author zaum) and restart. Fair warning: this pack is brand new (Comfy Registry, September 2026, comfyui-outpaintmask 1.15.0) and its legacy Manager listing was still pending when it shipped, so if the search comes up empty, clone it:

cd ComfyUI/custom_nodes
git clone https://github.com/zaum/ComfyUI-OutpaintMask

Then restart ComfyUI. There are no Python dependencies - the package metadata declares an empty list and the code only touches torch, numpy and PIL, all of which ComfyUI already ships. No dependency-conflict roulette with the rest of your node zoo.

Getting the generated tile back in

Composite with the core Image Composite Masked: destination original_image, source your rendered tile, x crop_x, y crop_y, mask cropped_mask. The mask convention was picked so this just works - 1.0 in the outpaint area means "take the generated pixels", 0.0 over your source means "leave my image alone". That's the reason to do it this way rather than an img2img pass on the full frame: the original never goes through VAE encode/decode, so it comes back byte-identical. The pack ships a sample workflow (examples/flux2_klein_outpaint.json) doing exactly this with Flux 2 Klein.

Things that bite

  • The megapixel cap is a safety rail, not a suggestion. Default 2.0 MP, clamped between 0.05 and 8. At the default, a 1024×1024 source can grow to roughly 1450×1450 before the editor stops you - a bit over 200 px of new canvas per side. Past that you're paying VRAM and coherence for padding the sampler has to fill anyway.
  • mm is a readout, not a setting. The px/mm switch and DPI field are frontend display only - the backend ignores both keys. Don't expect changing DPI to change your output.
  • The node never raises. On failure it returns an error preview plus zeroed tensors, so a bad run can hand you a black mask and a plausible-looking graph instead of a stack trace. If the preview tile comes up dark red, that's the fallback; check the ComfyUI console for the real traceback.
  • Stale JavaScript after an update. The pack installs a no-cache middleware for its own frontend files, but if the editor button is missing or misbehaves, hard-refresh the browser before you file anything.

One last bit of context. Since Klein and the other instruction editors landed, "pad the canvas with a solid colour and ask the model to fill it in" is a two-node outpaint that often looks great. Reach for Outpaint Mask Editor when the original has to survive untouched, when you're cutting a tile to re-render a region at native resolution, or when the frame has to be reproducible across a chain of passes.

Categoryimage/inpaint

Inputs (3)

NameTypeDefaultDescription
imageCOMBO1 options: example.png
outpaint_stateSTRING{}
image_optoptIMAGE

Outputs (6)

NameTypeDescription
cropped_imageIMAGE
cropped_maskMASK
original_imageIMAGE
original_maskMASK
crop_xINT
crop_yINT