diff --git a/README.MD b/README.MD index eb04fc0..7749f9e 100644 --- a/README.MD +++ b/README.MD @@ -12,10 +12,12 @@ Nodes are divided into four groups according to their functions: LayerStyle, Lay [中文说明点这里](./README_CN.MD) ## Update -* Commit [MaskBoxDetect](#MaskBoxDetect)node, which can automatically detect the position through the mask and output it to the composite node. -Commit [X,Y to Percent](#Percent) node to convert absolute coordinates to percent coordinates. -Commit [GaussianBlur](#GaussianBlur)node. -Commit [GetImageSize](#GetImageSize)node. +* Commit [GetColorTone](#GetColorTone) node, can obtain the main color or average color of the image. +Commit [MaskGrow](#MaskGrow) and [MaskEdgeShink](#MaskEdgeShrink) nodes. +* Commit [MaskBoxDetect](#MaskBoxDetect) node, which can automatically detect the position through the mask and output it to the composite node. +Commit [XY to Percent](#Percent) node to convert absolute coordinates to percent coordinates. +Commit [GaussianBlur](#GaussianBlur) node. +Commit [GetImageSize](#GetImageSize) node. * Commit [ExtendCanvas](#ExtendCanvas) node. * Commit [ImageBlendAdvance](#ImageBlendAdvance) node. This node allows for the synthesis of background images and layers of different sizes, providing a more free synthesis experience. Commit [PrintInfo](#PrintInfo) node as a workflow debugging aid. @@ -173,7 +175,6 @@ Node options: - # LayerMask ![image](image/layermask_nodes.png) @@ -198,6 +199,30 @@ Output: * y: The y-coordinate of the top left corner position. +### MaskGrow +Grow and shrink edges and blur the mask + +Node options: +![image](image/mask_grow_node.png) +* invert_mask: Whether to reverse the mask. +* grow: Positive values expand outward, while negative values contract inward. +* blur: Blur the edge. + +### MaskEdgeShrink +Smooth transition and shrink the mask edges while preserving edge details. + +Node options: +![image](image/mask_edge_shrink_node.png) +* invert_mask: Whether to reverse the mask. +* shrink_level: Shrink the smoothness level. +* soft: Smooth amplitude. +* edge_shrink: Edge shrinkage amplitude. +* edge_reserve: Preserve the amplitude of edge details, 100 represents complete preservation, and 0 represents no preservation at all. + +Comparison of MaskGrow and MaskEdgeShrink +![image](image/mask_edge_compare.png) + + ### MaskPreview Preview the input mask ![image](image/mask_invert.png) @@ -270,7 +295,18 @@ Modify web extensions from [mtb nodes](https://github.com/melMass/comfy_mtb). Se Node options: * mode: The output format is available in hexadecimal (HEX) and decimal (DEC). -Output Type: +Output type: +* string + +### GetColorTone +Obtain the main color or average color from the image and output RGB values. +![image](image/get_color_tone_example.png) + +Node options: +![image](image/get_color_tone_node.png) +* mode: There are two modes to choose from, with the main color and average color. + +Output type: * string ### ExtendCanvas @@ -286,7 +322,7 @@ Node options: * right: Right extension value. * color; Color of canvas. -### X,Y to Percent +### XY to Percent ![image](image/xy2percent_example.png) Convert absolute coordinates to percentage coordinates. @@ -337,27 +373,26 @@ image Some JSON workflow files in the workflow directory, that is example for Co ## The following tasks are in the planned list LayerUtility: -* color image -* gradient image * image channel split * image channel merge LayerMask: * text mask -* gradient mask -* mask grow&blur * mask subtract * mask combine * mask diffrent * mask gradient -* mask softedge LayerFilter: -* blur * emboss * contour * findedge +LayerColor: +RGB adjust +HSV adjust +brightness & contrast adjust +LUT apply ## How to install diff --git a/README_CN.MD b/README_CN.MD index f6e2db3..a510e9d 100644 --- a/README_CN.MD +++ b/README_CN.MD @@ -9,7 +9,8 @@ ComfyUI * [LayerFilter](#LayerFilter)½Úµã×éÌṩͼÏñЧ¹ûÂ˾µ¡£ ## ¸üÐÂ˵Ã÷ -* Ìí¼Ó[MaskBoxDetect](#MaskBoxDetect)½Úµã£¬¿ÉÒÔͨ¹ýmask×Ô¶¯Ì½²âλÖò¢Êä³öµ½ºÏ³É½Úµã¡£Ìí¼Ó[X,Y to Percent](#Percent)½Úµã£¬½«¾ø¶Ô×ø±êת»»Îªpercent×ø±ê¡£Ìí¼Ó[GaussianBlur](#GaussianBlur)½Úµã¡£Ìí¼Ó[GetImageSize](#GetImageSize)½Úµã¡£ +* Ìí¼Ó[GetColorTone](#GetColorTone)½Úµã£¬¿ÉÒÔ»ñȡͼƬµÄÖ÷É«»òƽ¾ùÉ«¡£Ìí¼Ó[MaskGrow](#MaskGrow)ºÍ[MaskEdgeShink](#MaskEdgeShrink)½Úµã¡£ +* Ìí¼Ó[MaskBoxDetect](#MaskBoxDetect)½Úµã£¬¿ÉÒÔͨ¹ýmask×Ô¶¯Ì½²âλÖò¢Êä³öµ½ºÏ³É½Úµã¡£Ìí¼Ó[XY to Percent](#Percent)½Úµã£¬½«¾ø¶Ô×ø±êת»»Îªpercent×ø±ê¡£Ìí¼Ó[GaussianBlur](#GaussianBlur)½Úµã¡£Ìí¼Ó[GetImageSize](#GetImageSize)½Úµã¡£ * Ìí¼Ó[ExtendCanvas](#ExtendCanvas)½Úµã¡£ * Ìí¼Ó[ImageBlendAdvance](#ImageBlendAdvance)½Úµã¡£Õâ¸ö½ÚµãÔÊÐíºÏ³É³ß´ç²»Í¬µÄ±³¾°Í¼ºÍͼ²ã£¬ÌṩÁ˸ü¼Ó×ÔÓɵĺϳÉÌåÑé¡£ Ìí¼Ó[PrintInfo](#PrintInfo)½Úµã×÷Ϊ¹¤×÷Á÷µ÷ÊÔ¸¨Öú¹¤¾ß¡£ @@ -190,6 +191,28 @@ ComfyUI * x: ×óÉϽÇλÖÃx×ø±êÊä³ö¡£ * y: ×óÉϽÇλÖÃy×ø±êÊä³ö¡£ +### MaskGrow +¶Ômask½øÐÐÀ©ÕÅÊÕËõ±ßÔµºÍÄ£ºý´¦Àí + +½ÚµãÑ¡Ïî˵Ã÷: +![image](image/mask_grow_node.png) +* invert_mask: ÊÇ·ñ·´×ªÕÚÕÖ¡£ +* grow: À©ÕÅ·ù¶È¡£ÕýÖµÊÇÏòÍâÀ©ÕÅ£¬¸ºÖµÊÇÏòÄÚÊÕËõ¡£ +* blur: Ä£ºý¡£ + +### MaskEdgeShrink +ʹmask±ßԵƽ»¬µØ¹ý¶ÉÊÕËõ£¬²¢±£Áô±ßԵϸ½Ú¡£ + +½ÚµãÑ¡Ïî˵Ã÷: +![image](image/mask_edge_shrink_node.png) +* invert_mask: ÊÇ·ñ·´×ªÕÚÕÖ¡£ +* shrink_level: ÊÕËõƽ»¬¼¶±ð¡£ +* soft: ƽ»¬·ù¶È¡£ +* edge_shrink: ±ßÔµÊÕËõ·ù¶È¡£ +* edge_reserve: ±£Áô±ßԵϸ½Ú·ù¶È, 100ΪÍêÈ«±£Áô£¬0ΪÍêÈ«²»±£Áô¡£ + +MaskGrowÓëMaskEdgeShrinkЧ¹û¶Ô±È +![image](image/mask_edge_compare.png) ### MaskPreview Ô¤ÀÀmask @@ -267,6 +290,17 @@ mask Êä³ö: * string +### GetColorTone +´ÓͼƬÖлñÈ¡Ö÷ÑÕÉ«»òƽ¾ùÉ«¡£ +![image](image/get_color_tone_example.png) + +½ÚµãÑ¡Ïî˵Ã÷: +![image](image/get_color_tone_node.png) +* mode£º ģʽ£¬ÓÐÁ½ÖÖ¿ÉÑ¡Ôñ£¬Ö÷ÑÕÉ«main_colorºÍƽ¾ùÉ«average¡£ + +Êä³ö: +* string + ### ExtendCanvas À©Õ¹»­²¼¡£ ![image](image/extend_canvas_example.png) @@ -280,7 +314,7 @@ mask * right: ÓÒ²àÀ©Õ¹Öµ¡£ * color; »­²¼ÑÕÉ« -### X,Y to Percent +### XY to Percent ![image](image/xy2percent_example.png) ½«¾ø¶Ô×ø±êת»»Îª°Ù·Ö±È×ø±ê¡£ @@ -333,26 +367,27 @@ mask ## ÒÔϹ¤×÷¼Æ»®ÖÐ LayerUtility: -* color image -* gradient image * image channel split * image channel merge LayerMask: * text mask -* gradient mask -* mask grow&blur * mask subtract * mask combine * mask diffrent * mask gradient -* mask softedge LayerFilter: * emboss * contour * findedge +LayerColor: +RGB adjust +HSV adjust +brightness & contrast adjust +LUT apply + ## °²×°·½·¨ * ÔÚCompyUI²å¼þĿ¼(ÀýÈç¡°CompyUI\custom_nodes\¡±)Öдò¿ªcmd´°¿Ú£¬¼üÈë```git clone https://github.com/chflame163/ComfyUI_LayerStyle.git```°²×°¡£»òÕßÏÂÔØ½âѹzipÎļþ£¬½«µÃµ½µÄÎļþ¼Ð¸´ÖƵ½ ComfyUI\custom_nodes\ diff --git a/image/get_color_tone_example.png b/image/get_color_tone_example.png new file mode 100644 index 0000000..d9f9571 Binary files /dev/null and b/image/get_color_tone_example.png differ diff --git a/image/get_color_tone_node.png b/image/get_color_tone_node.png new file mode 100644 index 0000000..c776201 Binary files /dev/null and b/image/get_color_tone_node.png differ diff --git a/image/mask_edge_compare.png b/image/mask_edge_compare.png new file mode 100644 index 0000000..461ba9a Binary files /dev/null and b/image/mask_edge_compare.png differ diff --git a/image/mask_edge_shrink_node.png b/image/mask_edge_shrink_node.png new file mode 100644 index 0000000..6f5aeef Binary files /dev/null and b/image/mask_edge_shrink_node.png differ diff --git a/image/mask_grow_node.png b/image/mask_grow_node.png new file mode 100644 index 0000000..6529ac7 Binary files /dev/null and b/image/mask_grow_node.png differ diff --git a/py/color_image.py b/py/color_image.py new file mode 100644 index 0000000..00be88f --- /dev/null +++ b/py/color_image.py @@ -0,0 +1,38 @@ +from .imagefunc import * + +class ColorImage: + + def __init__(self): + pass + + @classmethod + def INPUT_TYPES(self): + + return { + "required": { + "width": ("INT", {"default": 512, "min": 4, "max": 99999, "step": 1}), + "height": ("INT", {"default": 512, "min": 4, "max": 99999, "step": 1}), + "color": ("STRING", {"default": "#000000"},), + }, + "optional": { + } + } + + RETURN_TYPES = ("IMAGE", ) + RETURN_NAMES = ("image", ) + FUNCTION = 'color_image' + CATEGORY = '😺dzNodes/LayerUtility' + OUTPUT_NODE = True + + def color_image(self, width, height, color, ): + + ret_image = Image.new('RGB', (width, height), color=color) + return (pil2tensor(ret_image), ) + +NODE_CLASS_MAPPINGS = { + "LayerUtility: ColorImage": ColorImage +} + +NODE_DISPLAY_NAME_MAPPINGS = { + "LayerUtility: ColorImage": "LayerUtility: ColorImage" +} \ No newline at end of file diff --git a/py/color_picker.py b/py/color_picker.py index 7b31384..07de9ce 100644 --- a/py/color_picker.py +++ b/py/color_picker.py @@ -1,8 +1,6 @@ from .imagefunc import Hex_to_RGB -mode_list = ['HEX', 'DEC'] - class ColorPicker: def __init__(self): @@ -10,7 +8,7 @@ class ColorPicker: @classmethod def INPUT_TYPES(self): - + mode_list = ['HEX', 'DEC'] return { "required": { "color": ("COLOR", {"default": "#FFFFFF"},), diff --git a/py/get_color_tone.py b/py/get_color_tone.py new file mode 100644 index 0000000..b045a01 --- /dev/null +++ b/py/get_color_tone.py @@ -0,0 +1,43 @@ +from .imagefunc import * + +class GetColorTone: + + def __init__(self): + pass + + @classmethod + def INPUT_TYPES(self): + mode_list = ['main_color', 'average'] + return { + "required": { + "image": ("IMAGE", ), # + "mode": (mode_list,), # 主色/å¹³å�‡è‰² + }, + "optional": { + } + } + + RETURN_TYPES = ("STRING",) + RETURN_NAMES = ("RGB color in HEX",) + FUNCTION = 'get_color_tone' + CATEGORY = '😺dzNodes/LayerUtility' + OUTPUT_NODE = True + + def get_color_tone(self, image, mode,): + + _canvas = tensor2pil(image).convert('RGB') + _canvas = gaussian_blur(_canvas, int((_canvas.width + _canvas.height) / 200)) + if mode == 'main_color': + ret_color = get_image_color_tone(_canvas) + else: + ret_color = get_image_color_average(_canvas) + + return (ret_color,) + +NODE_CLASS_MAPPINGS = { + "LayerUtility: GetColorTone": GetColorTone +} + +NODE_DISPLAY_NAME_MAPPINGS = { + "LayerUtility: GetColorTone": "LayerUtility: GetColorTone" +} \ No newline at end of file diff --git a/py/get_image_size.py b/py/get_image_size.py index 5953702..fe74986 100644 --- a/py/get_image_size.py +++ b/py/get_image_size.py @@ -1,9 +1,5 @@ from .imagefunc import * -def process(image:Image) -> Image: - - return image - class GetImageSize: def __init__(self): diff --git a/py/gradient_image.py b/py/gradient_image.py new file mode 100644 index 0000000..827cf3a --- /dev/null +++ b/py/gradient_image.py @@ -0,0 +1,41 @@ +from .imagefunc import * + +class GradientImage: + + def __init__(self): + pass + + @classmethod + def INPUT_TYPES(self): + + return { + "required": { + "width": ("INT", {"default": 512, "min": 4, "max": 99999, "step": 1}), + "height": ("INT", {"default": 512, "min": 4, "max": 99999, "step": 1}), + "angle": ("INT", {"default": 0, "min": -360, "max": 360, "step": 1}), + "start_color": ("STRING", {"default": "#FFFFFF"},), + "end_color": ("STRING", {"default": "#000000"},), + }, + "optional": { + } + } + + RETURN_TYPES = ("IMAGE", ) + RETURN_NAMES = ("image", ) + FUNCTION = 'gradient_image' + CATEGORY = '😺dzNodes/LayerUtility' + OUTPUT_NODE = True + + def gradient_image(self, width, height, angle, start_color, end_color, ): + + ret_image = gradint(start_color, end_color, width, height, angle) + + return (pil2tensor(ret_image), ) + +NODE_CLASS_MAPPINGS = { + "LayerUtility: GradientImage": GradientImage +} + +NODE_DISPLAY_NAME_MAPPINGS = { + "LayerUtility: GradientImage": "LayerUtility: GradientImage" +} \ No newline at end of file diff --git a/py/imagefunc.py b/py/imagefunc.py index 8428dff..9c9289d 100644 --- a/py/imagefunc.py +++ b/py/imagefunc.py @@ -7,6 +7,7 @@ import scipy.ndimage import cv2 from typing import Union, List from PIL import Image, ImageFilter, ImageChops, ImageDraw +import colorsys def log(message): name = 'LayerStyle' @@ -213,6 +214,42 @@ def draw_rect(image:Image, x:int, y:int, width:int, height:int, line_color:str, draw.rectangle((x, y, x + width, y + height), fill=box_color, outline=line_color, width=line_width, ) return image +def get_image_color_tone(image:Image) -> str: + image = image.convert('RGB') + max_score = 0.0001 + dominant_color = None + for count, (r, g, b) in image.getcolors(image.size[0] * image.size[1]): + saturation = colorsys.rgb_to_hsv(r / 255.0, g / 255.0, b / 255.0)[1] + y = min(abs(r * 2104 + g * 4130 + b * 802 + 4096 + 131072) >> 13,235) + y = (y - 16.0) / (235 - 16) + if y>0.9: + continue + score = (saturation+0.1)*count + if score > max_score: + max_score = score + dominant_color = (r, g, b) + ret_color = RGB_to_Hex(dominant_color) + return ret_color + +def get_image_color_average(image:Image) -> str: + image = image.convert('RGB') + width, height = image.size + total_red = 0 + total_green = 0 + total_blue = 0 + for y in range(height): + for x in range(width): + rgb = image.getpixel((x, y)) + total_red += rgb[0] + total_green += rgb[1] + total_blue += rgb[2] + + average_red = total_red // (width * height) + average_green = total_green // (width * height) + average_blue = total_blue // (width * height) + color = (average_red, average_green, average_blue) + ret_color = RGB_to_Hex(color) + return ret_color '''Mask Functions''' diff --git a/py/mask_edge_shrink.py b/py/mask_edge_shrink.py new file mode 100644 index 0000000..9d1a110 --- /dev/null +++ b/py/mask_edge_shrink.py @@ -0,0 +1,68 @@ +from .imagefunc import * + +class MaskEdgeShrink: + + def __init__(self): + pass + + @classmethod + def INPUT_TYPES(self): + + return { + "required": { + "mask": ("MASK", ), # + "invert_mask": ("BOOLEAN", {"default": True}), # å��转mask + "shrink_level": ("INT", {"default": 4, "min": 0, "max": 16, "step": 1}), + "soft": ("INT", {"default": 6, "min": 0, "max": 64, "step": 1}), + "edge_shrink": ("INT", {"default": 1, "min": 0, "max": 999, "step": 1}), + "edge_reserve": ("INT", {"default": 25, "min": 0, "max": 100, "step": 1}), # é€�明度 + }, + "optional": { + } + } + + RETURN_TYPES = ("MASK",) + RETURN_NAMES = ("mask",) + FUNCTION = 'mask_edge_shrink' + CATEGORY = '😺dzNodes/LayerMask' + OUTPUT_NODE = True + + def mask_edge_shrink(self, mask, invert_mask, shrink_level, soft, edge_shrink, edge_reserve): + + glow_range = shrink_level * soft + blur = 12 + _inputmask = mask2image(mask) + _canvas = Image.new('RGB', size=_inputmask.size, color='black') + _layer = Image.new('RGB', size=_inputmask.size, color='white') + + if invert_mask: + mask = 1 - mask + _mask = mask2image(mask).convert('L') + + loop_grow = glow_range + inner_mask = _mask + for x in range(shrink_level): + + _color = step_color('#FFFFFF', '#000000', shrink_level, x) + glow_mask = expand_mask(image2mask(inner_mask), -loop_grow, blur / (x+0.1)) #扩张,模糊 + # å�ˆæˆ� + color_image = Image.new("RGB", _layer.size, color=_color) + alpha = tensor2pil(mask_invert(glow_mask)).convert('L') + _glow = chop_image(_layer, color_image, 'subtract', int(step_value(1, 100, shrink_level, x))) + _layer.paste(_glow, mask=alpha) + loop_grow = loop_grow - int(glow_range / shrink_level) + # å�ˆæˆ�layer + _edge = tensor2pil(expand_mask(mask, -edge_shrink, 0)).convert('RGB') + _layer = chop_image(_layer, _edge, 'normal', edge_reserve) + _layer.paste(_canvas, mask=ImageChops.invert(_mask)) + ret_mask = image2mask(_layer) + + return (ret_mask,) + +NODE_CLASS_MAPPINGS = { + "LayerMask: MaskEdgeShrink": MaskEdgeShrink +} + +NODE_DISPLAY_NAME_MAPPINGS = { + "LayerMask: MaskEdgeShrink": "LayerMask: MaskEdgeShrink" +} \ No newline at end of file diff --git a/py/mask_grow.py b/py/mask_grow.py new file mode 100644 index 0000000..c2d4f34 --- /dev/null +++ b/py/mask_grow.py @@ -0,0 +1,44 @@ +from .imagefunc import * + +class MaskGrow: + + def __init__(self): + pass + + @classmethod + def INPUT_TYPES(self): + + return { + "required": { + "mask": ("MASK", ), # + "invert_mask": ("BOOLEAN", {"default": True}), # å��转mask + "grow": ("INT", {"default": 4, "min": -999, "max": 999, "step": 1}), + "blur": ("INT", {"default": 4, "min": 0, "max": 999, "step": 1}), + }, + "optional": { + } + } + + RETURN_TYPES = ("MASK",) + RETURN_NAMES = ("mask",) + FUNCTION = 'mask_grow' + CATEGORY = '😺dzNodes/LayerMask' + OUTPUT_NODE = True + + def mask_grow(self, mask, invert_mask, grow, blur,): + + if invert_mask: + mask = 1 - mask + # _mask = mask2image(mask).convert('L') + + ret_mask = expand_mask(mask, grow, blur) # 扩张,模糊 + + return (ret_mask,) + +NODE_CLASS_MAPPINGS = { + "LayerMask: MaskGrow": MaskGrow +} + +NODE_DISPLAY_NAME_MAPPINGS = { + "LayerMask: MaskGrow": "LayerMask: MaskGrow" +} \ No newline at end of file diff --git a/py/xy2percent.py b/py/xy2percent.py index 8a429c4..1a909b4 100644 --- a/py/xy2percent.py +++ b/py/xy2percent.py @@ -1,9 +1,5 @@ from .imagefunc import * -def process(image:Image) -> Image: - - return image - class XYtoPercent: def __init__(self):