Nodes/aioli-nodes/๐Ÿ“ Aioli Mask BBox
ComfyUI Node

๐Ÿ“ Aioli Mask BBox

The crop step that stops your inpaint from drifting

By aiolicollectiveยทCreated 6 months agoยทUpdated 2 days agoยท 0
๐Ÿ“ Aioli Mask BBox
  • image
  • mask
  • image_cropped
  • mask_cropped
  • x
  • y
  • orig_width
  • orig_height
  • width
  • height
  • target_size
โ—„padding0โ–บ
โ—„multipleโ–พโ–บ
โ—„targetnoneโ–บ
โ—„force_squarefalseโ–บ
โ—„force_target_downscalefalseโ–บ

The problem this node exists for

Hand an edit model the whole frame and it hands you back a whole new frame. The pixels you didn't mask come back close to what you had - not identical - and across three edits, "close" compounds into mush.

The answer the community converged on is crop, edit, stitch: cut a rectangle around the mask, run the model on just that rectangle, paste the result back at the same coordinates. Your unmasked pixels never touch the VAE, and a small face gets 1024px of generation budget instead of 70.

๐Ÿ“ Aioli Mask BBox is the "crop" half of that pattern, in one node. Feed it the full image and the full mask; it finds the bounding box itself, pads it, snaps the crop to a VAE-friendly multiple, optionally scales it toward a target resolution, and hands back the crop plus every number you need to paste it home again.

It replaces the old two-node chain of Mask Bounding Box (ComfyUI Essentials) โ†’ BBox Multiple Fix. Essentials went maintenance-only in April 2025 - the author's argument being that the first step of every inpaint graph shouldn't depend on it.

How it works

The bbox comes from torch.nonzero(mask > 0) and min/max on the index tensors; padding is applied in source coordinates and clamped to the image. If your mask is a different resolution than your image, the mask gets resized onto the image - the inverse of what Mask Bounding Box did.

The interesting part is compute_crop() in bbox_core.py. It measures the space available around the bbox centre and constrains the crop to it, then fits the ratio with a GCD-based multiplier so it stays exact - no rounding drift, even when the mask touches the image border. Resizing is Lanczos via PIL, with a hard 2048px ceiling. The legacy node calls the same function, so both give bit-identical crops.

The inputs you actually touch

Five of the seven are set-and-forget. Two aren't:

  • padding - margin in pixels around the bbox, before rounding. Default 0; for anything but a tightly-drawn mask you want 24โ€“64. It replaces both padding and blur from Mask Bounding Box, which only ever widened the box.
  • target - none ยท 512 ยท 768 ยท 1024 ยท 1536 ยท 2048. This is the "render the crop at the model's native size" knob: 1024 for SDXL/Klein/Qwen, 512 for SD 1.5, none to just crop.

multiple (8, 16, 32, 64) should match your VAE - 16 (Flux) is the sane default. force_square is off by default, but the pack's own ComfyCloud subgraph ships it on, because most edit models quietly recrop or letterbox non-square input, and that internal recrop is what shifts your pixels. force_target_downscale only matters when the bbox is bigger than target and you want it shrunk.

Wiring the nine outputs

image_cropped and mask_cropped go into VAE Encode - or, on a modern edit model, serve as the reference latent. x and y go into ImageCompositeMasked. orig_width/orig_height are the crop's size in the source, before scaling - that's what you resize the decoded crop back to. width/height are what you sent to the model, and target_size is the numeric target (0 for none), so it plugs straight into ImageResize+.

Plain path: Aioli Mask BBox โ†’ VAE Encode โ†’ KSampler โ†’ VAEDecode โ†’ ImageCompositeMasked (x, y).

Scale path: same, but VAEDecode โ†’ ImageResize+ (โ†’ orig_width/orig_height) โ†’ ImageCompositeMasked (x, y).

Install

ComfyUI Manager โ†’ search "Aioli Nodes" โ†’ Install โ†’ restart. Or:

cd ComfyUI/custom_nodes
git clone https://github.com/aiolicollective/aioli-nodes

No dependency step: the package declares none, and the source only imports torch, numpy and Pillow, all of which ComfyUI already ships.

Where people get bitten

An all-black mask no longer crashes - the bbox falls back to the whole image and logs a warning, so if your crop came back as the entire frame, that's why. The node handles one mask per run: a list of masks makes ComfyUI execute it once per item and collect the results, which is how the pack's multi-region examples work.

force_square when the box is bigger than your image. If max(bbox_w, bbox_h) exceeds the smallest source dimension, the square won't fit: the crop is clamped to a rectangle and then stretched to square. The node warns and still returns orig_width/orig_height pre-stretch. Set your downstream ImageResize+ to stretch mode (keep_proportion = False) or the paste-back comes home squashed.

Your target silently doing nothing. When the bbox is larger than target and force_target_downscale is off, the node ignores the target and rounds to multiple with a 2048 cap instead. By design, but it reads as a broken widget.

Should you switch to it?

If Inpaint Crop and Stitch already works in your graph, don't rewrite anything. What you get here is the numbers as first-class outputs - coordinates, source crop size, final size - which is exactly what multi-region work needs. The pack has almost no Reddit footprint, so judge it on the source, which is short and readable.

CategoryAioli Nodes

Inputs (7)

NameTypeDefaultDescription
imageIMAGEโ€”
maskMASKโ€”
paddingINT00โ€“4096โ€”
multipleCOMBO4 options: 8 (VAE minimum), 16 (Flux), 32 (SD1.5), 64 (SDXL)
targetCOMBOnone6 options: none, 512, 768, 1024, 1536, 2048
force_squareBOOLEANfalseโ€”
force_target_downscaleBOOLEANfalseโ€”

Outputs (9)

NameTypeDescription
image_croppedIMAGEโ€”
mask_croppedMASKโ€”
xINTโ€”
yINTโ€”
orig_widthINTโ€”
orig_heightINTโ€”
widthINTโ€”
heightINTโ€”
target_sizeINTโ€”