From 078ed5b9e6781bedbbbe68ea54391e02f86a7eea Mon Sep 17 00:00:00 2001 From: Machine Delusions <55672949+filliptm@users.noreply.github.com> Date: Sun, 8 Mar 2026 00:17:18 -0800 Subject: [PATCH] feat: add FL_ImageToMask node, fix InpaintCrop force_square + auto-resize masks (v2.3.4) - Add FL_ImageToMask node to convert IMAGE to MASK via luminance/channel extraction - Fix force_square shadowing target_size parameter, breaking resize modes - Auto-resize mask and context_mask to match image size instead of raising errors Co-Authored-By: Claude Opus 4.6 --- __init__.py | 3 +++ nodes/image/FL_ImageToMask.py | 39 +++++++++++++++++++++++++++++++++ nodes/utility/FL_InpaintCrop.py | 31 +++++++++++--------------- pyproject.toml | 2 +- 4 files changed, 56 insertions(+), 19 deletions(-) create mode 100644 nodes/image/FL_ImageToMask.py diff --git a/__init__.py b/__init__.py index e7d4c36..0ee716c 100644 --- a/__init__.py +++ b/__init__.py @@ -118,6 +118,7 @@ from .nodes.image.FL_ImageOverlay import FL_ImageOverlay from .nodes.image.FL_ImageSelector import FL_ImageSelector from .nodes.image.FL_ImagePicker import FL_ImagePicker from .nodes.image.FL_ImageSlicer import FL_ImageSlicer +from .nodes.image.FL_ImageToMask import FL_ImageToMask from .nodes.image.FL_Image_AddToBatch import FL_ImageAddToBatch from .nodes.image.FL_Image_Blank import FL_ImageBlank from .nodes.image.FL_Image_Crop import FL_ImageCrop @@ -390,6 +391,7 @@ NODE_CLASS_MAPPINGS = { "FL_RunwayImageAPI": FL_RunwayImageAPI, "FL_RunwayAct2": FL_RunwayAct2, "FL_ImageCrop": FL_ImageCrop, + "FL_ImageToMask": FL_ImageToMask, "FL_WanFirstLastFrameToVideo": FL_WanFirstLastFrameToVideo, "FL_WanVideoContinue": FL_WanVideoContinue, "FL_WanVideoBlender": FL_WanVideoBlender, @@ -578,6 +580,7 @@ NODE_DISPLAY_NAME_MAPPINGS = { "FL_TextOverlayNode": "FL Text Overlay", "FL_SaveWebM": "FL Save WebM", "FL_ImageCrop": "FL Image Crop", + "FL_ImageToMask": "FL Image To Mask", "FL_WanFirstLastFrameToVideo": "FL Wan First Frame Last Frame", "FL_WanVideoContinue": "FL Wan Video Continue", "FL_WanVideoBlender": "FL Wan Video Blender", diff --git a/nodes/image/FL_ImageToMask.py b/nodes/image/FL_ImageToMask.py new file mode 100644 index 0000000..2c2c4c7 --- /dev/null +++ b/nodes/image/FL_ImageToMask.py @@ -0,0 +1,39 @@ +import torch + + +class FL_ImageToMask: + @classmethod + def INPUT_TYPES(cls): + return { + "required": { + "image": ("IMAGE",), + "channel": (["luminance", "red", "green", "blue", "alpha"], { + "default": "luminance", + "description": "Which channel to extract as the mask" + }), + } + } + + RETURN_TYPES = ("MASK",) + RETURN_NAMES = ("mask",) + FUNCTION = "image_to_mask" + CATEGORY = "🏵️Fill Nodes/Image" + + def image_to_mask(self, image, channel): + # image shape: (B, H, W, C) + if channel == "luminance": + # Standard luminance weights + mask = 0.2989 * image[:, :, :, 0] + 0.5870 * image[:, :, :, 1] + 0.1140 * image[:, :, :, 2] + elif channel == "red": + mask = image[:, :, :, 0] + elif channel == "green": + mask = image[:, :, :, 1] + elif channel == "blue": + mask = image[:, :, :, 2] + elif channel == "alpha": + if image.shape[3] > 3: + mask = image[:, :, :, 3] + else: + mask = torch.ones(image.shape[0], image.shape[1], image.shape[2], device=image.device) + + return (mask,) diff --git a/nodes/utility/FL_InpaintCrop.py b/nodes/utility/FL_InpaintCrop.py index bbe27dd..8f40925 100644 --- a/nodes/utility/FL_InpaintCrop.py +++ b/nodes/utility/FL_InpaintCrop.py @@ -218,13 +218,10 @@ class FL_InpaintCrop: original_mask = mask original_height, original_width = image.shape[1], image.shape[2] - # Validate mask size + # Auto-resize mask to match image size if mask.shape[1] != image.shape[1] or mask.shape[2] != image.shape[2]: - non_zero_indices = torch.nonzero(mask[0], as_tuple=True) - if not non_zero_indices[0].size(0): - mask = torch.zeros_like(image[:, :, :, 0]) - else: - raise ValueError("mask size must match image size") + mask = F.interpolate(mask.unsqueeze(1), size=(image.shape[1], image.shape[2]), mode='nearest').squeeze(1) + print(f"[FL Inpaint Crop] Auto-resized mask to {image.shape[2]}x{image.shape[1]}") # Invert mask if requested if invert_mask: @@ -258,11 +255,9 @@ class FL_InpaintCrop: if optional_context_mask is None: context_mask = mask elif optional_context_mask.shape[1] != image.shape[1] or optional_context_mask.shape[2] != image.shape[2]: - non_zero_indices = torch.nonzero(optional_context_mask[0], as_tuple=True) - if not non_zero_indices[0].size(0): - context_mask = mask - else: - raise ValueError("context_mask size must match image size") + optional_context_mask = F.interpolate(optional_context_mask.unsqueeze(1), size=(image.shape[1], image.shape[2]), mode='nearest').squeeze(1) + print(f"[FL Inpaint Crop] Auto-resized context mask to {image.shape[2]}x{image.shape[1]}") + context_mask = torch.clamp(optional_context_mask + mask, 0.0, 1.0) else: context_mask = torch.clamp(optional_context_mask + mask, 0.0, 1.0) @@ -298,22 +293,22 @@ class FL_InpaintCrop: if force_square: x_size = x_max - x_min + 1 y_size = y_max - y_min + 1 - target_size = max(x_size, y_size) + square_size = max(x_size, y_size) # Center the square x_mid = (x_min + x_max) // 2 y_mid = (y_min + y_max) // 2 - x_min = max(x_mid - target_size // 2, 0) - x_max = min(x_min + target_size - 1, original_width - 1) - y_min = max(y_mid - target_size // 2, 0) - y_max = min(y_min + target_size - 1, original_height - 1) + x_min = max(x_mid - square_size // 2, 0) + x_max = min(x_min + square_size - 1, original_width - 1) + y_min = max(y_mid - square_size // 2, 0) + y_max = min(y_min + square_size - 1, original_height - 1) # Adjust if we hit boundaries if x_max == original_width - 1: - x_min = max(0, x_max - target_size + 1) + x_min = max(0, x_max - square_size + 1) if y_max == original_height - 1: - y_min = max(0, y_max - target_size + 1) + y_min = max(0, y_max - square_size + 1) print(f"[FL Inpaint Crop] After square adjustment: x[{x_min}, {x_max}] y[{y_min}, {y_max}]") diff --git a/pyproject.toml b/pyproject.toml index 1dd338e..c01316e 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,7 +1,7 @@ [project] name = "comfyui_fill-nodes" description = "Fill-Nodes is a versatile collection of custom nodes for ComfyUI that extends functionality across multiple domains. Features include advanced image processing (pixelation, slicing, masking), visual effects generation (glitch, halftone, pixel art), comprehensive file handling (PDF creation/extraction, Google Drive integration), AI model interfaces (GPT, DALL-E, Hugging Face), utility nodes for workflow enhancement, and specialized tools for video processing, captioning, and batch operations. The pack provides both practical workflow solutions and creative tools within a unified node collection." -version = "2.3.3" +version = "2.3.4" license = "LICENSE" dependencies = ["librosa", "sounddevice", "glitch_this", "PyOpenGL", "glfw", "scipy>=1.13.1", "requests", "aiohttp", "moviepy", "matplotlib", "reportlab", "openai", "PyPDF2", "pdf2image", "PyMuPDF", "reportlab", "PyPDF2", "ollama", "kornia", "opencv-python", "gdown", "open_clip_torch", "google-genai"]