-
Notifications
You must be signed in to change notification settings - Fork 1
Expand file tree
/
Copy pathresize_image.py
More file actions
364 lines (310 loc) · 13.4 KB
/
resize_image.py
File metadata and controls
364 lines (310 loc) · 13.4 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
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
import torch
import math
from comfy.utils import common_upscale
try:
import comfy.model_management as model_management
COMFY_AVAILABLE = True
except ImportError:
COMFY_AVAILABLE = False
MAX_RESOLUTION = 8192
class PixelForgeResizeImage:
"""
PixelForge Resize Image
Combines resolution selection with advanced resizing options.
"""
ASPECT_RATIOS = {
"1:1": (1, 1),
"3:2": (3, 2),
"4:3": (4, 3),
"16:9": (16, 9),
"16:10": (16, 10),
}
MP_BASE = 1024 * 1024
upscale_methods = ["nearest-exact", "bilinear", "area", "bicubic", "lanczos"]
@classmethod
def INPUT_TYPES(cls):
# Build all possible resolutions
all_resolutions = cls._build_all_resolutions()
return {
"required": {
"image": ("IMAGE",),
# Resolution Matrix parameters
"aspect_ratio": (
list(cls.ASPECT_RATIOS.keys()),
{"default": "1:1"},
),
"orientation": (
["landscape", "portrait", "square"],
{"default": "square"},
),
"divisible_by": (
[16, 32, 64],
{"default": 16},
),
"max_megapixels": (
["1 MP", "2 MP", "4 MP", "6 MP", "8 MP", "12 MP", "16 MP"],
{"default": "1 MP"},
),
"resolution": (all_resolutions, {"default": "1024×1024"}),
# KJ Resize v2 parameters
"upscale_method": (cls.upscale_methods, {"default": "lanczos"}),
"keep_proportion": (
["stretch", "resize", "pad", "pad_edge", "crop"],
{"default": "crop"}
),
"pad_color": ("STRING", {"default": "0, 0, 0", "tooltip": "RGB color for padding (e.g., '0, 0, 0' for black)"}),
"crop_position": (
["center", "top", "bottom", "left", "right"],
{"default": "center"}
),
},
"optional": {
"mask": ("MASK",),
},
}
@classmethod
def VALIDATE_INPUTS(cls, **kwargs):
return True
RETURN_TYPES = ("IMAGE", "INT", "INT", "MASK")
RETURN_NAMES = ("image", "width", "height", "mask")
FUNCTION = "resize"
CATEGORY = "PixelForge"
DESCRIPTION = """
Resizes images to selected resolution with advanced options.
Combines PixelForge resolution selection with KJNodes Resize v2 features.
"""
@classmethod
def _build_all_resolutions(cls):
"""Generate all possible resolutions across all aspect ratios and settings."""
unique_resolutions = set()
for aspect_name, (ratio_w, ratio_h) in cls.ASPECT_RATIOS.items():
for div in [16, 32, 64]:
for max_mp in [1, 2, 4, 6, 8, 12, 16]:
max_pixels = max_mp * cls.MP_BASE
k = div
while True:
w = ratio_w * k
h = ratio_h * k
total = w * h
if total > max_pixels:
break
unique_resolutions.add(f"{w}×{h}")
unique_resolutions.add(f"{h}×{w}")
k += div
sorted_resolutions = sorted(
unique_resolutions,
key=lambda r: (
int(r.split("×")[0]) * int(r.split("×")[1]),
int(r.split("×")[0])
)
)
return sorted_resolutions if sorted_resolutions else ["1024×1024"]
def resize(self, image, aspect_ratio, orientation, divisible_by, max_megapixels, resolution,
upscale_method, keep_proportion, pad_color, crop_position, mask=None):
from comfy.utils import ProgressBar
B, H, W, C = image.shape
# Initialize progress bar for large batches
pbar = None
if B >= 100:
pbar = ProgressBar(B)
# Parse target resolution from PixelForge parameters
target_width, target_height = map(int, resolution.split("×"))
# Apply orientation (handle square, portrait, landscape swapping)
if orientation == "portrait" and target_width > target_height:
target_width, target_height = target_height, target_width
elif orientation == "landscape" and target_width < target_height:
target_width, target_height = target_height, target_width
# For square (1:1), no swapping needed
# Parse pad color
try:
pad_rgb = [int(x.strip()) / 255.0 for x in pad_color.split(",")]
if len(pad_rgb) != 3:
pad_rgb = [0.0, 0.0, 0.0]
except:
pad_rgb = [0.0, 0.0, 0.0]
# Initialize padding variables
pad_left = pad_right = pad_top = pad_bottom = 0
width = target_width
height = target_height
# Calculate new dimensions based on keep_proportion mode
if keep_proportion in ["resize", "pad", "pad_edge"]:
# Calculate dimensions to maintain aspect ratio of INPUT image
ratio = min(target_width / W, target_height / H)
new_width = round(W * ratio)
new_height = round(H * ratio)
# Calculate padding if needed
if keep_proportion in ["pad", "pad_edge"]:
if crop_position == "center":
pad_left = (target_width - new_width) // 2
pad_right = target_width - new_width - pad_left
pad_top = (target_height - new_height) // 2
pad_bottom = target_height - new_height - pad_top
elif crop_position == "top":
pad_left = (target_width - new_width) // 2
pad_right = target_width - new_width - pad_left
pad_top = 0
pad_bottom = target_height - new_height
elif crop_position == "bottom":
pad_left = (target_width - new_width) // 2
pad_right = target_width - new_width - pad_left
pad_top = target_height - new_height
pad_bottom = 0
elif crop_position == "left":
pad_left = 0
pad_right = target_width - new_width
pad_top = (target_height - new_height) // 2
pad_bottom = target_height - new_height - pad_top
elif crop_position == "right":
pad_left = target_width - new_width
pad_right = 0
pad_top = (target_height - new_height) // 2
pad_bottom = target_height - new_height - pad_top
width = new_width
height = new_height
# Crop logic
if keep_proportion == "crop":
old_height = H
old_width = W
old_aspect = old_width / old_height
new_aspect = target_width / target_height
if old_aspect > new_aspect:
crop_w = round(old_height * new_aspect)
crop_h = old_height
else:
crop_w = old_width
crop_h = round(old_width / new_aspect)
# Calculate crop position
if crop_position == "center":
x = (old_width - crop_w) // 2
y = (old_height - crop_h) // 2
elif crop_position == "top":
x = (old_width - crop_w) // 2
y = 0
elif crop_position == "bottom":
x = (old_width - crop_w) // 2
y = old_height - crop_h
elif crop_position == "left":
x = 0
y = (old_height - crop_h) // 2
elif crop_position == "right":
x = old_width - crop_w
y = (old_height - crop_h) // 2
# Apply crop
image = image[:, y:y+crop_h, x:x+crop_w, :]
if mask is not None:
mask = mask[:, y:y+crop_h, x:x+crop_w]
# After crop, resize to target
width = target_width
height = target_height
# Process images with progress tracking for large batches
if pbar is not None:
# Process images one by one for progress tracking
resized_images = []
resized_masks = []
for i in range(B):
single_image = image[i:i+1]
single_mask = mask[i:i+1] if mask is not None else None
# Resize single image
resized_single = common_upscale(
single_image.movedim(-1, 1),
width,
height,
upscale_method,
crop="disabled"
).movedim(1, -1)
resized_images.append(resized_single)
# Resize mask if present
if single_mask is not None:
if upscale_method == "lanczos":
resized_single_mask = common_upscale(
single_mask.unsqueeze(1).repeat(1, 3, 1, 1),
width,
height,
upscale_method,
crop="disabled"
).movedim(1, -1)[:, :, :, 0]
else:
resized_single_mask = common_upscale(
single_mask.unsqueeze(1),
width,
height,
upscale_method,
crop="disabled"
).squeeze(1)
resized_masks.append(resized_single_mask)
pbar.update(1)
resized_image = torch.cat(resized_images, dim=0)
resized_mask = torch.cat(resized_masks, dim=0) if resized_masks else None
else:
# Process entire batch at once for small batches
resized_image = common_upscale(
image.movedim(-1, 1),
width,
height,
upscale_method,
crop="disabled"
).movedim(1, -1)
# Resize mask if present
resized_mask = None
if mask is not None:
if upscale_method == "lanczos":
resized_mask = common_upscale(
mask.unsqueeze(1).repeat(1, 3, 1, 1),
width,
height,
upscale_method,
crop="disabled"
).movedim(1, -1)[:, :, :, 0]
else:
resized_mask = common_upscale(
mask.unsqueeze(1),
width,
height,
upscale_method,
crop="disabled"
).squeeze(1)
# Apply padding if needed
if (keep_proportion in ["pad", "pad_edge"]) and (pad_left > 0 or pad_right > 0 or pad_top > 0 or pad_bottom > 0):
# Create padded image
if keep_proportion == "pad_edge":
# Edge padding (replicate edge pixels)
resized_image = torch.nn.functional.pad(
resized_image.permute(0, 3, 1, 2),
(pad_left, pad_right, pad_top, pad_bottom),
mode='replicate'
).permute(0, 2, 3, 1)
else:
# Color padding
pad_tensor = torch.tensor(pad_rgb, device=resized_image.device, dtype=resized_image.dtype)
padded_image = pad_tensor.view(1, 1, 1, 3).expand(
B,
height + pad_top + pad_bottom,
width + pad_left + pad_right,
C
)
padded_image[:, pad_top:pad_top+height, pad_left:pad_left+width, :] = resized_image
resized_image = padded_image
# Pad mask if present
if resized_mask is not None:
resized_mask = torch.nn.functional.pad(
resized_mask.unsqueeze(1),
(pad_left, pad_right, pad_top, pad_bottom),
mode='constant',
value=0.0
).squeeze(1)
# Create default mask if none provided
if resized_mask is None:
resized_mask = torch.zeros(
(B, resized_image.shape[1], resized_image.shape[2]),
device=resized_image.device,
dtype=torch.float32
)
final_width = resized_image.shape[2]
final_height = resized_image.shape[1]
return (resized_image, final_width, final_height, resized_mask)
NODE_CLASS_MAPPINGS = {
"PixelForgeResizeImage": PixelForgeResizeImage
}
NODE_DISPLAY_NAME_MAPPINGS = {
"PixelForgeResizeImage": "PixelForge · Resize Image"
}