Paint Mask (WepeNerd)
The brush ComfyUI keeps making you leave the graph to find
- image
- MASK
- IMAGE
Ask a room full of ComfyUI users what's wrong with inpainting and nobody says "the models." They say the interface: "Most seem to have extremely simple functionality or are overly extravagant workarounds for the simplest part of the process (masking)." That's the gap this node fills. It's a canvas that lives in your graph, next to the sampler, instead of a right-click detour off a Load Image node.
Worth saying why you'd still bother with masks at all in 2026, when instruction-edit models fix most things from a sentence. Masks still own exactly five things: bit-identical unmasked pixels, exact placement, the denoise dial as a continuum, local detail at full resolution, and the uncensored stack. You'll be painting for a while yet.
The mechanism (it's smarter than it looks)
You don't wire pixels into this node. You paint, and the painting is serialised into the node's mask_data widget as a small JSON blob holding a base64 PNG - the coverage lives in the PNG's alpha channel, at a stored width and height. The magenta you see while painting is display-only; the test suite is explicit that the display colour never determines coverage. That's why the mask survives a save and reopen.
On the backend the blob decodes into a MASK tensor in 0–1 (painted = 1, untouched = 0) with antialiased edges. From there:
- No image connected → the canvas is the authority.
mask_dataalso carries a reference to whatever image you opened, and if there is one the node loads it back fromComfyUI/inputand returns it as IMAGE. With nothing opened at all, you get a black 1024×1024 IMAGE and a same-size mask - fine for sketching a shape. - IMAGE connected → the image is passed through completely unchanged, the mask is bilinearly resized to the image's dimensions if they differ, and then repeated across the whole batch (
expand) so every frame gets the same region.
That last detail is the one to remember: your painted mask is scaled to whatever the incoming image happens to be. Paint over a 1:1 square and hang it in front of a 16:9 generation and your region squashes. Use the actual image you're inpainting, not a stand-in.
Inputs and outputs
The only visible input is image (optional). mask_data is a string widget you never type into - the canvas writes it for you. The interesting button is Load input: with an IMAGE wired in, it queues the upstream path so you can paint over your real first frame even when the preview is empty. It resolves reroutes and bypassed nodes, and it never runs anything downstream. Regular Queue also refreshes the preview and passes the batch through.
Two outputs: MASK and IMAGE. The mask goes wherever a mask goes - InpaintModelConditioning, SetLatentNoiseMask, VAE Encode (for Inpainting), or an Impact Pack detailer. The image output is the same RGB you fed in (or the file you opened), minus the overlay, so you can hand both to the same inpaint branch without splitting the wire. An empty mask does nothing, silently.
Because the node is an output node, it runs and shows its preview even with nothing attached downstream. It's a usable sketchpad, not just a mask supplier.
Install
The whole pack installs together; there's nothing model-like to download.
cd ComfyUI/custom_nodes
git clone https://github.com/WepeNerd/ComfyUI-WepeNerd.git
python -m pip install -r ComfyUI-WepeNerd/requirements.txt
Or find ComfyUI-WepeNerd in ComfyUI Manager. Dependencies are Pillow, numpy and av>=17.0.0 - the PyAV one belongs to the pack's video node, not this one. Restart ComfyUI and refresh the browser.
Where people get burned
Moving machines breaks the painting's reference, not the painting. Saved workflows store the source image as a small file reference inside ComfyUI/input/wepenerd_paint_mask rather than embedding pixels in base64. Copy that folder - and wepenerd_masked_lora - along with your workflows, or you'll meet this error: "Paint Mask: source image is missing. Restore its input asset folder or reopen the image." Older workflows that still carry embedded base64 sources raise "Paint Mask: reload the source image."
Undo is a session, not a document. You get 20 actions / 64 MiB of history, and it's gone when you reload the page. Undo also restores Clear and image replacements, which is more than you'd expect.
Replacing the image is conditional. Swap in a different-size image and the node asks before clearing your painting; same-size replacements keep it. The Open reference image… menu item opens a file while IMAGE is connected, and removing that reference leaves the mask and its dimensions intact - so you can decouple "what I'm painting on" from "what goes into the graph."
And a boring one that eats afternoons: if the reference comes from a control_after_generate-controlled upstream like a sampler, ComfyUI may hand you a cached result rather than a fresh one. That's the engine's caching, not the node being precious about your seed.
Inputs (2)
| Name | Type | Default | Description |
|---|---|---|---|
| mask_data | STRING | — | |
| imageopt | IMAGE | Passed through unchanged. Load input previews the first image for painting; Queue also refreshes the preview. |
Outputs (2)
| Name | Type | Description |
|---|---|---|
| MASK | MASK | — |
| IMAGE | IMAGE | — |