+ implemented

This commit is contained in:
Lex Darlog (DRL)
2025-07-19 09:55:26 +05:00
parent ae12b82372
commit ec509ddc75
5 changed files with 189 additions and 21 deletions
-18
View File
@@ -261,21 +261,3 @@ 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
+184
View File
@@ -0,0 +1,184 @@
# encoding: utf-8
"""
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 .enums import *
from .return_tuples import *
@_dataclass_with_slots_if_possible
class _CropPadInput:
"""
For the ease of passing between the functions, the entire set of inputs for "Upscaled Crop/Pad" node
is internally turned into a dataclass.
"""
upscale: float
orig_w: int
orig_h: int
up_w: int
up_h: int
strategy: str
align_x: float
align_y: float
def _upscaled_crop_xy_offset(extra_w: int, extra_h: int, align_x: float, align_y: float):
crop_x_origin = _round_pos_int(align_x * extra_w)
crop_y_origin = _round_pos_int((1.0 - align_y) * extra_h)
return crop_x_origin, crop_y_origin
def _upscaled_crop(_in: _CropPadInput, real_upscale: float) -> ResultUpscaledCropPad:
raw_upscaled_w = _round_pos_int(real_upscale * _in.orig_w)
raw_upscaled_h = _round_pos_int(real_upscale * _in.orig_h)
extra_w = max(raw_upscaled_w - _in.up_w, 0)
extra_h = max(raw_upscaled_h - _in.up_h, 0)
crop_x_offset, crop_y_offset = _upscaled_crop_xy_offset(extra_w, extra_h, _in.align_x, _in.align_y)
return ResultUpscaledCropPad(
real_upscale,
True, _in.up_w, _in.up_h, crop_x_offset, crop_y_offset,
False, 0, 0, 0, 0,
)
def _upscaled_pad_side_values(extra_w: int, extra_h: int, align_x: float, align_y: float):
pad_left = _round_pos_int(align_x * extra_w)
pad_right = extra_w - pad_left
pad_bottom = _round_pos_int(align_y * extra_h)
pad_top = extra_h - pad_bottom
return pad_left, pad_top, pad_right, pad_bottom
def _upscaled_pad(_in: _CropPadInput, real_upscale: float) -> ResultUpscaledCropPad:
raw_upscaled_w = _round_pos_int(real_upscale * _in.orig_w)
raw_upscaled_h = _round_pos_int(real_upscale * _in.orig_h)
pad_w = max(_in.up_w - raw_upscaled_w, 0)
pad_h = max(_in.up_h - raw_upscaled_h, 0)
return ResultUpscaledCropPad(
real_upscale,
False, raw_upscaled_w, raw_upscaled_h, 0, 0,
True, *_upscaled_pad_side_values(pad_w, pad_h, _in.align_x, _in.align_y),
)
def _upscaled_crop_delta_pixels(_in: _CropPadInput, result: ResultUpscaledCropPad) -> int:
"""Calculate the area of total cropped patch for crop-only mode."""
raw_upscaled_w = _round_pos_int(result.upscale * _in.orig_w)
raw_upscaled_h = _round_pos_int(result.upscale * _in.orig_h)
cropped_w = min(result.crop_width, raw_upscaled_w)
cropped_h = min(result.crop_height, raw_upscaled_h)
return abs(raw_upscaled_w * raw_upscaled_h - cropped_w * cropped_h)
def _upscaled_pad_delta_pixels(_in: _CropPadInput, result: ResultUpscaledCropPad) -> int:
"""Calculate the area of total out-painted patch for pad-only mode."""
raw_upscaled_w = _round_pos_int(result.upscale * _in.orig_w)
raw_upscaled_h = _round_pos_int(result.upscale * _in.orig_h)
padded_w = raw_upscaled_w + max(result.pad_left, 0) + max(result.pad_right, 0)
padded_h = raw_upscaled_h + max(result.pad_top, 0) + max(result.pad_bottom, 0)
return abs(padded_w * padded_h - raw_upscaled_w * raw_upscaled_h)
def _upscaled_crop_pad(_in: _CropPadInput) -> ResultUpscaledCropPad:
"""The actual function for "Upscaled Crop/Pad" node - extracted to wrap it with displaying the message."""
needs_resize, real_upscale_avg, real_upscale_x, real_upscale_y = _need_post_resize(_in.orig_w, _in.orig_h, _in.up_w, _in.up_h)
if not needs_resize:
return ResultUpscaledCropPad(
real_upscale_avg,
False, _in.up_w, _in.up_h, 0, 0,
False, 0, 0, 0, 0,
)
_in.align_x = min(max(_in.align_x, 0.0), 1.0)
_in.align_y = min(max(_in.align_y, 0.0), 1.0)
if _in.strategy == UpscaledCropPadStrategy.CROP:
return _upscaled_crop(_in, max(real_upscale_x, real_upscale_y))
if _in.strategy == UpscaledCropPadStrategy.PAD:
return _upscaled_pad(_in, min(real_upscale_x, real_upscale_y))
if _in.strategy == UpscaledCropPadStrategy.NEAREST:
crop_result = _upscaled_crop(_in, max(real_upscale_x, real_upscale_y))
pad_result = _upscaled_pad(_in, min(real_upscale_x, real_upscale_y))
return (
pad_result
if _upscaled_pad_delta_pixels(_in, pad_result) < _upscaled_crop_delta_pixels(_in, crop_result)
else crop_result
)
assert _in.strategy == UpscaledCropPadStrategy.EXACT_UPSCALE
upscale = _in.upscale
raw_upscaled_w = _round_pos_int(upscale * _in.orig_w)
raw_upscaled_h = _round_pos_int(upscale * _in.orig_h)
# We're in the most complex case. These ^ can be both below and above the target up-res.
# So, we need to follow the whole upscale->crop->pad chain: we'll just get zeroes where there will be nothing to do.
cropped_w = min(_in.up_w, raw_upscaled_w)
cropped_h = min(_in.up_h, raw_upscaled_h)
crop_extra_w = raw_upscaled_w - cropped_w
crop_extra_h = raw_upscaled_h - cropped_h
crop_x_offset, crop_y_offset = _upscaled_crop_xy_offset(crop_extra_w, crop_extra_h, _in.align_x, _in.align_y)
pad_extra_w = _in.up_w - cropped_w
pad_extra_h = _in.up_h - cropped_h
pad_side_values = _upscaled_pad_side_values(pad_extra_w, pad_extra_h, _in.align_x, _in.align_y)
return ResultUpscaledCropPad(
upscale,
crop_extra_w > 0 or crop_extra_h > 0, cropped_w, cropped_h, crop_x_offset, crop_y_offset,
pad_extra_w > 0 or pad_extra_h > 0, *pad_side_values,
)
def upscaled_crop_pad(
upscale: float,
orig_w: int, orig_h: int, up_w: int, up_h: int,
strategy: str,
align_x: float, align_y: float,
show: bool = True,
unique_id: str = None
) -> ResultUpscaledCropPad:
"""
Detect, whether some cropping/out-painting needs to be done after upscaling the original image.
And if so - what are the values for them.
"""
# noinspection PyArgumentList
result = _upscaled_crop_pad(_CropPadInput(
upscale,
orig_w, orig_h, up_w, up_h,
strategy,
align_x, align_y,
))
if not unique_id:
return result
if not show:
_show_text_on_node(None, unique_id)
return result
if not(result.do_crop or result.do_padding):
_show_text_on_node(f"x{result.upscale:.3f}✅", unique_id)
return result
# Either crop, or pad, or both
text_parts: _t.List[str] = [f"x{result.upscale:.3f}", ]
if result.do_crop:
crop_r = up_w - (result.crop_x_origin + result.crop_width)
crop_b = up_h - (result.crop_y_origin + result.crop_height)
text_parts.append(f"Crop:\nL {result.crop_x_origin}, R {crop_r}, B {crop_b}, T {result.crop_y_origin}")
if result.do_padding:
text_parts.append(f"Padding:\nL {result.pad_left}, R {result.pad_right}, B {result.pad_bottom}, T {result.pad_top}")
_show_text_on_node('\n\n'.join(text_parts), unique_id)
return result
+3 -1
View File
@@ -45,9 +45,11 @@ class UpscaledCropPadStrategy(__BaseEnum):
• pad - upscale the image to fit it into the desired frame on one side,
then add missing pixels on the other one for outpaint.
• crop - upscale the image to fill the entire frame, then crop extra pixels on one side.
• nearest - automatically choose one of the above, to crop/pad the least number of pixels.
• nearest - automatically choose one of the above, to crop/pad the least number of pixels (by area).
• exact-upscale - follow the provided upscale-value precisely. This is the only option
that uses it. Also, it's the only one that might require both outpainting and crop.
In case when both are required, the output values assume this order: upscale -> crop -> pad.
"""
PAD = 'pad'
CROP = 'crop'
+1 -1
View File
@@ -11,7 +11,7 @@ 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 ._funcs_crop_pad import upscaled_crop_pad as _upscaled_crop_pad
from . import _meta
from .docstring_formatter import format_docstring as _format_docstring
from .enums import *
+1 -1
View File
@@ -77,7 +77,7 @@ _up_strategy_data_type_set = set(_up_strategy_data_type)
_up_strategy_in_type = (
_up_strategy_data_type,
{
'default': UpscaledCropPadStrategy.PAD.value,
'default': UpscaledCropPadStrategy.EXACT_UPSCALE.value,
'tooltip': _format_object_docstring(UpscaledCropPadStrategy),
}
)