LC Mask Refine ✨
A trimap is how you stop losing hair
- image
- mask
- detail_region
- cutout
- mask
- trimap
Every mask you get out of a segmentation model is a decision made per pixel: foreground or background, no maybe. Which is structurally wrong for hair. A strand three pixels wide doesn't have crisp sides, it fades - and a model that has to call it white or black will either fragment it into noise or swallow it into a helmet. That's why a mask that looks clean against grey falls apart the moment you composite it over something else.
This node is the fix, and its whole design is one idea: don't let a model decide the whole mask, only the part that's genuinely uncertain.
The mechanism
It builds a trimap from your mask. Inside edge_erode pixels of the edge is sure foreground - locked white, nothing touches it. Beyond edge_dilate pixels outside the edge is sure background - locked black. Between the two is the unknown band, and that band is the only thing method decides:
- vitmatte - a real matting model (ViTMatte, Apache-2.0, small or base). Slow, and the one that keeps flyaway hair.
- guided_filter - no model, instant. Snaps the uncertain pixels to nearby image edges using a color-guided filter.
- none - skip matting entirely. You still get grow, gap fixing, black/white points and feather.
A narrower band means less to decide, which means a tighter mask with less model improvisation. That's the intuition that makes edge_erode and edge_dilate the two sliders you'll touch first: start at 10 and 10, then shrink both toward 3 for a tight edge. The defaults ship at 10/10.
Everything on this node is in real pixels, and that's the trap. 10px on a 1024px portrait is a wide band; the same 10px on a 4K frame is nothing. Scale your numbers with your resolution, not with a tutorial.
The inputs that matter
Beyond image and mask (any size - the mask gets resized to the image):
grow shifts the mask before the trimap is built, positive to grow, negative to shrink. Good for taking back a mask that a detection model was a little stingy with.
black_point and white_point (0.01 / 0.99 by default) crush the extremes - everything at or below black becomes 0, everything at or above white becomes 1. This is the pair that makes a soft, hesitant mask usable: raise black_point to clear faint haze where the model wasn't sure, lower white_point to solidify a subject it dithered on.
threshold is where the input mask splits into inside and outside when the trimap is built. Raise it toward 1.0 to trust only the solid core of a soft mask.
fix_gap closes holes and cracks up to about N pixels wide (with fix_threshold deciding how white a pixel must be to count as solid). Useful when a detector left speckle in the middle of a subject.
feather is final edge softness, edge_color_spread pushes solid subject colors outward a few pixels to cut the background halo you see when you drop a cutout onto a green or blue field - a genuinely fiddly problem and the one control here that's specific to cutouts rather than masks.
max_megapixels (3 by default) caps what gets matted - anything bigger is matted at that size and scaled back up, because VITMatte is the expensive part.
Then the hair-specific half: plug a mask into the optional detail_region - hair, usually - and that area uses detail_erode / detail_dilate (72/64 by default) instead of the edge values, so fine strands get a much wider band while the rest of the mask stays tight. detail_blur softens the seam where that region blends back in.
Outputs: cutout (RGBA - the image with the mask as alpha), mask (the refined mask), and trimap, which is mostly for looking at.
The node is also its own preview: preview_view switches the wipe between cutout, mask and trimap, and hovering shows the after image to the left of the pointer. Run the graph once to get that preview at all.
Where it goes
Chain it after anything that produces a rough mask - LC Remove Background, LC Segment Anything, LC Person Mask, or a detector from another pack - and before whatever composites or inpaints with that mask. It's also the reason those nodes don't each need their own matting settings: the pack put the trimap machinery in one place, and they optionally call into it.
Test it on the hard image, not the easy one. A wine glass, a veil, a fluffy cat: that's where the band width does something visible.
💡 Two settings gotchas. Wire sliders in as FLOAT - INT sockets will not plug into these inputs, by design. And every pixel value takes decimals in 0.1 steps (the point-type values step by 0.001), so 7.3 is a legal edge_erode.
Install
cd ComfyUI/custom_nodes
git clone https://github.com/lonecatone23/ComfyUI_LC_MaskMaker_nodes
# restart ComfyUI
Manager → search LC MaskMaker. Guided filter and none modes need no model at all, so you can evaluate the node before committing to a download. vitmatte fetches vitmatte-small-composition-1k or vitmatte-base-composition-1k (both Apache-2.0) into models/vitmatte on first run, and picks up a copy that's already there.
Gotchas
- Fine hair vanishes. Widen edge_dilate, or better, feed a hair mask into detail_region and raise detail_dilate - that's what the detail band is for.
- A grey fringe around the cutout. Raise black_point a touch, and use edge_color_spread if you're compositing over a saturated background.
- vitmatte takes forever on a 6K image. Lower max_megapixels. The matte gets computed smaller and upscaled, which is almost always invisible.
- The whole mask is soft and mushy. You're in guided_filter with a high softness. Lower it so the filter follows image edges more tightly.
- Nothing renders at all on the node. Preview only exists after a run. Queue once.
Inputs (21)
| Name | Type | Default | Description |
|---|---|---|---|
| image | IMAGE | The image the mask belongs to. Used as the guide. | |
| mask | MASK | Mask to refine. Any size; it is resized to the image. | |
| method | COMBO | vitmatte | vitmatte: matting model, best for hair and fine detail. guided_filter: fast, no model, snaps the unknown band to image edges. none: skip matting, only grow, fix gaps, levels and feather. |
| vitmatte_model | COMBO | Only used by vitmatte. Entries marked 'Download' are fetched the first time you run. | |
| grow | FLOAT | 0.0-512–512 | Pixels to grow (positive) or shrink (negative) the mask before the trimap is built. |
| fix_gap | FLOAT | 0.00–64 | Close small holes and cracks up to about this many pixels wide. 0 = off. |
| fix_threshold | FLOAT | 0.7500.01–0.999 | How white a mask pixel must be to count as solid when fixing gaps. |
| edge_erode | FLOAT | 10.00–1024 | TRIMAP. How far the sure-foreground area is pulled in from the mask edge, in pixels. Everything inside this is locked white. Smaller = tighter, less to decide. |
| edge_dilate | FLOAT | 10.00–1024 | TRIMAP. How far the unknown band reaches out past the mask edge, in pixels. Everything beyond this is locked black. Smaller = tighter, less to decide. |
| softness | FLOAT | 0.00100.00001–0.1 | guided_filter only. Lower follows image edges tighter, higher is smoother. |
| threshold | FLOAT | 0.5000.05–0.999 | Where the input mask is split into inside and outside when the trimap is built. Raise toward 1.0 to use only the solid core of a soft mask. |
| black_point | FLOAT | 0.0100–0.999 | Mask values at or below this become 0. Clears faint haze. |
| white_point | FLOAT | 0.9900.001–1 | Mask values at or above this become 1. Solidifies the subject. |
| feather | FLOAT | 0.00–100 | Final edge softness in pixels. |
| edge_color_spread | INT | 00–64 | Cutout only. Pushes solid subject colors outward this many pixels to cut background halos. 0 = off. |
| max_megapixels | FLOAT | 3.000.25–16 | vitmatte only. Larger images are matted at this size, then scaled back. |
| detail_erode | FLOAT | 72.00–1024 | Same as edge_erode, but for the detail_region area (hair). Wider gives fine strands room. |
| detail_dilate | FLOAT | 64.00–1024 | Same as edge_dilate, but for the detail_region area. |
| detail_blur | FLOAT | 4.00–512 | Softens the seam where the detail_region result blends into the rest. |
| preview_view | COMBO | cutout | What the wipe on the node shows. trimap: left is the trimap (black = sure background, gray = unknown, white = sure foreground), right is the result. |
| detail_regionopt | MASK | Optional. White areas (like hair) use detail_erode / detail_dilate instead of edge_erode / edge_dilate. |
Outputs (3)
| Name | Type | Description |
|---|---|---|
| cutout | IMAGE | — |
| mask | MASK | — |
| trimap | MASK | — |