DHan-MaskingADV
Paint the mask on the node instead of in that popup editor
- image
- mask
You know the loop: right-click the Load Image node, open the Mask Editor, a modal eats the screen, you paint, save, exit, discover the mask is slightly off, do it again. MaskingADV ("DHan-MaskingADV") exists to delete that loop. The editor lives inside the node, drawn straight onto the widget that shows the image, and it outputs an image and a mask like any other loader.
It's for the plain mask work that never went away - fix a hand, swap a jacket, composite an element where you decide the boundary. Instruction-edit models took the sentence-shaped jobs, but they still can't leave the pixels outside the edit untouched, and mask-plus-denoise is still the only dial for how much changes. See the inpainting doc for that argument in full.
The behavior that will bite you first
With nothing painted, the mask output is white - a full-frame mask. Not empty. Everything masked, the opposite of Load Image's all-zeros. That's deliberate: the author's own tooltip on the Fill button reads "create a visible full mask for reverse masking with Eraser." Fill, then erase what you want to keep, is a genuinely better way to do object removal. But if you wire this into a SetLatentNoiseMask and hit queue expecting nothing to be masked, you get an img2img pass over the whole frame.
The mask becomes real the moment you touch it: your first brush stroke flips it to an explicit mask that starts black and then takes your paint. So for the normal "paint what gets regenerated" flow, just start painting - the first stroke resets it for you. The Clear button does the same thing on purpose.
Under the hood
Small Python node, surprisingly serious frontend. The Python side resolves image to a file in ComfyUI/input, EXIF-transposes it, converts to RGB, and returns one IMAGE tensor plus a 1×H×W float mask - batch size 1, one image per node.
mask_data is the clever input, and it's a STRING you never type in: the frontend writes a base64 PNG data URL into it when a stroke finishes, and the Python side decodes it and resizes it to the image's dimensions with LANCZOS. Two things follow. Mask edges come back feathered rather than hard 0/1 - good for inpainting, but threshold it downstream if a node needs a strictly binary mask. And since mask_data is a node input, the mask is stored in your workflow JSON: it travels with a shared workflow, and it's why a heavily-masked workflow file gets chunky.
The editor itself is a DOM widget: brush and eraser with a 1–100 size slider (remembered in browser localStorage), rectangle and lasso, fill, clear, 90° rotate buttons, a −180…180° fine rotation slider, horizontal and vertical mirror, wheel zoom, middle-drag pan, double middle-click to re-fit. Hold Alt while dragging a rectangle or lasso to invert it into an erase. IS_CHANGED is keyed on the image name plus the mask blob, so editing the mask does re-run what's downstream.
Inputs and outputs
Two inputs, one of which is yours. image is a dropdown of every .png/.jpg/.jpeg/.webp/.bmp under ComfyUI/input, with upload/drop/paste owned by the node's own editor - it's the only place you pick the image. mask_data you leave alone. Outputs are image (the possibly rotated/mirrored source, ready for VAE Encode → SetLatentNoiseMask, or VAEEncodeForInpaint / InpaintModelConditioning) and mask (wire it into that same conditioning, or into GrowMask / FeatherMask / ImageCompositeMasked for compositing). Standard semantics: white is masked.
Installing it
Search Comfyui-DHan-MaskingADV in ComfyUI Manager, or:
cd ComfyUI/custom_nodes
git clone https://github.com/DHan315/Comfyui-DHan-MaskingADV
Then restart ComfyUI and hard-refresh the browser - most of this node is JavaScript, and a plain refresh will happily serve you the cached old extension. No model downloads, nothing to pip install: the pack's pyproject.toml declares no dependencies and it only leans on torch, numpy and Pillow, all of which core ComfyUI already ships.
One migration note. Our node index files this class under the module path custom_nodes.Comfyui-DHan-InlineImage, and the README says plainly that "existing workflows using the old node ID must replace the node." So if an older Comfyui-DHan-InlineImage folder is sitting in your custom_nodes, delete it - two copies of the class under different node IDs is how you end up with a "node type not found" error on an old workflow.
Where people get burned
Rotating or mirroring wipes your mask. The 90° rotate buttons, both mirror buttons and Reset rebuild the preview and clear the mask canvas. The fine-rotation slider doesn't. Transform first, paint second.
Transforms dump files into your input folder. Each rotate or mirror uploads the transformed canvas as inline_mask_transformed_<timestamp>.png into ComfyUI/input and points the image widget at it. They accumulate, and being in the input directory they also clutter every Load Image dropdown you own. Delete the ones no workflow references; don't empty the folder.
Small node, big mask. The mask canvas is drawn at your source image's full resolution, so on a 4K image in a small node the brush and cursor aren't 1:1 - zoom in for detail work.
And when you'd rather not paint at all, this isn't the tool. Detectors and promptable segmenters (YOLO detailers for faces and hands, SAM for "mask that specific object") do the finding for you; MaskingADV is the hands-on brush.
Inputs (2)
| Name | Type | Default | Description |
|---|---|---|---|
| image | COMBO | 1 options: example.png | |
| mask_data | STRING | — |
Outputs (2)
| Name | Type | Description |
|---|---|---|
| image | IMAGE | — |
| mask | MASK | — |