Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
890e0a75ae | ||
|
|
3faac4a543 | ||
|
|
8df7e2144f | ||
|
|
9cf9684e39 | ||
|
|
6e1ae21e94 | ||
|
|
f5613e4652 | ||
|
|
0726a4624d | ||
|
|
5c984dbe5c | ||
|
|
4574de2d48 | ||
|
|
ccfaadaa69 | ||
|
|
88e4966b45 | ||
|
|
3bc40d6250 | ||
|
|
a263afbde5 | ||
|
|
94bd72207f | ||
|
|
3b63799c00 | ||
|
|
69e39e386d |
+12
-3
@@ -1,8 +1,17 @@
|
||||
## `TODO`
|
||||
> [!NOTE]
|
||||
> - Add options for `crop/pad` node - to determine the order of [crop/pad] -> upscale -> [crop/pad].
|
||||
> - One of:
|
||||
> - Add `upscale` versions for `simple` and `ratio` nodes, too.
|
||||
> - (maybe?) separate the upscale part into its own node - to make it compatible with any simple nodes.
|
||||
> - Add node for auto-detecting USDU-padding from initial res.
|
||||
|
||||
- Add `upscale` versions for `simple` and `ratio` nodes, too.
|
||||
- Add options for `crop/pad` node - to determine the order of [crop/pad] -> upscale -> [crop/pad].
|
||||
- Add node for auto-detecting USDU-padding from initial res.
|
||||
# v3.0.0
|
||||
|
||||
- From now on, the major version number will reflect the Comfy's node API version used.
|
||||
- `❗ MAJOR` internal change: All the nodes migrated to Comfy's nodes v3 API schema.
|
||||
- Increased widget precision for various float inputs.
|
||||
- No other new features or changes in behavior.
|
||||
|
||||
# v1.1.6
|
||||
|
||||
|
||||
@@ -0,0 +1,8 @@
|
||||
# encoding: utf-8
|
||||
"""
|
||||
Compatibility layer for dataclasses in different py3 versions.
|
||||
"""
|
||||
|
||||
from sys import version_info as __version_info
|
||||
|
||||
slots_args = dict() if __version_info < (3, 10) else dict(slots=True)
|
||||
@@ -1,5 +1,6 @@
|
||||
# encoding: utf-8
|
||||
"""
|
||||
Internal utility functions for auto-creating tooltips from dosctrings.
|
||||
"""
|
||||
|
||||
import typing as _t
|
||||
@@ -8,16 +9,16 @@ from inspect import getdoc as _getdoc
|
||||
import re as _re
|
||||
|
||||
|
||||
_re_indent_match = _re.compile("(\t*)( +)(\t*)(.*?)$").match
|
||||
_re_tab_indent_match = _re.compile("(\t+)(.*?)$").match
|
||||
_re_indent_match = _re.compile(r"(\t*)( +)(\t*)(.*?)$").match
|
||||
_re_tab_indent_match = _re.compile(r"(\t+)(.*?)$").match
|
||||
_re_list_line_match = _re.compile(
|
||||
"(\s*)("
|
||||
"[-*•]+"
|
||||
"|"
|
||||
"[a-zA-Z]\s*[.)]"
|
||||
"|"
|
||||
"[0-9+]\s*[.)]"
|
||||
")\s+"
|
||||
r"(\s*)("
|
||||
r"[-*•]+"
|
||||
r"|"
|
||||
r"[a-zA-Z]\s*[.)]"
|
||||
r"|"
|
||||
r"[0-9+]\s*[.)]"
|
||||
r")\s+"
|
||||
).match
|
||||
|
||||
|
||||
+30
-33
@@ -2,44 +2,41 @@
|
||||
"""
|
||||
"""
|
||||
|
||||
import typing as _t
|
||||
try:
|
||||
# To let the internal non-comfy functions be used without comfy itself
|
||||
from comfy_api.latest import ComfyExtension as _ComfyExtension, io as _io
|
||||
from typing_extensions import override as _override
|
||||
|
||||
from .node_crop_pad import BestResolutionUpscaledCropPad
|
||||
from .node_scale import BestResolutionScale
|
||||
from .node_upscale_by import ImageUpscaleByWithModel
|
||||
from .nodes_prims import *
|
||||
from .nodes_simple import *
|
||||
from .nodes_upscale import *
|
||||
class BestResolutionExtension(_ComfyExtension):
|
||||
@_override
|
||||
async def get_node_list(self) -> list[type[_io.ComfyNode]]:
|
||||
from .node_crop_pad import BestResolutionUpscaledCropPad
|
||||
from .node_scale import BestResolutionScale
|
||||
from .node_upscale_by import ImageUpscaleByWithModel
|
||||
from .nodes_prims import BestResolutionPrimResPriority, BestResolutionPrimCropPadStrategy
|
||||
from .nodes_simple import BestResolutionSimple, BestResolutionFromAspectRatio, BestResolutionFromArea
|
||||
from .nodes_upscale import BestResolutionFromAreaUpscale
|
||||
|
||||
NODE_CLASS_MAPPINGS: _t.Dict[str, type] = {
|
||||
"BestResolutionFromArea": BestResolutionFromArea,
|
||||
"BestResolutionFromAreaUpscale": BestResolutionFromAreaUpscale,
|
||||
"BestResolutionFromAspectRatio": BestResolutionFromAspectRatio,
|
||||
"BestResolutionSimple": BestResolutionSimple,
|
||||
return [
|
||||
BestResolutionFromArea,
|
||||
BestResolutionFromAreaUpscale,
|
||||
BestResolutionFromAspectRatio,
|
||||
BestResolutionSimple,
|
||||
|
||||
"BestResolutionScale": BestResolutionScale,
|
||||
BestResolutionScale,
|
||||
|
||||
"BestResolutionPrimCropPadStrategy": BestResolutionPrimCropPadStrategy,
|
||||
"BestResolutionPrimResPriority": BestResolutionPrimResPriority,
|
||||
BestResolutionPrimCropPadStrategy,
|
||||
BestResolutionPrimResPriority,
|
||||
|
||||
"BestResolutionUpscaledCropPad": BestResolutionUpscaledCropPad,
|
||||
BestResolutionUpscaledCropPad,
|
||||
|
||||
"ImageUpscaleByWithModel": ImageUpscaleByWithModel,
|
||||
}
|
||||
NODE_DISPLAY_NAME_MAPPINGS: _t.Dict[str, str] = {
|
||||
"BestResolutionFromArea": "Best-Res (area)",
|
||||
"BestResolutionFromAreaUpscale": "Best-Res (area+scale)",
|
||||
"BestResolutionFromAspectRatio": "Best-Res (ratio)",
|
||||
"BestResolutionSimple": "Best-Res (simple)",
|
||||
ImageUpscaleByWithModel,
|
||||
]
|
||||
except ImportError:
|
||||
class BestResolutionExtension:
|
||||
pass
|
||||
|
||||
"BestResolutionScale": "Scale (Best-Res)",
|
||||
async def comfy_entrypoint() -> BestResolutionExtension:
|
||||
return BestResolutionExtension()
|
||||
|
||||
"BestResolutionPrimCropPadStrategy": "Crop-Pad Strategy (Best-Res)",
|
||||
"BestResolutionPrimResPriority": "Priority (Best-Res)",
|
||||
|
||||
"BestResolutionUpscaledCropPad": "Upscaled Crop/Pad (Best-Res)",
|
||||
|
||||
"ImageUpscaleByWithModel": "Upscale Image By (with Model)"
|
||||
}
|
||||
|
||||
__all__ = ["NODE_CLASS_MAPPINGS", "NODE_DISPLAY_NAME_MAPPINGS"]
|
||||
__all__ = ("BestResolutionExtension", "comfy_entrypoint")
|
||||
|
||||
@@ -1,15 +0,0 @@
|
||||
# encoding: utf-8
|
||||
"""
|
||||
"""
|
||||
|
||||
import typing as _t
|
||||
|
||||
from dataclasses import dataclass as dataclass_with_slots_if_possible
|
||||
from sys import version_info as _version_info
|
||||
|
||||
if _version_info > (3, 10):
|
||||
_dataclass_raw = dataclass_with_slots_if_possible
|
||||
|
||||
def dataclass_with_slots_if_possible(*args, **kwargs):
|
||||
kwargs['slots'] = True
|
||||
return _dataclass_raw(*args, **kwargs)
|
||||
+28
@@ -0,0 +1,28 @@
|
||||
# encoding: utf-8
|
||||
"""
|
||||
Pre-defined combo-inputs for Node-v3, shared between nodes.
|
||||
"""
|
||||
|
||||
from comfy_api.latest import io as _io
|
||||
|
||||
from .enums import *
|
||||
from .__docstring_formatter import format_object_docstring as _format_object_docstring
|
||||
|
||||
|
||||
res_priority_in = _io.Combo.Input(
|
||||
'priority', options=RoundingPriority.all_values(),
|
||||
default=RoundingPriority.ORIGINAL,
|
||||
tooltip=_format_object_docstring(RoundingPriority),
|
||||
)
|
||||
res_priority_out = _io.Combo.Output(
|
||||
# In v3 schema, IDs must be unique between BOTH lists: inputs and outputs, so uppercase ID + `display_name`:
|
||||
'PRIORITY', display_name='priority', options=RoundingPriority.all_values(),
|
||||
)
|
||||
|
||||
crop_pad_strategy_in = _io.Combo.Input(
|
||||
'strategy', options=UpscaledCropPadStrategy.all_values(),
|
||||
default=UpscaledCropPadStrategy.PAD, tooltip=_format_object_docstring(UpscaledCropPadStrategy)
|
||||
)
|
||||
crop_pad_strategy_out = _io.Combo.Output(
|
||||
'STRATEGY', display_name='strategy', options=UpscaledCropPadStrategy.all_values(),
|
||||
)
|
||||
@@ -0,0 +1,44 @@
|
||||
# encoding: utf-8
|
||||
"""
|
||||
Pre-defined simple (non-combo) inputs for Node-v3, shared between nodes.
|
||||
"""
|
||||
|
||||
import typing as _t
|
||||
|
||||
import sys as _sys
|
||||
|
||||
from comfy_api.latest import io as _io
|
||||
|
||||
from . import _tooltips as _tt
|
||||
|
||||
T_Widget = _t.TypeVar('T_Widget', bound=_io.WidgetInput)
|
||||
|
||||
|
||||
def _int(id: str, default: int = 512, min: int = 1, max: int = _sys.maxsize, step: int = 1, **kwargs) -> _io.Int.Input:
|
||||
return _io.Int.Input(id, default=default, min=min, max=max, step=step, **kwargs)
|
||||
|
||||
|
||||
def _io_override(input: T_Widget, id: str = None, **overrides) -> T_Widget:
|
||||
assert isinstance(input, _io.WidgetInput)
|
||||
tp = type(input)
|
||||
if not (isinstance(id, str) and id):
|
||||
id = input.id
|
||||
assert isinstance(id, str) and id
|
||||
|
||||
kwargs = {k: v for k, v in input.as_dict().items() if k != 'id'}
|
||||
kwargs.update(overrides)
|
||||
return tp(id, **kwargs)
|
||||
|
||||
|
||||
res = _int('res', default=1024)
|
||||
step_default = _int('step_default', default=8)
|
||||
step_init = _int('step_init', default=8*2*3, tooltip=_tt.step_init)
|
||||
step_upscale1 = _int('step_upscale1', default=8*2*3*3, tooltip=_tt.step_upscale1)
|
||||
|
||||
rel_pos = _io.Float.Input('rel_pos', default=0.5, min=0.0, max=1.0, step=0.125, round=0.0001)
|
||||
upscale = _io.Float.Input('upscale', default=1.5, min=1.0, max=_sys.float_info.max, step=0.25, round=0.0001)
|
||||
scale = _io_override(upscale, 'scale', min=upscale.round)
|
||||
aspect_a = _io.Float.Input('aspect_a', default=16.0, min=1.0, max=_sys.float_info.max, step=1.0, round=0.0001, tooltip=_tt.aspect)
|
||||
aspect_b = _io_override(aspect_a, 'aspect_b', default=9.0)
|
||||
toggle_size_is_big = _io.Boolean.Input('size_is_big', default=True, label_on='bigger', label_off='smaller', tooltip=_tt.toggle_size_is_big)
|
||||
toggle_landscape = _io.Boolean.Input('landscape', default=True, label_on='landscape', label_off='portrait', tooltip=_tt.toggle_landscape)
|
||||
@@ -0,0 +1,53 @@
|
||||
# encoding: utf-8
|
||||
"""
|
||||
Pre-defined tooltips, shared between node inputs.
|
||||
"""
|
||||
|
||||
upscale = (
|
||||
"If the HD resolution can be achieved by uniformly scaling the initial one "
|
||||
"(there is \"✅\" and not \"⚠️\" in the upscale-line of the status message), "
|
||||
"outputs this precise upscale-value (might be different from the one originally set on the node itself).\n\n"
|
||||
"Otherwise, outputs the original upscale-value intact.\n"
|
||||
"In this case, you shouldn't use it directly and instead should pass it "
|
||||
"to the \"Upscaled Crop/Pad (Best-Res)\" node and use the upscale-output from it.\n\n"
|
||||
"In both cases, it won't hurt to use the \"Upscaled Crop/Pad (Best-Res)\" node and just rely "
|
||||
"on it's `do_crop` and `do_padding` toggle-outputs."
|
||||
)
|
||||
|
||||
init_width = "Width for original/initial image"
|
||||
init_height = "Height for original/initial image"
|
||||
HD_width = "Width for the (main/upscaled) HD-image"
|
||||
HD_height = "Height for the (main/upscaled) HD-image"
|
||||
|
||||
size_one_side = "Approximate size of one of the image sides.\nWhich one - see the 'size_is_big' and 'landscape' toggles."
|
||||
size_square = (
|
||||
"The total resolution of the image would be the same as of a square with this side.\n"
|
||||
"The width and height would be such to respect aspect ratio, but also be as close as possible to the "
|
||||
"total number of pixels as in this square image.\n\n"
|
||||
"- 512x512 square (SD 1.5): ~0.25 megapixels\n"
|
||||
"- 1024x1024 square (SDXL): ~1 megapixel"
|
||||
)
|
||||
|
||||
step_init = (
|
||||
"Both width and height will be divisible by this value - by rounding them "
|
||||
"to the closest appropriate resolution.\n\n"
|
||||
"The default 48 is (8 * 3 * 2), so it's a safe choice because:\n"
|
||||
"- it's compatible with SD1.5/XL downsampling factor (divisible by 8),\n"
|
||||
"- it can be upscaled by x1.5 or x1.333 at the first iteration, which is optimal for latent-upscale,\n"
|
||||
"- after x1.5 upscale, if you only do x2 later (it's OK for already high resolutions) - it will be divisible "
|
||||
"by 3 AND 9, which might become handy at that point, where you'll probably use UltimateSDUpscale.\n\n"
|
||||
"Other values worth trying first: 64, 96, 128."
|
||||
)
|
||||
step_upscale1 = "Same as the main `step`, but for the upscaled resolution.\n144 = 8 * 2 * 3 * 3",
|
||||
|
||||
aspect = (
|
||||
"Two aspects together define an aspect ratio (16:9, 4:3, etc).\n"
|
||||
"Order doesn't matter: image orientation is defined by the 'landscape' toggle.\n\n"
|
||||
"The specified aspect ratio is APPROXIMATE: step parameter has priority over the exact image proportions."
|
||||
)
|
||||
toggle_size_is_big = "When ON, size parameter represents the bigger image side.\nWhen OFF, it specifies the smaller one."
|
||||
toggle_landscape = (
|
||||
"Specifies image orientation:\n\n"
|
||||
"When ON, width is bigger (image is horizontal).\n"
|
||||
"When OFF, height is bigger (image is vertical)."
|
||||
)
|
||||
@@ -1,10 +1,8 @@
|
||||
# encoding: utf-8
|
||||
"""
|
||||
Internal enums.
|
||||
Internal enums: option choices, shared between nodes.
|
||||
"""
|
||||
|
||||
import typing as _t
|
||||
|
||||
try:
|
||||
from enum import StrEnum as _StrEnum
|
||||
except ImportError:
|
||||
@@ -13,9 +11,13 @@ except ImportError:
|
||||
|
||||
class __BaseEnum(_StrEnum):
|
||||
@classmethod
|
||||
def all_values(cls) -> _t.Tuple[str, ...]:
|
||||
def all_values(cls) -> tuple[str, ...]:
|
||||
return tuple(x.value for x in cls)
|
||||
|
||||
@classmethod
|
||||
def validate(cls, value) -> str:
|
||||
raise NotImplementedError
|
||||
|
||||
|
||||
class RoundingPriority(__BaseEnum):
|
||||
"""
|
||||
@@ -35,6 +37,16 @@ class RoundingPriority(__BaseEnum):
|
||||
ORIGINAL = 'original'
|
||||
UPSCALED = 'upscaled'
|
||||
|
||||
@classmethod
|
||||
def validate(cls, value) -> str:
|
||||
if value not in _res_priority_values_set:
|
||||
raise ValueError(f"Invalid value for resolution priority: {value!r}\nExpected one of: {_res_priority_values!r}")
|
||||
return str(value)
|
||||
|
||||
|
||||
_res_priority_values = RoundingPriority.all_values()
|
||||
_res_priority_values_set = set(RoundingPriority.all_values())
|
||||
|
||||
|
||||
class UpscaledCropPadStrategy(__BaseEnum):
|
||||
"""
|
||||
@@ -55,3 +67,13 @@ class UpscaledCropPadStrategy(__BaseEnum):
|
||||
CROP = 'crop only'
|
||||
NEAREST = 'nearest'
|
||||
EXACT_UPSCALE = 'exact-upscale'
|
||||
|
||||
@classmethod
|
||||
def validate(cls, value) -> str:
|
||||
if value not in _up_strategy_values_set:
|
||||
raise ValueError(f"Invalid value for upscale strategy: {value!r}\nExpected one of: {_up_strategy_values!r}")
|
||||
return str(value)
|
||||
|
||||
|
||||
_up_strategy_values = UpscaledCropPadStrategy.all_values()
|
||||
_up_strategy_values_set = set(UpscaledCropPadStrategy.all_values())
|
||||
|
||||
+17
-13
@@ -8,8 +8,6 @@ import typing as _t
|
||||
|
||||
from math import sqrt as _sqrt
|
||||
|
||||
from server import PromptServer as _PromptServer
|
||||
|
||||
from .enums import *
|
||||
from .return_tuples import *
|
||||
|
||||
@@ -17,6 +15,23 @@ from .return_tuples import *
|
||||
_t_number = _t.Union[int, float]
|
||||
|
||||
|
||||
try:
|
||||
from server import PromptServer as _PromptServer
|
||||
|
||||
def _show_text_on_node(text: str = None, node_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 = '<span></span>'
|
||||
# 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, node_id)
|
||||
except ImportError:
|
||||
def _show_text_on_node(text: str = None, node_id: str = None):
|
||||
pass
|
||||
|
||||
|
||||
def aspect_ratios_sorted(aspect_a: float, aspect_b: float, min_clamp: float = 1.0):
|
||||
"""Return aspect ratio in a standard form: 2 floats, both 1+, in descending order (16:9, not 9:16)."""
|
||||
aspect_a = float(max(abs(aspect_a), min_clamp))
|
||||
@@ -93,17 +108,6 @@ 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 = '<span></span>'
|
||||
# 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,
|
||||
@@ -5,13 +5,15 @@ The actual behavior of "Upscaled Crop/Pad" node.
|
||||
|
||||
import typing as _t
|
||||
|
||||
from ._dataclass import dataclass_with_slots_if_possible as _dataclass_with_slots_if_possible
|
||||
from ._funcs import round_pos_int as _round_pos_int, _need_post_resize, _show_text_on_node
|
||||
from dataclasses import dataclass as _dataclass
|
||||
|
||||
from .__dataclass import slots_args as __dataclass_slots_args
|
||||
from .funcs import round_pos_int as _round_pos_int, _need_post_resize, _show_text_on_node
|
||||
from .enums import *
|
||||
from .return_tuples import *
|
||||
|
||||
|
||||
@_dataclass_with_slots_if_possible
|
||||
@_dataclass(**__dataclass_slots_args)
|
||||
class _CropPadInput:
|
||||
"""
|
||||
For the ease of passing between the functions, the entire set of inputs for "Upscaled Crop/Pad" node
|
||||
+70
-91
@@ -3,116 +3,95 @@
|
||||
Nodes to calculate crop / padding values (for out-paint).
|
||||
"""
|
||||
|
||||
import typing as _t
|
||||
|
||||
from inspect import cleandoc as _cleandoc
|
||||
|
||||
from frozendict import deepfreeze as _deepfreeze, frozendict as _frozendict
|
||||
from comfy_api.latest import io as _io
|
||||
|
||||
from comfy.comfy_types.node_typing import IO as _IO
|
||||
|
||||
from ._funcs_crop_pad import upscaled_crop_pad as _upscaled_crop_pad
|
||||
from . import _io_enum as _enums
|
||||
from . import _io_simple as _i
|
||||
from ._io_simple import _io_override
|
||||
from . import _meta
|
||||
from .docstring_formatter import format_docstring as _format_docstring
|
||||
from .enums import *
|
||||
from .nodes_prims import _up_strategy_in_type, _up_strategy_verify
|
||||
from .nodes_upscale import _return_ttips_upscale
|
||||
from .slot_types import (
|
||||
type_dict_res as _type_dict_res,
|
||||
rel_pos_in_type as _rel_pos_in_type,
|
||||
upscale_in_type as _upscale_in_type
|
||||
)
|
||||
from . import _tooltips as _tt
|
||||
from .__docstring_formatter import format_docstring as _format_docstring
|
||||
from .funcs_crop_pad import upscaled_crop_pad as _upscaled_crop_pad
|
||||
|
||||
# ----------------------------------------------------------
|
||||
|
||||
_return_ttips_crop_pad = _frozendict({
|
||||
'upscale': "The actual uniform upscale value to do.",
|
||||
|
||||
'do_crop': "Whether post-upscale crop needs to be done to get the specified resolution.",
|
||||
'crop_width': "Width for the post-upscale crop.",
|
||||
'crop_height': "Height for the post-upscale crop.",
|
||||
'crop_x': "X-offset for the post-upscale crop.",
|
||||
'crop_y': "Y-offset for the post-upscale crop.",
|
||||
|
||||
'do_padding': "Whether post-upscale padding and out-paint need to be done to get the specified resolution.",
|
||||
'pad_left': "Left padding for the post-upscale out-paint.",
|
||||
'pad_top': "Top padding for the post-upscale out-paint.",
|
||||
'pad_right': "Right padding for the post-upscale out-paint.",
|
||||
'pad_bottom': "Bottom padding for the post-upscale out-paint.",
|
||||
})
|
||||
_input_types_crop_pad = _deepfreeze({
|
||||
'required': {
|
||||
'upscale': (_IO.FLOAT, dict(_upscale_in_type[1], **{
|
||||
'tooltip': f"Only used when {UpscaledCropPadStrategy.EXACT_UPSCALE!r} strategy selected."
|
||||
})),
|
||||
'init_width': (_IO.INT, dict(_type_dict_res, **{'tooltip': _return_ttips_upscale['init_width']})),
|
||||
'init_height': (_IO.INT, dict(_type_dict_res, **{'tooltip': _return_ttips_upscale['init_height']})),
|
||||
'HD_width': (_IO.INT, dict(_type_dict_res, **{'tooltip': _return_ttips_upscale['HD_width'], 'default': 1536})),
|
||||
'HD_height': (_IO.INT, dict(_type_dict_res, **{'tooltip': _return_ttips_upscale['HD_height'], 'default': 1536})),
|
||||
'strategy': _up_strategy_in_type,
|
||||
'align_x': (_IO.FLOAT, dict(_rel_pos_in_type[1], **{
|
||||
'tooltip': (
|
||||
"Where's the image pivot for horizontal alignment (which side stays in place during crop/out-paint):\n"
|
||||
"0 - Left\n"
|
||||
"0.5 - Center\n"
|
||||
"1 - Right"
|
||||
),
|
||||
})),
|
||||
'align_y': (_IO.FLOAT, dict(_rel_pos_in_type[1], **{
|
||||
'tooltip': (
|
||||
"Where's the image pivot for vertical alignment (which side stays in place during crop/out-paint):\n"
|
||||
"0 - Bottom\n"
|
||||
"0.5 - Center\n"
|
||||
"1 - Top"
|
||||
),
|
||||
'default': 0.0
|
||||
})),
|
||||
},
|
||||
'hidden': {
|
||||
'unique_id': 'UNIQUE_ID',
|
||||
},
|
||||
# 'optional': {},
|
||||
})
|
||||
|
||||
|
||||
class BestResolutionUpscaledCropPad:
|
||||
class BestResolutionUpscaledCropPad(_io.ComfyNode):
|
||||
"""
|
||||
If the original resolution can't be scaled to the higher one uniformly (i.e., without stretching),
|
||||
this node detects the appropriate values for cropping/out-painting.
|
||||
"""
|
||||
NODE_NAME = 'BestResolutionUpscaledCropPad'
|
||||
CATEGORY = _meta.category
|
||||
DESCRIPTION = _format_docstring(_cleandoc(__doc__))
|
||||
_schema = _io.Schema(
|
||||
'BestResolutionUpscaledCropPad',
|
||||
display_name="Upscaled Crop/Pad (Best-Res)",
|
||||
category=_meta.category,
|
||||
description=_format_docstring(_cleandoc(__doc__)),
|
||||
inputs=[
|
||||
_io_override(_i.upscale, tooltip=f"Only used when {_enums.UpscaledCropPadStrategy.EXACT_UPSCALE!r} strategy selected."),
|
||||
_io_override(_i.res, 'init_width', tooltip=_tt.init_width),
|
||||
_io_override(_i.res, 'init_height', tooltip=_tt.init_height),
|
||||
|
||||
OUTPUT_NODE = True
|
||||
_io_override(_i.res, 'HD_width', default=1536, tooltip=_tt.HD_width),
|
||||
_io_override(_i.res, 'HD_height', default=1536, tooltip=_tt.HD_height),
|
||||
|
||||
FUNCTION = 'main'
|
||||
RETURN_TYPES = (
|
||||
_IO.FLOAT,
|
||||
_IO.BOOLEAN, _IO.INT, _IO.INT, _IO.INT, _IO.INT,
|
||||
_IO.BOOLEAN, _IO.INT, _IO.INT, _IO.INT, _IO.INT,
|
||||
_enums.crop_pad_strategy_in,
|
||||
|
||||
_io_override(_i.rel_pos, 'align_x', tooltip=(
|
||||
"Where's the image pivot for horizontal alignment (which side stays in place during crop/out-paint):\n"
|
||||
"0 - Left\n"
|
||||
"0.5 - Center\n"
|
||||
"1 - Right"
|
||||
)),
|
||||
_io_override(_i.rel_pos, 'align_y', default=0.0, tooltip=(
|
||||
"Where's the image pivot for vertical alignment (which side stays in place during crop/out-paint):\n"
|
||||
"0 - Bottom\n"
|
||||
"0.5 - Center\n"
|
||||
"1 - Top"
|
||||
)),
|
||||
],
|
||||
hidden=[_io.Hidden.unique_id],
|
||||
outputs=[
|
||||
# In v3 schema, IDs must be unique between BOTH lists: inputs and outputs, so uppercase ID + `display_name`:
|
||||
_io.Float.Output('UPSCALE', display_name='upscale', tooltip="The actual uniform upscale value to do."),
|
||||
|
||||
# Once we have a single ID in the list, we need to specify all the display names.
|
||||
# Without it, outputs will be drawn with their types.
|
||||
_io.Boolean.Output('do_crop', display_name='do_crop', tooltip="Whether post-upscale crop needs to be done to get the specified resolution."),
|
||||
_io.Int.Output('crop_width', display_name='crop_width', tooltip="Width for the post-upscale crop."),
|
||||
_io.Int.Output('crop_height', display_name='crop_height', tooltip="Height for the post-upscale crop."),
|
||||
_io.Int.Output('crop_x', display_name='crop_x', tooltip="X-offset for the post-upscale crop."),
|
||||
_io.Int.Output('crop_y', display_name='crop_y', tooltip="Y-offset for the post-upscale crop."),
|
||||
|
||||
_io.Boolean.Output('do_padding', display_name='do_padding', tooltip="Whether post-upscale padding and out-paint need to be done to get the specified resolution."),
|
||||
_io.Int.Output('pad_left', display_name='pad_left', tooltip="Left padding for the post-upscale out-paint."),
|
||||
_io.Int.Output('pad_top', display_name='pad_top', tooltip="Top padding for the post-upscale out-paint."),
|
||||
_io.Int.Output('pad_right', display_name='pad_right', tooltip="Right padding for the post-upscale out-paint."),
|
||||
_io.Int.Output('pad_bottom', display_name='pad_bottom', tooltip="Bottom padding for the post-upscale out-paint."),
|
||||
],
|
||||
is_output_node=True,
|
||||
)
|
||||
RETURN_NAMES = tuple(_return_ttips_crop_pad.keys())
|
||||
OUTPUT_TOOLTIPS = tuple(_return_ttips_crop_pad.values())
|
||||
|
||||
@classmethod
|
||||
def INPUT_TYPES(cls):
|
||||
return _input_types_crop_pad
|
||||
def define_schema(cls) -> _io.Schema:
|
||||
return cls._schema
|
||||
|
||||
def main(
|
||||
self,
|
||||
@classmethod
|
||||
def execute(
|
||||
cls,
|
||||
upscale: float,
|
||||
init_width: int, init_height: int, HD_width: int, HD_height: int,
|
||||
strategy: _t.Union[UpscaledCropPadStrategy, str],
|
||||
strategy: _enums.UpscaledCropPadStrategy | str,
|
||||
align_x: float, align_y: float,
|
||||
# show: bool,
|
||||
unique_id: str = None
|
||||
):
|
||||
return _upscaled_crop_pad(
|
||||
upscale,
|
||||
init_width, init_height, HD_width, HD_height,
|
||||
_up_strategy_verify(strategy),
|
||||
align_x, align_y,
|
||||
# show,
|
||||
unique_id=unique_id
|
||||
) -> _io.NodeOutput:
|
||||
return _io.NodeOutput(
|
||||
*_upscaled_crop_pad(
|
||||
upscale,
|
||||
init_width, init_height, HD_width, HD_height,
|
||||
_enums.UpscaledCropPadStrategy.validate(strategy),
|
||||
align_x, align_y,
|
||||
# show,
|
||||
unique_id=cls.hidden.unique_id
|
||||
)
|
||||
)
|
||||
|
||||
+38
-48
@@ -2,69 +2,58 @@
|
||||
"""
|
||||
"""
|
||||
|
||||
import typing as _t
|
||||
|
||||
from inspect import cleandoc as _cleandoc
|
||||
from math import sqrt as _sqrt
|
||||
|
||||
from frozendict import deepfreeze as _deepfreeze
|
||||
from comfy_api.latest import io as _io
|
||||
|
||||
from comfy.comfy_types.node_typing import IO as _IO
|
||||
|
||||
from ._funcs import simple_result_from_approx_wh as _simple_result_from_approx_wh
|
||||
from . import _io_simple as _i
|
||||
from ._io_simple import _io_override
|
||||
from . import _meta
|
||||
from .docstring_formatter import format_docstring as _format_docstring
|
||||
from .slot_types import (
|
||||
type_dict_res as _type_dict_res,
|
||||
type_dict_step_default as _type_dict_step_default,
|
||||
upscale_in_type as _upscale_in_type
|
||||
)
|
||||
|
||||
_return_types = (_IO.INT, _IO.INT, _IO.FLOAT)
|
||||
_return_names = ('width', 'height', 'scale')
|
||||
_scale_type_dict = {k: v for k, v in _upscale_in_type[1].items() if k != 'tooltip'}
|
||||
_scale_type_dict['min'] = 0.001
|
||||
|
||||
_input_types = _deepfreeze({
|
||||
'required': {
|
||||
'width': (_IO.INT, _type_dict_res),
|
||||
'height': (_IO.INT, _type_dict_res),
|
||||
'step': (_IO.INT, _type_dict_step_default),
|
||||
'scale': (_IO.FLOAT, _scale_type_dict),
|
||||
'direction': (_IO.BOOLEAN, {'default': True, 'label_on': 'res * scale', 'label_off': 'res / scale', 'tooltip': (
|
||||
"Sometimes it's easier to specify scale by it's inverse: how much to scale DOWN by. This toggle controls that."
|
||||
)}),
|
||||
},
|
||||
'hidden': {
|
||||
'unique_id': 'UNIQUE_ID', # used for text display at the bottom of the node
|
||||
},
|
||||
})
|
||||
from .__docstring_formatter import format_docstring as _format_docstring
|
||||
from .funcs import simple_result_from_approx_wh as _simple_result_from_approx_wh
|
||||
|
||||
|
||||
class BestResolutionScale:
|
||||
class BestResolutionScale(_io.ComfyNode):
|
||||
"""
|
||||
Scale width and height by the same value + round them.
|
||||
"""
|
||||
NODE_NAME = 'BestResolutionScale'
|
||||
CATEGORY = _meta.category
|
||||
DESCRIPTION = _format_docstring(_cleandoc(__doc__))
|
||||
_schema = _io.Schema(
|
||||
'BestResolutionScale',
|
||||
display_name="Scale (Best-Res)",
|
||||
category=_meta.category,
|
||||
description=_format_docstring(_cleandoc(__doc__)),
|
||||
inputs=[
|
||||
_io_override(_i.res, 'width'),
|
||||
_io_override(_i.res, 'height'),
|
||||
_io_override(_i.step_default, 'step'),
|
||||
_i.scale,
|
||||
_io.Boolean.Input('direction', default=True, label_on='res * scale', label_off='res / scale', tooltip=(
|
||||
"Sometimes it's easier to specify scale by it's inverse: how much to scale DOWN by. This toggle controls that."
|
||||
)),
|
||||
],
|
||||
|
||||
OUTPUT_NODE = True
|
||||
hidden=[_io.Hidden.unique_id],
|
||||
|
||||
FUNCTION = 'main'
|
||||
RETURN_TYPES = _return_types
|
||||
RETURN_NAMES = _return_names
|
||||
outputs=[
|
||||
_io.Int.Output('WIDTH', display_name='width'),
|
||||
_io.Int.Output('HEIGHT', display_name='height'),
|
||||
_io.Float.Output('SCALE', display_name='scale'),
|
||||
],
|
||||
|
||||
is_output_node=True,
|
||||
)
|
||||
|
||||
@classmethod
|
||||
def INPUT_TYPES(cls):
|
||||
return _input_types
|
||||
def define_schema(cls) -> _io.Schema:
|
||||
return cls._schema
|
||||
|
||||
@staticmethod
|
||||
def main(
|
||||
@classmethod
|
||||
def execute(
|
||||
cls,
|
||||
width: int, height: int, step: int, scale: float, direction: bool,
|
||||
# show: bool,
|
||||
unique_id: str = None
|
||||
) -> _t.Tuple[int, int, float]:
|
||||
) -> _io.NodeOutput:
|
||||
scale = float(scale)
|
||||
if not direction:
|
||||
scale = 1.0 / scale
|
||||
@@ -73,7 +62,8 @@ class BestResolutionScale:
|
||||
out_width, out_height = _simple_result_from_approx_wh(
|
||||
width_f, height_f, step,
|
||||
# show,
|
||||
unique_id=unique_id, target_square_size=_sqrt(width_f * height_f),
|
||||
unique_id=cls.hidden.unique_id,
|
||||
target_square_size=_sqrt(width_f * height_f),
|
||||
status_suffix=f"\n({width}/{height}) * {scale:.3f} scale"
|
||||
)
|
||||
return out_width, out_height, scale
|
||||
return _io.NodeOutput(out_width, out_height, scale)
|
||||
|
||||
+67
-165
@@ -2,192 +2,93 @@
|
||||
"""
|
||||
"""
|
||||
|
||||
import typing as _t
|
||||
|
||||
from inspect import cleandoc as _cleandoc
|
||||
|
||||
from frozendict import deepfreeze as _deepfreeze
|
||||
|
||||
from comfy.comfy_types.node_typing import IO as _IO
|
||||
# from comfy_extras.nodes_upscale_model import ImageUpscaleWithModel as _ImageUpscaleWithModel
|
||||
# from nodes import ImageScaleBy as _ImageScaleBy
|
||||
from comfy_api.latest import io as _io
|
||||
from comfy_extras.nodes_upscale_model import ImageUpscaleWithModel as _ImageUpscaleWithModel
|
||||
from nodes import ImageScaleBy as _ImageScaleBy
|
||||
|
||||
from . import _io_simple as _i
|
||||
from ._io_simple import _io_override
|
||||
from . import _meta
|
||||
from .docstring_formatter import format_docstring as _format_docstring
|
||||
from .node_scale import _scale_type_dict as __scale_type_dict_base
|
||||
from ._funcs import _show_text_on_node
|
||||
|
||||
|
||||
# ==========================================================
|
||||
# Copy of built-in ComfyUI nodes with v1 schema,
|
||||
# as a temporary workaround
|
||||
|
||||
|
||||
import torch
|
||||
|
||||
from comfy import model_management
|
||||
import comfy.utils
|
||||
|
||||
|
||||
class _ImageUpscaleWithModel:
|
||||
@classmethod
|
||||
def INPUT_TYPES(s):
|
||||
return {"required": {
|
||||
"upscale_model": ("UPSCALE_MODEL",), "image": ("IMAGE",),
|
||||
}}
|
||||
RETURN_TYPES = ("IMAGE",)
|
||||
FUNCTION = "upscale"
|
||||
|
||||
CATEGORY = "image/upscaling"
|
||||
|
||||
def upscale(self, upscale_model, image):
|
||||
device = model_management.get_torch_device()
|
||||
|
||||
memory_required = model_management.module_size(upscale_model.model)
|
||||
memory_required += (512 * 512 * 3) * image.element_size() * max(upscale_model.scale, 1.0) * 384.0 #The 384.0 is an estimate of how much some of these models take, TODO: make it more accurate
|
||||
memory_required += image.nelement() * image.element_size()
|
||||
model_management.free_memory(memory_required, device)
|
||||
|
||||
upscale_model.to(device)
|
||||
in_img = image.movedim(-1,-3).to(device)
|
||||
|
||||
tile = 512
|
||||
overlap = 32
|
||||
|
||||
oom = True
|
||||
while oom:
|
||||
try:
|
||||
steps = in_img.shape[0] * comfy.utils.get_tiled_scale_steps(in_img.shape[3], in_img.shape[2], tile_x=tile, tile_y=tile, overlap=overlap)
|
||||
pbar = comfy.utils.ProgressBar(steps)
|
||||
s = comfy.utils.tiled_scale(in_img, lambda a: upscale_model(a), tile_x=tile, tile_y=tile, overlap=overlap, upscale_amount=upscale_model.scale, pbar=pbar)
|
||||
oom = False
|
||||
except model_management.OOM_EXCEPTION as e:
|
||||
tile //= 2
|
||||
if tile < 128:
|
||||
raise e
|
||||
|
||||
upscale_model.to("cpu")
|
||||
s = torch.clamp(s.movedim(-3,-1), min=0, max=1.0)
|
||||
return (s,)
|
||||
|
||||
|
||||
class _ImageScaleBy:
|
||||
upscale_methods = ["nearest-exact", "bilinear", "area", "bicubic", "lanczos"]
|
||||
|
||||
@classmethod
|
||||
def INPUT_TYPES(s):
|
||||
return {"required": {
|
||||
"image": ("IMAGE",), "upscale_method": (s.upscale_methods,),
|
||||
"scale_by": ("FLOAT", {"default": 1.0, "min": 0.01, "max": 8.0, "step": 0.01}),
|
||||
}}
|
||||
RETURN_TYPES = ("IMAGE",)
|
||||
FUNCTION = "upscale"
|
||||
|
||||
CATEGORY = "image/upscaling"
|
||||
|
||||
def upscale(self, image, upscale_method, scale_by):
|
||||
samples = image.movedim(-1,1)
|
||||
width = round(samples.shape[3] * scale_by)
|
||||
height = round(samples.shape[2] * scale_by)
|
||||
s = comfy.utils.common_upscale(samples, width, height, upscale_method, "disabled")
|
||||
s = s.movedim(1,-1)
|
||||
return (s,)
|
||||
|
||||
|
||||
# ==========================================================
|
||||
from .__docstring_formatter import format_docstring as _format_docstring
|
||||
from .funcs import _show_text_on_node
|
||||
|
||||
|
||||
_ImageUpscaleWithModel_instance = _ImageUpscaleWithModel()
|
||||
__ImageUpscaleWithModel_input_types: _t.Dict[str, dict] = _ImageUpscaleWithModel_instance.INPUT_TYPES()
|
||||
__ImageUpscaleWithModel_input_types_required = __ImageUpscaleWithModel_input_types.get('required', dict())
|
||||
|
||||
_ImageScaleBy_instance = _ImageScaleBy()
|
||||
|
||||
__scale_type_dict: _t.Dict[str, _t.Any] = dict(__scale_type_dict_base, round=0.00001)
|
||||
|
||||
_input_types = _deepfreeze({
|
||||
'required': {
|
||||
'upscale_model': __ImageUpscaleWithModel_input_types_required.get('upscale_model', (_IO.UPSCALE_MODEL, )),
|
||||
'image': __ImageUpscaleWithModel_input_types_required.get('image', (_IO.IMAGE, )),
|
||||
'model_scale': (
|
||||
_IO.FLOAT,
|
||||
dict(__scale_type_dict, default=2.0, tooltip='The upscale factor a model natively increases image by'),
|
||||
),
|
||||
'scale_method': (_ImageScaleBy.upscale_methods, {'default': 'bicubic'}),
|
||||
'scale': (
|
||||
_IO.FLOAT,
|
||||
dict(__scale_type_dict, default=1.5, tooltip='The actual factor you want to upscale by'),
|
||||
),
|
||||
'show_status': (
|
||||
_IO.BOOLEAN,
|
||||
{
|
||||
'default': False, 'label_on': 'scales performed', 'label_off': 'no',
|
||||
'tooltip': "Show the extracted string on the node itself?"
|
||||
},
|
||||
),
|
||||
},
|
||||
# 'optional': {},
|
||||
'hidden': {
|
||||
'unique_id': 'UNIQUE_ID', # used for text display at the bottom of the node
|
||||
},
|
||||
})
|
||||
|
||||
# Threshold for "almost equal to 1.0".
|
||||
# Supposedly, no image would go beyond 250k pixels in either side (even polygraphy),
|
||||
# and the double of that equals change of less than a half-pixel.
|
||||
_epsilon = 1.0 / 500_000
|
||||
_half_upper_threshold = 0.5 + _epsilon * 0.5
|
||||
|
||||
|
||||
def _status_message(no_model_scale: bool, do_downscale: bool, model_scale: float, scale: float, second_downscale: float):
|
||||
if no_model_scale:
|
||||
return f"🤔❔ x{scale:.3f} (no model)"
|
||||
if not do_downscale:
|
||||
return f"x{scale:.3f} (no second scale)"
|
||||
|
||||
base_msg = f"x{scale:.3f} = x{model_scale:.3f} → <strong>x{second_downscale:.3f}</strong>"
|
||||
if scale > model_scale:
|
||||
return f"‼️ <strong>VERY</strong> blurry output\n{base_msg}"
|
||||
if scale > (model_scale - 0.5 + _epsilon):
|
||||
return f"⚠️ Blurry output\n{base_msg}"
|
||||
return base_msg
|
||||
|
||||
|
||||
class ImageUpscaleByWithModel:
|
||||
class ImageUpscaleByWithModel(_io.ComfyNode):
|
||||
"""
|
||||
A simple wrapper over "Upscale Image (using Model)" and "Upscale Image By".
|
||||
|
||||
First, up-scales with model. Then, (down)scales to get to the desired scale factor.
|
||||
"""
|
||||
NODE_NAME = 'ImageUpscaleByWithModel'
|
||||
CATEGORY = _meta.category
|
||||
DESCRIPTION = _format_docstring(_cleandoc(__doc__))
|
||||
_schema = _io.Schema(
|
||||
'ImageUpscaleByWithModel',
|
||||
display_name="Upscale Image By (with Model)",
|
||||
category=_meta.category,
|
||||
description=_format_docstring(_cleandoc(__doc__)),
|
||||
|
||||
FUNCTION = 'main'
|
||||
RETURN_TYPES = (_IO.IMAGE, )
|
||||
RETURN_NAMES = ('image', )
|
||||
# OUTPUT_TOOLTIPS = tuple()
|
||||
inputs=[
|
||||
_io.UpscaleModel.Input('upscale_model'),
|
||||
_io.Image.Input('image'),
|
||||
_io_override(_i.scale, 'model_scale', default=2.0, round=0.00001, tooltip=(
|
||||
"The upscale factor a model natively increases image by"
|
||||
)),
|
||||
_io.Combo.Input('scale_method', options=_ImageScaleBy.upscale_methods, default='bicubic'),
|
||||
_io_override(_i.scale, 'scale', default=1.5, round=0.00001, tooltip=(
|
||||
"The actual factor you want to upscale by"
|
||||
)),
|
||||
_io.Boolean.Input('show_status', default=False, label_on='scales performed', label_off='no', tooltip=(
|
||||
"Show the extracted string on the node itself?"
|
||||
)),
|
||||
],
|
||||
|
||||
hidden=[_io.Hidden.unique_id],
|
||||
|
||||
outputs=[_io.Image.Output('IMAGE', display_name='image')],
|
||||
)
|
||||
|
||||
@classmethod
|
||||
def INPUT_TYPES(cls):
|
||||
return _input_types
|
||||
def define_schema(cls) -> _io.Schema:
|
||||
return cls._schema
|
||||
|
||||
@staticmethod
|
||||
def main(
|
||||
# Threshold for "almost equal to 1.0".
|
||||
# Supposedly, no image would go beyond 250k pixels in either side (even polygraphy),
|
||||
# and the double of that equals change of less than a half-pixel.
|
||||
_epsilon = 1.0 / 500_000
|
||||
_half_upper_threshold = 0.5 + _epsilon * 0.5
|
||||
|
||||
@classmethod
|
||||
def _status_message(
|
||||
cls,
|
||||
no_model_scale: bool, do_downscale: bool, model_scale: float, scale: float, second_downscale: float
|
||||
):
|
||||
if no_model_scale:
|
||||
return f"🤔❔ x{scale:.3f} (no model)"
|
||||
if not do_downscale:
|
||||
return f"x{scale:.3f} (no second scale)"
|
||||
|
||||
base_msg = f"x{scale:.3f} = x{model_scale:.3f} → <strong>x{second_downscale:.3f}</strong>"
|
||||
if scale > model_scale:
|
||||
return f"‼️ <strong>VERY</strong> blurry output\n{base_msg}"
|
||||
if scale > (model_scale - 0.5 + cls._epsilon):
|
||||
return f"⚠️ Blurry output\n{base_msg}"
|
||||
return base_msg
|
||||
|
||||
@classmethod
|
||||
def execute(
|
||||
cls,
|
||||
upscale_model, image, model_scale: float, scale_method, scale: float,
|
||||
show_status: bool = False,
|
||||
unique_id: str = None,
|
||||
) -> _t.Tuple[str]:
|
||||
"""
|
||||
A simple wrapper over ``ImageUpscaleWithModel`` and ``ImageScaleBy``.
|
||||
|
||||
First, up-scales with model. Then, (down)scales to get to the desired scale factor.
|
||||
"""
|
||||
) -> _io.NodeOutput:
|
||||
second_downscale = scale / model_scale
|
||||
do_downscale = abs(second_downscale - 1.0) > _epsilon
|
||||
do_downscale = abs(second_downscale - 1.0) > cls._epsilon
|
||||
no_model_scale = (
|
||||
scale <= _half_upper_threshold
|
||||
or abs(model_scale - 1.0) <= _epsilon
|
||||
scale <= cls._half_upper_threshold
|
||||
or abs(model_scale - 1.0) <= cls._epsilon
|
||||
)
|
||||
if no_model_scale:
|
||||
out_image = _ImageScaleBy_instance.upscale(image, scale_method, scale)[0]
|
||||
@@ -196,8 +97,9 @@ class ImageUpscaleByWithModel:
|
||||
if do_downscale:
|
||||
out_image = _ImageScaleBy_instance.upscale(out_image, scale_method, second_downscale)[0]
|
||||
|
||||
unique_id = cls.hidden.unique_id
|
||||
if show_status and unique_id:
|
||||
msg = _status_message(no_model_scale, do_downscale, model_scale, scale, second_downscale)
|
||||
msg = cls._status_message(no_model_scale, do_downscale, model_scale, scale, second_downscale)
|
||||
_show_text_on_node(msg, unique_id)
|
||||
|
||||
return (out_image, )
|
||||
return _io.NodeOutput(out_image, )
|
||||
|
||||
+37
-88
@@ -7,119 +7,68 @@ import typing as _t
|
||||
|
||||
from inspect import cleandoc as _cleandoc
|
||||
|
||||
from frozendict import deepfreeze as _deepfreeze
|
||||
from comfy_api.latest import io as _io
|
||||
|
||||
from . import _io_enum as _enums
|
||||
from . import _meta
|
||||
from .docstring_formatter import (
|
||||
format_docstring as _format_docstring,
|
||||
format_object_docstring as _format_object_docstring
|
||||
)
|
||||
from .enums import *
|
||||
from .__docstring_formatter import format_docstring as _format_docstring
|
||||
|
||||
# ----------------------------------------------------------
|
||||
|
||||
_res_priority_data_type = RoundingPriority.all_values()
|
||||
_res_priority_data_type_set = set(_res_priority_data_type)
|
||||
_res_priority_in_type = (
|
||||
_res_priority_data_type,
|
||||
{
|
||||
'default': RoundingPriority.ORIGINAL,
|
||||
'tooltip': _format_object_docstring(RoundingPriority),
|
||||
}
|
||||
)
|
||||
_input_types_res_priority = _deepfreeze({
|
||||
'required': {
|
||||
'priority': _res_priority_in_type,
|
||||
},
|
||||
# 'hidden': {
|
||||
# 'unique_id': 'UNIQUE_ID',
|
||||
# },
|
||||
# 'optional': {},
|
||||
})
|
||||
|
||||
|
||||
def _res_priority_verify(priority: _t.Union[RoundingPriority, str]) -> str:
|
||||
if priority not in _res_priority_data_type_set:
|
||||
raise ValueError(f"Invalid value for resolution priority: {priority!r}\nExpected one of: {_res_priority_data_type!r}")
|
||||
return str(priority)
|
||||
|
||||
|
||||
class BestResolutionPrimResPriority:
|
||||
class BestResolutionPrimResPriority(_io.ComfyNode):
|
||||
"""
|
||||
'priority' selector for "Best Resolution" upscale-nodes.
|
||||
"""
|
||||
NODE_NAME = 'BestResolutionPrimResPriority'
|
||||
CATEGORY = _meta.category
|
||||
DESCRIPTION = _format_docstring(_cleandoc(__doc__))
|
||||
_schema = _io.Schema(
|
||||
'BestResolutionPrimResPriority',
|
||||
display_name="Priority (Best-Res)",
|
||||
category=_meta.category,
|
||||
description=_format_docstring(_cleandoc(__doc__)),
|
||||
inputs=[_enums.res_priority_in],
|
||||
|
||||
OUTPUT_NODE = False
|
||||
outputs=[_enums.res_priority_out],
|
||||
|
||||
FUNCTION = 'main'
|
||||
RETURN_TYPES = (_res_priority_data_type, )
|
||||
RETURN_NAMES = ('priority', )
|
||||
# OUTPUT_TOOLTIPS = ('', )
|
||||
is_output_node=False,
|
||||
)
|
||||
|
||||
@classmethod
|
||||
def INPUT_TYPES(cls):
|
||||
return _input_types_res_priority
|
||||
def define_schema(cls) -> _io.Schema:
|
||||
return cls._schema
|
||||
|
||||
def main(
|
||||
self, priority: _t.Union[RoundingPriority, str],
|
||||
@classmethod
|
||||
def execute(
|
||||
cls, priority: _t.Union[_enums.RoundingPriority, str],
|
||||
# show: bool,
|
||||
# unique_id: str = None
|
||||
):
|
||||
return (_res_priority_verify(priority), )
|
||||
) -> _io.NodeOutput:
|
||||
return _io.NodeOutput(_enums.RoundingPriority.validate(priority), )
|
||||
|
||||
# ----------------------------------------------------------
|
||||
|
||||
_up_strategy_data_type = UpscaledCropPadStrategy.all_values()
|
||||
_up_strategy_data_type_set = set(_up_strategy_data_type)
|
||||
_up_strategy_in_type = (
|
||||
_up_strategy_data_type,
|
||||
{
|
||||
'default': UpscaledCropPadStrategy.PAD,
|
||||
'tooltip': _format_object_docstring(UpscaledCropPadStrategy),
|
||||
}
|
||||
)
|
||||
_input_types_up_strategy = _deepfreeze({
|
||||
'required': {
|
||||
'strategy': _up_strategy_in_type,
|
||||
},
|
||||
# 'hidden': {
|
||||
# 'unique_id': 'UNIQUE_ID',
|
||||
# },
|
||||
# 'optional': {},
|
||||
})
|
||||
|
||||
|
||||
def _up_strategy_verify(strategy: _t.Union[RoundingPriority, str]) -> str:
|
||||
if strategy not in _up_strategy_data_type_set:
|
||||
raise ValueError(f"Invalid value for upscale strategy: {strategy!r}\nExpected one of: {_up_strategy_data_type!r}")
|
||||
return str(strategy)
|
||||
|
||||
|
||||
class BestResolutionPrimCropPadStrategy:
|
||||
class BestResolutionPrimCropPadStrategy(_io.ComfyNode):
|
||||
"""
|
||||
'strategy' selector for "Upscaled Crop/Pad" node in "Best Resolution" pack.
|
||||
"""
|
||||
NODE_NAME = 'BestResolutionPrimCropPadStrategy'
|
||||
CATEGORY = _meta.category
|
||||
DESCRIPTION = _format_docstring(_cleandoc(__doc__))
|
||||
_schema = _io.Schema(
|
||||
'BestResolutionPrimCropPadStrategy',
|
||||
display_name="Crop-Pad Strategy (Best-Res)",
|
||||
category=_meta.category,
|
||||
description=_format_docstring(_cleandoc(__doc__)),
|
||||
inputs=[_enums.crop_pad_strategy_in],
|
||||
|
||||
OUTPUT_NODE = False
|
||||
outputs=[_enums.crop_pad_strategy_out],
|
||||
|
||||
FUNCTION = 'main'
|
||||
RETURN_TYPES = (_up_strategy_data_type, )
|
||||
RETURN_NAMES = ('strategy', )
|
||||
# OUTPUT_TOOLTIPS = ('', )
|
||||
is_output_node=False,
|
||||
)
|
||||
|
||||
@classmethod
|
||||
def INPUT_TYPES(cls):
|
||||
return _input_types_up_strategy
|
||||
def define_schema(cls) -> _io.Schema:
|
||||
return cls._schema
|
||||
|
||||
def main(
|
||||
self, strategy: _t.Union[RoundingPriority, str],
|
||||
@classmethod
|
||||
def execute(
|
||||
cls, strategy: _t.Union[_enums.UpscaledCropPadStrategy, str],
|
||||
# show: bool,
|
||||
# unique_id: str = None
|
||||
):
|
||||
return (_up_strategy_verify(strategy), )
|
||||
) -> _io.NodeOutput:
|
||||
return _io.NodeOutput(_enums.UpscaledCropPadStrategy.validate(strategy), )
|
||||
|
||||
+94
-161
@@ -3,163 +3,107 @@
|
||||
Simple (aka no-upscale) versions of nodes.
|
||||
"""
|
||||
|
||||
import typing as _t
|
||||
|
||||
from inspect import cleandoc as _cleandoc
|
||||
from math import sqrt as _sqrt
|
||||
import sys as _sys
|
||||
|
||||
from frozendict import deepfreeze as _deepfreeze
|
||||
from comfy_api.latest import io as _io
|
||||
|
||||
from comfy.comfy_types.node_typing import IO as _IO
|
||||
|
||||
from ._funcs import (
|
||||
from . import _io_simple as _i
|
||||
from ._io_simple import _io_override
|
||||
from . import _meta
|
||||
from . import _tooltips as _tt
|
||||
from .__docstring_formatter import format_docstring as _format_docstring
|
||||
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
|
||||
)
|
||||
from . import _meta
|
||||
from .docstring_formatter import format_docstring as _format_docstring
|
||||
from .slot_types import (
|
||||
type_dict_res as _type_dict_res,
|
||||
type_dict_step_init as _type_dict_step_init
|
||||
)
|
||||
|
||||
# ----------------------------------------------------------
|
||||
|
||||
# _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({
|
||||
'required': {
|
||||
'width': (_IO.INT, dict(_type_dict_res, **{'tooltip': "Approximate width"})),
|
||||
'height': (_IO.INT, dict(_type_dict_res, **{'tooltip': "Approximate height"})),
|
||||
'step': (_IO.INT, dict(_type_dict_step_init, **{'tooltip': (
|
||||
"Both width and height will be divisible by this value - by rounding them "
|
||||
"to the closest appropriate resolution.\n\n"
|
||||
"The default 48 is (8 * 3 * 2), so it's a safe choice because:\n"
|
||||
"- it's compatible with SD1.5/XL downsampling factor (divisible by 8),\n"
|
||||
"- it can be upscaled by x1.5 or x1.333 at the first iteration, which is optimal for latent-upscale,\n"
|
||||
"- after x1.5 upscale, if you only do x2 later (it's OK for already high resolutions) - it will be divisible "
|
||||
"by 3 AND 9, which might become handy at that point, where you'll probably use UltimateSDUpscale.\n\n"
|
||||
"Other values worth trying first: 64, 96, 128."
|
||||
)})),
|
||||
# 'show': (_IO.BOOLEAN, {
|
||||
# 'default': True,
|
||||
# # 'label_on': 'on', 'label_off': 'off',
|
||||
# 'tooltip': "Show the result on the node itself?",
|
||||
# }),
|
||||
},
|
||||
'hidden': {
|
||||
'unique_id': 'UNIQUE_ID', # used for text display at the bottom of the node
|
||||
},
|
||||
# 'optional': {},
|
||||
})
|
||||
|
||||
|
||||
class BestResolutionSimple:
|
||||
class BestResolutionSimple(_io.ComfyNode):
|
||||
"""
|
||||
The simplest node for easier selection of an optimal resolution:
|
||||
only rounding performed, desired image size specified directly.
|
||||
"""
|
||||
NODE_NAME = 'BestResolutionSimple'
|
||||
CATEGORY = _meta.category
|
||||
DESCRIPTION = _format_docstring(_cleandoc(__doc__))
|
||||
_schema = _io.Schema(
|
||||
'BestResolutionSimple',
|
||||
display_name="Best-Res (simple)",
|
||||
category=_meta.category,
|
||||
description=_format_docstring(_cleandoc(__doc__)),
|
||||
|
||||
OUTPUT_NODE = True
|
||||
inputs=[
|
||||
_io_override(_i.res, 'width', tooltip="Approximate width"),
|
||||
_io_override(_i.res, 'height', tooltip="Approximate height"),
|
||||
_io_override(_i.step_init, 'step'),
|
||||
],
|
||||
|
||||
FUNCTION = 'main'
|
||||
RETURN_TYPES = _return_types_simple
|
||||
# OUTPUT_TOOLTIPS = {}
|
||||
RETURN_NAMES = _return_names_simple
|
||||
hidden=[_io.Hidden.unique_id],
|
||||
|
||||
outputs=[
|
||||
_io.Int.Output('WIDTH', display_name='width'),
|
||||
_io.Int.Output('HEIGHT', display_name='height'),
|
||||
],
|
||||
|
||||
is_output_node=True,
|
||||
)
|
||||
|
||||
@classmethod
|
||||
def INPUT_TYPES(cls):
|
||||
return _input_types_simple
|
||||
def define_schema(cls) -> _io.Schema:
|
||||
return cls._schema
|
||||
|
||||
def main(
|
||||
self, width: int, height: int, step: int,
|
||||
@classmethod
|
||||
def execute(
|
||||
cls,
|
||||
width: int, height: int, step: int,
|
||||
# show: bool,
|
||||
unique_id: str = None
|
||||
):
|
||||
return _simple_result_from_approx_wh(
|
||||
) -> _io.NodeOutput:
|
||||
return _io.NodeOutput(*_simple_result_from_approx_wh(
|
||||
float(width), height, step,
|
||||
# show,
|
||||
unique_id=unique_id, target_square_size=_sqrt(width * height)
|
||||
)
|
||||
unique_id=cls.hidden.unique_id, target_square_size=_sqrt(width * height)
|
||||
))
|
||||
|
||||
# ----------------------------------------------------------
|
||||
|
||||
_tooltip_aspect = (
|
||||
"Two aspects together define an aspect ratio (16:9, 4:3, etc).\n"
|
||||
"Order doesn't matter: image orientation is defined by the 'landscape' toggle.\n\n"
|
||||
"The specified aspect ratio is APPROXIMATE: step parameter has priority over the exact image proportions."
|
||||
)
|
||||
|
||||
_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."
|
||||
)})),
|
||||
'step': _input_types_simple['required']['step'],
|
||||
'size_is_big': (_IO.BOOLEAN, {
|
||||
'default': True, 'label_on': 'bigger', 'label_off': 'smaller',
|
||||
'tooltip': "When ON, size parameter represents the bigger image side.\nWhen OFF, it specifies the smaller one."
|
||||
}),
|
||||
'landscape': (_IO.BOOLEAN, {
|
||||
'default': True, 'label_on': 'landscape', 'label_off': 'portrait',
|
||||
'tooltip': (
|
||||
"Specifies image orientation:\n\n"
|
||||
"When ON, width is bigger (image is horizontal).\n"
|
||||
"When OFF, height is bigger (image is vertical)."
|
||||
),
|
||||
}),
|
||||
'aspect_a': (_IO.FLOAT, {
|
||||
'default': 16.0, 'min': 1.0, 'max': _sys.float_info.max, 'step': 1.0, 'round': 0.001,
|
||||
'tooltip': _tooltip_aspect
|
||||
}),
|
||||
'aspect_b': (_IO.FLOAT, {
|
||||
'default': 9.0, 'min': 1.0, 'max': _sys.float_info.max, 'step': 1.0, 'round': 0.001,
|
||||
'tooltip': _tooltip_aspect
|
||||
}),
|
||||
# 'show': _input_types_simple['required']['show'],
|
||||
},
|
||||
'hidden': {
|
||||
'unique_id': 'UNIQUE_ID',
|
||||
},
|
||||
# 'optional': {},
|
||||
})
|
||||
|
||||
|
||||
class BestResolutionFromAspectRatio:
|
||||
class BestResolutionFromAspectRatio(_io.ComfyNode):
|
||||
"""
|
||||
A more advanced node for easier selection of an optimal resolution:
|
||||
image size selected indirectly - by one of the sides + aspect ratio.
|
||||
"""
|
||||
NODE_NAME = 'BestResolutionFromAspectRatio'
|
||||
CATEGORY = _meta.category
|
||||
DESCRIPTION = _format_docstring(_cleandoc(__doc__))
|
||||
_schema = _io.Schema(
|
||||
'BestResolutionFromAspectRatio',
|
||||
display_name="Best-Res (ratio)",
|
||||
category=_meta.category,
|
||||
description=_format_docstring(_cleandoc(__doc__)),
|
||||
|
||||
OUTPUT_NODE = True
|
||||
inputs=[
|
||||
_io_override(_i.res, 'size', tooltip=_tt.size_one_side),
|
||||
_io_override(_i.step_init, 'step'),
|
||||
_i.toggle_size_is_big,
|
||||
_i.toggle_landscape,
|
||||
_i.aspect_a,
|
||||
_i.aspect_b,
|
||||
],
|
||||
|
||||
FUNCTION = 'main'
|
||||
RETURN_TYPES = _return_types_simple
|
||||
RETURN_NAMES = _return_names_simple
|
||||
hidden=[_io.Hidden.unique_id],
|
||||
|
||||
outputs=list(BestResolutionSimple._schema.outputs),
|
||||
|
||||
is_output_node=True,
|
||||
)
|
||||
|
||||
@classmethod
|
||||
def INPUT_TYPES(cls):
|
||||
return _input_types_orient
|
||||
def define_schema(cls) -> _io.Schema:
|
||||
return cls._schema
|
||||
|
||||
def main(
|
||||
self,
|
||||
@classmethod
|
||||
def execute(
|
||||
cls,
|
||||
size: int, step: int, size_is_big: bool, landscape: bool, aspect_a: float, aspect_b: float,
|
||||
# show: bool,
|
||||
unique_id: str = None
|
||||
):
|
||||
) -> _io.NodeOutput:
|
||||
aspect_big, aspect_small = _aspect_ratios_sorted(aspect_a, aspect_b)
|
||||
|
||||
side_main_f = float(size)
|
||||
@@ -172,37 +116,15 @@ class BestResolutionFromAspectRatio:
|
||||
# The opposite: height is bigger
|
||||
width_f, height_f = height_f, width_f
|
||||
|
||||
return _simple_result_from_approx_wh(
|
||||
return _io.NodeOutput(*_simple_result_from_approx_wh(
|
||||
width_f, height_f, step,
|
||||
# show,
|
||||
unique_id=unique_id, target_square_size=_sqrt(width_f * height_f)
|
||||
)
|
||||
unique_id=cls.hidden.unique_id, target_square_size=_sqrt(width_f * height_f)
|
||||
))
|
||||
|
||||
# ----------------------------------------------------------
|
||||
|
||||
_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"
|
||||
"The width and height would be such to respect aspect ratio, but also be as close as possible to the "
|
||||
"total number of pixels as in this square image.\n\n"
|
||||
"- 512x512 square (SD 1.5): ~0.25 megapixels\n"
|
||||
"- 1024x1024 square (SDXL): ~1 megapixel"
|
||||
)})),
|
||||
'step': _input_types_simple['required']['step'],
|
||||
'landscape': _input_types_orient['required']['landscape'],
|
||||
'aspect_a': _input_types_orient['required']['aspect_a'],
|
||||
'aspect_b': _input_types_orient['required']['aspect_b'],
|
||||
# 'show': _input_types_simple['required']['show'],
|
||||
},
|
||||
'hidden': {
|
||||
'unique_id': 'UNIQUE_ID',
|
||||
},
|
||||
# 'optional': {},
|
||||
})
|
||||
|
||||
|
||||
class BestResolutionFromArea:
|
||||
class BestResolutionFromArea(_io.ComfyNode):
|
||||
"""
|
||||
The most efficient way of selecting an optimal resolution:
|
||||
image size selected indirectly - by the total desired resolution (area) + aspect ratio.
|
||||
@@ -217,30 +139,41 @@ class BestResolutionFromArea:
|
||||
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 = 'BestResolutionFromArea'
|
||||
CATEGORY = _meta.category
|
||||
DESCRIPTION = _format_docstring(_cleandoc(__doc__))
|
||||
_schema = _io.Schema(
|
||||
'BestResolutionFromArea',
|
||||
display_name="Best-Res (area)",
|
||||
category=_meta.category,
|
||||
description=_format_docstring(_cleandoc(__doc__)),
|
||||
|
||||
OUTPUT_NODE = True
|
||||
inputs=[
|
||||
_io_override(_i.res, 'square_size', tooltip=_tt.size_square),
|
||||
_io_override(_i.step_init, 'step'),
|
||||
_i.toggle_landscape,
|
||||
_i.aspect_a,
|
||||
_i.aspect_b,
|
||||
],
|
||||
|
||||
FUNCTION = 'main'
|
||||
RETURN_TYPES = _return_types_simple
|
||||
RETURN_NAMES = _return_names_simple
|
||||
hidden=[_io.Hidden.unique_id],
|
||||
|
||||
outputs=list(BestResolutionSimple._schema.outputs),
|
||||
|
||||
is_output_node=True,
|
||||
)
|
||||
|
||||
@classmethod
|
||||
def INPUT_TYPES(cls):
|
||||
return _input_types_area
|
||||
def define_schema(cls) -> _io.Schema:
|
||||
return cls._schema
|
||||
|
||||
def main(
|
||||
self,
|
||||
@classmethod
|
||||
def execute(
|
||||
cls,
|
||||
square_size: int, step: int, landscape: bool, aspect_a: float, aspect_b: float,
|
||||
# show: bool,
|
||||
unique_id: str = None
|
||||
):
|
||||
) -> _io.NodeOutput:
|
||||
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 _simple_result_from_approx_wh(
|
||||
return _io.NodeOutput(*_simple_result_from_approx_wh(
|
||||
width_f, height_f, step,
|
||||
# show,
|
||||
unique_id=unique_id, target_square_size=square_size
|
||||
)
|
||||
unique_id=cls.hidden.unique_id, target_square_size=square_size
|
||||
))
|
||||
|
||||
+51
-72
@@ -6,68 +6,26 @@ Advanced versions of nodes - with upscale.
|
||||
import typing as _t
|
||||
|
||||
from inspect import cleandoc as _cleandoc
|
||||
from itertools import chain as _chain
|
||||
|
||||
from frozendict import deepfreeze as _deepfreeze, frozendict as _frozendict
|
||||
from comfy_api.latest import io as _io
|
||||
|
||||
from comfy.comfy_types.node_typing import IO as _IO
|
||||
|
||||
from ._funcs import (
|
||||
from . import _io_enum as _enums
|
||||
from . import _io_simple as _i
|
||||
from ._io_simple import _io_override
|
||||
from . import _meta
|
||||
from . import _tooltips as _tt
|
||||
from .__docstring_formatter import format_docstring as _format_docstring
|
||||
from .funcs import (
|
||||
number_to_int as _number_to_int,
|
||||
float_width_height_from_area as _float_width_height_from_area,
|
||||
upscale_result_from_approx_wh as _upscale_result_from_approx_wh
|
||||
)
|
||||
from . import _meta
|
||||
from .docstring_formatter import format_docstring as _format_docstring
|
||||
from .enums import *
|
||||
from .nodes_simple import _input_types_area
|
||||
from .nodes_prims import _res_priority_in_type, _res_priority_verify
|
||||
from .slot_types import (
|
||||
type_dict_step_upscale1 as _type_dict_step_upscale1,
|
||||
upscale_in_type as _upscale_in_type
|
||||
)
|
||||
from .nodes_simple import BestResolutionFromArea as _BestResolutionFromArea
|
||||
|
||||
# ----------------------------------------------------------
|
||||
|
||||
_return_types_upscale = (_IO.FLOAT, _IO.INT, _IO.INT, _IO.INT, _IO.INT)
|
||||
_return_ttips_upscale = _frozendict({
|
||||
'upscale': (
|
||||
"If the HD resolution can be achieved by uniformly scaling the initial one "
|
||||
"(there is \"✅\" and not \"⚠️\" in the upscale-line of the status message), "
|
||||
"outputs this precise upscale-value (might be different from the one originally set on the node itself).\n\n"
|
||||
"Otherwise, outputs the original upscale-value intact.\n"
|
||||
"In this case, you shouldn't use it directly and instead should pass it "
|
||||
"to the \"Upscaled Crop/Pad (Best-Res)\" node and use the upscale-output from it.\n\n"
|
||||
"In both cases, it won't hurt to use the \"Upscaled Crop/Pad (Best-Res)\" node and just rely "
|
||||
"on it's `do_crop` and `do_padding` toggle-outputs."
|
||||
),
|
||||
'init_width': "Width for original/initial image",
|
||||
'init_height': "Height for original/initial image",
|
||||
'HD_width': "Width for the (main/upscaled) HD-image",
|
||||
'HD_height': "Height for the (main/upscaled) HD-image",
|
||||
})
|
||||
|
||||
__extra_inputs_for_upscale_only = {
|
||||
'priority': _res_priority_in_type,
|
||||
'upscale': _upscale_in_type,
|
||||
'HD_step': (_IO.INT, dict(_type_dict_step_upscale1, **{
|
||||
'tooltip': "Same as the main `step`, but for the upscaled resolution.\n144 = 8 * 2 * 3 * 3",
|
||||
})),
|
||||
}
|
||||
|
||||
_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:
|
||||
class BestResolutionFromAreaUpscale(_io.ComfyNode):
|
||||
"""
|
||||
The most efficient way of selecting an optimal resolution:
|
||||
image size selected indirectly - by the total desired resolution (area) + aspect ratio...
|
||||
@@ -84,32 +42,53 @@ class BestResolutionFromAreaUpscale:
|
||||
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 = _meta.category
|
||||
DESCRIPTION = _format_docstring(_cleandoc(__doc__))
|
||||
_schema = _io.Schema(
|
||||
'BestResolutionFromAreaUpscale',
|
||||
display_name="Best-Res (area+scale)",
|
||||
category=_meta.category,
|
||||
description=_format_docstring(_cleandoc(__doc__)),
|
||||
|
||||
OUTPUT_NODE = True
|
||||
inputs=list(_chain(
|
||||
(
|
||||
x for x in _BestResolutionFromArea._schema.inputs
|
||||
if x.id != 'show'
|
||||
),
|
||||
[
|
||||
_enums.res_priority_in,
|
||||
_i.upscale,
|
||||
_io_override(_i.step_upscale1, 'HD_step')
|
||||
]
|
||||
)),
|
||||
|
||||
FUNCTION = 'main'
|
||||
RETURN_TYPES = _return_types_upscale
|
||||
RETURN_NAMES = tuple(_return_ttips_upscale.keys())
|
||||
OUTPUT_TOOLTIPS = tuple(_return_ttips_upscale.values())
|
||||
hidden=[_io.Hidden.unique_id],
|
||||
|
||||
outputs=[
|
||||
_io.Float.Output('UPSCALE', display_name='upscale', tooltip=_tt.upscale),
|
||||
_io.Int.Output('init_width', display_name='init_width', tooltip=_tt.init_width),
|
||||
_io.Int.Output('init_height', display_name='init_height', tooltip=_tt.init_height),
|
||||
_io.Int.Output('HD_width', display_name='HD_width', tooltip=_tt.HD_width),
|
||||
_io.Int.Output('HD_height', display_name='HD_height', tooltip=_tt.HD_height),
|
||||
],
|
||||
|
||||
is_output_node=True,
|
||||
)
|
||||
|
||||
@classmethod
|
||||
def INPUT_TYPES(cls):
|
||||
return _input_types_area_upscale
|
||||
def define_schema(cls) -> _io.Schema:
|
||||
return cls._schema
|
||||
|
||||
def main(
|
||||
self, square_size: int, step: int, landscape: bool, aspect_a: float, aspect_b: float,
|
||||
priority: _t.Union[RoundingPriority, str], upscale: float, HD_step:int,
|
||||
@classmethod
|
||||
def execute(
|
||||
cls,
|
||||
square_size: int, step: int, landscape: bool, aspect_a: float, aspect_b: float,
|
||||
priority: _t.Union[_enums.RoundingPriority, str], upscale: float, HD_step:int,
|
||||
# show: bool,
|
||||
unique_id: str = None
|
||||
):
|
||||
) -> _io.NodeOutput:
|
||||
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(
|
||||
return _io.NodeOutput(*_upscale_result_from_approx_wh(
|
||||
width_f, height_f, step,
|
||||
_res_priority_verify(priority), upscale, HD_step,
|
||||
_enums.RoundingPriority.validate(priority), upscale, HD_step,
|
||||
# show,
|
||||
unique_id=unique_id, target_square_size=square_size
|
||||
)
|
||||
unique_id=cls.hidden.unique_id, target_square_size=square_size
|
||||
))
|
||||
|
||||
+13
-5
@@ -1,19 +1,27 @@
|
||||
[project]
|
||||
name = "best-resolution"
|
||||
version = "3.0.0"
|
||||
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.1.6"
|
||||
license = {file = "LICENSE.md"}
|
||||
readme = "README.md"
|
||||
authors = [
|
||||
{name = "Lex Darlog"}
|
||||
]
|
||||
license = {file = "LICENSE.md"}
|
||||
readme = "README.md"
|
||||
dependencies = ["frozendict"]
|
||||
requires-python = ">=3.10"
|
||||
dependencies = []
|
||||
classifiers = [
|
||||
# https://docs.comfy.org/registry/specifications#classifiers-recommended
|
||||
"Operating System :: OS Independent",
|
||||
]
|
||||
|
||||
[project.urls]
|
||||
Repository = "https://github.com/Lex-DRL/ComfyUI-BestResolution"
|
||||
"Bugs/Feature Requests" = "https://github.com/Lex-DRL/ComfyUI-BestResolution/issues"
|
||||
# Used by Comfy Registry https://comfyregistry.org
|
||||
|
||||
[tool.comfy]
|
||||
PublisherId = "lex-drl"
|
||||
DisplayName = "Best Resolution"
|
||||
Icon = ""
|
||||
# Icon = "https://raw.githubusercontent.com/username/repo/main/icon.png"
|
||||
# Banner = "https://raw.githubusercontent.com/username/repo/main/banner.png"
|
||||
requires-comfyui = ">=0.3.60" # The first version to include io.Combo.Output
|
||||
|
||||
@@ -1 +0,0 @@
|
||||
frozendict
|
||||
|
||||
+1
-1
@@ -1,6 +1,6 @@
|
||||
# encoding: utf-8
|
||||
"""
|
||||
For readability, all nodes in this node pack return NamedTuples instead of regular tuples.
|
||||
For readability, the results of all node-functions in this node pack are NamedTuples instead of regular tuples.
|
||||
"""
|
||||
|
||||
import typing as _t
|
||||
|
||||
@@ -1,37 +0,0 @@
|
||||
# encoding: utf-8
|
||||
"""
|
||||
Pre-defined dictionaries for nice input types.
|
||||
"""
|
||||
|
||||
import typing as _t
|
||||
|
||||
import sys as _sys
|
||||
|
||||
from comfy.comfy_types.node_typing import IO as _IO
|
||||
|
||||
|
||||
def number_type_dict(default: int = 512, min: int = 1, max: int = _sys.maxsize, step: int = 1) -> _t.Dict[str, int]:
|
||||
"""Prepare type-dict for an input int/float parameter."""
|
||||
|
||||
# sys.maxsize is Py3's closest equivalent to 'sys.maxint' from Py2
|
||||
return {
|
||||
"default": default,
|
||||
"min": min,
|
||||
"max": max,
|
||||
"step": step
|
||||
}
|
||||
|
||||
|
||||
type_dict_res = number_type_dict(1024)
|
||||
type_dict_step_default = number_type_dict(8)
|
||||
type_dict_step_init = number_type_dict(8*2*3)
|
||||
type_dict_step_upscale1 = number_type_dict(8*2*3*3)
|
||||
|
||||
upscale_in_type = (_IO.FLOAT, {
|
||||
'default': 1.5, 'min': 1.0, 'max': _sys.float_info.max, 'step': 0.25, 'round': 0.001,
|
||||
# 'tooltip': "", # TODO
|
||||
})
|
||||
rel_pos_in_type = (_IO.FLOAT, {
|
||||
'default': 0.5, 'min': 0.0, 'max': 1.0, 'step': 0.125, 'round': 0.0001,
|
||||
# 'display': 'slider', # Cosmetic only: display as 'number' or 'slider'
|
||||
})
|
||||
Reference in New Issue
Block a user