Proportional Dimension
Resize math that doesn't butcher your aspect ratio — and snaps to the divisor your model needs
- from_image
- width
- height
- shortest_side
- longest_side
- scale
You've typed 768 and 512 into an EmptyLatentImage widget one too many times, and at some point you asked the obvious question: why am I doing division in my head? Proportional Dimension is the answer - it takes a source size, scales it so one chosen side hits a target, snaps both dimensions to a divisor, and hands you the result as clean integers plus the scale factor.
The practical reason this matters more than it sounds: diffusion models are picky. SDXL wants dimensions that are multiples of 8, Wan wants its own divisors, and upscaling at a random non-snapped resolution is a fast way to get garbage or an OOM. This node does the arithmetic so the numbers you feed the graph are always model-friendly.
The inputs that matter
The core set is small, and most of it you'll leave at defaults:
widthandheight- your source dimensions. Defaults are 1440x1024, but you'll usually overwrite these from a Dimension From Image node or similar.target_size- the pixel size for the chosen side. Default 480.target_side-shortestorlongest. Pickshortestto upscale so the smaller side hits the target (great for taking a tiny image up); picklongestto cap the bigger side (great for keeping a big image inside a budget).orientation-autokeeps the source orientation;landscape/portraitforce one and swap as needed;squarejust producestarget_sizextarget_sizeand ignorestarget_side.divisible_byandrounding- the reason you're here. Setdivisible_byto 8 (or 64, or whatever your model wants) androundingtonearest,floor, orceil. Floor guarantees the result is ≤ the ideal size, ceil guarantees ≥. For latent-size safety,flooris your friend.
There's also an optional from_image input: wire an IMAGE tensor in and it reads the source dimensions from the image instead of the widgets. That's the version you use in reusable workflows.
The outputs
width and height are the obvious ones - straight into an EmptyLatentImage or an upscale node. Then there are three that cost nothing but earn their keep: shortest_side and longest_side (the scaled smaller and larger dimensions), and scale (a FLOAT, the ratio of output to input). scale is the sneaky useful one - feed it into a denoise or strength widget and the whole graph scales proportionally with the image.
Installing it
Standard MoonPack install:
cd ComfyUI/custom_nodes
git clone https://github.com/moonwhaler/comfyui-moonpack.git
Or ComfyUI Manager → search MoonPack → install, then restart. Found under MoonPack/image. No models, no extra Python dependencies - the pack ships clean.
The one trap
The orientation/target_side combo reads like a menu of options, but if you just want "same aspect, capped at a reasonable size," the fastest setup is target_side = longest, divisible_by = 8, rounding = floor. Everything else is for the cases you'll hit once a month. Don't overthink it - the defaults are sane, and the outputs are there for you to inspect rather than guess.
Inputs (10)
| Name | Type | Default | Description |
|---|---|---|---|
| width | INT | 14401–16384 | Original width in pixels. |
| height | INT | 10241–16384 | Original height in pixels. |
| target_size | INT | 4801–16384 | Desired size for the selected side. Only used when resize_mode is 'target_side'. |
| target_side | COMBO | shortest | Which side target_size applies to. 'shortest' upscales the smaller side; 'longest' caps the larger side. Only used when resize_mode is 'target_side'. |
| orientation | COMBO | auto | Force output orientation. 'auto' keeps source orientation. 'landscape'/'portrait' swap width/height when source doesn't match. 'square' produces target_size x target_size (ignores target_side). |
| divisible_by | INT | 11–1024 | Snap output dimensions to a multiple of this value (1 = no snapping). |
| rounding | COMBO | nearest | How to snap to the divisor. 'floor' guarantees output ≤ ideal; 'ceil' guarantees ≥. |
| resize_mode | COMBO | target_side | 'target_side' uses target_size/target_side above. 'megapixels' uses the megapixels field instead and ignores target_size/target_side. |
| megapixels | FLOAT | 1.000.01–100 | Target total megapixels (width x height / 1e6), aspect ratio preserved. Only used when resize_mode is 'megapixels'. |
| from_imageopt | IMAGE | Optional: read width/height from an IMAGE tensor instead of the widgets. |
Outputs (5)
| Name | Type | Description |
|---|---|---|
| width | INT | — |
| height | INT | — |
| shortest_side | INT | — |
| longest_side | INT | — |
| scale | FLOAT | — |