Mask Bounding Box
The 40-line node that still earns its keep
- mask_bounding_box
- image_mapped
- X1
- X2
- Y1
- Y2
- width
- height
- bounded mask
- bounded image
The name is the entire job description. You feed Mask Bounding Box a mask and an image, it finds the tightest rectangle that wraps every pixel above a threshold, and hands back the four corner coordinates plus the image and mask cropped to that rectangle. That's it. It's roughly forty lines of torch with zero dependencies, no models to download, no API, no keys. It dates from late 2023, when ComfyUI was mostly SDXL-versus-SD1.5 plumbing, and it shows. But the primitive is so basic it's still genuinely useful - and you can read the whole thing in one sitting and know exactly what it does.
Why you'd reach for it
Anytime you have a mask and want only the region it covers. That comes up constantly. The README's own pitch is the classic: replace part of an SDXL image with an SD1.5 output - crop a masked region down to SD1.5's native 512×512, regenerate, and paste it back. That's the same crop-and-stitch pattern the whole inpainting world converged on: crop the masked area so the model renders only that at full resolution, then composite it over the original, where every unmasked pixel stays bit-identical (see the inpainting knowledge doc for why that still beats full-frame edit models). Same trick powers comic-panel extraction, face swapping, background replacement - anything where you've already got the mask from SAM, CLIPSeg, or a segmentation model and just need the crop out of it.
How it works
Mechanically, nothing fancy: the mask is compared against threshold (pixels above it count), the bounding box of all those pixels is found, and the box is then grown out to min_width × min_height - one pixel per side per pass, clamping at the image edges. That's the README's "centered as much as available." Two things worth knowing from reading the source. First, the box is the union of every pixel above threshold: give it a mask with three separate blobs and you get one rectangle wrapping all three, not three crops. The README's "selects the largest bounded mask" overpromises a bit there. Second, the center calculation in the code is dead - the variables get computed and never used, and the grown box just expands from its own edges. Harmless, but it tells you the kind of pack this is: minimal and honest about it.
Inputs and outputs that matter
You set three things and feed two tensors:
- mask_bounding_box (MASK) - any mask, hand-painted or from a segmenter.
- image_mapped (IMAGE) - the image you want cropped.
- threshold (default 0.5) - how bright a mask pixel must be to count. Masks are usually already 0–1, so the default is right; lower it if your mask is a soft alpha with no hard edge.
- min_width / min_height (default 512 each) - the box gets padded out to at least this. Keep them at your model's native resolution and you get a crop that feeds straight into a sampler.
Outputs are X1, X2, Y1, Y2 (left, right, top, bottom - X is the width axis, Y the height), width and height, and the two crops themselves: bounded mask and bounded image. The image crop feeds straight into an inpaint pass; the coordinates are what you want for controlnet wiring or downstream math. One subtlety: bounded mask is cropped from the raw mask, not the thresholded one, so its edges can carry soft sub-threshold values.
Install
No dependencies, no model files - just the code. Via ComfyUI Manager, search "Mask Bounding Box", or:
cd ComfyUI/custom_nodes
git clone https://github.com/mikkel/comfyui-mask-boundingbox
Then restart ComfyUI. The node shows up under image/processing.
Common issues
- Multiple objects → one box. If you need per-blob crops, you need a connected-components approach, or ComfyUI-Essentials' similarly-named mask bounding box - which is what most people actually mean when they say "the mask bounding box node" in the wild.
- Empty mask → silent zeros. No pixels above threshold returns zeroed crops and coordinates. It won't crash, but it also won't tell you - check your mask before it hits the node.
- Min dimensions bigger than the image. The grow loop clamps at the edges, so the crop can come back smaller than requested. That's by design ("as much as available"), just don't assume.
- Threshold too high for soft masks. If your "mask" is a gradient, 0.5 may chop off everything interesting - drop it to 0.2 and watch.
It's a primitive, not a feature set, and there are fancier packs that do this plus more. But for a plain "mask → crop," this is the one I'd keep around: zero deps, no surprises in the install, and forty lines you can audit yourself.
Inputs (5)
| Name | Type | Default | Description |
|---|---|---|---|
| mask_bounding_box | MASK | — | |
| min_width | INT | 512 | — |
| min_height | INT | 512 | — |
| image_mapped | IMAGE | — | |
| threshold | FLOAT | 0.500–1 | — |
Outputs (8)
| Name | Type | Description |
|---|---|---|
| X1 | INT | — |
| X2 | INT | — |
| Y1 | INT | — |
| Y2 | INT | — |
| width | INT | — |
| height | INT | — |
| bounded mask | MASK | — |
| bounded image | IMAGE | — |