Add ImageTransform module
This commit is contained in:
@@ -38,18 +38,20 @@ class Loader:
|
||||
def setup_override(self):
|
||||
override_nodes_len = 0
|
||||
|
||||
if self.config()["override"]["postprocessing"]:
|
||||
def override(function):
|
||||
start_len = nodes.NODE_CLASS_MAPPINGS.__len__()
|
||||
|
||||
nodes.NODE_CLASS_MAPPINGS = dict(
|
||||
filter(
|
||||
lambda item: not item[1].CATEGORY.startswith("image/postprocessing"),
|
||||
nodes.NODE_CLASS_MAPPINGS.items()
|
||||
)
|
||||
filter(function, nodes.NODE_CLASS_MAPPINGS.items())
|
||||
)
|
||||
|
||||
end_len = nodes.NODE_CLASS_MAPPINGS.__len__()
|
||||
override_nodes_len += start_len - end_len
|
||||
return start_len - nodes.NODE_CLASS_MAPPINGS.__len__()
|
||||
|
||||
if self.config()["override"]["postprocessing"]:
|
||||
override_nodes_len += override(lambda item: not item[1].CATEGORY.startswith("image/postprocessing"))
|
||||
|
||||
if self.config()["override"]["transform"]:
|
||||
override_nodes_len += override(lambda item: not item[0] == "ImageScale" and not item[0] == "ImageInvert")
|
||||
|
||||
self.__log(str(override_nodes_len) + " standard nodes was overridden.")
|
||||
|
||||
@@ -88,6 +90,10 @@ class Loader:
|
||||
from .modules import ImageText
|
||||
modules.update(ImageText.NODE_CLASS_MAPPINGS)
|
||||
|
||||
if self.config()["modules"]["ImageTransform"]:
|
||||
from .modules import ImageTransform
|
||||
modules.update(ImageTransform.NODE_CLASS_MAPPINGS)
|
||||
|
||||
modules_len = dict(
|
||||
filter(
|
||||
lambda item: item[1],
|
||||
|
||||
@@ -639,6 +639,79 @@ You also can change the fonts folder in config.
|
||||
|
||||
</details>
|
||||
|
||||
---
|
||||
### Image Transform
|
||||

|
||||
> Transform your images.
|
||||
|
||||
<details>
|
||||
<summary>Nodes:</summary>
|
||||
|
||||
### Resize
|
||||

|
||||
> Change size of images.
|
||||
|
||||
<details>
|
||||
<summary>Params:</summary>
|
||||
|
||||
#### Absolute
|
||||
* [width, height] `[1 - *]` - New size of images.
|
||||
|
||||
#### Relative
|
||||
* scale_[width, height] `[0.0 - 1.0]` - New size of images.
|
||||
|
||||
</details>
|
||||
|
||||
### Crop
|
||||

|
||||
> Returns area from images.
|
||||
|
||||
<details>
|
||||
<summary>Params:</summary>
|
||||
|
||||
#### Absolute
|
||||
* start_[x, y] `[1 - *]` - Start of rectangle point.
|
||||
* end_[x, y] `[1 - *]` - End of rectangle point.
|
||||
|
||||
#### Relative
|
||||
* start_[x, y] `[0.0 - 1.0]` - Start of rectangle point.
|
||||
* end_[x, y] `[0.0 - 1.0]` - End of rectangle point.
|
||||
|
||||
</details>
|
||||
|
||||
### Crop Corners
|
||||

|
||||
> Round corners of your images.
|
||||
|
||||
<details>
|
||||
<summary>Params:</summary>
|
||||
|
||||
* radius `[0 -360]` - Radius of the corners.
|
||||
* [top_left, top_right, bottom_right, bottom_left]_corner `[boolean]` - The ability to choose for which angle to apply the radius.
|
||||
* SSAO [1 - 16] - [Super Sampling Anti-Aliasing](https://en.wikipedia.org/wiki/Supersampling). The figure is drawn initially at a higher resolution, and then compressed to the specified resolution.
|
||||
|
||||
</details>
|
||||
|
||||
|
||||
### Rotate
|
||||

|
||||
> Rotate your images.
|
||||
|
||||
<details>
|
||||
<summary>Params:</summary>
|
||||
|
||||
* angle `[0 -360]` - Angle in degrees. Angles are measured from 3 o’clock, increasing clockwise.
|
||||
* expand `[boolean]` - If "true" when rotating, change the size of the image to fit into it.
|
||||
* SSAO [1 - 16] - [Super Sampling Anti-Aliasing](https://en.wikipedia.org/wiki/Supersampling). The figure is drawn initially at a higher resolution, and then compressed to the specified resolution.
|
||||
|
||||
</details>
|
||||
|
||||
### Transpose
|
||||

|
||||
> Transpose your images.
|
||||
|
||||
</details>
|
||||
|
||||
---
|
||||
### Clamp
|
||||

|
||||
@@ -670,6 +743,8 @@ You can configurate Allor with `config.json`.
|
||||
* fonts_folder_path - Array with a relative path to fonts folder, by default is `["comfy_extras", "fonts"]`. (Converted to `ComfyUI/comfy_extras/fonts` for Unix and `ComfyUI\comfy_extras\fonts` for Windows).
|
||||
* modules - You can disable (or enable) modules at will. Nodes from disabled modules will not be loaded during the start of ComfyUI.
|
||||
* override - If the nested value is set to `true`, similar functionality from the original nodes is disabled.
|
||||
* * postprocessing - Disable `image/postprocessing` nodes. (See `ImageFilter` module)
|
||||
* * transform - Disable `ImageScale` and `ImageInvert` nodes. (See `ImageTransform` module)
|
||||
|
||||
## Examples:
|
||||

|
||||
|
||||
+4
-2
@@ -8,9 +8,11 @@
|
||||
"ImageDraw": true,
|
||||
"ImageFilter": true,
|
||||
"ImageSegmentation": true,
|
||||
"ImageText": true
|
||||
"ImageText": true,
|
||||
"ImageTransform": true
|
||||
},
|
||||
"override" : {
|
||||
"postprocessing": true
|
||||
"postprocessing": true,
|
||||
"transform": true
|
||||
}
|
||||
}
|
||||
|
||||
@@ -161,7 +161,7 @@ class ImageContainerInheritanceScale:
|
||||
CATEGORY = "image/container"
|
||||
|
||||
def image_container_inheritance_scale(self, images, scale_width, scale_height, red, green, blue, alpha, method):
|
||||
width, height = images[0, :, :, 0].shape
|
||||
height, width = images[0, :, :, 0].shape
|
||||
|
||||
width = int((width * scale_width) - width)
|
||||
height = int((height * scale_height) - height)
|
||||
|
||||
@@ -1520,7 +1520,7 @@ class ImageDrawRectangleRounded:
|
||||
(width * start_x * SSAA, height * start_y * SSAA),
|
||||
(width * end_x * SSAA, height * end_y * SSAA)
|
||||
),
|
||||
radius,
|
||||
radius * SSAA,
|
||||
(fill_red, fill_green, fill_blue, int(fill_alpha * 255)),
|
||||
(outline_red, outline_green, outline_blue, int(outline_alpha * 255)),
|
||||
outline_size * SSAA,
|
||||
|
||||
@@ -0,0 +1,393 @@
|
||||
import torch
|
||||
from PIL import Image, ImageDraw
|
||||
|
||||
|
||||
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 = "image_transform_resize_absolute"
|
||||
CATEGORY = "image/transform"
|
||||
|
||||
def image_transform_resize_absolute(self, images, width, height, method):
|
||||
def resize_tensor(tensor):
|
||||
if method == "lanczos":
|
||||
sampler = Image.LANCZOS
|
||||
elif method == "bicubic":
|
||||
sampler = Image.BICUBIC
|
||||
elif method == "hamming":
|
||||
sampler = Image.HAMMING
|
||||
elif method == "bilinear":
|
||||
sampler = Image.BILINEAR
|
||||
elif method == "box":
|
||||
sampler = Image.BOX
|
||||
elif method == "nearest":
|
||||
sampler = Image.NEAREST
|
||||
else:
|
||||
raise ValueError()
|
||||
|
||||
return tensor.tensor_to_image().resize((width, height), sampler).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 = "image_transform_resize_relative"
|
||||
CATEGORY = "image/transform"
|
||||
|
||||
def image_transform_resize_relative(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().image_transform_resize_absolute(images, width, height, 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 = "image_transform_crop_absolute"
|
||||
CATEGORY = "image/transform"
|
||||
|
||||
def image_transform_crop_absolute(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 = "image_transform_crop_relative"
|
||||
CATEGORY = "image/transform"
|
||||
|
||||
def image_transform_crop_relative(self, images, start_x, start_y, end_x, end_y):
|
||||
height, width = images[0, :, :, 0].shape
|
||||
|
||||
return ImageTransformCropAbsolute().image_transform_crop_absolute(
|
||||
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 = "image_transform_crop_corners"
|
||||
CATEGORY = "image/transform"
|
||||
|
||||
# noinspection PyUnresolvedReferences, PyArgumentList
|
||||
def image_transform_crop_corners(
|
||||
self,
|
||||
images,
|
||||
radius,
|
||||
top_left_corner,
|
||||
top_right_corner,
|
||||
bottom_right_corner,
|
||||
bottom_left_corner,
|
||||
SSAA,
|
||||
method
|
||||
):
|
||||
if method == "lanczos":
|
||||
sampler = Image.LANCZOS
|
||||
elif method == "bicubic":
|
||||
sampler = Image.BICUBIC
|
||||
elif method == "hamming":
|
||||
sampler = Image.HAMMING
|
||||
elif method == "bilinear":
|
||||
sampler = Image.BILINEAR
|
||||
elif method == "box":
|
||||
sampler = Image.BOX
|
||||
elif method == "nearest":
|
||||
sampler = Image.NEAREST
|
||||
else:
|
||||
raise ValueError()
|
||||
|
||||
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 for i in range(tensor.shape[2])
|
||||
], dim=2)
|
||||
|
||||
return (torch.stack([
|
||||
crop_tensor(images[i]) for i in range(len(images))
|
||||
]),)
|
||||
|
||||
|
||||
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 = "image_transform_rotate"
|
||||
CATEGORY = "image/transform"
|
||||
|
||||
def image_transform_rotate(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 = "image_transform_transpose"
|
||||
CATEGORY = "image/transform"
|
||||
|
||||
def image_transform_transpose(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,
|
||||
"ImageTransformCropAbsolute": ImageTransformCropAbsolute,
|
||||
"ImageTransformCropRelative": ImageTransformCropRelative,
|
||||
"ImageTransformCropCorners": ImageTransformCropCorners,
|
||||
"ImageTransformRotate": ImageTransformRotate,
|
||||
"ImageTransformTranspose": ImageTransformTranspose
|
||||
}
|
||||
Reference in New Issue
Block a user