diff --git a/__init__.py b/__init__.py index 33c2e81..0ba59ab 100644 --- a/__init__.py +++ b/__init__.py @@ -4,9 +4,10 @@ import typing as _t +from .node_crop_pad import BestResolutionUpscaledCropPad +from .nodes_prims import * from .nodes_simple import * from .nodes_upscale import * -from .nodes_prims import * NODE_CLASS_MAPPINGS: _t.Dict[str, type] = { "BestResolutionFromArea": BestResolutionFromArea, @@ -16,6 +17,8 @@ NODE_CLASS_MAPPINGS: _t.Dict[str, type] = { "BestResolutionPrimResPriority": BestResolutionPrimResPriority, "BestResolutionPrimUpStrategy": BestResolutionPrimUpStrategy, + + "BestResolutionUpscaledCropPad": BestResolutionUpscaledCropPad, } NODE_DISPLAY_NAME_MAPPINGS: _t.Dict[str, str] = { "BestResolutionFromArea": "Best-Res (area)", @@ -25,6 +28,8 @@ NODE_DISPLAY_NAME_MAPPINGS: _t.Dict[str, str] = { "BestResolutionPrimResPriority": "Priority (Best-Res)", "BestResolutionPrimUpStrategy": "Upscale Strategy (Best-Res)", + + "BestResolutionUpscaledCropPad": "Upscaled Crop/Pad (Best-Res)", } __all__ = ["NODE_CLASS_MAPPINGS", "NODE_DISPLAY_NAME_MAPPINGS"] diff --git a/_funcs.py b/_funcs.py index 7b73c9d..6fd750d 100644 --- a/_funcs.py +++ b/_funcs.py @@ -261,3 +261,21 @@ def upscale_result_from_approx_wh( _show_text_on_node(text, unique_id) return result + + +def upscaled_crop_pad( + upscale: float, + orig_w: int, orig_h: int, up_w: int, up_h: int, + strategy: _t.Union[UpscaledCropPadStrategy, str], + align_x: float, align_y: float, + # show: bool, + unique_id: str = None +) -> ResultUpscaledCropPad: +): + # TODO + result = ResultUpscaledCropPad( + upscale, + False, 1024, 1024, 0, 0, + False, 10, 10, 10, 10, + ) + return result diff --git a/node_crop_pad.py b/node_crop_pad.py new file mode 100644 index 0000000..b9cac96 --- /dev/null +++ b/node_crop_pad.py @@ -0,0 +1,119 @@ +# encoding: utf-8 +""" +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.comfy_types.node_typing import IO as _IO + +from ._funcs import upscaled_crop_pad as _upscaled_crop_pad +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 +) + +# ---------------------------------------------------------- + +_return_types_crop_pad = ( + _IO.FLOAT, + _IO.BOOLEAN, _IO.INT, _IO.INT, _IO.INT, _IO.INT, + _IO.BOOLEAN, _IO.INT, _IO.INT, _IO.INT, _IO.INT, +) +_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." + })), + 'orig_w': (_IO.INT, dict(_type_dict_res, **{'tooltip': _return_ttips_upscale['orig_w']})), + 'orig_h': (_IO.INT, dict(_type_dict_res, **{'tooltip': _return_ttips_upscale['orig_h']})), + 'up_w': (_IO.INT, dict(_type_dict_res, **{'tooltip': _return_ttips_upscale['up_w']})), + 'up_h': (_IO.INT, dict(_type_dict_res, **{'tooltip': _return_ttips_upscale['up_h']})), + '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: + """ + 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__)) + + OUTPUT_NODE = True + + FUNCTION = 'main' + RETURN_TYPES = _return_types_crop_pad + 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 main( + self, + upscale: float, + orig_w: int, orig_h: int, up_w: int, up_h: int, + strategy: _t.Union[UpscaledCropPadStrategy, str], + align_x: float, align_y: float, + # show: bool, + unique_id: str = None + ): + return _upscaled_crop_pad( + upscale, + orig_w, orig_h, up_w, up_h, + _up_strategy_verify(strategy), + align_x, align_y, + # show, + unique_id=unique_id + ) diff --git a/return_tuples.py b/return_tuples.py index 2bce606..20915b5 100644 --- a/return_tuples.py +++ b/return_tuples.py @@ -5,6 +5,7 @@ For readability, all nodes in this node pack return NamedTuples instead of regul import typing as _t + class ResultSimple(_t.NamedTuple): """Returned NamedTuple for simple (non-upscale) nodes.""" width: int @@ -19,3 +20,20 @@ class ResultUpscaled(_t.NamedTuple): up_width: int up_height: int needs_resize: bool + + +class ResultUpscaledCropPad(_t.NamedTuple): + """Returned NamedTuple for "Upscaled Crop/Pad" node.""" + upscale: float + + do_crop: bool + crop_width: int + crop_height: int + crop_x_origin: int + crop_y_origin: int + + do_padding: bool + pad_left: int + pad_top: int + pad_right: int + pad_bottom: int diff --git a/slot_types.py b/slot_types.py index 3ce0758..7118d6c 100644 --- a/slot_types.py +++ b/slot_types.py @@ -29,4 +29,7 @@ type_dict_step_upscale1 = number_type_dict(128) upscale_in_type = (_IO.FLOAT, { 'default': 1.5, 'min': 1.0, 'max': _sys.float_info.max, 'step': 0.25, 'round': 0.001, # 'tooltip': "", # TODO -}) \ No newline at end of file +}) +rel_pos_in_type = (_IO.FLOAT, { + 'default': 0.5, 'min': 0.0, 'max': 1.0, 'step': 0.125, 'round': 0.0001, +})