Crop Image Batch to Mask Bounds (ae)
Every Crop in Your Batch Comes Out a Different Size. This Fixes That
- images
- masks
- images
- masks
A ComfyUI IMAGE batch is one tensor, and a tensor is a rectangle - every image shares a single width and height. Fine until you crop to content: mask five products, cut each one to its own bounds, and you're holding five differently-shaped pictures that can no longer be a batch. So you crop one at a time and loop, which is slow and quietly breaks the pairing between each image and its mask.
Crop Image Batch to Mask Bounds (ae) does the whole batch in one pass and gets you out with one shape - every cropped image and mask at identical dimensions, so the batch survives and whatever's downstream doesn't care. It's from ae-in-workflow, a small pack of interactive tools (image selector, pose editor, image-to-LoRA) with basically no community footprint: the pack name returns zero hits in the reddit corpus I check. Useful unknown, not community standard.
If you know how detailers work, this is that move minus the automation, for when the thing you're cropping isn't a face.
How it works, and why the two modes aren't the same tool
Each mask is thresholded at 0.5, the bounding box of everything above it is computed, and that rectangle is cut out. Then the modes diverge.
mask_for_each handles every image separately: crop to its own bounds, scale the crop up with Lanczos to fill a shared canvas, centre it, pad the leftovers with your padding colour. The canvas is sized to the largest crop in the batch. Individual framing.
sum_of_masks computes one bounding box across all masks and applies it to everything. Relative scale and position survive untouched, which is what you want for a scene or a video sequence where subjects must not jump between frames.
That runs deeper than the tooltip suggests. mask_for_each gives consistent dimensions but not consistent scale - a tall thin object and a squat wide one both fill the frame, so a face and a full body in one batch come out at wildly different effective resolution. sum_of_masks keeps them comparable but wastes canvas.
Second mechanism: with color_fill_mask on, and it's on by default, the node paints the region it is not keeping with your padding colour before cropping. That's destructive - the kept pixels are real, everything outside them becomes flat hex instead of what the model drew. Need the original back? Set it false and accept padding that won't match.
The inputs and outputs that matter
images and masks are required and must have the same batch count. The node loops over the image count and indexes the mask batch by that same index, so one mask against four images is an IndexError, not a broadcast. Repeat your mask first.
Three optional widgets you'll actually touch:
mode-mask_for_eachfor per-object framing,sum_of_masksfor scene-level cropping.padding_color- hex,#RGBor#RRGGBBonly. There's a regex in the code and it doesn't negotiate.optional_padding- extra pixels on each side, 0–512, clamped at the image edge.
Then invert_mask, which flips which side counts as the subject - bounds included, so it re-frames rather than just swapping colours. And optional_new_width / optional_new_height, which people misread: they don't resize your output, they force the final canvas and pre-pad the source to at least that size. In sum_of_masks they also make the node skip the mask crop entirely and use the full image bounds. Every crop at 1024×1024? Set them. Just the stuff? Leave them at 0.
Outputs are images and masks at matching sizes, mask zeroed wherever the canvas was padded, so the pair still lines up for masked img2img or a composite. No batch count, no crop coordinates - if you need to paste back into the original frame, that's the crop-and-stitch pair's job.
Install
ComfyUI Manager → Install via Git URL → paste https://github.com/by-ae/ae-in-workflow.git → restart. Or by hand:
cd ComfyUI/custom_nodes
git clone https://github.com/by-ae/ae-in-workflow.git
cd ae-in-workflow
pip install -r requirements.txt
requirements.txt is four honest packages (pygame, torch, numpy, Pillow) plus safetensors and optional screeninfo. Watch out for pygame: this node doesn't use it, but the pack's __init__.py imports the pygame pose editor at load, so without it the whole pack fails to import and you get zero nodes. DiffSynth-Studio is only for the Z-Image LoRA node.
Where people get burned
Batch count mismatch is the number one trip, and it's an ugly IndexError rather than a helpful message. Then soft or feathered masks: the 0.5 threshold means a faint edge you can see on screen may not be inside the box. Empty masks fall back to the full frame - sane, but that one image then lands at a completely different zoom than its neighbours with no warning. If a frame looks zoomed out, check its mask actually had pixels above 0.5.
Inputs (9)
| Name | Type | Default | Description |
|---|---|---|---|
| images | IMAGE | Batch of images to crop (shape: B,H,W,C) | |
| masks | MASK | Batch of masks defining crop boundaries (shape: B,H,W) | |
| invert_maskopt | BOOLEAN | false | Invert the mask before finding bounds |
| modeopt | COMBO | mask_for_each | mask_for_each: crop each to individual mask bounds, then resize then pad to match combined bounds. sum_of_masks: crop all to combined mask bounds |
| padding_coloropt | STRING | #000000 | Hex color for padding (e.g., #FF0000 for red) |
| optional_paddingopt | INT | 00–512 | Additional padding pixels to add around cropped area on each side |
| optional_new_widthopt | INT | 00–4096 | Force output width to this size (0 = use combined mask bounds) |
| optional_new_heightopt | INT | 00–4096 | Force output height to this size (0 = use combined mask bounds) |
| color_fill_maskopt | BOOLEAN | true | Fill masked areas with padding color before processing |
Outputs (2)
| Name | Type | Description |
|---|---|---|
| images | IMAGE | Cropped and resized images with consistent dimensions |
| masks | MASK | Corresponding masks cropped, resized, and padded to match images |