|
|
import torch |
|
|
import torchvision.transforms.functional as TF |
|
|
from PIL import Image, ImageDraw |
|
|
|
|
|
from .Utils import get_sampler_by_name |
|
|
|
|
|
|
|
|
class ImageTransformResizeAbsolute: |
|
|
def __init__(self): |
|
|
pass |
|
|
|
|
|
@classmethod |
|
|
def INPUT_TYPES(cls): |
|
|
return { |
|
|
"required": { |
|
|
"images": ("IMAGE",), |
|
|
"width": ("INT", { |
|
|
"default": 256, |
|
|
"min": 1, |
|
|
"step": 1 |
|
|
}), |
|
|
"height": ("INT", { |
|
|
"default": 256, |
|
|
"min": 1, |
|
|
"step": 1 |
|
|
}), |
|
|
"method": (["lanczos", "bicubic", "hamming", "bilinear", "box", "nearest"],), |
|
|
}, |
|
|
} |
|
|
|
|
|
RETURN_TYPES = ("IMAGE",) |
|
|
FUNCTION = "node" |
|
|
CATEGORY = "image/transform" |
|
|
|
|
|
def node(self, images, width, height, method): |
|
|
def resize_tensor(tensor): |
|
|
return tensor.tensor_to_image().resize((width, height), get_sampler_by_name(method)).image_to_tensor() |
|
|
|
|
|
return (torch.stack([ |
|
|
resize_tensor(images[i]) for i in range(len(images)) |
|
|
]),) |
|
|
|
|
|
|
|
|
class ImageTransformResizeRelative: |
|
|
def __init__(self): |
|
|
pass |
|
|
|
|
|
@classmethod |
|
|
def INPUT_TYPES(cls): |
|
|
return { |
|
|
"required": { |
|
|
"images": ("IMAGE",), |
|
|
"scale_width": ("FLOAT", { |
|
|
"default": 1.0, |
|
|
"step": 0.1 |
|
|
}), |
|
|
"scale_height": ("FLOAT", { |
|
|
"default": 1.0, |
|
|
"step": 0.1 |
|
|
}), |
|
|
"method": (["lanczos", "bicubic", "hamming", "bilinear", "box", "nearest"],), |
|
|
}, |
|
|
} |
|
|
|
|
|
RETURN_TYPES = ("IMAGE",) |
|
|
FUNCTION = "node" |
|
|
CATEGORY = "image/transform" |
|
|
|
|
|
def node(self, images, scale_width, scale_height, method): |
|
|
height, width = images[0, :, :, 0].shape |
|
|
|
|
|
width = int(width * scale_width) |
|
|
height = int(height * scale_height) |
|
|
|
|
|
return ImageTransformResizeAbsolute().node(images, width, height, method) |
|
|
|
|
|
|
|
|
class ImageTransformResizeClip: |
|
|
def __init__(self): |
|
|
pass |
|
|
|
|
|
@classmethod |
|
|
def INPUT_TYPES(cls): |
|
|
return { |
|
|
"required": { |
|
|
"images": ("IMAGE",), |
|
|
"max_width": ("INT", { |
|
|
"default": 1024, |
|
|
}), |
|
|
"max_height": ("INT", { |
|
|
"default": 1024, |
|
|
}), |
|
|
"min_width": ("INT", { |
|
|
"default": 0, |
|
|
}), |
|
|
"min_height": ("INT", { |
|
|
"default": 0, |
|
|
}), |
|
|
"method": (["lanczos", "bicubic", "hamming", "bilinear", "box", "nearest"],), |
|
|
}, |
|
|
} |
|
|
|
|
|
RETURN_TYPES = ("IMAGE",) |
|
|
FUNCTION = "node" |
|
|
CATEGORY = "image/transform" |
|
|
|
|
|
def node(self, images, max_width, max_height, min_width, min_height, method): |
|
|
height, width = images[0, :, :, 0].shape |
|
|
|
|
|
if min_width >= max_width or min_height >= max_height: |
|
|
return (images,) |
|
|
|
|
|
scale_min = max(min_width / width, min_height / height) |
|
|
scale_max = min(max_width / width, max_height / height) |
|
|
|
|
|
scale = max(scale_min, scale_max) |
|
|
|
|
|
return ImageTransformResizeRelative().node(images, scale, scale, method) |
|
|
|
|
|
|
|
|
class ImageTransformCropAbsolute: |
|
|
def __init__(self): |
|
|
pass |
|
|
|
|
|
@classmethod |
|
|
def INPUT_TYPES(cls): |
|
|
return { |
|
|
"required": { |
|
|
"images": ("IMAGE",), |
|
|
"start_x": ("INT", { |
|
|
"default": 0, |
|
|
"step": 1 |
|
|
}), |
|
|
"start_y": ("INT", { |
|
|
"default": 0, |
|
|
"step": 1 |
|
|
}), |
|
|
"end_x": ("INT", { |
|
|
"default": 128, |
|
|
"step": 1 |
|
|
}), |
|
|
"end_y": ("INT", { |
|
|
"default": 128, |
|
|
"step": 1 |
|
|
}), |
|
|
}, |
|
|
} |
|
|
|
|
|
RETURN_TYPES = ("IMAGE",) |
|
|
FUNCTION = "node" |
|
|
CATEGORY = "image/transform" |
|
|
|
|
|
def node(self, images, start_x, start_y, end_x, end_y): |
|
|
def resize_tensor(tensor): |
|
|
return tensor.tensor_to_image().crop([start_x, start_y, end_x, end_y]).image_to_tensor() |
|
|
|
|
|
return (torch.stack([ |
|
|
resize_tensor(images[i]) for i in range(len(images)) |
|
|
]),) |
|
|
|
|
|
|
|
|
class ImageTransformCropRelative: |
|
|
def __init__(self): |
|
|
pass |
|
|
|
|
|
@classmethod |
|
|
def INPUT_TYPES(cls): |
|
|
return { |
|
|
"required": { |
|
|
"images": ("IMAGE",), |
|
|
"start_x": ("FLOAT", { |
|
|
"default": 0.25, |
|
|
"max": 1.0, |
|
|
"step": 0.01 |
|
|
}), |
|
|
"start_y": ("FLOAT", { |
|
|
"default": 0.25, |
|
|
"max": 1.0, |
|
|
"step": 0.01 |
|
|
}), |
|
|
"end_x": ("FLOAT", { |
|
|
"default": 0.75, |
|
|
"max": 1.0, |
|
|
"step": 0.01 |
|
|
}), |
|
|
"end_y": ("FLOAT", { |
|
|
"default": 0.75, |
|
|
"max": 1.0, |
|
|
"step": 0.01 |
|
|
}), |
|
|
}, |
|
|
} |
|
|
|
|
|
RETURN_TYPES = ("IMAGE",) |
|
|
FUNCTION = "node" |
|
|
CATEGORY = "image/transform" |
|
|
|
|
|
def node(self, images, start_x, start_y, end_x, end_y): |
|
|
height, width = images[0, :, :, 0].shape |
|
|
|
|
|
return ImageTransformCropAbsolute().node( |
|
|
images, |
|
|
width * start_x, |
|
|
height * start_y, |
|
|
width * end_x, |
|
|
height * end_y |
|
|
) |
|
|
|
|
|
|
|
|
class ImageTransformCropCorners: |
|
|
def __init__(self): |
|
|
pass |
|
|
|
|
|
@classmethod |
|
|
def INPUT_TYPES(cls): |
|
|
return { |
|
|
"required": { |
|
|
"images": ("IMAGE",), |
|
|
"radius": ("INT", { |
|
|
"default": 180, |
|
|
"max": 360, |
|
|
"step": 1 |
|
|
}), |
|
|
"top_left_corner": (["true", "false"],), |
|
|
"top_right_corner": (["true", "false"],), |
|
|
"bottom_right_corner": (["true", "false"],), |
|
|
"bottom_left_corner": (["true", "false"],), |
|
|
"SSAA": ("INT", { |
|
|
"default": 4, |
|
|
"min": 1, |
|
|
"max": 16, |
|
|
"step": 1 |
|
|
}), |
|
|
"method": (["lanczos", "bicubic", "hamming", "bilinear", "box", "nearest"],), |
|
|
}, |
|
|
} |
|
|
|
|
|
RETURN_TYPES = ("IMAGE",) |
|
|
FUNCTION = "node" |
|
|
CATEGORY = "image/transform" |
|
|
|
|
|
|
|
|
def node( |
|
|
self, |
|
|
images, |
|
|
radius, |
|
|
top_left_corner, |
|
|
top_right_corner, |
|
|
bottom_right_corner, |
|
|
bottom_left_corner, |
|
|
SSAA, |
|
|
method |
|
|
): |
|
|
sampler = get_sampler_by_name(method) |
|
|
|
|
|
height, width = images[0, :, :, 0].shape |
|
|
|
|
|
canvas = Image.new("RGBA", (width * SSAA, height * SSAA), (0, 0, 0, 0)) |
|
|
draw = ImageDraw.Draw(canvas) |
|
|
|
|
|
draw.rounded_rectangle( |
|
|
((0, 0), (width * SSAA, height * SSAA)), |
|
|
radius * SSAA, (255, 255, 255, 255), |
|
|
corners=( |
|
|
True if top_left_corner == "true" else False, |
|
|
True if top_right_corner == "true" else False, |
|
|
True if bottom_right_corner == "true" else False, |
|
|
True if bottom_left_corner == "true" else False |
|
|
) |
|
|
) |
|
|
|
|
|
canvas = canvas.resize((width, height), sampler) |
|
|
mask = 1.0 - canvas.image_to_tensor()[:, :, 3] |
|
|
|
|
|
def crop_tensor(tensor): |
|
|
return torch.stack([ |
|
|
(tensor[:, :, i] - mask).clamp(0, 1) for i in range(tensor.shape[2]) |
|
|
], dim=2) |
|
|
|
|
|
return (torch.stack([ |
|
|
crop_tensor(images[i]) for i in range(len(images)) |
|
|
]),) |
|
|
|
|
|
|
|
|
class ImageTransformPaddingAbsolute: |
|
|
def __init__(self): |
|
|
pass |
|
|
|
|
|
@classmethod |
|
|
def INPUT_TYPES(cls): |
|
|
return { |
|
|
"required": { |
|
|
"images": ("IMAGE",), |
|
|
"add_width": ("INT", { |
|
|
"default": 64, |
|
|
"min": 0, |
|
|
}), |
|
|
"add_height": ("INT", { |
|
|
"default": 64, |
|
|
"min": 0, |
|
|
}), |
|
|
"method": (["reflect", "edge", "constant"],), |
|
|
}, |
|
|
} |
|
|
|
|
|
RETURN_TYPES = ("IMAGE",) |
|
|
FUNCTION = "node" |
|
|
CATEGORY = "image/transform" |
|
|
|
|
|
def node(self, images, add_width, add_height, method): |
|
|
def transpose_tensor(image): |
|
|
tensor = image.clone().detach() |
|
|
tensor_pad = TF.pad(tensor.permute(2, 0, 1), [add_height, add_width], padding_mode=method).permute(1, 2, 0) |
|
|
|
|
|
return tensor_pad |
|
|
|
|
|
return (torch.stack([ |
|
|
transpose_tensor(images[i]) for i in range(len(images)) |
|
|
]),) |
|
|
|
|
|
|
|
|
class ImageTransformPaddingRelative: |
|
|
def __init__(self): |
|
|
pass |
|
|
|
|
|
@classmethod |
|
|
def INPUT_TYPES(cls): |
|
|
return { |
|
|
"required": { |
|
|
"images": ("IMAGE",), |
|
|
"scale_width": ("FLOAT", { |
|
|
"default": 0.25, |
|
|
"step": 0.1, |
|
|
}), |
|
|
"scale_height": ("FLOAT", { |
|
|
"default": 0.25, |
|
|
"step": 0.1, |
|
|
}), |
|
|
"method": (["reflect", "edge", "constant"],), |
|
|
}, |
|
|
} |
|
|
|
|
|
RETURN_TYPES = ("IMAGE",) |
|
|
FUNCTION = "node" |
|
|
CATEGORY = "image/transform" |
|
|
|
|
|
def node(self, images, scale_width, scale_height, method): |
|
|
height, width = images[0, :, :, 0].shape |
|
|
|
|
|
add_width = int(width * scale_width) |
|
|
add_height = int(height * scale_height) |
|
|
|
|
|
return ImageTransformPaddingAbsolute().node(images, add_width, add_height, method) |
|
|
|
|
|
|
|
|
class ImageTransformRotate: |
|
|
def __init__(self): |
|
|
pass |
|
|
|
|
|
@classmethod |
|
|
def INPUT_TYPES(cls): |
|
|
return { |
|
|
"required": { |
|
|
"images": ("IMAGE",), |
|
|
"angle": ("FLOAT", { |
|
|
"default": 35.0, |
|
|
"max": 360.0, |
|
|
"step": 0.1 |
|
|
}), |
|
|
"expand": (["true", "false"],), |
|
|
"SSAA": ("INT", { |
|
|
"default": 4, |
|
|
"min": 1, |
|
|
"max": 16, |
|
|
"step": 1 |
|
|
}), |
|
|
"method": (["lanczos", "bicubic", "hamming", "bilinear", "box", "nearest"],), |
|
|
}, |
|
|
} |
|
|
|
|
|
RETURN_TYPES = ("IMAGE",) |
|
|
FUNCTION = "node" |
|
|
CATEGORY = "image/transform" |
|
|
|
|
|
def node(self, images, angle, expand, SSAA, method): |
|
|
height, width = images[0, :, :, 0].shape |
|
|
|
|
|
def rotate_tensor(tensor): |
|
|
if method == "lanczos": |
|
|
resize_sampler = Image.LANCZOS |
|
|
rotate_sampler = Image.BICUBIC |
|
|
elif method == "bicubic": |
|
|
resize_sampler = Image.BICUBIC |
|
|
rotate_sampler = Image.BICUBIC |
|
|
elif method == "hamming": |
|
|
resize_sampler = Image.HAMMING |
|
|
rotate_sampler = Image.BILINEAR |
|
|
elif method == "bilinear": |
|
|
resize_sampler = Image.BILINEAR |
|
|
rotate_sampler = Image.BILINEAR |
|
|
elif method == "box": |
|
|
resize_sampler = Image.BOX |
|
|
rotate_sampler = Image.NEAREST |
|
|
elif method == "nearest": |
|
|
resize_sampler = Image.NEAREST |
|
|
rotate_sampler = Image.NEAREST |
|
|
else: |
|
|
raise ValueError() |
|
|
|
|
|
if SSAA > 1: |
|
|
img = tensor.tensor_to_image() |
|
|
img_us_scaled = img.resize((width * SSAA, height * SSAA), resize_sampler) |
|
|
img_rotated = img_us_scaled.rotate(angle, rotate_sampler, expand == "true", fillcolor=(0, 0, 0, 0)) |
|
|
img_down_scaled = img_rotated.resize((img_rotated.width // SSAA, img_rotated.height // SSAA), resize_sampler) |
|
|
result = img_down_scaled.image_to_tensor() |
|
|
else: |
|
|
img = tensor.tensor_to_image() |
|
|
img_rotated = img.rotate(angle, rotate_sampler, expand == "true", fillcolor=(0, 0, 0, 0)) |
|
|
result = img_rotated.image_to_tensor() |
|
|
|
|
|
return result |
|
|
|
|
|
if angle == 0.0 or angle == 360.0: |
|
|
return (images,) |
|
|
else: |
|
|
return (torch.stack([ |
|
|
rotate_tensor(images[i]) for i in range(len(images)) |
|
|
]),) |
|
|
|
|
|
|
|
|
class ImageTransformTranspose: |
|
|
def __init__(self): |
|
|
pass |
|
|
|
|
|
@classmethod |
|
|
def INPUT_TYPES(cls): |
|
|
return { |
|
|
"required": { |
|
|
"images": ("IMAGE",), |
|
|
"method": (["flip_horizontally", "flip_vertically", "rotate_90", "rotate_180", "rotate_270", "transpose", "transverse"],), |
|
|
}, |
|
|
} |
|
|
|
|
|
RETURN_TYPES = ("IMAGE",) |
|
|
FUNCTION = "node" |
|
|
CATEGORY = "image/transform" |
|
|
|
|
|
def node(self, images, method): |
|
|
def transpose_tensor(tensor): |
|
|
if method == "flip_horizontally": |
|
|
transpose = Image.FLIP_LEFT_RIGHT |
|
|
elif method == "flip_vertically": |
|
|
transpose = Image.FLIP_TOP_BOTTOM |
|
|
elif method == "rotate_90": |
|
|
transpose = Image.ROTATE_90 |
|
|
elif method == "rotate_180": |
|
|
transpose = Image.ROTATE_180 |
|
|
elif method == "rotate_270": |
|
|
transpose = Image.ROTATE_270 |
|
|
elif method == "transpose": |
|
|
transpose = Image.TRANSPOSE |
|
|
elif method == "transverse": |
|
|
transpose = Image.TRANSVERSE |
|
|
else: |
|
|
raise ValueError() |
|
|
|
|
|
return tensor.tensor_to_image().transpose(transpose).image_to_tensor() |
|
|
|
|
|
return (torch.stack([ |
|
|
transpose_tensor(images[i]) for i in range(len(images)) |
|
|
]),) |
|
|
|
|
|
|
|
|
NODE_CLASS_MAPPINGS = { |
|
|
"ImageTransformResizeAbsolute": ImageTransformResizeAbsolute, |
|
|
"ImageTransformResizeRelative": ImageTransformResizeRelative, |
|
|
"ImageTransformResizeClip": ImageTransformResizeClip, |
|
|
"ImageTransformCropAbsolute": ImageTransformCropAbsolute, |
|
|
"ImageTransformCropRelative": ImageTransformCropRelative, |
|
|
"ImageTransformCropCorners": ImageTransformCropCorners, |
|
|
"ImageTransformPaddingAbsolute": ImageTransformPaddingAbsolute, |
|
|
"ImageTransformPaddingRelative": ImageTransformPaddingRelative, |
|
|
"ImageTransformRotate": ImageTransformRotate, |
|
|
"ImageTransformTranspose": ImageTransformTranspose |
|
|
} |
|
|
|