242 lines
6.8 KiB
Python
242 lines
6.8 KiB
Python
import re
|
|
|
|
|
|
class Dimensions:
|
|
def __init__(self, width: int, height: int):
|
|
self.width = width
|
|
self.height = height
|
|
|
|
@property
|
|
def width(self):
|
|
return self._width
|
|
|
|
@width.setter
|
|
def width(self, value: int):
|
|
if value < 64:
|
|
raise ValueError("width of less than 64 pixel")
|
|
self._width = int(value / 2) * 2 if value % 2 else value
|
|
|
|
@property
|
|
def height(self):
|
|
return self._height
|
|
|
|
@height.setter
|
|
def height(self, value: int):
|
|
if value < 64:
|
|
raise ValueError("height of less than 64 pixel")
|
|
self._height = int(value / 2) * 2 if value % 2 else value
|
|
|
|
|
|
def calculate_aspect_ratio(
|
|
base_resolution: int, ratio: float, overextend: bool
|
|
) -> Dimensions:
|
|
width = base_resolution
|
|
height = base_resolution
|
|
|
|
if overextend:
|
|
if ratio > 1:
|
|
height *= ratio
|
|
else:
|
|
width /= ratio
|
|
else:
|
|
if ratio > 1:
|
|
width /= ratio
|
|
else:
|
|
height *= ratio
|
|
|
|
return Dimensions(int(width), int(height))
|
|
|
|
|
|
def calculate_constant_constant_resolution(
|
|
base_resolution: int, ratio: float
|
|
) -> Dimensions:
|
|
pixel_count = base_resolution ** 2
|
|
new_height = (pixel_count * ratio) ** 0.5
|
|
new_width = new_height / ratio
|
|
return Dimensions(new_width, new_height)
|
|
|
|
|
|
class YARS:
|
|
def __init__(self):
|
|
pass
|
|
|
|
@classmethod
|
|
def INPUT_TYPES(self):
|
|
ratios = [
|
|
"1:1",
|
|
"landscape (4:3)",
|
|
"landscape (3:2)",
|
|
"landscape (16:9)",
|
|
"landscape (16:10)",
|
|
"landscape (21:9)",
|
|
"portrait (3:4)",
|
|
"portrait (2:3)",
|
|
"portrait (9:16)",
|
|
"portrait (9:10)",
|
|
"portrait (9:21)",
|
|
]
|
|
|
|
return {
|
|
"required": {
|
|
"base_resolution": (
|
|
"INT",
|
|
{
|
|
"default": 512,
|
|
"min": 512,
|
|
"max": 8192,
|
|
"step": 128,
|
|
},
|
|
),
|
|
"aspect_ratio": ((ratios),),
|
|
"overextend": (
|
|
"BOOLEAN",
|
|
{"default": False, "label_on": "yes ", "label_off": "no "},
|
|
),
|
|
},
|
|
}
|
|
|
|
RETURN_TYPES = ("INT", "INT")
|
|
RETURN_NAMES = ("width", "height")
|
|
FUNCTION = "calculate"
|
|
OUTPUT_NODE = False
|
|
CATEGORY = "utils"
|
|
|
|
def calculate(
|
|
self,
|
|
base_resolution: int,
|
|
aspect_ratio: str,
|
|
overextend: bool,
|
|
) -> tuple[int, int]:
|
|
if m := re.search(r"(\d+):(\d+)", aspect_ratio):
|
|
ratio: float = int(m.group(2)) / int(m.group(1))
|
|
d = calculate_aspect_ratio(base_resolution, ratio, overextend)
|
|
|
|
return (d.width, d.height)
|
|
|
|
raise ValueError(f"Could't find aspect ratio in string `{aspect_ratio}`")
|
|
|
|
|
|
class YARSAdv:
|
|
def __init__(self):
|
|
pass
|
|
|
|
@classmethod
|
|
def INPUT_TYPES(self):
|
|
return {
|
|
"required": {
|
|
"base_resolution": (
|
|
"INT",
|
|
{
|
|
"default": 512,
|
|
"min": 512,
|
|
"max": 8192,
|
|
"step": 128,
|
|
},
|
|
),
|
|
"width_ratio": (
|
|
"INT",
|
|
{
|
|
"default": 1,
|
|
"min": 1,
|
|
"max": 1024,
|
|
"step": 1,
|
|
},
|
|
),
|
|
"height_ratio": (
|
|
"INT",
|
|
{
|
|
"default": 1,
|
|
"min": 1,
|
|
"max": 1024,
|
|
"step": 1,
|
|
},
|
|
),
|
|
"overextend": (
|
|
"BOOLEAN",
|
|
{"default": False, "label_on": "yes ", "label_off": "no "},
|
|
),
|
|
"constant_resolution": (
|
|
"BOOLEAN",
|
|
{"default": False, "label_on": "yes ", "label_off": "no "},
|
|
),
|
|
},
|
|
}
|
|
|
|
RETURN_TYPES = ("INT", "INT")
|
|
RETURN_NAMES = ("width", "height")
|
|
FUNCTION = "calculate"
|
|
OUTPUT_NODE = False
|
|
CATEGORY = "utils"
|
|
|
|
def calculate(
|
|
self,
|
|
base_resolution: int,
|
|
width_ratio: int,
|
|
height_ratio: int,
|
|
overextend: bool,
|
|
constant_resolution: bool,
|
|
) -> tuple[int, int]:
|
|
ratio: float = height_ratio / width_ratio
|
|
|
|
if constant_resolution:
|
|
d = calculate_constant_constant_resolution(base_resolution, ratio)
|
|
else:
|
|
d = calculate_aspect_ratio(base_resolution, ratio, overextend)
|
|
|
|
return (d.width, d.height)
|
|
|
|
|
|
NODE_CLASS_MAPPINGS = {"YARS": YARS, "YARSAdv": YARSAdv}
|
|
NODE_DISPLAY_NAME_MAPPINGS = {
|
|
"YARS": "yaResolution Selector",
|
|
"YARSAdv": "yaResolution Selector (Advanced)",
|
|
}
|
|
|
|
if __name__ == "__main__":
|
|
import unittest
|
|
|
|
class TestDimensions(unittest.TestCase):
|
|
def test_constant_resolution_calculates_correct_dimensions(self):
|
|
d = calculate_constant_constant_resolution(1024, 1)
|
|
self.assertEqual((d.width, d.height), (1024, 1024), "Incorrect dimensions")
|
|
|
|
def test_constant_resolution_calculates_correct_aspect_ratio(self):
|
|
d = calculate_constant_constant_resolution(1024, 4 / 3)
|
|
self.assertAlmostEqual(
|
|
d.height / d.width, 4 / 3, 2, "Incorrect aspect ratio"
|
|
)
|
|
|
|
def test_constant_resolution_calculates_correct_pixel_count(self):
|
|
d = calculate_constant_constant_resolution(1024, 4 / 3)
|
|
self.assertEqual(d.width * d.height, 1047252, "Incorrect pixel count")
|
|
|
|
def test_calculate_dimensions_with_false_overextend(self):
|
|
d = calculate_aspect_ratio(1024, 4 / 3, False)
|
|
self.assertEqual(
|
|
(d.width, d.height),
|
|
(768, 1024),
|
|
"Incorrect dimensions without overextending",
|
|
)
|
|
|
|
def test_calculate_dimensions_with_true_overextend(self):
|
|
d = calculate_aspect_ratio(512, 4 / 3, True)
|
|
self.assertEqual(
|
|
(d.width, d.height),
|
|
(512, 682),
|
|
"Incorrect dimensions with overextending",
|
|
)
|
|
|
|
def test_cleanup_dimensions_raises_error_with_low_height(self):
|
|
with self.assertRaises(
|
|
ValueError, msg="No error raised with height less than 64 pixels"
|
|
):
|
|
Dimensions(512, 60)
|
|
|
|
def test_cleanup_dimensions_raises_error_with_low_width(self):
|
|
with self.assertRaises(
|
|
ValueError, msg="No error raised with width less than 64 pixels"
|
|
):
|
|
Dimensions(55, 512)
|
|
|
|
unittest.main()
|