Nodes/aioli-nodes/๐Ÿ“ BBox Multiple Fix
ComfyUI Node

๐Ÿ“ BBox Multiple Fix

The crop node that makes inpaint actually stitch back cleanly

By aiolicollectiveยทCreated 6 months agoยทUpdated 2 months agoยท 0
๐Ÿ“ BBox Multiple Fix
  • image
  • mask
  • image_cropped
  • mask_cropped
  • x
  • y
  • orig_width
  • orig_height
  • width
  • height
  • target_size
โ—„x0โ–บ
โ—„y0โ–บ
โ—„width64โ–บ
โ—„height64โ–บ
โ—„multipleโ–พโ–บ
โ—„targetnoneโ–บ
โ—„force_squarefalseโ–บ
โ—„force_target_downscalefalseโ–บ

The modern way to edit with a mask is crop-and-stitch: crop tight around the masked region so the model spends its entire generation budget on that zone, run it, and paste the result back over the untouched original. That's the whole case for masked inpainting in 2026 - bit-identical pixels outside the mask, full-resolution detail inside it. This node is the crop half of that, done properly.

It plugs in right after Mask Bounding Box (MaskBoundingBox+ from ComfyUI Essentials) and fixes the two things that quietly ruin inpaint crops. First, dimension drift: if the crop isn't a multiple the VAE or model is happy with, the model internally recrops or letterboxes it, and your stitch comes back a few pixels off no matter how careful you were. Second, resolution mismatch: a 512ร—512 crop runs beautifully through Flux, a 493ร—517 one doesn't, and hand-scaling it changes the aspect ratio so the paste no longer lines up.

How it works

BBoxMultipleFix takes the bbox values from MaskBoundingBox+, rounds the crop to the multiple you pick (8 VAE minimum, 16 Flux, 32 SD1.5, 64 SDXL), and rescales it toward a target resolution using GCD math so the aspect ratio is preserved exactly - that's what keeps the recompose pixel-perfect. The genuinely clever bit is the anti-clamp guarantee: it computes the space actually available around the bbox centre and constrains the crop to it, so the crop never exceeds the source image and nothing gets clamped after scaling. No clamp means no drift, even when the mask zone sits right at the image border, which is where this normally falls apart.

force_square = True forces a 1:1 crop before scaling. The pack's reasoning: several popular edit models (nano-banana, Flux, Qwen-Edit, SDXL inpaint) quietly recrop any non-square input, so squaring up-front prevents that internal "pixel shift."

The target dropdown (512/768/1024/1536/2048) is your resolution budget. bbox smaller than target โ†’ upscale toward it; bbox larger, with force_target_downscale = True โ†’ downscale toward it; bbox larger without it โ†’ fall back to rounding plus a 2048px cap. target: none just rounds to the multiple and caps at 2048.

Inputs and outputs

Inputs: image (full source), mask (full source), then x, y, width, height straight from MaskBoundingBox+, plus the multiple / target / force_square / force_target_downscale widgets.

The outputs are the whole point - they're designed to wire into the standard stitch chain:

  • image_cropped, mask_cropped โ†’ VAE Encode (Inpaint)
  • x, y โ†’ ImageCompositeMasked (where to paste back)
  • orig_width, orig_height โ†’ the crop size in the source before scaling - use these to resize back after VAE Decode
  • width, height โ†’ final sizes after scaling
  • target_size โ†’ the numeric target, straight into ImageResize+

So the loop is: BBoxMultipleFix โ†’ VAE Encode (Inpaint) โ†’ KSampler โ†’ VAE Decode โ†’ ImageResize+ (orig_width/orig_height) โ†’ ImageCompositeMasked (x/y).

Installing it

It ships in the Aioli Nodes pack. In ComfyUI Manager search "Aioli Nodes", or:

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

Restart ComfyUI; it appears under Aioli Nodes. No extra dependencies - everything it needs (torch, numpy, math, Pillow) ships with ComfyUI.

Where people get burned

The big one is force_square on a very large bbox. If the theoretical square (max of width/height) exceeds the smallest source dimension, the crop can't be square - the node clamps it to a rectangle and then stretches it to fit the square target. It logs a warning when this happens. To stay pixel-perfect in that case, set the downstream ImageResize+ to stretch mode (keep_proportion = False) and feed it orig_width/orig_height so the inverse stretch restores the true shape before compositing. Easy to miss, worth knowing before it bites.

Note this node handles one region per call - the multi-region examples in the pack run it per mask, driven by MaskBoundingBox+ over the region list from MaskSplitRegions / RegionMaskList. It's a young pack from a small studio with no big community trail yet, so if you hit an edge case, the GitHub issues page is the real support channel.

CategoryAioli Nodes

Inputs (10)

NameTypeDefaultDescription
imageIMAGEโ€”
maskMASKโ€”
xINT00โ€“99999โ€”
yINT00โ€“99999โ€”
widthINT641โ€“99999โ€”
heightINT641โ€“99999โ€”
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โ€”