From 28dcce0bc4c899073ab4bc0580c0acfb578a96a3 Mon Sep 17 00:00:00 2001 From: "Lex Darlog (DRL)" <3897975+Lex-DRL@users.noreply.github.com> Date: Sun, 13 Jul 2025 14:31:16 +0500 Subject: [PATCH 1/6] README: info/alert block --- README.md | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index 18429b9..ac79517 100644 --- a/README.md +++ b/README.md @@ -4,13 +4,16 @@ # `Best Resolution` nodes for ComfyUI -A small modular pack - QoL nodes for semi-automatic calculation of the best (most optimal) sampling resolution: -- ... model-agnostic - i.e., compatible with **ANY** model (from now **or** the future), +A small modular pack — QoL nodes for semi-automatic calculation of the best (most optimal) sampling resolution: +- ... model-agnostic — i.e., compatible with **ANY** model (from now **or** the future), - ... accounting for upscale, - ... and for (the essential) pixel-step. ![image](img/screenshot1.png) +> [!IMPORTANT] +> Nodes from this pack do **nothing** with actual images on their own — they only calculate the optimal **values** for the resolution. + Read any further only if you feel you need to. ## The purpose From 0e39962dd38d0fe57e4eaa77068de1e44efa1a2b Mon Sep 17 00:00:00 2001 From: "Lex Darlog (DRL)" <3897975+Lex-DRL@users.noreply.github.com> Date: Sun, 13 Jul 2025 13:12:48 +0500 Subject: [PATCH 2/6] enums: `StrEnum` fallback import --- enums.py | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/enums.py b/enums.py index 3fb70af..99b4871 100644 --- a/enums.py +++ b/enums.py @@ -5,7 +5,10 @@ Internal enums. import typing as _t -from enum import StrEnum as _StrEnum +try: + from enum import StrEnum as _StrEnum +except ImportError: + from comfy.comfy_types.node_typing import StrEnum as _StrEnum class RoundingPriority(_StrEnum): From ffd787a411800963ef442e7c141fc744ac5aafe1 Mon Sep 17 00:00:00 2001 From: "Lex Darlog (DRL)" <3897975+Lex-DRL@users.noreply.github.com> Date: Sun, 13 Jul 2025 13:14:27 +0500 Subject: [PATCH 3/6] ren: `nodes` -> `nodes_simple` --- __init__.py | 2 +- nodes.py => nodes_simple.py | 0 2 files changed, 1 insertion(+), 1 deletion(-) rename nodes.py => nodes_simple.py (100%) diff --git a/__init__.py b/__init__.py index 9760949..0769a4b 100644 --- a/__init__.py +++ b/__init__.py @@ -2,7 +2,7 @@ """ """ -from .nodes import * +from .nodes_simple import * NODE_CLASS_MAPPINGS = { "BestResolutionFromArea": BestResolutionFromArea, diff --git a/nodes.py b/nodes_simple.py similarity index 100% rename from nodes.py rename to nodes_simple.py From ecb6f5f2920ef327f3ffcfe420de78faddc742f6 Mon Sep 17 00:00:00 2001 From: "Lex Darlog (DRL)" <3897975+Lex-DRL@users.noreply.github.com> Date: Sun, 13 Jul 2025 13:25:52 +0500 Subject: [PATCH 4/6] `nodes_upscale` extracted --- __init__.py | 1 + nodes_simple.py | 107 +++-------------------------------------- nodes_upscale.py | 122 +++++++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 130 insertions(+), 100 deletions(-) create mode 100644 nodes_upscale.py diff --git a/__init__.py b/__init__.py index 0769a4b..480d6ec 100644 --- a/__init__.py +++ b/__init__.py @@ -3,6 +3,7 @@ """ from .nodes_simple import * +from .nodes_upscale import * NODE_CLASS_MAPPINGS = { "BestResolutionFromArea": BestResolutionFromArea, diff --git a/nodes_simple.py b/nodes_simple.py index 8a0dfba..af4a442 100644 --- a/nodes_simple.py +++ b/nodes_simple.py @@ -1,6 +1,6 @@ # encoding: utf-8 """ -The actual nodes defined by the package. +Simple (aka no-upscale) versions of nodes. """ import typing as _t @@ -13,8 +13,6 @@ from frozendict import deepfreeze, frozendict from comfy.comfy_types.node_typing import IO as _IO -from .node_types import * -from .enums import * from ._funcs import ( aspect_ratios_sorted as _aspect_ratios_sorted, number_to_int as _number_to_int, @@ -22,9 +20,12 @@ from ._funcs import ( simple_result_from_approx_wh as _simple_result_from_approx_wh, upscale_result_from_approx_wh as _upscale_result_from_approx_wh ) +from .enums import * +from .node_types import * +# ---------------------------------------------------------- -# Tiny optimization by reusing the same immutable dict: +# A tiny optimization by reusing the same immutable dict: _input_types_simple = deepfreeze({ 'required': { 'width': (_IO.INT, dict(type_dict_res, **{'tooltip': "Approximate width"})), @@ -88,6 +89,7 @@ class BestResolutionSimple: unique_id=unique_id, target_square_size=_sqrt(width * height) ) +# ---------------------------------------------------------- _tooltip_aspect = ( "Two aspects together define an aspect ratio (16:9, 4:3, etc).\n" @@ -173,6 +175,7 @@ class BestResolutionFromAspectRatio: unique_id=unique_id, target_square_size=_sqrt(width_f * height_f) ) +# ---------------------------------------------------------- _input_types_area = deepfreeze({ 'required': { @@ -237,99 +240,3 @@ class BestResolutionFromArea: # show, unique_id=unique_id, target_square_size=square_size ) - - -_return_types_upscale = (_IO.INT, _IO.INT, _IO.INT, _IO.INT) -_return_ttips_upscale = frozendict({ - 'orig_w': "Width for original image", - 'orig_h': "Height for original image", - 'up_w': "Width for upscaled image", - 'up_h': "Height for upscaled image" -}) -_return_names_upscale = tuple(_return_ttips_upscale.keys()) - - - - - -# __priority_type = (_IO.BOOLEAN, { -# 'default': True, 'label_on': 'upscaled', 'label_off': 'original', -# 'tooltip': ( -# "Which of the resolutions is more important to be as close to the desired as possible:\n\n" -# "When ON, the rounded with/height are first calculated for the UPSCALED resolution - and " -# "only then the original ones are back-tracked from them.\n" -# "When OFF, they're first calculated for the ORIGINAL resolution - and " -# "the upscaled ones are tracked forward from them." -# ) -# }) -__priority_type = ( - RoundingPriority.all_values(), - { - 'default': RoundingPriority.DESIRED.value, - 'tooltip': ( - "Defines what resolution to prioritize, as well as which order to perform rounding in:\n\n" - "• desired - first, approximate resolutions are calculated for both initial and upscaled size; " - "then, both are rounded. In both cases, the rounded resolution is closest to the desired one, " - "but aspect ratio might differ the most between sizes.\n\n" - "• original - first, initial resolution is calculated and rounded; then, upscaled one is detected from it. " - "Upscaled resolution might differ the most from the desired aspect ratio, but it follows the ratio from " - "initial size as much as possible.\n\n" - "• upscaled - vice versa: first, the rounded upscaled resolution is calculated; then, the initial one " - "back-tracked from it." - ) - } -) -__extra_inputs_for_upscale_only = { - 'priority': __priority_type, - 'upscale': (_IO.FLOAT, { - 'default': 1.5, 'min': 1.0, 'max': _sys.float_info.max, 'step': 0.25, 'round': 0.001, - # 'tooltip': "", # TODO - }), - 'up_step': (_IO.INT, dict(type_dict_step_upscale1, **{ - 'tooltip': "Same as the main `step`, but for the upscaled resolution.", - })), -} - -_input_types_area_upscale = deepfreeze({ - 'required': dict( - (k_v for k_v in _input_types_area['required'].items() if k_v[0] != 'show'), - **__extra_inputs_for_upscale_only, - # show=_input_types_area['required']['show'], - ), - 'hidden': { - 'unique_id': 'UNIQUE_ID', - }, - # 'optional': {}, -}) - - -class BestResolutionFromAreaUpscale: - NODE_NAME = 'BestResolutionFromAreaUpscale' - CATEGORY = "utils/resolution" - DESCRIPTION = _cleandoc(__doc__) - - OUTPUT_NODE = True - - FUNCTION = 'main' - RETURN_TYPES = _return_types_upscale - RETURN_NAMES = _return_names_upscale - RETURN_TYPES_TOOLTIPS = _return_ttips_upscale - - @classmethod - def INPUT_TYPES(cls): - return _input_types_area_upscale - - def main( - self, square_size: int, step: int, landscape: bool, aspect_a: float, aspect_b: float, - priority: _t.Union[RoundingPriority, str], upscale: float, up_step:int, - # show: bool, - unique_id: str = None - ): - square_size: int = _number_to_int(square_size) - width_f, height_f = _float_width_height_from_area(square_size, landscape, aspect_a, aspect_b) - return _upscale_result_from_approx_wh( - width_f, height_f, step, - priority, upscale, up_step, - # show, - unique_id=unique_id, target_square_size=square_size - ) diff --git a/nodes_upscale.py b/nodes_upscale.py new file mode 100644 index 0000000..0c03198 --- /dev/null +++ b/nodes_upscale.py @@ -0,0 +1,122 @@ +# encoding: utf-8 +""" +Advanced versions of nodes - with upscale. +""" + +import typing as _t + +from inspect import cleandoc as _cleandoc +from math import sqrt as _sqrt +import sys as _sys + +from frozendict import deepfreeze, frozendict + +from comfy.comfy_types.node_typing import IO as _IO + +from ._funcs import ( + aspect_ratios_sorted as _aspect_ratios_sorted, + number_to_int as _number_to_int, + float_width_height_from_area as _float_width_height_from_area, + simple_result_from_approx_wh as _simple_result_from_approx_wh, + upscale_result_from_approx_wh as _upscale_result_from_approx_wh +) +from .enums import * +from .node_types import * +from .nodes_simple import _input_types_area + +# ---------------------------------------------------------- + +_return_types_upscale = (_IO.INT, _IO.INT, _IO.INT, _IO.INT) +_return_ttips_upscale = frozendict({ + 'orig_w': "Width for original image", + 'orig_h': "Height for original image", + 'up_w': "Width for upscaled image", + 'up_h': "Height for upscaled image" +}) +_return_names_upscale = tuple(_return_ttips_upscale.keys()) + + + + + +# __priority_type = (_IO.BOOLEAN, { +# 'default': True, 'label_on': 'upscaled', 'label_off': 'original', +# 'tooltip': ( +# "Which of the resolutions is more important to be as close to the desired as possible:\n\n" +# "When ON, the rounded with/height are first calculated for the UPSCALED resolution - and " +# "only then the original ones are back-tracked from them.\n" +# "When OFF, they're first calculated for the ORIGINAL resolution - and " +# "the upscaled ones are tracked forward from them." +# ) +# }) +__priority_type = ( + RoundingPriority.all_values(), + { + 'default': RoundingPriority.DESIRED.value, + 'tooltip': ( + "Defines what resolution to prioritize, as well as which order to perform rounding in:\n\n" + "• desired - first, approximate resolutions are calculated for both initial and upscaled size; " + "then, both are rounded. In both cases, the rounded resolution is closest to the desired one, " + "but aspect ratio might differ the most between sizes.\n\n" + "• original - first, initial resolution is calculated and rounded; then, upscaled one is detected from it. " + "Upscaled resolution might differ the most from the desired aspect ratio, but it follows the ratio from " + "initial size as much as possible.\n\n" + "• upscaled - vice versa: first, the rounded upscaled resolution is calculated; then, the initial one " + "back-tracked from it." + ) + } +) +__extra_inputs_for_upscale_only = { + 'priority': __priority_type, + 'upscale': (_IO.FLOAT, { + 'default': 1.5, 'min': 1.0, 'max': _sys.float_info.max, 'step': 0.25, 'round': 0.001, + # 'tooltip': "", # TODO + }), + 'up_step': (_IO.INT, dict(type_dict_step_upscale1, **{ + 'tooltip': "Same as the main `step`, but for the upscaled resolution.", + })), +} + +_input_types_area_upscale = deepfreeze({ + 'required': dict( + (k_v for k_v in _input_types_area['required'].items() if k_v[0] != 'show'), + **__extra_inputs_for_upscale_only, + # show=_input_types_area['required']['show'], + ), + 'hidden': { + 'unique_id': 'UNIQUE_ID', + }, + # 'optional': {}, +}) + + +class BestResolutionFromAreaUpscale: + NODE_NAME = 'BestResolutionFromAreaUpscale' + CATEGORY = "utils/resolution" + DESCRIPTION = _cleandoc(__doc__) + + OUTPUT_NODE = True + + FUNCTION = 'main' + RETURN_TYPES = _return_types_upscale + RETURN_NAMES = _return_names_upscale + RETURN_TYPES_TOOLTIPS = _return_ttips_upscale + + @classmethod + def INPUT_TYPES(cls): + return _input_types_area_upscale + + def main( + self, square_size: int, step: int, landscape: bool, aspect_a: float, aspect_b: float, + priority: _t.Union[RoundingPriority, str], upscale: float, up_step:int, + # show: bool, + unique_id: str = None + ): + square_size: int = _number_to_int(square_size) + width_f, height_f = _float_width_height_from_area(square_size, landscape, aspect_a, aspect_b) + return _upscale_result_from_approx_wh( + width_f, height_f, step, + priority, upscale, up_step, + # show, + unique_id=unique_id, target_square_size=square_size + ) From d7d4dab444c2328477458b640ea7d009e47ba637 Mon Sep 17 00:00:00 2001 From: "Lex Darlog (DRL)" <3897975+Lex-DRL@users.noreply.github.com> Date: Sun, 13 Jul 2025 14:34:17 +0500 Subject: [PATCH 5/6] `BestResolutionFromAreaUpscale`: additional outputs --- _funcs.py | 93 +++++++++++++++++++++++++++++------------------- nodes_simple.py | 24 ++++++------- nodes_upscale.py | 53 ++++++++++++++++++--------- 3 files changed, 103 insertions(+), 67 deletions(-) diff --git a/_funcs.py b/_funcs.py index 1fe93c5..f442baf 100644 --- a/_funcs.py +++ b/_funcs.py @@ -92,6 +92,17 @@ def float_width_height_from_area(square_size: _t_number, landscape: bool, aspect return width_f, height_f +def _show_text_on_node(text: str = None, unique_id: str = None): + if not text: + # TODO: Planned for the future - currently, there's no point removing the text since it's box is shown anyway + # An odd workaround since `send_progress_text()` doesn't want to update text when '' passed + text = '' + # print(f"{unique_id} text: {text!r}") + + # Snatched from: https://github.com/comfyanonymous/ComfyUI/blob/27870ec3c30e56be9707d89a120eb7f0e2836be1/comfy_extras/nodes_images.py#L581-L582 + _PromptServer.instance.send_progress_text(text, unique_id) + + def _format_report_square_part( width_f: float, height_f: _t_number, width: int, height: int, @@ -161,16 +172,29 @@ def simple_result_from_approx_wh( text = ( format_report_simple(width_f, height_f, step, width, n_steps_x, height, n_steps_y, target_square_size) if show - # TODO: Planned for the future - currently, there's no point removing the text since it's box is shown anyway - else '' # An odd workaround since `send_progress_text()` doesn't want to update text when '' passed + else None ) - # print(f"{unique_id} text: {text!r}") - # Snatched from: https://github.com/comfyanonymous/ComfyUI/blob/27870ec3c30e56be9707d89a120eb7f0e2836be1/comfy_extras/nodes_images.py#L581-L582 - _PromptServer.instance.send_progress_text(text, unique_id) - + _show_text_on_node(text, unique_id) return result +def _need_post_resize(width: int, height: int, up_width: int, up_height: int): + """ + Detect whether the final init-res CAN NOT be uniformly scaled to the higher res, + and thus would require cropping/out-painting. + """ + real_upscale_x: float = float(up_width) / width + real_upscale_y: float = float(up_height) / height + real_upscale_avg: float = (real_upscale_x + real_upscale_y) * 0.5 + + needs_resize: bool = ( + round_pos_int(real_upscale_avg * width) != up_width or + round_pos_int(real_upscale_avg * height) != up_height + ) + return needs_resize, real_upscale_avg, real_upscale_x, real_upscale_y + + + def upscale_result_from_approx_wh( width_f: float, height_f: _t_number, step: int, priority: _t.Union[RoundingPriority, str], upscale: float, up_step:int, @@ -204,41 +228,36 @@ def upscale_result_from_approx_wh( float(up_width) / upscale, float(up_height) / upscale, step ) - result = (width, height, up_width, up_height) + needs_resize, real_upscale_avg, real_upscale_x, real_upscale_y = _need_post_resize(width, height, up_width, up_height) + out_upscale: float = upscale if needs_resize else real_upscale_avg + + result = (width, height, out_upscale, up_width, up_height, needs_resize) if not unique_id: return result - # TODO: Planned for the future - currently, there's no point removing the text since it's box is shown anyway - text = '' # An odd workaround since `send_progress_text()` doesn't want to update text when '' passed - if show: - reports: _t.List[str] = list() - for prefix, w_f, h_f, s, w, n_x, h, n_y, trg_sq in [ - ('◻️ ', width_f, height_f, step, width, n_steps_x, height, n_steps_y, target_square_size), - ('🔲 ', up_width_f, up_height_f, up_step, up_width, up_steps_x, up_height, up_steps_y, _sqrt(up_width_f * up_height_f)) - ]: - cur_report = format_report_simple(w_f, h_f, s, w, n_x, h, n_y, trg_sq) - reports.append('\n'.join( - f"{prefix}{x}" for x in cur_report.split('\n') - )) - real_upscale_x = float(up_width) / width - real_upscale_y = float(up_height) / height + if not show: + _show_text_on_node(None, unique_id) + return result - # Pixel size relative to the upscaled image: - up_half_pixel_x = 0.5 / up_width - up_half_pixel_y = 0.5 / up_height - # Threshold for upscale-multiplier to be considered the same res: - almost_equal_rel_delta_x = real_upscale_x * up_half_pixel_x - almost_equal_rel_delta_y = real_upscale_y * up_half_pixel_y - almost_equal_rel_delta = max(almost_equal_rel_delta_x, almost_equal_rel_delta_y) - if abs(real_upscale_x - real_upscale_y) < almost_equal_rel_delta: - # The x/y delta between upscale multipliers is below a threshold to contribute even 1 pixel. - # So, basically, the upscale is uniform: - divider_line = f"\n--- x{real_upscale_x:.3f} ---\n" - else: - divider_line = f"\n--- ⚠️ x{real_upscale_x:.3f} / x{real_upscale_y:.3f} ---\n" - text = divider_line.join(reports) - # Snatched from: https://github.com/comfyanonymous/ComfyUI/blob/27870ec3c30e56be9707d89a120eb7f0e2836be1/comfy_extras/nodes_images.py#L581-L582 - _PromptServer.instance.send_progress_text(text, unique_id) + report_parts: _t.List[str] = list() + for prefix, w_f, h_f, s, w, n_x, h, n_y, trg_sq in [ + ('◻️ ', width_f, height_f, step, width, n_steps_x, height, n_steps_y, target_square_size), + ('🔲 ', up_width_f, up_height_f, up_step, up_width, up_steps_x, up_height, up_steps_y, _sqrt(up_width_f * up_height_f)) + ]: + cur_report = format_report_simple(w_f, h_f, s, w, n_x, h, n_y, trg_sq) + report_parts.append('\n'.join( + f"{prefix}{x}" for x in cur_report.split('\n') + )) + separator_line: str = ( + # Real upscale factor in X and Y differ enough so we can't get the up-res from init-res with ANY uniform value: + f"\n--- ⚠️ x{real_upscale_x:.3f} / x{real_upscale_y:.3f} ---\n" + if needs_resize + # The upscale is uniform: + else f"\n--- x{real_upscale_x:.3f} ---\n" + ) + text = separator_line.join(report_parts) + + _show_text_on_node(text, unique_id) return result diff --git a/nodes_simple.py b/nodes_simple.py index af4a442..1418ae9 100644 --- a/nodes_simple.py +++ b/nodes_simple.py @@ -9,7 +9,7 @@ from inspect import cleandoc as _cleandoc from math import sqrt as _sqrt import sys as _sys -from frozendict import deepfreeze, frozendict +from frozendict import deepfreeze as _deepfreeze from comfy.comfy_types.node_typing import IO as _IO @@ -17,16 +17,19 @@ from ._funcs import ( aspect_ratios_sorted as _aspect_ratios_sorted, number_to_int as _number_to_int, float_width_height_from_area as _float_width_height_from_area, - simple_result_from_approx_wh as _simple_result_from_approx_wh, - upscale_result_from_approx_wh as _upscale_result_from_approx_wh + simple_result_from_approx_wh as _simple_result_from_approx_wh ) -from .enums import * from .node_types import * # ---------------------------------------------------------- +# _return_types_simple = (_IO.INT, _IO.INT, _IO.STRING) +# _return_names_simple = ('width', 'height', 'report') +_return_types_simple = (_IO.INT, _IO.INT) +_return_names_simple = ('width', 'height') + # A tiny optimization by reusing the same immutable dict: -_input_types_simple = deepfreeze({ +_input_types_simple = _deepfreeze({ 'required': { 'width': (_IO.INT, dict(type_dict_res, **{'tooltip': "Approximate width"})), 'height': (_IO.INT, dict(type_dict_res, **{'tooltip': "Approximate height"})), @@ -52,11 +55,6 @@ _input_types_simple = deepfreeze({ # 'optional': {}, }) -# _return_types_simple = (_IO.INT, _IO.INT, _IO.STRING) -# _return_names_simple = ('width', 'height', 'report') -_return_types_simple = (_IO.INT, _IO.INT) -_return_names_simple = ('width', 'height') - class BestResolutionSimple: """ @@ -71,7 +69,7 @@ class BestResolutionSimple: FUNCTION = 'main' RETURN_TYPES = _return_types_simple - # RETURN_TYPES_TOOLTIPS = {} + # OUTPUT_TOOLTIPS = {} RETURN_NAMES = _return_names_simple @classmethod @@ -97,7 +95,7 @@ _tooltip_aspect = ( "The specified aspect ratio is APPROXIMATE: step parameter has priority over the exact image proportions." ) -_input_types_orient = deepfreeze({ +_input_types_orient = _deepfreeze({ 'required': { 'size': (_IO.INT, dict(type_dict_res, **{'tooltip': ( "Approximate size of one of the image sides.\nWhich one - see the 'size_is_big' and 'landscape' toggles." @@ -177,7 +175,7 @@ class BestResolutionFromAspectRatio: # ---------------------------------------------------------- -_input_types_area = deepfreeze({ +_input_types_area = _deepfreeze({ 'required': { 'square_size': (_IO.INT, dict(type_dict_res, **{'tooltip': ( "The total resolution of the image would be the same as of a square with this side.\n" diff --git a/nodes_upscale.py b/nodes_upscale.py index 0c03198..28a4b69 100644 --- a/nodes_upscale.py +++ b/nodes_upscale.py @@ -6,10 +6,9 @@ Advanced versions of nodes - with upscale. import typing as _t from inspect import cleandoc as _cleandoc -from math import sqrt as _sqrt import sys as _sys -from frozendict import deepfreeze, frozendict +from frozendict import deepfreeze as _deepfreeze, frozendict as _frozendict from comfy.comfy_types.node_typing import IO as _IO @@ -17,7 +16,6 @@ from ._funcs import ( aspect_ratios_sorted as _aspect_ratios_sorted, number_to_int as _number_to_int, float_width_height_from_area as _float_width_height_from_area, - simple_result_from_approx_wh as _simple_result_from_approx_wh, upscale_result_from_approx_wh as _upscale_result_from_approx_wh ) from .enums import * @@ -26,18 +24,24 @@ from .nodes_simple import _input_types_area # ---------------------------------------------------------- -_return_types_upscale = (_IO.INT, _IO.INT, _IO.INT, _IO.INT) -_return_ttips_upscale = frozendict({ - 'orig_w': "Width for original image", - 'orig_h': "Height for original image", - 'up_w': "Width for upscaled image", - 'up_h': "Height for upscaled image" +_return_types_upscale = (_IO.INT, _IO.INT, _IO.FLOAT, _IO.INT, _IO.INT, _IO.BOOLEAN) +_return_ttips_upscale = _frozendict({ + 'orig_w': "Width for original/initial image", + 'orig_h': "Height for original/initial image", + 'upscale': ( + "If 'needs_resize' is FALSE, this would be the actual uniform value to scale your initial-res in order " + "to get the upscaled-res.\n" + "If 'needs_resize' is TRUE, this slot simply outputs the same upscale-value you've set on the node." + ), + 'up_w': "Width for the (main) upscaled image", + 'up_h': "Height for the (main) upscaled image", + 'needs_resize': ( + "This will be FALSE if you can get the exact upscaled resolution by simply scaling the initial-res uniformly " + "(by the value output into 'upscale' slot).\n\n" + "Otherwise, it will be TRUE — indicating that you'll need to do some cropping/out-painting right AFTER " + "the actual upscale (increasing the resolution itself) but BEFORE post-upscale sampling (\"HD-fix\")." + ), }) -_return_names_upscale = tuple(_return_ttips_upscale.keys()) - - - - # __priority_type = (_IO.BOOLEAN, { # 'default': True, 'label_on': 'upscaled', 'label_off': 'original', @@ -77,7 +81,7 @@ __extra_inputs_for_upscale_only = { })), } -_input_types_area_upscale = deepfreeze({ +_input_types_area_upscale = _deepfreeze({ 'required': dict( (k_v for k_v in _input_types_area['required'].items() if k_v[0] != 'show'), **__extra_inputs_for_upscale_only, @@ -91,6 +95,21 @@ _input_types_area_upscale = deepfreeze({ class BestResolutionFromAreaUpscale: + """ + The most efficient way of selecting an optimal resolution: + image size selected indirectly - by the total desired resolution (area) + aspect ratio... + + ... PLUS, account for the immediate upscale right away. + + Desired resolution (aka image area/megapixels/pixel count) is specified with a side of a square image. This isn't + accidental: most models disclose what image resolution they're trained on, and usually they're square: + + - SD 1.5 - 512x512 pixels + - SDXL - 1024x1024 pixels + + By simply providing this single number and setting your aspect ratio/orientation, you get the width and height to + produce the closest total resolution to the training set, while also respecting image proportions and step-rounding. + """ NODE_NAME = 'BestResolutionFromAreaUpscale' CATEGORY = "utils/resolution" DESCRIPTION = _cleandoc(__doc__) @@ -99,8 +118,8 @@ class BestResolutionFromAreaUpscale: FUNCTION = 'main' RETURN_TYPES = _return_types_upscale - RETURN_NAMES = _return_names_upscale - RETURN_TYPES_TOOLTIPS = _return_ttips_upscale + RETURN_NAMES = tuple(_return_ttips_upscale.keys()) + OUTPUT_TOOLTIPS = tuple(_return_ttips_upscale.values()) @classmethod def INPUT_TYPES(cls): From bcc9699e098be23ab5e8c806f9a3f5277cb90925 Mon Sep 17 00:00:00 2001 From: "Lex Darlog (DRL)" <3897975+Lex-DRL@users.noreply.github.com> Date: Sun, 13 Jul 2025 14:35:35 +0500 Subject: [PATCH 6/6] `v1.0.1` --- pyproject.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pyproject.toml b/pyproject.toml index 73cbae9..8d26e77 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,7 +1,7 @@ [project] name = "best-resolution" description = "QoL nodes for semi-automatic calculation of the best (most optimal) sampling resolution \n• compatible with ANY model (from now or the future), \n• accounting for upscale \n• and pixel-step." -version = "1.0.0" +version = "1.0.1" authors = [ {name = "Lex Darlog"} ]