MaskComposite and/or/xor silently binarize feathered masks
- Dominant language
- Python
- Stars
- 133k
- Forks
- 15.7k
- Avg merge
- 1d 7h
- Merged PRs (30d)
- 158
Description
If you combine two feathered (soft-edged) masks with `MaskComposite`'s `and`, `or` or `xor`, the soft edges do not survive: these operations first round every pixel at 0.5. Everything below 0.5 opacity is discarded to 0 and everything at or above it becomes 1, so the mask collapses to a hard edge along its own 0.5 contour — the outer half of every feather ramp is gone. Nothing warns you; it usually surfaces much later, as a hard seam where an inpaint or composite was supposed to blend.
[PR #15335](https://github.com/Comfy-Org/ComfyUI/pull/15335) adds `max` and `min` operations — the same union and intersection, with the feathering kept — and turns the hidden 0.5 cut point into an explicit `threshold` input for the times a binary result really is what you want.
## What happens now
A `MASK` is a float tensor in `[0, 1]`, and much of the ecosystem exists to produce the in-between shades — blur, feather, grow-with-blur, segmentation confidence. But `MaskComposite`'s three boolean operations snap each input to 0 or 1 before combining, via `.round().bool()` in [`comfy_extras/nodes_mask.py#L290-L294`](https://github.com/comfyanonymous/ComfyUI/blob/master/comfy_extras/nodes_mask.py#L290-L294):
```python
elif operation == "or":
output[...] = torch.bitwise_or(destination_portion.round().bool(), source_portion.round().bool()).float()
```
The result only ever contains 0.0 and 1.0. Two feathered masks combined with `or`:

## What is wrong with this
- **The feathering is lost everywhere**, not just where the masks overlap — a soft edge nowhere near the other mask still comes out hard.
- **Nothing fails.** The output is a valid mask, the graph runs, and the preview still looks roughly right. The damage shows up later, somewhere else.
- **No operation on this node unions or intersects soft masks.** `multiply` and `add` are product-style and saturating variants; the three operations actually named after set operations all round first — and nothing in their names says so.
- **The cut point is silent, and it is always 0.5.** Even when a hard result *is* what you want, there is no widget suggesting a threshold is involved, so a mask that peaks below 0.5 — a wide feather, a low-confidence segmentation — comes out empty.
## What PR #15335 changes
Two new operations, `max` and `min`: take the larger (or smaller) value at each pixel. On black-and-white masks they behave exactly like `or` and `and`; on soft masks they keep the gradient.
```python
elif operation == "max":
output[...] = torch.max(destination_portion, source_portion)
elif operation == "min":
output[...] = torch.min(destination_portion, source_portion)
```

For the cases where a binary result is genuinely wanted, `and`, `or` and `xor` gain an optional `threshold` input (default `0.5`) in place of the hardcoded rounding. A pixel counts as set when it is strictly above the threshold, so `threshold = 0.0` keeps every pixel that is present at all — the union of the two footprints rather than of their confident cores.
Existing operations are untouched, the new ones are appended to the end of the list, and the default threshold reproduces the old rounding exactly, so saved workflows are unaffected. Both widgets carry a tooltip stating which operations keep intermediate values and which binarize.
Contributor guide
Research direction
Start in comfy_extras/nodes_mask.py at lines 290-294 and review PR #15335, which is named in the issue. Check the existing boolean operations and the described max/min and threshold behavior against soft-mask cases; done means feathering is preserved where required and the default behavior remains unchanged.
Written by the indexing model from the issue text.
Assessment
- Tech stack
- python, pytorch
- Domain
- backend, machine-learning
- Issue type
- Bug
- Difficulty
- 2/5
- Estimated time
- 1-3 hours
- Activity status
- Stale
- Clarity
- Clearly specified
- Newbie friendliness
- 35/100