Fit Aspect (Head-Safe) - Closest AR + Tight Cover
Snap your crop to a real aspect ratio without decapitating anyone
- image
- w
- h
- x
- y
- aspect_used
- debug
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_ratioand/or lowerbottom_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:5for 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: imageignores the box and matches the full frame ratio; switch tomq_boxif 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.
Inputs (12)
| Name | Type | Default | Description |
|---|---|---|---|
| image | IMAGE | — | |
| x | INT | — | |
| y | INT | — | |
| w | INT | — | |
| h | INT | — | |
| aspects_csv | STRING | 2:3,3:4,1:1,9:16,16:9,5:8,8:5 | — |
| match_to | COMBO | mq_box | 2 options: image, mq_box |
| headroom_ratio | FLOAT | 0.120–0.5 | — |
| footroom_ratio | FLOAT | 0.060–0.5 | — |
| side_margin_ratio | FLOAT | 0.080–0.5 | — |
| bottom_priority | FLOAT | 0.750–1 | — |
| horiz_gravity | COMBO | center | 3 options: center, left, right |
Outputs (6)
| Name | Type | Description |
|---|---|---|
| w | INT | — |
| h | INT | — |
| x | INT | — |
| y | INT | — |
| aspect_used | STRING | — |
| debug | STRING | — |