Inline Mask
Paint the mask on the node instead of in a popup you'll lose
- image
- mask
- full_image
The annoyance this fixes
ComfyUI's built-in way to make an inpaint mask is: right-click a Load Image node, open the mask editor, paint in a modal, save, close, queue. Do it twice in a row and you notice the modal doesn't remember anything. People have been asking for a live-mask surface in the graph for years, and that's the gap this node fills: paint on the node, paint as you iterate, and keep the mask when the image underneath changes.
What actually happens when you paint
Here's the bit that surprises people. Inline Mask has no painter inside the backend - the painting is a browser canvas, and the node's three required inputs are STRING widgets that the frontend fills in for you:
image- the source path inComfyUI/input/, written by the inline upload button.mask_data- the painted mask, serialized as a PNG data URL. White repaints.source_data- the picture embedded in the workflow, if you asked for that.
At execution the backend decodes whichever of those it got, turns the source into an RGB tensor, and builds the mask from mask_data - or, if there is no mask data, from the alpha channel of the uploaded PNG.
Because the mask lives in the browser and serializes with the workflow, it saves with your workflow. Reload and the strokes are still there, with no disk round-trip per stroke.
IS_CHANGED hashes the three strings plus the mode/crop/blend state, and when the source isn't embedded it fingerprints the file by size and mtime instead of hashing a huge PNG on every near-keystroke re-execution - a 0.8.0 fix, and the reason it stopped feeling sluggish on big sources.
Inputs, outputs, and the two modes
Set edit_mode at the upper right of the node. mask is straightforward: paint, output image + mask, feed them to an inpaint sampler. crop is the Crop & Blend workspace - you place a dashed crop rectangle, generate into it, adopt the result back, and blend the edges with a second, independent mask. The optional crop_state, blend_data and layer_image widgets are that workspace's serialized state, not something you type into.
The three outputs:
image- the current source, or the cropped region when a crop is locked. This goes to your inpaint node and toImage Compare.original.mask- the repaint mask at source resolution. To your inpaint node's mask input.full_image- the whole picture with any adopted layer stitched in. This is the one you save;imagemay only be the crop.
Note what isn't there: no result input socket. Adoption happens only through Image Compare's Adopt this result button, and the pack's server-side prompt normalizer actively strips an obsolete result link if you load a workflow from an older version. Old tutorials with a feedback wire are out of date.
The 0.8.0 change worth knowing about
The README still walks through a ✓ merge button and a ◐ blend layers step in that order. The changelog for 0.8.0 removed the merge step: adoption now bakes the layer into the source immediately and drops you back into repaint-mask editing, and right-click (or Escape) releases the selected region and flattens any remaining layer in one action. So if you follow a video tutorial and can't find ✓, your install is newer than the tutorial. Hold right mouse to preview the original before committing; release to put the workspace back.
Install
cd ComfyUI/custom_nodes
git clone https://github.com/adachi0130/Adachi-Inpaint-Workbench.git
Then a full ComfyUI restart and a hard browser refresh (Ctrl+F5 / Cmd+Shift+R) - the frontend modules are what draw the canvas, so a plain refresh on a half-loaded cache will show you a node with no editor in it. There are no models to download and no extra Python packages: requirements.txt lists numpy, Pillow, torch, all shipped with ComfyUI. Windows users can run install.ps1 -ComfyUIPath "D:\ComfyUI_windows_portable\ComfyUI" instead, which backs up any previous install.
Common issues
The node is empty after loading a shared workflow. Source images are referenced by relative input/ path by default. Either ask the sender to enable Embed source in workflow, or drop the image at the same path. Masks travel inside the JSON; sources don't, unless embedded.
"Failed to save workflow draft" on every edit. Embedding a large source can blow past browser draft storage. The pack handles the normal case by uploading an exact copy and switching to an input-file reference, but if draft storage is already wedged, save with Ctrl+S and reload with Ctrl+F5 to reset ComfyUI's disabled-draft state. Bigger sources: disable workflow persistence before embedding, or export the file - exports don't count against the quota.
Your cut-out disappeared when you reopened a moved workflow. An unmerged crop layer is a relative reference under input/adachi-inpaint-layers/. Copy that file along, or flatten before you move. Unmerged layers are image files; masks aren't.
Hard limits: 64 MiB per file, 32 megapixels, 16384 px on a side. And this is still-image inpainting - 4D latents and single-frame 5D latents, not multi-frame video.
Inputs (7)
| Name | Type | Default | Description |
|---|---|---|---|
| image | STRING | Input image path populated by inline upload. | |
| mask_data | STRING | Editor mask PNG data URL; white repaints. | |
| source_data | STRING | Embedded source image for portable workflows. | |
| edit_modeopt | COMBO | mask | 2 options: mask, crop |
| crop_stateopt | STRING | {} | — |
| blend_dataopt | STRING | — | |
| layer_imageopt | STRING | — |
Outputs (3)
| Name | Type | Description |
|---|---|---|
| image | IMAGE | — |
| mask | MASK | — |
| full_image | IMAGE | — |