157 lines
6.6 KiB
Markdown
157 lines
6.6 KiB
Markdown
|
|
# Documentation
|
|
- Class name: ImageCompositeAbsolute
|
|
- Category: image/composite
|
|
- Output node: False
|
|
|
|
ImageCompositeAbsolute 节点用于通过精确定位两个输入图像来创建复合图像。它根据给定的坐标、容器尺寸和指定的合成方法来处理这些图像的精确放置和混合,从而实现复杂的视觉布局创作。
|
|
|
|
# Input types
|
|
## Required
|
|
- images_a
|
|
- 要在容器内合成的第一个图像。它在复合图像的图层顺序和视觉效果中起着关键作用。
|
|
- Comfy dtype: IMAGE
|
|
- Python dtype: numpy.ndarray
|
|
- images_b
|
|
- 要在容器内合成的第二个图像,为最终复合图像增添复杂性和深度。
|
|
- Comfy dtype: IMAGE
|
|
- Python dtype: numpy.ndarray
|
|
- images_a_x
|
|
- 第一个图像在容器内左上角的 x 坐标,定义其水平位置。
|
|
- Comfy dtype: INT
|
|
- Python dtype: int
|
|
- images_a_y
|
|
- 第一个图像在容器内左上角的 y 坐标,定义其垂直位置。
|
|
- Comfy dtype: INT
|
|
- Python dtype: int
|
|
- images_b_x
|
|
- 第二个图像在容器内左上角的 x 坐标,定义其水平位置。
|
|
- Comfy dtype: INT
|
|
- Python dtype: int
|
|
- images_b_y
|
|
- 第二个图像在容器内左上角的 y 坐标,定义其垂直位置。
|
|
- Comfy dtype: INT
|
|
- Python dtype: int
|
|
- container_width
|
|
- 指定用于合成输入图像的容器宽度。它决定了合成的水平边界。
|
|
- Comfy dtype: INT
|
|
- Python dtype: int
|
|
- container_height
|
|
- 指定用于合成输入图像的容器高度。它决定了合成的垂直边界。
|
|
- Comfy dtype: INT
|
|
- Python dtype: int
|
|
- background
|
|
- 确定将哪个输入图像(images_a 或 images_b)用作复合图像的背景。这个选择会影响最终图像的图层和视觉效果。
|
|
- Comfy dtype: COMBO[STRING]
|
|
- Python dtype: str
|
|
- method
|
|
- 指定要使用的合成方法,影响图像在容器内的混合方式。
|
|
- Comfy dtype: COMBO[STRING]
|
|
- Python dtype: str
|
|
|
|
# Output types
|
|
- image
|
|
- 根据指定参数和方法合成输入图像后得到的结果图像。
|
|
- Comfy dtype: IMAGE
|
|
- Python dtype: numpy.ndarray
|
|
|
|
# Usage tips
|
|
- Infra type: CPU
|
|
- Common nodes:
|
|
- [PreviewImage](https://github.com/comfyanonymous/ComfyUI/blob/master/nodes/image.py)
|
|
- VAEEncode
|
|
- [ImageScale](https://github.com/comfyanonymous/ComfyUI/blob/master/nodes/image.py)
|
|
|
|
# Source code
|
|
python
|
|
class ImageCompositeAbsolute:
|
|
@classmethod
|
|
def INPUT_TYPES(s):
|
|
return {
|
|
"required": {
|
|
"images_a": ("IMAGE",),
|
|
"images_b": ("IMAGE",),
|
|
"images_a_x": ("INT", {"default": 0, "min": 0, "max": MAX_RESOLUTION, "step": 1}),
|
|
"images_a_y": ("INT", {"default": 0, "min": 0, "max": MAX_RESOLUTION, "step": 1}),
|
|
"images_b_x": ("INT", {"default": 0, "min": 0, "max": MAX_RESOLUTION, "step": 1}),
|
|
"images_b_y": ("INT", {"default": 0, "min": 0, "max": MAX_RESOLUTION, "step": 1}),
|
|
"container_width": ("INT", {"default": 512, "min": 16, "max": MAX_RESOLUTION, "step": 1}),
|
|
"container_height": ("INT", {"default": 512, "min": 16, "max": MAX_RESOLUTION, "step": 1}),
|
|
"background": (["images_a", "images_b"],),
|
|
"method": (["normal", "multiply", "screen", "overlay", "soft_light"],)
|
|
}
|
|
}
|
|
|
|
RETURN_TYPES = ("IMAGE",)
|
|
FUNCTION = "composite"
|
|
|
|
CATEGORY = "image/composite"
|
|
|
|
def composite(self, images_a, images_b, images_a_x, images_a_y, images_b_x, images_b_y, container_width, container_height, background, method):
|
|
images_a = images_a.clone().movedim(-1,1)
|
|
images_b = images_b.clone().movedim(-1,1)
|
|
|
|
if background == "images_a":
|
|
results = []
|
|
canvas = torch.zeros((images_a.shape[0], 4, container_height, container_width), dtype=torch.float32, device="cpu")
|
|
canvas[:, :, images_a_y:images_a_y + images_a.shape[2], images_a_x:images_a_x + images_a.shape[3]] = images_a
|
|
images_b = torch.nn.functional.pad(images_b, (images_b_x, container_width - images_b_x - images_b.shape[3], images_b_y, container_height - images_b_y - images_b.shape[2]), "constant", 0)
|
|
images_b = images_b[:, :, :container_height, :container_width]
|
|
results.append(canvas)
|
|
results.append(images_b)
|
|
else:
|
|
results = []
|
|
canvas = torch.zeros((images_b.shape[0], 4, container_height, container_width), dtype=torch.float32, device="cpu")
|
|
canvas[:, :, images_b_y:images_b_y + images_b.shape[2], images_b_x:images_b_x + images_b.shape[3]] = images_b
|
|
images_a = torch.nn.functional.pad(images_a, (images_a_x, container_width - images_a_x - images_a.shape[3], images_a_y, container_height - images_a_y - images_a.shape[2]), "constant", 0)
|
|
images_a = images_a[:, :, :container_height, :container_width]
|
|
results.append(canvas)
|
|
results.append(images_a)
|
|
|
|
if method == "normal":
|
|
return (self.normal(results[0], results[1]).movedim(1,-1),)
|
|
elif method == "multiply":
|
|
return (self.multiply(results[0], results[1]).movedim(1,-1),)
|
|
elif method == "screen":
|
|
return (self.screen(results[0], results[1]).movedim(1,-1),)
|
|
elif method == "overlay":
|
|
return (self.overlay(results[0], results[1]).movedim(1,-1),)
|
|
elif method == "soft_light":
|
|
return (self.soft_light(results[0], results[1]).movedim(1,-1),)
|
|
|
|
def normal(self, bg, fg):
|
|
out = torch.where(fg[:,:4] == 0, bg, fg[:,:4])
|
|
out[:, 3:4] = 1 - (1 - fg[:, 3:4]) * (1 - bg[:, 3:4])
|
|
return out
|
|
|
|
def multiply(self, bg, fg):
|
|
out = fg * bg
|
|
out[:, 3:4] = 1 - (1 - fg[:, 3:4]) * (1 - bg[:, 3:4])
|
|
return out
|
|
|
|
def screen(self, bg, fg):
|
|
out = bg + fg - (bg * fg)
|
|
out[:, 3:4] = 1 - (1 - fg[:, 3:4]) * (1 - bg[:, 3:4])
|
|
return out
|
|
|
|
def overlay(self, bg, fg):
|
|
def f(bg, fg):
|
|
return torch.where(bg <= 0.5, 2 * bg * fg, 1 - 2 * (1 - bg) * (1 - fg))
|
|
out = fg.clone()
|
|
out[:, :3] = f(bg[:, :3], fg[:, :3])
|
|
out[:, 3:4] = 1 - (1 - fg[:, 3:4]) * (1 - bg[:, 3:4])
|
|
return out
|
|
|
|
def soft_light(self, bg, fg):
|
|
def f(bg, fg):
|
|
return torch.where(fg <= 0.5, bg - (1 - 2 * fg) * bg * (1 - bg), bg + (2 * fg - 1) * (self.d(bg) - bg))
|
|
|
|
def d(x):
|
|
return torch.where(x <= 0.25, ((16 * x - 12) * x + 4) * x, torch.sqrt(x))
|
|
|
|
self.d = d
|
|
out = fg.clone()
|
|
out[:, :3] = f(bg[:, :3], fg[:, :3])
|
|
out[:, 3:4] = 1 - (1 - fg[:, 3:4]) * (1 - bg[:, 3:4])
|
|
return out
|
|
``` |