Files
xmarre-ComfyUI-ScaleLockedR…/resolution.py
T
xmarre b02989ff7c Release v1.1.0: experimental native Krea 2 flow-aligned regeneration
Add the Krea 2 Turbo composition-lock sampler and resolution ladder, uncapped Resolution Selector, and versioned release automation. Preserve generic sampler defaults. 28 numerical tests and native small-weight integration passed; real-image comparisons remain limited.
2026-11-05 09:13:08 +01:00

128 lines
5.1 KiB
Python

"""Resolution Selector without the core 16-megapixel input cap.
Same aspect ratios, megapixel convention (1 MP = 1024 x 1024 pixels), rounding
and live preview as ComfyUI's built-in Resolution Selector. Only the megapixel
limit differs: the outputs may use the full ComfyUI canvas range.
"""
from __future__ import annotations
import math
from nodes import MAX_RESOLUTION
ASPECT_RATIOS = {
"1:1 (Square)": (1, 1),
"2:3 (Portrait Photo)": (2, 3),
"3:2 (Photo)": (3, 2),
"3:4 (Portrait Standard)": (3, 4),
"4:3 (Standard)": (4, 3),
"9:16 (Portrait Widescreen)": (9, 16),
"16:9 (Widescreen)": (16, 9),
"21:9 (Ultrawide)": (21, 9),
}
# Largest square canvas ComfyUI latents accept, in 1024 x 1024 pixel megapixels.
MAX_MEGAPIXELS = float((MAX_RESOLUTION // 1024) ** 2)
NODE_ID = "ScaleLockedResolutionSelector"
DISPLAY_NAME = "Resolution Selector (Uncapped)"
DESCRIPTION = (
"Calculate width and height from an aspect ratio and a megapixel target, like the built-in "
"Resolution Selector but without its 16-megapixel limit. 1 MP = 1024x1024 pixels."
)
def compute_resolution(aspect_ratio: str, megapixels: float, multiple: int) -> tuple[int, int]:
if aspect_ratio not in ASPECT_RATIOS:
raise ValueError(f"unknown aspect ratio {aspect_ratio!r}")
megapixels = float(megapixels)
multiple = int(multiple)
if not math.isfinite(megapixels) or megapixels <= 0.0:
raise ValueError("megapixels must be positive")
if multiple < 1:
raise ValueError("multiple must be positive")
w_ratio, h_ratio = ASPECT_RATIOS[aspect_ratio]
scale = math.sqrt(megapixels * 1024 * 1024 / (w_ratio * h_ratio))
width = max(multiple, round(w_ratio * scale / multiple) * multiple)
height = max(multiple, round(h_ratio * scale / multiple) * multiple)
if max(width, height) > MAX_RESOLUTION:
raise ValueError(
f"{aspect_ratio} at {megapixels:g} MP gives {width}x{height}, beyond ComfyUI's "
f"{MAX_RESOLUTION}-pixel canvas limit per side"
)
return width, height
_TOOLTIPS = {
"aspect_ratio": "The aspect ratio for the output dimensions.",
"megapixels": "Target total megapixels. 1.0 MP = 1024x1024 for square. No 16 MP cap.",
"multiple": "Nearest multiple of the result to set the selected resolution to.",
"width": "Calculated width in pixels multiplied by the selected multiple.",
"height": "Calculated height in pixels multiplied by the selected multiple.",
}
def _build_v3_node():
from comfy_api.latest import io
if not hasattr(io, "ResolutionPreview"):
return None
class ResolutionSelectorUncapped(io.ComfyNode):
@classmethod
def define_schema(cls):
return io.Schema(
node_id=NODE_ID,
display_name=DISPLAY_NAME,
category="utilities",
description=DESCRIPTION,
inputs=[
io.Combo.Input("aspect_ratio", options=list(ASPECT_RATIOS),
default="1:1 (Square)", tooltip=_TOOLTIPS["aspect_ratio"]),
io.Float.Input("megapixels", default=1.0, min=0.1, max=MAX_MEGAPIXELS, step=0.1,
tooltip=_TOOLTIPS["megapixels"]),
io.ResolutionPreview.Input("preview", tooltip="Live preview of the calculated output resolution."),
io.Int.Input(id="multiple", default=8, min=8, max=128, step=4,
tooltip=_TOOLTIPS["multiple"], advanced=True),
],
outputs=[
io.Int.Output("width", tooltip=_TOOLTIPS["width"]),
io.Int.Output("height", tooltip=_TOOLTIPS["height"]),
],
)
@classmethod
def execute(cls, aspect_ratio: str, megapixels: float, multiple: int, preview=None) -> io.NodeOutput:
return io.NodeOutput(*compute_resolution(aspect_ratio, megapixels, multiple))
return ResolutionSelectorUncapped
class ResolutionSelectorUncappedClassic:
"""Fallback for ComfyUI builds without the resolution preview widget."""
CATEGORY = "utilities"
DESCRIPTION = DESCRIPTION
RETURN_TYPES = ("INT", "INT")
RETURN_NAMES = ("width", "height")
FUNCTION = "execute"
@classmethod
def INPUT_TYPES(cls):
return {
"required": {
"aspect_ratio": (list(ASPECT_RATIOS), {"default": "1:1 (Square)", "tooltip": _TOOLTIPS["aspect_ratio"]}),
"megapixels": ("FLOAT", {"default": 1.0, "min": 0.1, "max": MAX_MEGAPIXELS, "step": 0.1,
"tooltip": _TOOLTIPS["megapixels"]}),
"multiple": ("INT", {"default": 8, "min": 8, "max": 128, "step": 4, "tooltip": _TOOLTIPS["multiple"]}),
}
}
def execute(self, aspect_ratio, megapixels, multiple):
return compute_resolution(aspect_ratio, megapixels, multiple)
try:
ResolutionSelectorUncapped = _build_v3_node() or ResolutionSelectorUncappedClassic
except ImportError:
ResolutionSelectorUncapped = ResolutionSelectorUncappedClassic