Files
dreevelle-ComfyUI-Resolutio…/CLAUDE.md
T
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

4.9 KiB

CLAUDE.md

This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository.

What this is

A single-file ComfyUI custom node package. All code lives in __init__.py; there is no build step, test suite, or linter config. Two nodes, ResolutionSelectorMP and ResolutionAlignToGrid, both pure integer maths with no torch/numpy involvement.

Registered through ComfyUI's V3 schema API, not the legacy NODE_CLASS_MAPPINGS dict: comfy_entrypoint() returns a ComfyExtension whose get_node_list() yields IO.ComfyNode subclasses. Inputs are declared in define_schema() and arrive as named kwargs to execute().

Local environment

  • ComfyUI checkout: /home/dreevelle/comfy/ComfyUI

  • Python venv: /home/dreevelle/comfy-env/bin/python

  • ComfyUI/custom_nodes/ComfyUI-ResolutionTools is a symlink to this working tree, so edits are live on the next server restart:

    ln -sfn /home/dreevelle/Projects/ComfyUI-ResolutionTools \
            /home/dreevelle/comfy/ComfyUI/custom_nodes/ComfyUI-ResolutionTools
    

    This is the pre-publish testing setup. Once the package is on the registry, swap the symlink for an independent clone of the GitHub remote (the FastImageSequence arrangement) so the installed copy tracks releases rather than the working tree.

Verifying changes

__init__.py imports comfy_api.latest at module scope, so it needs ComfyUI on sys.path — but the maths functions are module-level and dependency-free, so they can be exercised directly:

cd /home/dreevelle/comfy/ComfyUI && PYTHONPATH=/home/dreevelle/comfy/ComfyUI \
/home/dreevelle/comfy-env/bin/python -c "
import importlib.util
spec = importlib.util.spec_from_file_location('rt', '/home/dreevelle/Projects/ComfyUI-ResolutionTools/__init__.py')
m = importlib.util.module_from_spec(spec); spec.loader.exec_module(m)
print(m.solve_exact(9, 16, 16, 2.36e6))   # -> (1152, 2048)
"

Two invariants any change to solve_exact must preserve, and which are worth re-fuzzing over random (ratio, alignment, target) triples:

  1. width % alignment == 0 and height % alignment == 0
  2. width * h_ratio == height * w_ratio (ratio held exactly, no drift)

Known-good anchors: 9:16 @ m=16, 2.36 MP -> 1152x2048; 16:9 @ m=32, 1.33 MP -> 1536x864; 21:9 @ m=16, 2.37 MP -> 2352x1008.

Why the maths is what it is

Real megapixels. Core's comfy_extras/nodes_resolution.py:78 uses megapixels * 1024 * 1024. That's the SDXL-era convention where "1 MP" meant 1024x1024. This package deliberately diverges to * 1_000_000. Do not "fix" this to match core — the divergence is the point, and the README documents it.

Lattice solving. lattice_step() computes the smallest scale increment that keeps both axes on the alignment grid while holding an exact ratio. Core rounds each axis independently, which drifts the ratio (9:16 at 2.36 MP becomes 0.5649 instead of 0.5625). solve_nearest() preserves core's behaviour for when the lattice is too coarse; it is opt-in via exact_ratio=False.

Alignment = VAE spatial downscale x DiT patch size. Verified against the ComfyUI source rather than assumed:

  • Krea 2 -> 16. latent_formats.LatentFormat.spacial_downscale_ratio = 8 (inherited by Wan21, which supported_models.Krea2 uses) x patch=2 in comfy/ldm/krea2/model.py.
  • MiniMax H3 -> 32. latent_formats.MiniMaxH3Video.spacial_downscale_ratio = 16 x patch_size=(1, 2, 2) in comfy/ldm/minimax/model.py.
  • SDXL/SD1.5 -> 64. UNet, no patchify; three downsample stages on an 8x VAE.

The failure mode when unaligned is comfy.ldm.common_dit.pad_to_patch_size, which pads with padding_mode="circular" and appends only at the end — hence a bottom/right band, never top/left. If a future model changes VAE or patch size, the alignment table in the README and ALIGNMENT_TOOLTIP both need updating.

Conventions

  • Version is duplicated in __version__ (__init__.py) and [project].version (pyproject.toml) — bump both.
  • dependencies stays empty; this package is stdlib-only by design.
  • Node IDs (ResolutionSelectorMP, ResolutionAlignToGrid) are the public API. Renaming one breaks every saved workflow that uses it.

Releasing

Intended for the Comfy Registry as comfyui-resolution-tools under publisher dreevelle, same setup as ComfyUI-FastImageSequence.

Changing [project].version in pyproject.toml on main publishes a release. .github/workflows/publish_action.yml watches that path and runs Comfy-Org/publish-node-action with the REGISTRY_ACCESS_TOKEN repo secret. Don't touch the version field unless a release is intended; republishing an existing version fails with 400 The node version already exists.

.comfyignore keeps CLAUDE.md and .github/ out of the published archive. comfy node validate runs the registry checks locally; comfy node pack produces the exact archive that would be uploaded.