diff --git a/__init__.py b/__init__.py
index d80379d..9760949 100644
--- a/__init__.py
+++ b/__init__.py
@@ -6,11 +6,13 @@ from .nodes import *
NODE_CLASS_MAPPINGS = {
"BestResolutionFromArea": BestResolutionFromArea,
+ "BestResolutionFromAreaUpscale": BestResolutionFromAreaUpscale,
"BestResolutionFromAspectRatio": BestResolutionFromAspectRatio,
"BestResolutionSimple": BestResolutionSimple,
}
NODE_DISPLAY_NAME_MAPPINGS = {
"BestResolutionFromArea": "Best-Res (area)",
+ "BestResolutionFromAreaUpscale": "Best-Res (area+scale)",
"BestResolutionFromAspectRatio": "Best-Res (ratio)",
"BestResolutionSimple": "Best-Res (simple)",
}
diff --git a/_funcs.py b/_funcs.py
index 53704ee..1fe93c5 100644
--- a/_funcs.py
+++ b/_funcs.py
@@ -10,6 +10,8 @@ from math import sqrt as _sqrt
from server import PromptServer as _PromptServer
+from .enums import *
+
_t_number = _t.Union[int, float]
@@ -120,7 +122,7 @@ def _format_report_square_part(
return f"~={actual_square_side_f:.2f}🔳"
-def format_report(
+def format_report_simple(
width_f: float, height_f: _t_number, step: int,
width: int, n_steps_x: int, height: int, n_steps_y: int,
target_square_size: _t_number = None
@@ -143,22 +145,100 @@ def format_report(
def simple_result_from_approx_wh(
- width_f: float, height_f: _t_number, step: int, show: bool = True,
+ width_f: float, height_f: _t_number, step: int,
+ show: bool = True,
unique_id: str = None, target_square_size: _t_number = None
):
- """Final part of the main func for simple (non-upscale) nodes - when desired float/height are already calculated"""
+ """Final part of the main func for simple (non-upscale) nodes - when desired width/height are already calculated."""
step = number_to_int(step)
width, n_steps_x, height, n_steps_y = round_width_and_height_closest_to_the_ratio(width_f, height_f, step)
- if unique_id:
- text = (
- format_report(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
- )
- # 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)
+ result = (width, height)
- return width, height
+ if not unique_id:
+ return result
+
+ 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
+ )
+ # 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)
+
+ return result
+
+
+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,
+ show: bool = True,
+ unique_id: str = None, target_square_size: _t_number = None
+):
+ """Primary part of the main func for nodes with upscaling - when desired initial-width/height are already calculated."""
+ upscale = max(float(upscale), 1.0)
+ up_width_f: float = upscale * width_f
+ up_height_f: float = upscale * height_f
+ width_f = float(width_f)
+
+ step = number_to_int(step)
+ up_step = number_to_int(up_step)
+
+ if priority == RoundingPriority.DESIRED:
+ width, n_steps_x, height, n_steps_y = round_width_and_height_closest_to_the_ratio(width_f, height_f, step)
+ up_width, up_steps_x, up_height, up_steps_y = round_width_and_height_closest_to_the_ratio(
+ up_width_f, up_height_f, up_step
+ )
+ elif priority == RoundingPriority.ORIGINAL:
+ width, n_steps_x, height, n_steps_y = round_width_and_height_closest_to_the_ratio(width_f, height_f, step)
+ up_width, up_steps_x, up_height, up_steps_y = round_width_and_height_closest_to_the_ratio(
+ upscale * width, upscale * height, up_step
+ )
+ else:
+ up_width, up_steps_x, up_height, up_steps_y = round_width_and_height_closest_to_the_ratio(
+ up_width_f, up_height_f, up_step
+ )
+ width, n_steps_x, height, n_steps_y = round_width_and_height_closest_to_the_ratio(
+ float(up_width) / upscale, float(up_height) / upscale, step
+ )
+
+ result = (width, height, up_width, up_height)
+
+ 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
+
+ # 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)
+
+ return result
diff --git a/enums.py b/enums.py
new file mode 100644
index 0000000..3fb70af
--- /dev/null
+++ b/enums.py
@@ -0,0 +1,18 @@
+# encoding: utf-8
+"""
+Internal enums.
+"""
+
+import typing as _t
+
+from enum import StrEnum as _StrEnum
+
+
+class RoundingPriority(_StrEnum):
+ DESIRED = 'desired'
+ ORIGINAL = 'original'
+ UPSCALED = 'upscaled'
+
+ @classmethod
+ def all_values(cls) -> _t.Tuple[str, ...]:
+ return tuple(x.value for x in cls)
diff --git a/nodes.py b/nodes.py
index 9641d92..8a0dfba 100644
--- a/nodes.py
+++ b/nodes.py
@@ -9,16 +9,18 @@ from inspect import cleandoc as _cleandoc
from math import sqrt as _sqrt
import sys as _sys
-from frozendict import deepfreeze
+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,
+ float_width_height_from_area as _float_width_height_from_area,
simple_result_from_approx_wh as _simple_result_from_approx_wh,
- float_width_height_from_area as _float_width_height_from_area
+ upscale_result_from_approx_wh as _upscale_result_from_approx_wh
)
@@ -235,3 +237,99 @@ 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
+ )