Files
CavinHuang-comfyui-nodes-docs/docs/ImageCompositeAbsolute.md
2024-07-04 08:39:03 +08:00

6.6 KiB

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

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