From e035ed905f6a86f4f98bb6e2b436660a88e1e748 Mon Sep 17 00:00:00 2001 From: EnragedAntelope <176548309+EnragedAntelope@users.noreply.github.com> Date: Sat, 26 Jul 2025 07:44:49 -0400 Subject: [PATCH] Update nodes.py --- nodes.py | 157 ++++++++++++++++++------------------------------------- 1 file changed, 52 insertions(+), 105 deletions(-) diff --git a/nodes.py b/nodes.py index 272fe48..216c140 100644 --- a/nodes.py +++ b/nodes.py @@ -15,6 +15,10 @@ class ConstrainResolution: "image": ("IMAGE", { "tooltip": "Input image to analyze for resolution constraints" }), + "constraint_mode": (["Prioritize Min Resolution", "Prioritize Max Resolution (Strict)"], { + "default": "Prioritize Min Resolution", + "tooltip": "Choose how to handle conflicting constraints for extreme aspect ratios" + }), "min_res": ("INT", { "default": 704, "min": 1, @@ -26,9 +30,9 @@ class ConstrainResolution: "tooltip": "Maximum resolution in pixels for both width and height" }), "multiple_of": ("INT", { - "default": 2, + "default": 8, "min": 1, - "tooltip": "Ensure dimensions are multiples of this number (e.g., 8 for SDXL, 16 for some models)" + "tooltip": "Ensure dimensions are multiples of this number (e.g., 8 for SDXL)" }), } } @@ -36,132 +40,75 @@ class ConstrainResolution: RETURN_TYPES = ("IMAGE", "INT", "INT", "FLOAT", "FLOAT") RETURN_NAMES = ("image_passthrough", "constrained_width", "constrained_height", "constrained_aspect_ratio", "original_aspect_ratio") - # Add tooltips for outputs - RETURN_TYPES_TOOLTIPS = { - "image_passthrough": "The unchanged input image (passed through)", - "constrained_width": "Suggested width that fits within min/max constraints while preserving aspect ratio", - "constrained_height": "Suggested height that fits within min/max constraints while preserving aspect ratio", - "constrained_aspect_ratio": "The aspect ratio of the constrained dimensions", - "original_aspect_ratio": "The original aspect ratio of the input image" - } - FUNCTION = "process" CATEGORY = "image/resolution" @staticmethod def calculate_aspect_ratio(width: int, height: int) -> float: + if height == 0: + return 0.0 return round(width / height, 4) @staticmethod def round_to_multiple(value: int, multiple: int) -> int: - """Round a value up to the nearest multiple of the specified number""" - return ((value + multiple - 1) // multiple) * multiple + """Round a value to the nearest multiple of the specified number""" + return multiple * round(value / multiple) @staticmethod - def calculate_optimal_dimensions(width: int, height: int, min_res: int, max_res: int, multiple_of: int) -> Tuple[int, int]: + def calculate_optimal_dimensions(width: int, height: int, min_res: int, max_res: int, multiple_of: int, constraint_mode: str) -> Tuple[int, int]: + if height == 0 or width == 0: + return 0, 0 + aspect_ratio = width / height - - # Calculate maximum allowed pixels based on max_res x min_res - max_pixels = max_res * min_res - - # Adjust min_res and max_res to be multiples of multiple_of - min_res = ConstrainResolution.round_to_multiple(min_res, multiple_of) - max_res = (max_res // multiple_of) * multiple_of # Round down max_res - - if abs(aspect_ratio - 1.0) < 0.01: # Square image (with small tolerance) - # Calculate optimal square dimension based on max pixels - optimal_dim = int(np.sqrt(max_pixels)) - # Round up to nearest multiple - optimal_dim = ConstrainResolution.round_to_multiple(optimal_dim, multiple_of) - # Ensure it's within min/max bounds - optimal_dim = min(max(optimal_dim, min_res), max_res) - return optimal_dim, optimal_dim - - if aspect_ratio > 1: # Landscape - # First try to set width to max_res - width = max_res - height = int(width / aspect_ratio) - height = ConstrainResolution.round_to_multiple(height, multiple_of) - - # If total pixels exceed max_pixels, scale down proportionally - if width * height > max_pixels: - width = int(np.sqrt(max_pixels * aspect_ratio)) - width = ConstrainResolution.round_to_multiple(width, multiple_of) - height = int(width / aspect_ratio) - height = ConstrainResolution.round_to_multiple(height, multiple_of) - - # Check minimum bounds - if height < min_res: - height = min_res - width = int(height * aspect_ratio) - width = ConstrainResolution.round_to_multiple(width, multiple_of) + + # 1. Initial scaling to fit the longest side to max_res + if aspect_ratio >= 1: # Landscape or square + new_width = max_res + new_height = new_width / aspect_ratio else: # Portrait - # First try to set height to max_res - height = max_res - width = int(height * aspect_ratio) - width = ConstrainResolution.round_to_multiple(width, multiple_of) + new_height = max_res + new_width = new_height * aspect_ratio + + # 2. Apply constraint logic based on user's choice + if constraint_mode == "Prioritize Min Resolution": + # If a dimension is below min_res, scale the entire image up to meet it + scale_factor = 1.0 + if new_width < min_res: + scale_factor = max(scale_factor, min_res / new_width) + if new_height < min_res: + scale_factor = max(scale_factor, min_res / new_height) - # If total pixels exceed max_pixels, scale down proportionally - if width * height > max_pixels: - height = int(np.sqrt(max_pixels / aspect_ratio)) - height = ConstrainResolution.round_to_multiple(height, multiple_of) - width = int(height * aspect_ratio) - width = ConstrainResolution.round_to_multiple(width, multiple_of) - - # Check minimum bounds - if width < min_res: - width = min_res - height = int(width / aspect_ratio) - height = ConstrainResolution.round_to_multiple(height, multiple_of) + new_width *= scale_factor + new_height *= scale_factor + + # If mode is "Prioritize Max Resolution (Strict)", we do nothing here. + # The initial scaling already guarantees we are within the max_res bounds, + # and we accept that a dimension may fall below min_res. - # Final bounds check - width = min(max(width, min_res), max_res) - width = ConstrainResolution.round_to_multiple(width, multiple_of) - height = min(max(height, min_res), max_res) - height = ConstrainResolution.round_to_multiple(height, multiple_of) - - # Final pixel count check (just in case rounding pushed us over) - while width * height > max_pixels: - if width > height: - width = width - multiple_of # Reduce by multiple_of - else: - height = height - multiple_of - - # Final multiple check (just to be absolutely certain) - width = ConstrainResolution.round_to_multiple(width, multiple_of) - height = ConstrainResolution.round_to_multiple(height, multiple_of) - - return width, height + # 3. Round final dimensions to the nearest multiple + final_width = ConstrainResolution.round_to_multiple(int(new_width), multiple_of) + final_height = ConstrainResolution.round_to_multiple(int(new_height), multiple_of) - def process(self, image, min_res, max_res, multiple_of): - # Input validation - if max_res <= min_res: - raise ValueError(f"max_res ({max_res}) must be greater than min_res ({min_res})") + return final_width, final_height + + def process(self, image, constraint_mode, min_res, max_res, multiple_of): + if max_res < min_res: + raise ValueError(f"max_res ({max_res}) must be greater than or equal to min_res ({min_res})") - # Get image dimensions - if len(image.shape) == 4: - height = image.shape[1] - width = image.shape[2] - else: - height = image.shape[0] - width = image.shape[1] + height = image.shape[1] + width = image.shape[2] - # Calculate original aspect ratio original_aspect_ratio = self.calculate_aspect_ratio(width, height) - # Calculate optimal dimensions - suggested_width, suggested_height = self.calculate_optimal_dimensions(width, height, min_res, max_res, multiple_of) + suggested_width, suggested_height = self.calculate_optimal_dimensions(width, height, min_res, max_res, multiple_of, constraint_mode) - # Calculate final aspect ratio final_aspect_ratio = self.calculate_aspect_ratio(suggested_width, suggested_height) - # Calculate aspect ratio deviation (as a percentage) - aspect_ratio_deviation = abs((final_aspect_ratio - original_aspect_ratio) / original_aspect_ratio * 100) + if original_aspect_ratio > 0: + aspect_ratio_deviation = abs((final_aspect_ratio - original_aspect_ratio) / original_aspect_ratio * 100) + if aspect_ratio_deviation > 1: # 1% tolerance for rounding + print(f"Warning: Aspect ratio deviation of {aspect_ratio_deviation:.2f}% detected due to rounding.") - # Optional: Add warnings for significant changes - if aspect_ratio_deviation > 10: # More than 10% deviation - print(f"Warning: Significant aspect ratio change: {aspect_ratio_deviation:.1f}% deviation from original") - return (image, suggested_width, suggested_height, final_aspect_ratio, original_aspect_ratio) NODE_CLASS_MAPPINGS = { @@ -170,4 +117,4 @@ NODE_CLASS_MAPPINGS = { NODE_DISPLAY_NAME_MAPPINGS = { "ConstrainResolution": "Constrain Resolution" -} +}