diff --git a/py/color_image_v2.py b/py/color_image_v2.py index f59fe30..6d21453 100644 --- a/py/color_image_v2.py +++ b/py/color_image_v2.py @@ -49,7 +49,7 @@ class ColorImageV2: width = int(_s[0].strip()) height = int(_s[1].strip()) except Exception as e: - log(f"Warning: {self.NODE_NAME} invalid size, check {custom_size_file}", message_type='warning') + log(f'Warning: {self.NODE_NAME} invalid size, check "custom_size.ini"', message_type='warning') width = custom_width height = custom_height diff --git a/py/color_image_v3.py b/py/color_image_v3.py new file mode 100644 index 0000000..beab02e --- /dev/null +++ b/py/color_image_v3.py @@ -0,0 +1,72 @@ +from PIL import Image +from .imagefunc import log, tensor2pil, pil2tensor, AnyType, load_custom_size +from .color_name import LS_ColorName + + +any = AnyType("*") + +class LS_ColorImageV3: + + def __init__(self): + self.NODE_NAME = 'ColorImage V3' + + @classmethod + def INPUT_TYPES(self): + size_list = ['custom'] + size_list.extend(load_custom_size()) + return { + "required": { + "size": (size_list,), + "custom_width": ("INT", {"default": 512, "min": 4, "max": 99999, "step": 1}), + "custom_height": ("INT", {"default": 512, "min": 4, "max": 99999, "step": 1}), + "color": ("STRING", {"default": "#000000"},), + }, + "optional": { + "size_as": (any, {}), + "color_name": ("STRING", {"default": "white",},), + } + } + + RETURN_TYPES = ("IMAGE", "STRING", ) + RETURN_NAMES = ("image", "color",) + FUNCTION = 'color_image_v2' + CATEGORY = '😺dzNodes/LayerUtility' + + def color_image_v2(self, size, custom_width, custom_height, color, size_as=None, color_name=None): + + if size_as is not None: + if size_as.shape[0] > 0: + _asimage = tensor2pil(size_as[0]) + else: + _asimage = tensor2pil(size_as) + width, height = _asimage.size + else: + if size == 'custom': + width = custom_width + height = custom_height + else: + try: + _s = size.split('x') + width = int(_s[0].strip()) + height = int(_s[1].strip()) + except Exception as e: + log(f'Warning: {self.NODE_NAME} invalid size, check "custom_size.ini"', message_type='warning') + width = custom_width + height = custom_height + + if color_name is not None: + try: + color_table = LS_ColorName() + color = color_table.XKCD_NAME_TO_HEX[color_name] + except KeyError: + log(f"{self.NODE_NAME}: {color_name} not in XKCD color table, use custom color value.") + ret_image = Image.new('RGB', (width, height), color=color) + return (pil2tensor(ret_image), color,) + +NODE_CLASS_MAPPINGS = { + "LayerUtility: ColorImage V3": LS_ColorImageV3 +} + +NODE_DISPLAY_NAME_MAPPINGS = { + "LayerUtility: ColorImage V3": "LayerUtility: ColorImage V3" +} \ No newline at end of file diff --git a/py/gradient_image_v2.py b/py/gradient_image_v2.py index 9402cd6..9db4154 100644 --- a/py/gradient_image_v2.py +++ b/py/gradient_image_v2.py @@ -53,7 +53,7 @@ class GradientImageV2: width = int(_s[0].strip()) height = int(_s[1].strip()) except Exception as e: - log(f"Warning: {self.NODE_NAME} invalid size, check {custom_size_file}", message_type='warning') + log(f'Warning: {self.NODE_NAME} invalid size, check "custom_size.ini"', message_type='warning') width = custom_width height = custom_height diff --git a/py/image_mask_scale_as.py b/py/image_mask_scale_as.py index 0638266..64c2d2d 100644 --- a/py/image_mask_scale_as.py +++ b/py/image_mask_scale_as.py @@ -90,10 +90,100 @@ class ImageMaskScaleAs: log(f"Error: {self.NODE_NAME} skipped, because the available image or mask is not found.", message_type='error') return (None, None, [orig_width, orig_height], 0, 0,) + +class LS_ImageMaskScaleAsV2: + + def __init__(self): + self.NODE_NAME = 'ImageMaskScaleAsV2' + + @classmethod + def INPUT_TYPES(self): + + fit_mode = ['letterbox', 'crop', 'fill'] + method_mode = ['lanczos', 'bicubic', 'hamming', 'bilinear', 'box', 'nearest'] + + return { + "required": { + "scale_as": (any, {}), + "fit": (fit_mode,), + "method": (method_mode,), + "background_color": ("STRING", {"default": "#000000"},), + }, + "optional": { + "image": ("IMAGE",), # + "mask": ("MASK",), # + } + } + + RETURN_TYPES = ("IMAGE", "MASK", "BOX", "INT", "INT") + RETURN_NAMES = ("image", "mask", "original_size", "widht", "height",) + FUNCTION = 'image_mask_scale_as_v2' + CATEGORY = '😺dzNodes/LayerUtility' + + def image_mask_scale_as_v2(self, scale_as, fit, method, background_color, + image=None, mask=None, + ): + if scale_as.shape[0] > 0: + _asimage = tensor2pil(scale_as[0]) + else: + _asimage = tensor2pil(scale_as) + target_width, target_height = _asimage.size + _mask = Image.new('L', size=_asimage.size, color='black') + _image = Image.new('RGB', size=_asimage.size, color=background_color) + orig_width = 4 + orig_height = 4 + resize_sampler = Image.LANCZOS + if method == "bicubic": + resize_sampler = Image.BICUBIC + elif method == "hamming": + resize_sampler = Image.HAMMING + elif method == "bilinear": + resize_sampler = Image.BILINEAR + elif method == "box": + resize_sampler = Image.BOX + elif method == "nearest": + resize_sampler = Image.NEAREST + + ret_images = [] + ret_masks = [] + + if image is not None: + for i in image: + i = torch.unsqueeze(i, 0) + _image = tensor2pil(i).convert('RGB') + orig_width, orig_height = _image.size + _image = fit_resize_image(_image, target_width, target_height, fit, resize_sampler, background_color=background_color) + ret_images.append(pil2tensor(_image)) + if mask is not None: + if mask.dim() == 2: + mask = torch.unsqueeze(mask, 0) + for m in mask: + m = torch.unsqueeze(m, 0) + _mask = tensor2pil(m).convert('L') + orig_width, orig_height = _mask.size + _mask = fit_resize_image(_mask, target_width, target_height, fit, resize_sampler, background_color=background_color).convert('L') + ret_masks.append(image2mask(_mask)) + if len(ret_images) > 0 and len(ret_masks) > 0: + log(f"{self.NODE_NAME} Processed {len(ret_images)} image(s).", message_type='finish') + return (torch.cat(ret_images, dim=0), torch.cat(ret_masks, dim=0), [orig_width, orig_height], target_width, + target_height,) + elif len(ret_images) > 0 and len(ret_masks) == 0: + log(f"{self.NODE_NAME} Processed {len(ret_images)} image(s).", message_type='finish') + return (torch.cat(ret_images, dim=0), None, [orig_width, orig_height], target_width, target_height,) + elif len(ret_images) == 0 and len(ret_masks) > 0: + log(f"{self.NODE_NAME} Processed {len(ret_masks)} image(s).", message_type='finish') + return (None, torch.cat(ret_masks, dim=0), [orig_width, orig_height], target_width, target_height,) + else: + log(f"Error: {self.NODE_NAME} skipped, because the available image or mask is not found.", + message_type='error') + return (None, None, [orig_width, orig_height], 0, 0,) + NODE_CLASS_MAPPINGS = { - "LayerUtility: ImageMaskScaleAs": ImageMaskScaleAs + "LayerUtility: ImageMaskScaleAs": ImageMaskScaleAs, + "LayerUtility: ImageMaskScaleAsV2": LS_ImageMaskScaleAsV2, } NODE_DISPLAY_NAME_MAPPINGS = { - "LayerUtility: ImageMaskScaleAs": "LayerUtility: ImageMaskScaleAs" + "LayerUtility: ImageMaskScaleAs": "LayerUtility: Image Mask Scale As", + "LayerUtility: ImageMaskScaleAsV2": "LayerUtility: Image Mask Scale As V2", } \ No newline at end of file diff --git a/pyproject.toml b/pyproject.toml index 5d19d8b..3c46d36 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,7 +1,7 @@ [project] name = "comfyui_layerstyle" description = "A set of nodes for ComfyUI it generate image like Adobe Photoshop's Layer Style. the Drop Shadow is first completed node, and follow-up work is in progress." -version = "2.0.10" +version = "2.0.11" license = "MIT" dependencies = ["numpy", "pillow", "torch", "matplotlib", "Scipy", "scikit_image", "scikit_learn", "opencv-contrib-python", "pymatting", "timm", "colour-science", "transformers", "blend_modes", "huggingface_hub", "loguru"]