Buckets:
| from comfy_extras.nodes_mask import ImageCompositeMasked | |
| import torch | |
| import torch.nn.functional as F | |
| class ImageCompositeWatermark(ImageCompositeMasked): | |
| def INPUT_TYPES(s): | |
| return { | |
| "required": { | |
| "destination": ("IMAGE",), | |
| "watermark": ("IMAGE",), | |
| "position": (["top_right", "top_center", "top_left", "bottom_right", "bottom_center", "bottom_left"], {"default": "bottom_right"}), | |
| "resize_ratio": ("FLOAT", {"default": 1, "min": 0, "max": 10, "step": 0.05}), | |
| "margin": ("INT", {"default": 0, "min": 0, "max": 1000, "step": 1}), | |
| }, | |
| "optional": { | |
| "mask": ("MASK",), | |
| "enabled": ("BOOLEAN", {"default": True, "label_on": "enabled", "label_off": "disabled"}), | |
| "invert_mask": ("BOOLEAN", {"default": False, "label_on": "enabled", "label_off": "disabled"}), | |
| } | |
| } | |
| RETURN_TYPES = ("IMAGE",) | |
| FUNCTION = "composite_watermark" | |
| CATEGORY = "utils/image" | |
| def composite_watermark(self, destination, watermark, position, resize_ratio, margin, mask=None, enabled=True, invert_mask=False): | |
| if not enabled: | |
| return (destination,) | |
| dest_h, dest_w = destination.shape[1:3] | |
| water_h, water_w = watermark.shape[1:3] | |
| scale = 1 | |
| if water_h > dest_h or water_w > dest_w: | |
| # 计算需要的缩放比例 | |
| scale_h = dest_h / water_h | |
| scale_w = dest_w / water_w | |
| scale = min(scale_h, scale_w) | |
| if resize_ratio != 1 or scale != 1: | |
| watermark = torch.nn.functional.interpolate( | |
| watermark.movedim(-1, 1), scale_factor=resize_ratio * scale, mode="bicubic", antialias=True).movedim(1, -1).clamp(0.0, 1.0) | |
| if mask is not None: | |
| mask = torch.nn.functional.interpolate(mask.unsqueeze( | |
| 0), scale_factor=resize_ratio * scale, mode="bicubic", antialias=True).squeeze(0).clamp(0.0, 1.0) | |
| water_h, water_w = watermark.shape[1:3] | |
| # 计算y坐标 | |
| if position.startswith("top"): | |
| y = margin | |
| else: # bottom positions | |
| y = dest_h - water_h - margin | |
| x = 0 | |
| # 根据position计算x坐标 | |
| if position.endswith("left"): | |
| x = margin | |
| elif position.endswith("center"): | |
| x = (dest_w - water_w) // 2 | |
| elif position.endswith("right"): | |
| x = dest_w - water_w - margin | |
| if invert_mask and mask is not None: | |
| mask = 1.0 - mask | |
| return self.composite(destination, watermark, x, y, False, mask) | |
| class ImageTransition: | |
| def INPUT_TYPES(s): | |
| return { | |
| "required": { | |
| "first_image": ("IMAGE",), | |
| "last_image": ("IMAGE",), | |
| "frames": ("INT", {"default": 24, "min": 2, "max": 120, "step": 1}), | |
| "transition_type": (["uniform", "smooth"], {"default": "uniform"}), | |
| "smooth_effect": ("FLOAT", {"default": 1.0, "min": 0.1, "max": 10.0, "step": 0.1}), | |
| } | |
| } | |
| RETURN_TYPES = ("IMAGE",) | |
| FUNCTION = "generate_transition" | |
| CATEGORY = "utils/image" | |
| def generate_transition(self, first_image, last_image, frames, transition_type="uniform", smooth_effect=1.0): | |
| # 生成插值权重 | |
| if transition_type == "uniform": | |
| weights = torch.linspace(0, 1, frames) | |
| else: # sigmoid | |
| x = torch.linspace(-20, 20, frames) | |
| weights = torch.sigmoid(x / smooth_effect) | |
| # 创建输出张量列表 | |
| output_frames = [] | |
| # 生成过渡帧 | |
| for w in weights: | |
| # 使用权重进行插值 | |
| transition_frame = first_image * (1 - w) + last_image * w | |
| output_frames.append(transition_frame) | |
| # 将所有帧拼接在一起 | |
| result = torch.cat(output_frames, dim=0) | |
| return (result,) | |
| class ImageMaskColorAverage: | |
| def INPUT_TYPES(s): | |
| return { | |
| "required": { | |
| "image": ("IMAGE",), | |
| "mask": ("MASK",), | |
| } | |
| } | |
| RETURN_TYPES = ("INT", "STRING") | |
| RETURN_NAMES = ("COLOR_DEC", "COLOR_HEX") | |
| FUNCTION = "calculate_average_color" | |
| CATEGORY = "utils/image" | |
| def calculate_average_color(self, image, mask): | |
| # 确保mask是二维的 | |
| if len(mask.shape) > 2: | |
| mask = mask.squeeze() | |
| # 将mask扩展为与图像相同的通道数 | |
| expanded_mask = mask.unsqueeze(-1).expand(-1, -1, 3) | |
| # 计算mask区域的像素总数 | |
| pixel_count = torch.sum(mask) | |
| if pixel_count == 0: | |
| # 如果mask中没有选中区域,返回黑色 | |
| return (0, "#000000") | |
| # 计算mask区域的颜色总和 | |
| masked_image = image * expanded_mask.unsqueeze(0) | |
| color_sum = torch.sum(masked_image, dim=[0, 1, 2]) | |
| # 计算平均颜色 | |
| avg_color = color_sum / pixel_count | |
| # 转换为0-255范围的整数 | |
| r = int(avg_color[0].item() * 255) | |
| g = int(avg_color[1].item() * 255) | |
| b = int(avg_color[2].item() * 255) | |
| # 生成十六进制颜色代码 | |
| hex_color = f"#{r:02x}{g:02x}{b:02x}" | |
| # 计算十进制颜色值 (R*65536 + G*256 + B) | |
| dec_color = r * 65536 + g * 256 + b | |
| return (dec_color, hex_color) | |
| class ImagesConcanateToGrid: | |
| def INPUT_TYPES(s): | |
| return {"required": { | |
| "image1": ("IMAGE",), | |
| "direction": ( | |
| ['right', | |
| 'down', | |
| ], | |
| { | |
| "default": 'right' | |
| }), | |
| "dimension_number": ("INT", {"default": 2, "min": 1, "max": 20, "step": 1}), | |
| } | |
| } | |
| RETURN_TYPES = ("IMAGE",) | |
| FUNCTION = "concanate" | |
| CATEGORY = "utils/image" | |
| def concanate(self, image1, direction='right', dimension_number=2): | |
| # 检查图像维度,如果不是4维直接返回 | |
| if len(image1.shape) != 4: | |
| return (image1,) | |
| batch_size = image1.shape[0] | |
| # 如果批次大小为1,直接返回 | |
| if batch_size == 1: | |
| return (image1,) | |
| # 将批次图像分离为单独的图像列表 | |
| images = [image1[i:i+1] for i in range(batch_size)] | |
| # 根据方向计算行数和列数 | |
| if direction == 'right': | |
| cols = dimension_number | |
| rows = (batch_size + cols - 1) // cols # 向上取整 | |
| else: # direction == 'down' | |
| rows = dimension_number | |
| cols = (batch_size + rows - 1) // rows # 向上取整 | |
| # 创建网格来存放图像 | |
| grid_rows = [] | |
| for row in range(rows): | |
| row_images = [] | |
| for col in range(cols): | |
| idx = row * cols + col if direction == 'right' else col * rows + row | |
| if idx < len(images): | |
| row_images.append(images[idx]) | |
| else: | |
| # 如果没有足够的图像,用黑色图像填充 | |
| black_image = torch.zeros_like(images[0]) | |
| row_images.append(black_image) | |
| # 水平拼接当前行的图像 | |
| if row_images: | |
| row_concat = torch.cat(row_images, dim=2) # 在宽度维度拼接 | |
| grid_rows.append(row_concat) | |
| # 垂直拼接所有行 | |
| if grid_rows: | |
| result = torch.cat(grid_rows, dim=1) # 在高度维度拼接 | |
| else: | |
| result = image1 | |
| return (result,) | |
| class NeedImageSizeAndCount: | |
| def INPUT_TYPES(s): | |
| return {"required": {"image": ("IMAGE",)}} | |
| RETURN_TYPES = ("INT", "INT", "INT") | |
| RETURN_NAMES = ("width", "height", "count") | |
| FUNCTION = "get_image_size_and_count" | |
| CATEGORY = "utils/image" | |
| def get_image_size_and_count(self, image): | |
| return (image.shape[2], image.shape[1], image.shape[0]) | |
| NODE_CLASS_MAPPINGS = { | |
| #image | |
| "ImageCompositeWatermark": ImageCompositeWatermark, | |
| "ImageTransition": ImageTransition, | |
| "ImageMaskColorAverage": ImageMaskColorAverage, | |
| "ImagesConcanateToGrid": ImagesConcanateToGrid, | |
| "NeedImageSizeAndCount": NeedImageSizeAndCount, | |
| } | |
| NODE_DISPLAY_NAME_MAPPINGS = { | |
| # Image | |
| "ImageCompositeWatermark": "Image Composite Watermark", | |
| "ImageTransition": "Image Transition", | |
| "ImageMaskColorAverage": "Image Mask Color Average", | |
| "ImagesConcanateToGrid": "Images Concanate To Grid", | |
| "NeedImageSizeAndCount": "get Image Size And Count by utils", | |
| } | |
Xet Storage Details
- Size:
- 8.98 kB
- Xet hash:
- 9b72bfdf40b42b30cda1da4e2375f631c8916c005de682145781595106926a16
·
Xet efficiently stores files, intelligently splitting them into unique chunks and accelerating uploads and downloads. More info.