LC Outpaint ποΈβ
Drag the canvas edges with your mouse, get the mask for free
- image
- control_image
- control_mask
- mask_image
- width
- height
Outpainting in ComfyUI has always been arithmetic homework. You decide to add 400px to the left, so you pad the image, then you build a mask that's black where the original is and white everywhere else, then you work out the new width and height and type them into a latent node, and then you discover the number isn't divisible by 8 and the sampler complains. Four nodes and a calculator, every time.
This node collapses that into dragging. Grab an edge of the canvas on the node itself, pull it out, queue. The image lands on a grey canvas at the new size, the mask comes out white where the new area is, and the width and height come out as integers. No math, no mask painting.
What comes out of it
Five outputs, and how you wire them is the whole trick:
- control_image - the source pasted onto a mid-grey canvas at the final size. This is what goes through VAE encode / your inpaint conditioning.
- control_mask - white = new area, black = your original. This is the mask you hand to SetLatentNoiseMask or InpaintModelConditioning.
- mask_image - the same mask as a 3-channel IMAGE, for nodes that want an image rather than a mask.
- width and height - the final canvas size, snapped. Feed these into your empty-latent or size node so the latent matches the canvas instead of hardcoding numbers that drift.
That grey fill isn't laziness. When you want a model to genuinely invent new content, overwriting the region with flat grey is the standard move - it forces the model to invent rather than lean on pixels that are there. Mid-grey is also what most inpaint models were trained to see in the masked region, so it's the least surprising thing you can put there.
The controls
The four expansions - left, top, right, bottom - are percentages of the source (left/right of its width, top/bottom of its height), 0 to 400, in steps of 0.01. You'll normally set them by dragging rather than typing, but they're real widgets, so a workflow can drive them.
aspect locks the final canvas to free, original, or one of the common ratios (1:1, 4:3, 3:2, 16:9 and the portrait mirrors). snap_to rounds the final width and height up to a multiple of 1, 8, 16, 32 or 64 - the extra goes to the right and bottom. Default is 8; set it to 1 to turn rounding off. Every sampler in ComfyUI wants multiples of 8, so leave it alone unless you have a reason.
Two toggles do the fiddly work:
block set to if_empty_mask stops everything downstream until you've pulled at least one edge out. Same idea as Impact Pack's Preview Bridge, and it's the difference between a workflow you can set up and a workflow that runs a 40-step sampler on an unmodified image because you forgot to drag. Worth knowing: it uses ComfyUI's ExecutionBlocker, and on an old ComfyUI it prints a message to the console and the blocking is silently disabled - so if if_empty_mask seems to do nothing, update ComfyUI.
hold_mask decides what happens run to run. held keeps the canvas you set up and reuses it every time, which is what you want when you're iterating. snap_to_image passes the image through untouched while nothing is pulled out; once you drag and queue, that run uses the edges and then the box snaps back - so the next image starts from a clean box rather than inheriting the last one's expansion. A failed or cancelled run keeps your edges either way.
Wrapping it in a workflow
The node is prep, not generation. Typical chain: image β LC Outpaint β control_image into VAE Encode β SetLatentNoiseMask with control_mask β sampler with a prompt describing the whole extended scene β decode β composite, or run the whole thing again for a bigger extension. If you use a dedicated extend-capable checkpoint (Flux Fill and its fine-tunes were trained on image-extend as a task, and the newer edit models handle it too), the grey canvas is exactly what they expect.
Two honest caveats. Repeated extension passes accumulate drift - three or four rounds and the original no longer survives byte-for-byte. And on a modern edit model, "pad the sides and ask it to remove the padding" is now a legitimate alternative, so masked outpainting is no longer the only route. It's still the route that keeps your original pixels untouched and gives you an explicit mask to reason about.
Install
cd ComfyUI/custom_nodes
git clone https://github.com/lonecatone23/ComfyUI_LC_MaskMaker_nodes
# restart ComfyUI
Or Manager β search LC MaskMaker. This node needs nothing beyond torch - it's one of the handful in the pack with no model behind it. Run the graph once to get the on-node preview; the canvas is drawn from that first pass, so an empty node that shows nothing is waiting for you to queue.
Gotchas
- The preview is blank. Run once. The node draws the source from the last execution, not from an unexecuted state.
- Your final size is odd.
snap_torounds up; that's the extra pixel or two on the right and bottom. Set it to 1 if you truly want the raw arithmetic. if_empty_maskisn't blocking. Old ComfyUI, noExecutionBlocker. Update.- The mask is the wrong way round for your inpaint node. Some nodes want the kept region white. It's the same mask either way - hit InvertMask, don't try to fix it in this node.
Inputs (9)
| Name | Type | Default | Description |
|---|---|---|---|
| image | IMAGE | Source image to extend. | |
| block | BOOLEAN | false | if_empty_mask: until you have pulled at least one edge out (all four expansions are 0), everything downstream of this node is stopped so you can set up the canvas, then queue again. never: always passes the canvas and mask through. |
| hold_mask | BOOLEAN | false | held: keep the canvas you set up (the expansions below) and pass the image through with it, run after run. snap_to_image: with nothing pulled out the image passes through as is (and with block set to if_empty_mask the run stops there). Drag the edges out and run again: that run uses them, then the box snaps back to the image, so a new image always starts from a clean box. |
| left | FLOAT | 0.000β400 | Expansion on the left, % of source width. |
| top | FLOAT | 0.000β400 | Expansion on the top, % of source height. |
| right | FLOAT | 0.000β400 | Expansion on the right, % of source width. |
| bottom | FLOAT | 0.000β400 | Expansion on the bottom, % of source height. |
| aspect | COMBO | free | Lock the final canvas aspect ratio. 'original' matches the source image. |
| snap_to | COMBO | 8 | Round the final width/height up to a multiple of this (extra goes to the right/bottom). 1 = off. |
Outputs (5)
| Name | Type | Description |
|---|---|---|
| control_image | IMAGE | β |
| control_mask | MASK | β |
| mask_image | IMAGE | β |
| width | INT | β |
| height | INT | β |