dreevelleandClaude Opus 5 2b7a1fecae Initial commit: exact resolution maths for latent-space models
Two nodes addressing distinct problems with ComfyUI's built-in
Resolution Selector (comfy_extras/nodes_resolution.py).

ResolutionSelectorMP
  Core computes `megapixels * 1024 * 1024`, so every target is inflated
  4.86% in area: 2.36 MP at 9:16 yields 1184x2096 = 2.48 real MP. This
  uses 10^6.

  Core also rounds each axis onto the grid independently, which drifts
  the aspect ratio (that same result is 0.5649, not 0.5625). With
  exact_ratio on, this solves instead on the lattice of sizes that are
  simultaneously grid-aligned and exactly on-ratio: for a reduced ratio
  a:b and alignment m those are (a*L*n, b*L*n) where

      L = lcm(m / gcd(a, m), m / gcd(b, m))

  9:16 at m=16 gives the lattice 144n x 256n, and n=8 lands on
  1152x2048 -- exact 9:16, on the grid, 2.36 MP. solve_nearest()
  preserves core's behaviour behind exact_ratio=False for when the
  lattice is too coarse.

ResolutionAlignToGrid
  Snaps arbitrary dimensions onto the grid (1920x1080 -> 1920x1088),
  with round/up/down.

Alignment is vae_spatial_downscale * dit_patch_size, verified against
the ComfyUI source rather than assumed: 16 for Krea 2 (Wan21 inherits
spacial_downscale_ratio 8, patch 2), 32 for MiniMax H3
(spacial_downscale_ratio 16, patch_size (1,2,2)), 64 for SDXL/SD1.5.
Off-grid dimensions get circular-padded by
comfy.ldm.common_dit.pad_to_patch_size -- the pad row is a wrapped copy
of the top of the image, folded into the same patch token as the real
bottom row, which decodes to a band of artifacts along the bottom edge.
Padding is only ever appended, so the artifact is never top/left.

Verified: ruff clean; both schemas construct and both execute() paths
run; a 4000-case fuzz over random ratio/alignment/target triples asserts
w % m == 0, h % m == 0 and w*b == h*a (zero ratio drift); core's formula
reimplemented and reproduces the observed 1184x2096 exactly.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01CdgKVDqEbD14EJ4RC3GPmg
2026-08-09 13:51:36 +01:00

ComfyUI-ResolutionTools

Resolution maths for latent-space models, done exactly.

Two nodes, no dependencies:

Node Does
Resolution Selector (Real MP) aspect ratio + real megapixels → grid-aligned width/height, ratio held exact
Align Resolution to Grid snap an arbitrary size onto the grid (1920×1080 → 1920×1088)

Why

1. "Megapixels" usually isn't megapixels

ComfyUI's built-in Resolution Selector computes megapixels * 1024 * 1024. That's mebipixels — every value you type is inflated 4.86% in area. Ask for 2.36 MP at 9:16 and you get 1184×2096 = 2.48 real MP.

This node uses megapixels * 1_000_000. Ask for 2.36 and you get 2.36.

2. Rounding each axis independently drifts the aspect ratio

That same 1184×2096 has a ratio of 0.5649. True 9:16 is 0.5625. Both axes were individually snapped to a multiple of 16, and the ratio fell between the cracks.

With exact_ratio on (the default), this node solves on the lattice of sizes that are simultaneously grid-aligned and exactly on-ratio. For a reduced ratio a:b and alignment m, those are exactly (a·L·n, b·L·n) where

L = lcm(m / gcd(a, m), m / gcd(b, m))

so it picks n = round(sqrt(target_px / (a·b·L²))). For 9:16 at m=16 the lattice is 144n × 256n, and n=8 gives 1152×2048 — exact 9:16, on the grid, 2.36 MP.

Turn exact_ratio off to get the old independent-rounding behaviour when you'd rather hit the area target precisely and don't care about a hair of ratio drift.

3. Getting alignment wrong bands the bottom of your image

A latent-space DiT reaches pixels through two reductions:

pixels ──/ VAE spatial downscale ──> latent ──/ DiT patch size ──> tokens

Both axes must be multiples of vae_downscale × patch_size:

Models Alignment
Krea 2, Flux, SD3, Qwen-Image, Wan 16 8 × 2
MiniMax H3 32 16 × 2
SDXL, SD1.5 (UNet, 3 downsample stages) 64 universally safe

Miss it and comfy.ldm.common_dit.pad_to_patch_size pads the latent up to the patch grid using circular padding — the pad row is a wrapped copy of the top of the image. That fabricated row gets folded into the same patch token as the real bottom row, a combination the model never saw in training, so the final latent row decodes to garbage: a band along the bottom edge, 8 px tall on Krea 2, 16 px on H3. Padding is only ever appended, so the artifact is always bottom/right, never top/left.

This is why 1920×1080 bands on Krea 2 (1080/8 = 135 latent rows, odd) and 1920×1088 doesn't (1088/8 = 136, even). The built-in selector defaults multiple to 8, which walks you straight into it.

Reference resolutions

Krea 2 (alignment 16, ~2.36 MP — Turbo's sanctioned 1K–2K band):

Ratio Resolution Real MP
1:1 1536 × 1536 2.36
16:9 2048 × 1152 2.36
9:16 1152 × 2048 2.36
3:2 1824 × 1216 2.22
4:3 1728 × 1296 2.24
21:9 2352 × 1008 2.37

MiniMax H3 (alignment 32; native canvas is 768 short edge / 1.03 MP, so treat anything above ~1.3 MP as extrapolation):

Ratio Resolution Real MP
16:9 1024 × 576 0.59
16:9 1536 × 864 1.33
~1.75:1 1344 × 768 1.03 (node default, exactly the trained canvas)

Note that on a 32-grid, exact 16:9 only exists at 512n × 288n — none of the commonly circulated H3 resolution tables are actually 16:9.

Nodes

Resolution Selector (Real MP)

Inputs — aspect_ratio (11 presets + Custom), megapixels (real, 10⁶), alignment (default 16), exact_ratio (default on), custom_w / custom_h (advanced, for the Custom ratio).

Outputs — width, height, megapixels (what it actually produced), label ("1152x2048", useful for filename prefixes).

Align Resolution to Grid

Inputs — width, height, alignment, mode (round / up / down).

Outputs — width, height, changed (true if anything was off-grid), label.

Use up when you must not lose framing, down when you must not exceed a budget.

Install

Via ComfyUI-Manager, or:

cd ComfyUI/custom_nodes
git clone https://github.com/dreevelle/ComfyUI-ResolutionTools

No dependencies, no restart quirks — it's pure stdlib maths.

License

MIT

S
Description
No description provided
Readme MIT
302 KiB
Languages
Python 100%