ComfyUI Node

Inpaint Canvas

The Krita-style mask editor that stops inpainting from fighting the graph

By DenRakEiw·Created 3 days ago·Updated about 20 hours ago· 10
Inpaint Canvas
  • result
  • result_local
  • crop_image
  • crop_mask
  • image
  • mask
  • stitch_info
  • crop_width
  • crop_height
  • prompt
  • control_image
  • denoise
  • seed
  • mode
  • negative
  • setting_1
  • setting_2
  • setting_3
  • setting_4
  • setting_5
  • setting_6
  • setting_7
  • setting_8
padding64
target_size1024
feather16
multiple_of64

The loudest inpainting complaint in 2026 isn't model quality - it's the masking interface. ComfyUI renders flawless patches, but painting where one goes means hand-rolling masks, feathering, cropping, compositing, hoping the seam doesn't show. This node wraps that whole chore in a layered image editor that lives inside ComfyUI - the closest thing the graph has to a Krita masking surface.

It also lands on the right side of the 2026 model debate: masked inpainting's whole remaining case is bit-identical unmasked pixels, which an instruction-edit model like Flux 2 Kontext won't give you. This is crop-and-stitch with a paintbrush bolted on, and it keeps paid API editing chains in the loop.

What it actually is

Inpaint Canvas is one node that holds the image, the layers, the selection and the prompt. Click Open editor, load a photo, paint a selection with the brush, rectangle, lasso or polygon, and type a prompt. It emits a crop_image and a matching crop_mask - the selected region plus context, scaled, filled, grown and feathered. Run that crop through any chain (a local sampler, or a Flux.2/Kontext API node) and wire the decoded result back into the same node as result_local or result. It lands on the canvas as a new layer: aligned, colour-matched, seam feathered. Select the next spot, generate again.

Beyond that it has editing muscles the plain crop-and-stitch pair never will: SAM2 hover selection, SAM3 or GroundingDINO text selection, blend modes, ControlNet control layers, RMBG cutouts, filter layers, outpainting, and reference layers that ride along in crop_image for Flux.2 / Kontext multi-reference editing.

How the round trip works

ComfyUI rejects cycles, so the result links never reach the backend at all: the frontend strips them and passes the source nodes along as result_source / result_source_local. When the canvas node runs it expands an ephemeral Inpaint Canvas Stitch node that reads whichever source matches the api/local switch - the other chain is never connected, so it never executes. The stitch's UI output is attributed back to the canvas node, so the layer arrives without a second node on your graph.

Because it's an output node the chain runs even without a Save Image node downstream - and it always re-executes (IS_CHANGED returns NaN), since the stitch only happens while it runs. That's by design and cheap; don't try to "fix" it by caching.

The outputs you'll actually wire

The node has a lot of outputs, but the ones that matter for a minimal chain are few:

  • crop_image and crop_mask - feed your inpaint chain; in auto mode the mask is grown and feathered, the crop scaled to target_size and rounded to multiple_of. crop_width / crop_height report its size for generators that need an explicit number.
  • result_local (decoded local chain) or result (API chain) - wired back in as inputs, not outputs. Only the chain of the selected mode runs; wire just one and it's used regardless of the switch.
  • denoise and seed - from the editor's Generate section, straight into your sampler.
  • prompt (and negative for SDXL) - what you typed in the editor.

The rest are there when you grow up to them: image/mask at full size, stitch_info for a standalone stitch, control_image for ControlNet, and setting_1setting_8, wildcard outputs that let the editor drive widget inputs on other nodes.

The four widgets

All four defer to the editor's auto settings when those are on. padding is context around the selection (manual context only); target_size is the crop's longest side - 0 keeps native resolution, right for API models capped at 2048px; feather is the stitch-time blur (manual only); multiple_of rounds the crop, 64 for Flux.

Installing it

cd ComfyUI/custom_nodes
git clone https://github.com/DenRakEiw/ComfyUI-InpaintCanvas

Restart ComfyUI. The node has no extra Python dependencies beyond what ComfyUI ships (OpenCV is used only for one fill mode). The rest is per-feature: comfyui-rmbg for SAM3 text selection and cutouts, comfyui_segment_anything as the fallback, Kijai's ComfyUI-segment-anything-2 for hover object selection, a Qwen3-VL or Gemini node for prompt upsampling. Their models (sam3.pt, sam2_hiera_base_plus.safetensors, Qwen3-VL) download on first use; the editor only lists backends it finds, so start bare.

