148 lines
4.4 KiB
Python
148 lines
4.4 KiB
Python
import comfy.utils
|
||
|
||
class PixelForge:
|
||
DESCRIPTION = "A ComfyUI node for selecting mathematically valid image resolutions filtered by aspect ratio, orientation, and megapixel limit."
|
||
|
||
"""
|
||
PixelForge Resolution Selector
|
||
A ComfyUI node for selecting mathematically valid resolutions
|
||
filtered by aspect ratio, divisibility, orientation, and megapixel limit.
|
||
"""
|
||
|
||
ASPECT_RATIOS = {
|
||
"1:1": (1, 1),
|
||
"3:2": (3, 2),
|
||
"4:3": (4, 3),
|
||
"16:9": (16, 9),
|
||
"16:10": (16, 10),
|
||
}
|
||
|
||
MP_BASE = 1024 * 1024 # 1 MP = 1,048,576 pixels
|
||
|
||
@classmethod
|
||
def INPUT_TYPES(cls):
|
||
# Generate ALL possible resolutions across all combinations
|
||
# JavaScript will filter them dynamically based on user selections
|
||
all_resolutions = cls._build_all_resolutions()
|
||
|
||
return {
|
||
"required": {
|
||
"aspect_ratio": (
|
||
list(cls.ASPECT_RATIOS.keys()),
|
||
{"default": "1:1"},
|
||
),
|
||
"orientation": (
|
||
["landscape", "portrait", "square"],
|
||
{"default": "square"},
|
||
),
|
||
"divisible_by": (
|
||
[16, 32, 64],
|
||
{"default": 16},
|
||
),
|
||
"max_megapixels": (
|
||
["1 MP", "2 MP", "4 MP", "6 MP", "8 MP", "12 MP", "16 MP"],
|
||
{"default": "1 MP"},
|
||
),
|
||
"resolution": (all_resolutions, {"default": "1024×1024"}),
|
||
}
|
||
}
|
||
|
||
@classmethod
|
||
def VALIDATE_INPUTS(cls, **kwargs):
|
||
# Accept any resolution string - JavaScript handles the filtering
|
||
return True
|
||
|
||
RETURN_TYPES = ("INT", "INT", "INT", "INT", "STRING", "FLOAT")
|
||
RETURN_NAMES = (
|
||
"width_px",
|
||
"height_px",
|
||
"ratio_w",
|
||
"ratio_h",
|
||
"orientation",
|
||
"total_megapixels",
|
||
)
|
||
|
||
FUNCTION = "forge"
|
||
CATEGORY = "PixelForge"
|
||
|
||
# ------------------------------------------------------------
|
||
|
||
@classmethod
|
||
def _build_all_resolutions(cls):
|
||
"""Generate all possible resolutions across all aspect ratios and settings."""
|
||
unique_resolutions = set()
|
||
|
||
# Iterate through all combinations
|
||
for aspect_name, (ratio_w, ratio_h) in cls.ASPECT_RATIOS.items():
|
||
for div in [16, 32, 64]:
|
||
for max_mp in [1, 2, 4, 6, 8, 12, 16]:
|
||
max_pixels = max_mp * cls.MP_BASE
|
||
k = div
|
||
|
||
while True:
|
||
w = ratio_w * k
|
||
h = ratio_h * k
|
||
total = w * h
|
||
|
||
if total > max_pixels:
|
||
break
|
||
|
||
# Add both landscape and portrait orientations
|
||
unique_resolutions.add(f"{w}×{h}")
|
||
unique_resolutions.add(f"{h}×{w}")
|
||
|
||
k += div
|
||
|
||
# Sort by total pixels for better UX
|
||
sorted_resolutions = sorted(
|
||
unique_resolutions,
|
||
key=lambda r: (
|
||
int(r.split("×")[0]) * int(r.split("×")[1]), # total pixels
|
||
int(r.split("×")[0]) # width
|
||
)
|
||
)
|
||
|
||
return sorted_resolutions if sorted_resolutions else ["1024×1024"]
|
||
|
||
def forge(
|
||
self,
|
||
aspect_ratio,
|
||
orientation,
|
||
divisible_by,
|
||
max_megapixels,
|
||
resolution,
|
||
):
|
||
ratio_w, ratio_h = self.ASPECT_RATIOS[aspect_ratio]
|
||
|
||
width, height = map(int, resolution.split("×"))
|
||
|
||
# Note: Orientation is already handled by the JavaScript selecting
|
||
# the appropriate dimension order, but we keep this for safety
|
||
if orientation == "portrait" and width > height:
|
||
width, height = height, width
|
||
elif orientation == "landscape" and width < height:
|
||
width, height = height, width
|
||
|
||
total_mp = (width * height) / self.MP_BASE
|
||
|
||
pbar = comfy.utils.ProgressBar(1)
|
||
pbar.update(1)
|
||
|
||
return (
|
||
width,
|
||
height,
|
||
ratio_w,
|
||
ratio_h,
|
||
orientation,
|
||
round(total_mp, 4),
|
||
)
|
||
|
||
|
||
NODE_CLASS_MAPPINGS = {
|
||
"PixelForge": PixelForge
|
||
}
|
||
|
||
NODE_DISPLAY_NAME_MAPPINGS = {
|
||
"PixelForge": "PixelForge · Resolution Selector"
|
||
}
|