Nodes/PortraitUtils/Fit Aspect (Head-Safe) - Closest AR + Tight Cover
ComfyUI Node

Fit Aspect (Head-Safe) - Closest AR + Tight Cover

Snap your crop to a real aspect ratio without decapitating anyone

By heyburns·Created 11 months ago·Updated 4 months ago· 1
Fit Aspect (Head-Safe) - Closest AR + Tight Cover
  • image
  • w
  • h
  • x
  • y
  • aspect_used
  • debug
x
y
w
h
aspects_csv2:3,3:4,1:1,9:16,16:9,5:8,8:5
match_tomq_box
headroom_ratio0.12
footroom_ratio0.06
side_margin_ratio0.08
bottom_priority0.75
horiz_gravitycenter

Resizing an image is easy; resizing it to a specific aspect ratio without cutting someone's head off is the hard part. Models want clean ratios - Flux and friends are happiest on their whitelist dimensions - but a blind center-crop to 9:16 will cheerfully chop a forehead in half. FitAspectHeadSafe is the pack's framing node: it takes a subject bounding box, picks the closest allowed aspect ratio, and computes a crop rectangle that respects headroom and footroom around the subject instead of just cropping to the middle.

It's a pure geometry node - no model, no masks, just arithmetic on your inputs. You feed it the image, plus a subject box x, y, w, h (typically straight from MQBBoxMin), and it returns w, h, x, y for the final crop, plus aspect_used and a debug string so you can see what it chose.

How it works, in plain terms: it compares your box's aspect ratio (or the image's, via the match_to toggle - mq_box matches the subject box, image matches the whole frame) against your aspects_csv list (default 2:3,3:4,1:1,9:16,16:9,5:8,8:5), picks the closest one using log-distance so 1:1 isn't "closer" to 2:3 than 9:16 just because of how the math divides out, then grows the box out by headroom, footroom, and side margins - headroom_ratio (0.12), footroom_ratio (0.06), side_margin_ratio (0.08) - and expands to a rectangle of the target ratio that covers all of it. Then it slides the result to honor bottom_priority (0.75): when the box can't fit inside the image in both directions, it sacrifices the top margin before the bottom, because cutting feet is better than cutting a chin. horiz_gravity (center/left/right) picks horizontal placement.

The debug output is genuinely useful here - it prints the chosen ratio, the computed pixel margins, and the violation amounts per edge, so you can see why it landed where it did. Pipe it into a log widget while you're tuning.

Install

Manager → search "PortraitUtils", or:

cd ComfyUI/custom_nodes
git clone https://github.com/heyburns/PortraitUtils

Restart. No extra deps; it's numpy/torch math.

Common issues

  • Subject gets cut on top anyway - raise headroom_ratio and/or lower bottom_priority. If the box is already at the top of the frame, there's literally no headroom left to protect; fix the crop upstream.
  • Aspect looks wrong - you're matching against aspects_csv; add your target ratio to the list (e.g. 4:5 for portrait crops) if it isn't there. The default list is a good starting point, not a law.
  • Crop doesn't match the box at all - match_to: image ignores the box and matches the full frame ratio; switch to mq_box if the subject box is what you care about.

The head-safe logic is the part nobody else's crop-to-ratio node bothers with, and it's the difference between a crop you ship and a crop you apologize for.

CategoryPortraitUtils/Transform

Inputs (12)

NameTypeDefaultDescription
imageIMAGE
xINT
yINT
wINT
hINT
aspects_csvSTRING2:3,3:4,1:1,9:16,16:9,5:8,8:5
match_toCOMBOmq_box2 options: image, mq_box
headroom_ratioFLOAT0.120–0.5
footroom_ratioFLOAT0.060–0.5
side_margin_ratioFLOAT0.080–0.5
bottom_priorityFLOAT0.750–1
horiz_gravityCOMBOcenter3 options: center, left, right

Outputs (6)

NameTypeDescription
wINT
hINT
xINT
yINT
aspect_usedSTRING
debugSTRING