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.

+> [!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"}
]