ComfyUI Node

Mask Levels

Two numbers between a mushy matte and a usable mask

By 1hew·Created about a year ago·Updated about 16 hours ago· 33
Mask Levels
  • mask
  • mask
black_point0
white_point255

What it is

Black point. White point. That's the input list, and it's a genuinely good thing that it stops there.

Masks are greyscale images, which means every levels/curves trick you already know applies to them. Mask Levels remaps a mask's 0–255 range: everything at or below black_point is pushed to 0, everything at or above white_point is pulled to 1, and the range in between is stretched linearly. It's a contrast control for masks.

Why you'd want that: a matte straight out of a background-removal model is full of soft uncertainty - 0.3 alpha here, 0.6 there, a faint 0.1 haze where the model wasn't sure. That's fine for compositing and terrible as an inpainting mask, where anything nonzero is "generate here". The KB's inpainting notes describe the same tension from the other direction: feathering is the thing you add when you want a soft boundary, "too high bleeds changes into surrounding areas". This node is how you take softness away, on purpose, without blurring or rebuilding anything.

How it works

Normalize both points to 0–1, then one formula:

out = clamp((mask - black_point) / (white_point - black_point), 0, 1)

Two edge cases the author built in deliberately:

  • black_point == white_point - the divisor goes to zero, so the node degenerates to a binary threshold: pixels at or above that value become 1, everything else 0. Hard cut, no ramp.
  • black_point > white_point - the slope goes negative and the output is an inverted mask. This is the trick worth knowing: you can flip a mask with the same two sliders instead of adding a separate invert node, and it composes with the stretch at the same time.

Nothing else happens. No blur, no feather, no morphological clean-up. That's the design: a levels adjustment is deterministic, costs microseconds, and can't invent structure - which is exactly what you want sitting in front of a sampler.

Input is a MASK, and 2D, [B,H,W] and [B,H,W,C] shapes are all handled (for 4D it reads the first channel of the last dimension). Output is a single mask, same device and dtype as the input. Invalid or non-tensor input returns an empty [0,64,64] mask instead of throwing.

What to set

Defaults are black_point = 0, white_point = 255 - a pure pass-through, so dropping the node in changes nothing until you touch it.

The settings that actually get used:

  • Crush noise, keep the soft edge: black_point 2–5, white_point 230–245. The faint haze around the matte's perimeter goes to zero, the truly-certain interior stays 1, and the transition band gets steeper without becoming a hard step.
  • Force a binary mask: set both points to the same value, e.g. 128. Feeder masks with soft edges will come out at 0 or 1 and nothing else.
  • Invert: black_point = 200, white_point = 50. Negative slope, flipped mask, one node.

That's the whole node. If you're wondering where the feathering knob is, it's the next node over - inpainting's rule of thumb is 4–12px of mask blur to blend, and this node is deliberately the opposite operation.

Install

It ships in ComfyUI 1hewNodes (a big multi-group pack: image, mask, resize, color, io, text, logic). Manager → search the pack title, or:

cd ComfyUI/custom_nodes
git clone https://github.com/1hew/ComfyUI-1hewNodes
pip install -r ComfyUI-1hewNodes/requirements.txt

Restart ComfyUI afterwards. Mask Levels itself is pure torch - no models, no downloads - but the pack's requirements file is long because of its background-removal and video nodes, so a manual clone install takes a minute.

Common issues

The output looks like the input. Check the points. With the defaults you have changed nothing, and a mask whose values all sit in the middle of the range won't visibly move much unless you narrow the interval hard.

Your mask came out inverted and you didn't ask. That's black_point > white_point. It's a documented behaviour, not a bug - but it's easy to do by accident while dragging the two sliders past each other.

You get a 64×64 black mask. That's the empty fallback for a non-tensor input, which in practice means something upstream didn't emit a mask. It's not an error message, so if the next node is mysteriously doing nothing, this is a frequent culprit.

It doesn't fix a broken matte. Levels stretch what's there; they can't add detail the matte never had, and they won't repair holes. Detection-side fixes live in the pack's mask group (Mask Fill Hole, Mask Alpha Clean), and edge work lives in Image Alpha Edge.

Old ComfyUI, no nodes. The pack is written against ComfyUI's newer V3 node schema, so on a stale install nothing from it registers. Update and restart.

Category1hewNodes/mask

Inputs (3)

NameTypeDefaultDescription
maskMASK
black_pointINT00–255
white_pointINT2550–255

Outputs (1)

NameTypeDescription
maskMASK