Nodes/m9-prompts-comfyui/CropToRatio [m9]
ComfyUI Node

CropToRatio [m9]

Stop Squashing Your Bounding-Box Crops

By MarcusNyne·Created 3 years ago·Updated 3 days ago· 1
CropToRatio [m9]
  • image
  • ratio_image
  • image
modeAlways
width
height

Here's the sequence that gets everyone. A detection node hands you a bounding box, ImageCrop cuts that region out, and now you want to run it through a detailer pass, a face model, or an upscaler that wants a specific shape. The crop came out at whatever aspect the box happened to be - a tall thin sliver of a face, say - and the node downstream rescales it to a fixed canvas. That rescale is a stretch, and a stretched face crop is exactly the kind of input that produces a subtly wrong result you can't quite diagnose.

CropToRatio [m9] fixes the shape without touching the size. Nothing is scaled, nothing is padded. One axis gets trimmed equally from both ends until what's left matches your target ratio. Too wide, and you lose the left and right edges; too tall, and you lose top and bottom. That's the entire node.

How it works

The ratio comparison is cross-multiplied integers (src_w * ratio_h against src_h * ratio_w), so an already-matching image is an exact test rather than a floating-point coin flip - it passes through untouched. When something does need trimming, the arithmetic rounds the smaller dimension and floors the offsets, which means an odd number of pixels to remove always puts the extra one on the bottom or the right. The kept region is a plain tensor slice: every frame in a batch shares a size, so the whole batch is cropped in one shot.

Worth saying plainly: this is not a resize and it is not a pad. If the crop needs to end up at 1024×1024, that resize still happens afterwards. What this node buys you is that the resize is no longer distorting anything, and neither is any latent it feeds.

The inputs that matter

  • image - the crop (or anything else you're tidying).
  • mode - Always, Vertical only, or Horizontal only. This is a gate on orientation, not a choice of which axis to trim, and misreading it is the number one way people conclude the node is broken.

Under Vertical only, a portrait image gets cropped and stays portrait; a landscape image is left alone no matter what ratio you asked for, because a portrait cut from a landscape image would have to change the image's orientation and this mode never does that. Square counts as neither, so square inputs always pass through. Always is the default and does what it says - a tall crop against a wide ratio loses most of its height and comes out wide. The restricted modes only ever decline to crop; when they do crop, the result is identical to Always.

The ratio itself comes in over sockets, not widgets, which trips people up on first use:

  • ratio_image (optional) - any image already at the shape you want. Only its proportions are read, never its pixels. The original frame works fine.
  • width / height (optional) - a target ratio, wired from a PrimitiveInt or straight from another node's integer outputs. FitPose [m9] has width/height outputs designed to be chained here.

ratio_image wins if it's connected. With nothing wired at all there is no ratio to crop to, and the image passes through unchanged - silently, by design.

One output: image.

Installing it

It ships in m9-prompts-comfyui with five other nodes. In ComfyUI Manager, search for m9-prompts-comfyui and install it (it's on the Comfy registry, registry.comfy.org/nodes/m9-prompts-comfyui), or do it by hand:

cd ComfyUI/custom_nodes
git clone https://github.com/MarcusNyne/m9-prompts-comfyui

Restart ComfyUI. There is no requirements.txt, no model to download, and no build step - the whole pack is pure Python on torch, numpy and Pillow, which ComfyUI already has. That's a rare and welcome thing in a custom-nodes folder. It's the same author as the old sd-scramble-prompts-m9 extension for A1111/Forge, ported to ComfyUI.

Where it goes wrong

The failure that actually generates support questions is nothing happening. Check, in order: is mode declining (a landscape image under Vertical only is a pass-through by design, not an error)? Did you wire width/height or ratio_image - because a 0 height and nothing connected means pass-through? And are the sockets not drawn on the node, because width and height are input-only INTs with no widget - a fresh instance shows two empty dots, not two numbers.

Second trap: people use it as a resize. It can't grow an image and it can't hit a pixel count. Feed it a 300×1200 crop and ask for a 1216×832 ratio and you get a 300×205 band - most of the figure is simply gone - which still has to go through an actual resize afterwards. That's Always doing its job, but it's worth being deliberate about: a tall subject against a wide ratio is a bad pairing, which is exactly what the restricted modes are for. Ratio 1216:832 and 152:104 are the same crop, because only the ratio matters.

Categoryimage/transform

Inputs (5)

NameTypeDefaultDescription
imageIMAGE
modeCOMBOAlways3 options: Vertical only, Horizontal only, Always
ratio_imageoptIMAGE
widthoptINT
heightoptINT

Outputs (1)

NameTypeDescription
imageIMAGE