Multi Mask Math Ops
Boolean masks in one node instead of five
- mask_1
- mask_2
- mask
What it's for
Combining masks is one of those jobs ComfyUI makes you do with a chain: one MaskComposite per operation, plus a preview node to see what you built. Combining the person detector and the hand detector so a detailer covers both. Subtracting a background mask from a subject. Keeping only the pixels where a color mask and a depth mask agree.
Multi Mask Math Ops is that chain collapsed into a single node with an operator dropdown: or, and, subtract (a-b), subtract (b-a), xor. It's not glamorous. It's the node you keep forgetting the name of and then use in nine workflows.
How it works
Wire mask_1 and, if you need it, mask_2; the pack's README describes the input side as dynamic mask_X, so more mask sockets appear as you connect them, and additional inputs fold into the same operation left to right. operation picks the math:
or→ per-pixelmaxand→ per-pixelminsubtract (a-b)→clamp(mask_1 - everything else)subtract (b-a)→clamp(mask_2 - everything else)xor→ chained absolute difference
Those are min/max on the 0–1 float values, not a binarize-then-AND. That's a real design choice with a real consequence: soft, feathered mask edges stay soft through the operation instead of snapping to 0/1, which is exactly what you want when the result goes straight into SetLatentNoiseMask.
Two behaviours you should know before you trust the output:
Sizes are aligned silently. Every mask that isn't the first one is resized to match the first mask's dimensions, via an 8-bit LANCZOS pass. So if your two masks came from different crops or different-sized sources, they get quietly rescaled to the first one's frame rather than erroring. Convenient - and a great way to feed yourself a mask that's subtly in the wrong place.
Batches cycle, they don't error. All inputs are broadcast to the longest batch by index (mask[idx % batch_size]), then processed per frame. subtract (b-a) is also the one asymmetric case: it starts from the second mask and subtracts the rest, so order matters. And xor across more than two masks is chained absolute difference, which is not a true parity XOR for odd counts.
Under the hood the per-frame work fans out across cores with asyncio.to_thread, so long batches don't run strictly one after another.
Inputs and outputs
mask_1 (MASK, required), mask_2 (MASK, optional), operation (COMBO, default or). One output: mask.
That's it, and that's the appeal. Wire the output into a mask-to-image node to eyeball it, or into an inpainting/detailer path. For a quick union-then-inpaint, or with two detector masks and a mask-to-image preview next door is a two-node answer to the question "why doesn't my ADetailer-equivalent cover the hands".
Install
ComfyUI Manager → search 1hewNodes, or:
cd ComfyUI/custom_nodes
git clone https://github.com/1hew/ComfyUI-1hewNodes
Restart. This node needs only torch, numpy and Pillow - the pack's scary dependencies (ultralytics, rembg, transparent-background, onnxruntime, av) are for its YOLO detection, background-removal and video nodes. If you're here for mask plumbing alone, they're not yours to install. The pack uses the current comfy_api.latest schema, so keep ComfyUI reasonably up to date.
Common issues
The masks look misaligned in the result. You connected a mask from a different crop or resolution and it got rescaled to the first input's frame. Align them upstream - the pack's Mask Crop by BBox Mask / Mask Paste by BBox Mask pair exists for exactly that - or make sure the first mask you connect is the one whose geometry you want to keep.
A "union" that isn't the whole thing. Because or is max, overlapping feathered edges blend rather than harden. If you specifically want a crisp union before feeding a binarizing step, that's fine; if you wanted the soft edges, also fine. Just don't expect or to produce a flat white shape.
Empty result after subtract. subtract (a-b) keeps only what's in mask_1 and not in the rest - swap to subtract (b-a) if you had the operands backwards, and note that everything after the second mask is also subtracted.
Everything returns one frame. The output batch length follows the longest input; if you gave a batch of 8 and a batch of 1, you get 8, with the single mask reused across all of them. That's usually what you want and occasionally not.
Inputs (3)
| Name | Type | Default | Description |
|---|---|---|---|
| mask_1 | MASK | — | |
| operation | COMBO | or | 5 options: or, and, subtract (a-b), subtract (b-a), xor |
| mask_2opt | MASK | — |
Outputs (1)
| Name | Type | Description |
|---|---|---|
| mask | MASK | — |