Where people get burned

  • Nothing comes back. The editor warns when no result input is wired at all; if your chain is wired but ignored, check the api/local switch matches where you plugged the result in.
  • VAE-encode chains plus extra batch images. Turn on reference layers or "Original" alongside a fill mode and crop_image becomes a batch - a VAE Encode would then encode the references too. Not meant for VAE chains.
  • The node "runs every time". Yes. That's the stitch working.
  • Keyboard shortcuts vanish. While the editor is open it owns Ctrl+Z, Ctrl+S, everything. Close it and the graph is yours again.
Categoryimage/inpaint

Inputs (6)

NameTypeDefaultDescription
paddingINT640–4096Context pixels added around the selection before cropping. Ignored while the editor's Context is set to auto.
target_sizeINT10240–8192Longest side of the emitted crop. 0 keeps the native size.
featherINT160–512Blur radius applied to the selection edge when the result is stitched back. Ignored while the editor's Feather is set to auto.
multiple_ofINT641–256crop_image width and height are made a multiple of this (Flux wants 64).
resultoptIMAGEWire the inpaint result of your API chain here (editor mode API). It is stitched back into the canvas as a new layer.
result_localoptIMAGEWire the decoded result of your local chain here (editor mode Local). Only the chain of the selected mode runs.

Outputs (21)

NameTypeDescription
crop_imageIMAGESelected region plus padding, scaled to target_size. Inpaint this. It becomes a batch when there is more to send: the untouched crop after the filled one (Crop option "Original" with a fill mode), then every visible reference layer fitted to the crop size (multi-reference editing with Flux.2 / Kontext, which flatten the batch into their image inputs).
crop_maskMASKSelection mask matching crop_image (grown and feathered when the editor's Feather is auto).
imageIMAGEThe flattened canvas at full size.
maskMASKSelection mask at full size.
stitch_infoSTRINGStitch parameters for a standalone Inpaint Canvas Stitch node.
crop_widthINTWidth of crop_image. Wire it into generators that need an explicit size.
crop_heightINTHeight of crop_image.
promptSTRINGThe prompt typed into the editor.
control_imageIMAGELayers marked as control (scribble, lineart, depth, pose) on black, cropped and scaled exactly like crop_image. Feed it to ControlNet.
denoiseFLOATDenoise strength from the editor's Generate section (1.0 = full repaint). Wire it into your local sampler.
seedINTSeed from the editor (random per run or fixed). Wire it into your local sampler.
modeSTRING"api" or "local": which result input the editor expects the result on.
negativeSTRINGNegative prompt from the editor (shown in local mode; for SDXL-class models).
setting_1*Editor-driven setting: wire it into any widget input (lora_name, ckpt_name, steps, ...) and a matching control appears in the editor. The next free slot shows up once this one is connected.
setting_2*Editor-driven setting: wire it into any widget input (lora_name, ckpt_name, steps, ...) and a matching control appears in the editor. The next free slot shows up once this one is connected.
setting_3*Editor-driven setting: wire it into any widget input (lora_name, ckpt_name, steps, ...) and a matching control appears in the editor. The next free slot shows up once this one is connected.
setting_4*Editor-driven setting: wire it into any widget input (lora_name, ckpt_name, steps, ...) and a matching control appears in the editor. The next free slot shows up once this one is connected.
setting_5*Editor-driven setting: wire it into any widget input (lora_name, ckpt_name, steps, ...) and a matching control appears in the editor. The next free slot shows up once this one is connected.
setting_6*Editor-driven setting: wire it into any widget input (lora_name, ckpt_name, steps, ...) and a matching control appears in the editor. The next free slot shows up once this one is connected.
setting_7*Editor-driven setting: wire it into any widget input (lora_name, ckpt_name, steps, ...) and a matching control appears in the editor. The next free slot shows up once this one is connected.
setting_8*Editor-driven setting: wire it into any widget input (lora_name, ckpt_name, steps, ...) and a matching control appears in the editor. The next free slot shows up once this one is connected.