-
Notifications
You must be signed in to change notification settings - Fork 1
Expand file tree
/
Copy pathnodes_simple.py
More file actions
246 lines (208 loc) · 7.9 KB
/
nodes_simple.py
File metadata and controls
246 lines (208 loc) · 7.9 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
# encoding: utf-8
"""
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.comfy_types.node_typing import IO as _IO
from ._funcs import (
aspect_ratios_sorted as _aspect_ratios_sorted,
number_to_int as _number_to_int,
float_width_height_from_area as _float_width_height_from_area,
simple_result_from_approx_wh as _simple_result_from_approx_wh
)
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:
"""
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__))
OUTPUT_NODE = True
FUNCTION = 'main'
RETURN_TYPES = _return_types_simple
# OUTPUT_TOOLTIPS = {}
RETURN_NAMES = _return_names_simple
@classmethod
def INPUT_TYPES(cls):
return _input_types_simple
def main(
self, width: int, height: int, step: int,
# show: bool,
unique_id: str = None
):
return _simple_result_from_approx_wh(
float(width), height, step,
# show,
unique_id=unique_id, target_square_size=_sqrt(width * height)
)
# ----------------------------------------------------------
_tooltip_aspect = (
"Two aspects together define an aspect ratio (16:9, 4:3, etc).\n"
"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:
"""
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__))
OUTPUT_NODE = True
FUNCTION = 'main'
RETURN_TYPES = _return_types_simple
RETURN_NAMES = _return_names_simple
@classmethod
def INPUT_TYPES(cls):
return _input_types_orient
def main(
self,
size: int, step: int, size_is_big: bool, landscape: bool, aspect_a: float, aspect_b: float,
# show: bool,
unique_id: str = None
):
aspect_big, aspect_small = _aspect_ratios_sorted(aspect_a, aspect_b)
side_main_f = float(size)
side_other_f = side_main_f * (
(aspect_small / aspect_big) if size_is_big else (aspect_big / aspect_small)
)
width_f, height_f = (side_other_f, side_main_f) if side_other_f > side_main_f else (side_main_f, side_other_f)
if not landscape:
# The opposite: height is bigger
width_f, height_f = height_f, width_f
return _simple_result_from_approx_wh(
width_f, height_f, step,
# show,
unique_id=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:
"""
The most efficient way of selecting an optimal resolution:
image size selected indirectly - by the total desired resolution (area) + aspect ratio.
Desired resolution (aka image area/megapixels/pixel count) is specified with a side of a square image. This isn't
accidental: most models disclose what image resolution they're trained on, and usually they're square:
- SD 1.5 - 512x512 pixels
- SDXL - 1024x1024 pixels
By simply providing this single number and setting your aspect ratio/orientation, you get the width and height to
produce the closest total resolution to the training set, while also respecting image proportions and step-rounding.
"""
NODE_NAME = 'BestResolutionFromArea'
CATEGORY = _meta.category
DESCRIPTION = _format_docstring(_cleandoc(__doc__))
OUTPUT_NODE = True
FUNCTION = 'main'
RETURN_TYPES = _return_types_simple
RETURN_NAMES = _return_names_simple
@classmethod
def INPUT_TYPES(cls):
return _input_types_area
def main(
self,
square_size: int, step: int, landscape: bool, aspect_a: float, aspect_b: float,
# 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 _simple_result_from_approx_wh(
width_f, height_f, step,
# show,
unique_id=unique_id, target_square_size=square_size
)