ComfyUI Node

FitPose [m9]

Letterbox That Pose Skeleton Instead of Squashing It

By MarcusNyne·Created 3 years ago·Updated 3 days ago· 1
FitPose [m9]
  • image
  • latent
  • image
  • width
  • height
subscale1.00
placementcentered
interpolationlanczos
width1024
height1024

A pose condition is a picture of a skeleton. That's the thing people forget when the aspect ratios don't line up. You pull a reference frame into a preprocessor, you get an OpenPose or DWPose skeleton out at the source's shape - 1920×1080, or a vertical phone shot, or whatever someone uploaded - and your generation canvas is 832×1216. If you scale that skeleton to fit, you've just moved every joint. The hips sit lower, the shoulders stretch. ControlNet faithfully reproduces the pose you gave it, and the pose you gave it is wrong.

FitPose [m9] does the boring correct thing: scale the image to fit inside a canvas, preserve its aspect ratio, then centre it on a black canvas and let the black be the padding. Skeleton stays true, ControlNet gets a condition the same size as the latent, nobody squashes anything.

How it works

The scale is min(target_w / width, target_h / height) - contain, never cover - multiplied by a subscale factor, then the resized frame is pasted onto a black canvas at an anchor offset. The paste goes through Pillow rather than tensor slicing on purpose: when the fitted image is larger than the canvas (subscale above 1.0), the paste offset goes negative and Pillow crops against the edge, where a numpy slice would wrap the overflow around to the other side. Frames are processed one at a time and re-stacked, so batches work, and each image is fitted independently.

It's not pose-specific, incidentally. It's a letterbox-to-canvas node that happens to have a pose image in mind - it's just as useful for a depth map or a canny edge map, or for padding any image to a fixed canvas without distortion.

The inputs that matter

  • image - the pose/control image. subscale - margin control, and the parameter that surprises people. At 1.0 (default) the image is scaled to touch the canvas exactly, with no margin. A smaller input gets scaled up to do it: there is no never-upscale toggle, that's the deliberate behaviour. Drop to 0.8 for a proportional margin, or push above 1.0 to deliberately overflow and let the canvas crop it.
  • placement - centered, bottom, left, right, bottom-left, bottom-right. Only visible once subscale moves off 1.0, since at 1.0 the image already fills the canvas. bottom is the useful one for full-body poses: the figure stands on the bottom edge and the margin opens up above their head. Above 1.0 it also decides which side survives the crop - left holds the left edge and trims the right.
  • interpolation - lanczos (default) is a good general choice; nearest keeps hard skeleton lines crisp without anti-aliasing softness. Worth trying on pose maps, since the skeleton lines are strokes, not photographs.
  • width / height - the canvas, default 1024×1024, 64–8192 in steps of 8.
  • latent (optional) - connect your latent and the canvas comes from it, overriding width/height. Only the spatial shape is read, multiplied by 8, which is correct for SD1.5, SDXL, SD3 and Flux VAEs.

Outputs are image, width and height - and those last two are the point of the node being more than a resize. Wire them into your EmptyLatentImage so the canvas the latent gets is the same one the pose was fitted into, instead of you typing 832/1216 in one place and 1216/832 in the other.

Installing it

ComfyUI Manager → search m9-prompts-comfyui, install, restart. Or:

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

No models, no requirements.txt, no compile step - torch, numpy and Pillow only, all of which ship with ComfyUI. Same pack as the prompt-scrambling nodes (ScramblePromptsText [m9]), and the same author as the older sd-scramble-prompts-m9 extension for A1111/Forge.

Where people get burned

The classic mess is a mismatch the node can't see: pose fitted to 832×1216, latent built at 768×1152, ControlNet conditioning quietly applied at the wrong scale. Use the width/height outputs and this stops happening.

Second: expecting a margin and not getting one. At the default subscale of 1.0 the fitted image touches both edges, so placement does nothing visible and a wide skeleton fills the canvas width-wise with black bars top and bottom. If you wanted breathing room, that's subscale 0.7–0.9.

Third: black padding is not neutral for every workflow. For ControlNet it's the standard, expected condition - black is how pose maps encode "no person here". If you're fitting something else and treating the padding as background, remember the model will too.

Categoryimage/transform

Inputs (7)

NameTypeDefaultDescription
imageIMAGE
subscaleFLOAT1.000–2
placementCOMBOcentered6 options: centered, bottom, left, right, bottom-left, bottom-right
interpolationCOMBOlanczos4 options: lanczos, bicubic, bilinear, nearest
widthINT102464–8192
heightINT102464–8192
latentoptLATENT

Outputs (3)

NameTypeDescription
imageIMAGE
widthINT
heightINT