261 lines
8.0 KiB
Python
261 lines
8.0 KiB
Python
# -----------------------------------------------------------------------
|
|
#
|
|
# !\file CNP_random_resolution.py
|
|
# !\brief Generate random image resolutions based on a base unit and various modes.
|
|
# !\author Colin J.D. Stewart
|
|
#
|
|
# Copyright (c) 2026 Colin J.D. Stewart. All rights reserved.
|
|
#
|
|
# -----------------------------------------------------------------------
|
|
|
|
import random
|
|
|
|
class CNPRandomResolutionNode:
|
|
"""
|
|
Generate image width/height from multiples of a base unit.
|
|
Supports fixed, fully random, and ratio-based random modes.
|
|
"""
|
|
|
|
@classmethod
|
|
def INPUT_TYPES(cls):
|
|
return {
|
|
"required": {
|
|
"mode": (["fixed", "random", "random_ratio"], {
|
|
"default": "random_ratio"
|
|
}),
|
|
"base_unit": ("INT", {
|
|
"default": 512,
|
|
"min": 64,
|
|
"max": 4096,
|
|
"step": 64
|
|
}),
|
|
"min_steps": ("INT", {
|
|
"default": 1,
|
|
"min": 1,
|
|
"max": 32,
|
|
"step": 1
|
|
}),
|
|
"max_steps": ("INT", {
|
|
"default": 4,
|
|
"min": 1,
|
|
"max": 32,
|
|
"step": 1
|
|
}),
|
|
"fixed_width_steps": ("INT", {
|
|
"default": 2,
|
|
"min": 1,
|
|
"max": 32,
|
|
"step": 1
|
|
}),
|
|
"fixed_height_steps": ("INT", {
|
|
"default": 2,
|
|
"min": 1,
|
|
"max": 32,
|
|
"step": 1
|
|
}),
|
|
"orientation": (["any", "square", "portrait", "landscape"], {
|
|
"default": "any"
|
|
}),
|
|
"ratio_mode": (["free", "common", "vertical_common", "horizontal_common"], {
|
|
"default": "common"
|
|
}),
|
|
"randomise_orientation": ("BOOLEAN", {
|
|
"default": True
|
|
}),
|
|
"seed": ("INT", {
|
|
"default": 0,
|
|
"min": 0,
|
|
"max": 0xffffffffffffffff
|
|
}),
|
|
}
|
|
}
|
|
|
|
RETURN_TYPES = ("INT", "INT", "STRING")
|
|
RETURN_NAMES = ("width", "height", "resolution")
|
|
FUNCTION = "generate"
|
|
CATEGORY = "ComfyNodesPlus/Utils"
|
|
|
|
def generate(
|
|
self,
|
|
mode,
|
|
base_unit,
|
|
min_steps,
|
|
max_steps,
|
|
fixed_width_steps,
|
|
fixed_height_steps,
|
|
orientation,
|
|
ratio_mode,
|
|
randomise_orientation,
|
|
seed
|
|
):
|
|
if min_steps > max_steps:
|
|
min_steps, max_steps = max_steps, min_steps
|
|
|
|
rng = random.Random(seed)
|
|
|
|
if mode == "fixed":
|
|
width = fixed_width_steps * base_unit
|
|
height = fixed_height_steps * base_unit
|
|
width, height = self._apply_orientation_hint(
|
|
width,
|
|
height,
|
|
orientation,
|
|
rng,
|
|
randomise_orientation
|
|
)
|
|
return (width, height, f"{width} x {height}")
|
|
|
|
if mode == "random":
|
|
width_steps = rng.randint(min_steps, max_steps)
|
|
height_steps = rng.randint(min_steps, max_steps)
|
|
|
|
width = width_steps * base_unit
|
|
height = height_steps * base_unit
|
|
|
|
width, height = self._apply_orientation_hint(
|
|
width,
|
|
height,
|
|
orientation,
|
|
rng,
|
|
randomise_orientation
|
|
)
|
|
return (width, height, f"{width} x {height}")
|
|
|
|
# mode == "random_ratio"
|
|
width, height = self._generate_ratio_size(
|
|
rng=rng,
|
|
base_unit=base_unit,
|
|
min_steps=min_steps,
|
|
max_steps=max_steps,
|
|
orientation=orientation,
|
|
ratio_mode=ratio_mode,
|
|
randomise_orientation=randomise_orientation
|
|
)
|
|
|
|
return (width, height, f"{width} x {height}")
|
|
|
|
def _apply_orientation_hint(self, width, height, orientation, rng, randomise_orientation):
|
|
if orientation == "square":
|
|
side = min(width, height)
|
|
return side, side
|
|
|
|
if orientation == "portrait":
|
|
if width > height:
|
|
return height, width
|
|
return width, height
|
|
|
|
if orientation == "landscape":
|
|
if height > width:
|
|
return height, width
|
|
return width, height
|
|
|
|
# any
|
|
if randomise_orientation and width != height and rng.choice([True, False]):
|
|
return height, width
|
|
|
|
return width, height
|
|
|
|
def _generate_ratio_size(
|
|
self,
|
|
rng,
|
|
base_unit,
|
|
min_steps,
|
|
max_steps,
|
|
orientation,
|
|
ratio_mode,
|
|
randomise_orientation
|
|
):
|
|
presets = self._build_presets(
|
|
base_unit=base_unit,
|
|
min_steps=min_steps,
|
|
max_steps=max_steps,
|
|
orientation=orientation,
|
|
ratio_mode=ratio_mode,
|
|
randomise_orientation=randomise_orientation
|
|
)
|
|
|
|
if not presets:
|
|
# Safe fallback
|
|
steps = min_steps
|
|
width = steps * base_unit
|
|
height = steps * base_unit
|
|
return width, height
|
|
|
|
width, height = rng.choice(presets)
|
|
return width, height
|
|
|
|
def _build_presets(
|
|
self,
|
|
base_unit,
|
|
min_steps,
|
|
max_steps,
|
|
orientation,
|
|
ratio_mode,
|
|
randomise_orientation
|
|
):
|
|
presets = set()
|
|
|
|
# Always allow square unless specifically filtered to only vertical/horizontal common
|
|
if ratio_mode in ("free", "common"):
|
|
for steps in range(min_steps, max_steps + 1):
|
|
side = steps * base_unit
|
|
presets.add((side, side))
|
|
|
|
if ratio_mode == "free":
|
|
# Any combination within range
|
|
for w_steps in range(min_steps, max_steps + 1):
|
|
for h_steps in range(min_steps, max_steps + 1):
|
|
presets.add((w_steps * base_unit, h_steps * base_unit))
|
|
|
|
else:
|
|
# Common sensible shapes using only integer step pairs
|
|
common_pairs = [
|
|
(1, 2), # 512 x 1024
|
|
(2, 3), # 1024 x 1536
|
|
(2, 4), # 1024 x 2048
|
|
(3, 4), # 1536 x 2048
|
|
(2, 1), # 1024 x 512
|
|
(3, 2), # 1536 x 1024
|
|
(4, 2), # 2048 x 1024
|
|
(4, 3), # 2048 x 1536
|
|
]
|
|
|
|
filtered_pairs = []
|
|
for w_steps, h_steps in common_pairs:
|
|
if w_steps < min_steps or w_steps > max_steps:
|
|
continue
|
|
if h_steps < min_steps or h_steps > max_steps:
|
|
continue
|
|
|
|
if ratio_mode == "vertical_common" and not (h_steps > w_steps):
|
|
continue
|
|
if ratio_mode == "horizontal_common" and not (w_steps > h_steps):
|
|
continue
|
|
|
|
filtered_pairs.append((w_steps, h_steps))
|
|
|
|
for w_steps, h_steps in filtered_pairs:
|
|
presets.add((w_steps * base_unit, h_steps * base_unit))
|
|
|
|
# Orientation filter
|
|
filtered = set()
|
|
for width, height in presets:
|
|
if orientation == "square" and width != height:
|
|
continue
|
|
if orientation == "portrait" and not (height > width):
|
|
continue
|
|
if orientation == "landscape" and not (width > height):
|
|
continue
|
|
filtered.add((width, height))
|
|
|
|
presets = filtered
|
|
|
|
# If orientation is any and randomise_orientation is enabled,
|
|
# mirror non-square sizes so both vertical/horizontal can appear.
|
|
if orientation == "any" and randomise_orientation:
|
|
mirrored = set(presets)
|
|
for width, height in presets:
|
|
mirrored.add((height, width))
|
|
presets = mirrored
|
|
|
|
return sorted(presets) |