MaskFlow
Stop repainting the same mask every single run
- image
- mask
- image
- mask
The mask editor has amnesia, and that's the actual problem
ComfyUI's built-in mask editor is a one-shot tool. Paint a mask on a LoadImage node, save, queue - then you want denoise 0.5 instead of 0.7, or a softer edge, or you come back tomorrow, and you repaint. Nothing hits disk, there's no history, and the editor opens on the source image: if your graph crops to 1024 before sampling, you were painting a picture nothing downstream ever saw.
MaskFlow is one node from a single-author pack (90-RED, MIT, still on 1.0.x) that fixes both: drop it wherever you like in the graph, and every capture is archived as a numbered file you can load back.
How it actually works
It doesn't ship a painting UI - it borrows ComfyUI's official MaskEditor via the clipspace route and adds bookkeeping. In draw mode the backend parks on a threading event (polled in 0.25s slices, so Cancel still interrupts) while the frontend opens the real editor. You paint, the editor uploads its composite, and the backend reads the mask out of the alpha channel - painted = transparent, mask = 1 − alpha, the convention LoadImage uses.
Captures land as 8-bit grayscale PNGs in output/MaskFlow/<source>/v00001.png - five-digit padded, numbered max+1 so a deleted file's number is never reused, sorted numerically so v10 follows v9. The folder comes from walking the executing prompt's graph up to your LoadImage node; source_name overrides it.
The decision worth caring about: the archive stores the raw drawing, before any shaping. Invert, feather and grain are applied at execution, every run - so you can retune the edge on a mask you drew last week without reopening the editor.
The inputs that matter
The handful you'll actually touch:
draw_mask- ON opens the editor and waits. OFF loads from the archive, picking the file named byuse_version(latestor a specificvNNNNN). Draw once, then flip to OFF and iterate on the sampler forever.blur_on/blur- the Edge Blur feather in px, default 5. Separable Gaussian with replicate padding, so a white plateau doesn't darken at the border. Classic inpainting advice feathers at 4–12px; the default sits in that band.noise_on/noise_kind/noise_amount/noise_size/noise_blur- grain for the soft edge:film grain,shadow grain(weighted to the dark side of the transition),digital grain(blocky clumps),cloud fbm(fractal). The grain's weight is the mask's own soft band, so it fills the blurred edge and leaves flat areas alone. Seeded off the image hash, so identical settings reproduce the mask exactly.countdown_seconds- 60 by default: at zero the panel clicks the editor's Save for you.0waits for you to close it.source_name- empty for auto-derivation, or type a name to pin the archive folder.
Optional mask seeds the editor with a mask you already have; invert flips white/black. if_no_mask decides what a failed load does: send blank carries on with an empty mask, cancel interrupts - which is what you want in a batch.
Outputs are image (pass-through, so the node can sit anywhere before your sampler) and mask, which feeds your masked sampling - SetLatentNoiseMask, InpaintModelConditioning, a crop-and-stitch pair. In load mode the change signature is the newest archive's mtime, so an idle queue skips the node and everything behind it; draw mode always reruns.
Install
cd ComfyUI/custom_nodes
git clone https://github.com/90-RED/ComfyUI-MaskFlow
# restart ComfyUI
Or search "MaskFlow" in ComfyUI Manager. No models, no downloads, no network calls - it writes only inside output/MaskFlow/. One Python requirement, comfyui-frontend-package>=1.51.0; the README says it was built against ComfyUI 0.35.0 / frontend 1.52.7.
Where people get burned
Old ComfyUI and the node never shows up. It registers through comfy_entrypoint (the V3 node API) and deliberately has no NODE_CLASS_MAPPINGS, so a build without comfy_api.latest errors on import or lists nothing.
Frontend coupling is the one real fragility. The panel hides the native widgets and drives them by DOM, the countdown auto-save clicks the editor's Save by hunting its check icon, and the capture hooks window.fetch on the editor's clipspace-mask-*.png upload - all observed behaviour of a frontend the author doesn't control. If the panel misbehaves after an update, check the frontend build first.
Draw mode blocks the queue - by design; you're meant to be at the screen. Fire it headless and it waits out the timeout (at least 10 minutes), then continues without a mask.
Noise does nothing? blur is 0, so there's no soft band for the grain to live in.
A version you just saved isn't in the dropdown - the list rebuilds with node definitions. Press F5.
Your archive folder is called node_17. Auto-naming needs a LoadImage upstream; anything else falls back to the node id, and recreating that node orphans the masks. Pin source_name if it matters.
Huge images stall the editor. The official MaskEditor is the bottleneck - one corpus report measured 3200×5500 taking 20 seconds to load. Crop first and put MaskFlow after that, which is where it wants to be anyway.
Where it sits
One mask, one image, once? The built-in editor is fine and this is overkill. MaskFlow earns its place when you're iterating - same mask, ten denoise values - or when the region only exists after a crop. It's new, small and single-author with almost no community footprint yet, so treat it as early-adopter gear, and for faces-and-hands-at-volume stick with the detector-driven detailers instead of painting by hand.
Inputs (15)
| Name | Type | Default | Description |
|---|---|---|---|
| image | IMAGE | — | |
| draw_mask | BOOLEAN | true | ON: open the mask editor. OFF: use the newest (or chosen) archived mask, no editor |
| countdown_seconds | INT | 600–3600 | Editor auto-saves after this many seconds (0 = wait for manual close). Only in draw mode |
| invert | BOOLEAN | false | Invert the mask (white <-> black) |
| blur_on | BOOLEAN | false | Feather the mask edge. The switch at the front of the Edge Blur row on the node drives this |
| blur | INT | 50–1000 | Mask edge feather radius in px (a Photoshop-style blur). 0 = hard edge (and then the noise has no soft band to sit on) |
| noise_on | BOOLEAN | false | Apply the edge noise. The switch at the front of the noise row on the node drives this |
| noise_kind | COMBO | film grain | Edge-noise style (grayscale - this is a mask). Click the thumbnail on the node to switch style. The grain fills the soft (blurred) edge and leaves the flat areas alone |
| noise_amount | INT | 500–100 | Noise strength / opacity in % |
| noise_size | INT | 11–200 | Grain size in px (bigger = coarser blobs) |
| noise_blur | INT | 00–200 | Blur applied to the noise itself, in px (softens the grain) |
| use_version | COMBO | latest | Which archived mask version to use (load mode). 'latest' = newest |
| source_name | STRING | Archive folder name. EMPTY = auto-derive from the connected LoadImage node. Type a name to override | |
| if_no_mask | COMBO | send blank | 2 options: send blank, cancel |
| maskopt | MASK | Optional external mask to start from |
Outputs (2)
| Name | Type | Description |
|---|---|---|
| image | IMAGE | — |
| mask | MASK | — |