From 1e5ce8538a23048d9c05f42aaa55311fc7c2d581 Mon Sep 17 00:00:00 2001 From: nofunstudio Date: Wed, 25 Jun 2025 14:08:45 -0600 Subject: [PATCH] MultiAlphaComposite --- __init__.py | 7 ++-- multi_alpha_composite.py | 69 ++++++++++++++++++++++++++++++++++++++++ 2 files changed, 74 insertions(+), 2 deletions(-) create mode 100644 multi_alpha_composite.py diff --git a/__init__.py b/__init__.py index 97c89e0..27b70b0 100644 --- a/__init__.py +++ b/__init__.py @@ -9,6 +9,7 @@ from .ksampler_fun import KSampler from .fal_recraft_upscale import FalAPI_recraft_upscale from .fal_kling_video import FalAPI_kling_video from .string_lower import StringLower +from .multi_alpha_composite import MultiAlphaComposite WEB_DIRECTORY = "./js" NODE_CLASS_MAPPINGS = { @@ -21,7 +22,8 @@ NODE_CLASS_MAPPINGS = { "Fun KSampler": KSampler, "FalAPI_recraft_upscale": FalAPI_recraft_upscale, "FalAPI_kling_video": FalAPI_kling_video, - "StringLower": StringLower + "StringLower": StringLower, + "MultiAlphaComposite": MultiAlphaComposite } NODE_DISPLAY_NAME_MAPPINGS = { "LayeredInfiniteZoom": "Layered Infinite Zoom", @@ -33,7 +35,8 @@ NODE_DISPLAY_NAME_MAPPINGS = { "Fun KSampler": "Fun KSampler", "FalAPI_recraft_upscale": "Fal API Recraft Upscale", "FalAPI_kling_video": "Fal API Kling Video", - "StringLower": "String to Lowercase" + "StringLower": "String to Lowercase", + "MultiAlphaComposite": "Multi Alpha Composite" } __all__ = [ diff --git a/multi_alpha_composite.py b/multi_alpha_composite.py new file mode 100644 index 0000000..07d425b --- /dev/null +++ b/multi_alpha_composite.py @@ -0,0 +1,69 @@ +from PIL import Image +import numpy as np +import torch + +def tensor_to_pil(tensor): + """Convert ComfyUI tensor to PIL Image""" + # ComfyUI tensors are typically in format [batch, height, width, channels] + if len(tensor.shape) == 4: + tensor = tensor.squeeze(0) # Remove batch dimension + + # Convert from tensor to numpy array and scale to 0-255 + np_image = (tensor.cpu().numpy() * 255).astype(np.uint8) + return Image.fromarray(np_image, 'RGB') + +def pil_to_tensor(pil_image): + """Convert PIL Image to ComfyUI tensor""" + np_image = np.array(pil_image).astype(np.float32) / 255.0 + tensor = torch.from_numpy(np_image) + return tensor.unsqueeze(0) # Add batch dimension + +class MultiAlphaComposite: + CATEGORY = "Image/Composite" + + @classmethod + def INPUT_TYPES(s): + return { + "required": { + "num_images": ("INT", {"default": 2, "min": 2, "max": 8, "step": 1}), + "image1": ("IMAGE",), + "image2": ("IMAGE",), + }, + "optional": { + "image3": ("IMAGE",), + "image4": ("IMAGE",), + "image5": ("IMAGE",), + "image6": ("IMAGE",), + "image7": ("IMAGE",), + "image8": ("IMAGE",), + } + } + + RETURN_TYPES = ("IMAGE",) + FUNCTION = "composite" + + def composite(self, num_images, image1, image2, image3=None, image4=None, image5=None, image6=None, image7=None, image8=None): + # Collect all provided images up to num_images + images = [image1, image2, image3, image4, image5, image6, image7, image8] + valid_images = [img for img in images[:num_images] if img is not None] + + if len(valid_images) < 2: + raise ValueError("At least 2 images are required for compositing") + + # Convert ComfyUI tensors to PIL Images + pil_images = [tensor_to_pil(img) for img in valid_images] + + # Convert to RGBA for alpha compositing + layers = [img.convert("RGBA") for img in pil_images] + + # Composite layers + base = layers[0] + for layer in layers[1:]: + # Resize layer to match base if needed + if layer.size != base.size: + layer = layer.resize(base.size, Image.LANCZOS) + base = Image.alpha_composite(base, layer) + + # Convert back to ComfyUI tensor format + result_tensor = pil_to_tensor(base.convert("RGB")) + return (result_tensor,) \ No newline at end of file