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

@@ -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:
+
+* 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:
+
+* 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
+
+
+
### MaskPreview
Preview the input mask

@@ -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.
+
+
+Node options:
+
+* 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

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½øÐÐÀ©ÕÅÊÕËõ±ßÔµºÍÄ£ºý´¦Àí
+
+½ÚµãÑ¡Ïî˵Ã÷:
+
+* invert_mask: ÊÇ·ñ·´×ªÕÚÕÖ¡£
+* grow: À©ÕÅ·ù¶È¡£ÕýÖµÊÇÏòÍâÀ©ÕÅ£¬¸ºÖµÊÇÏòÄÚÊÕËõ¡£
+* blur: Ä£ºý¡£
+
+### MaskEdgeShrink
+ʹmask±ßԵƽ»¬µØ¹ý¶ÉÊÕËõ£¬²¢±£Áô±ßԵϸ½Ú¡£
+
+½ÚµãÑ¡Ïî˵Ã÷:
+
+* invert_mask: ÊÇ·ñ·´×ªÕÚÕÖ¡£
+* shrink_level: ÊÕËõƽ»¬¼¶±ð¡£
+* soft: ƽ»¬·ù¶È¡£
+* edge_shrink: ±ßÔµÊÕËõ·ù¶È¡£
+* edge_reserve: ±£Áô±ßԵϸ½Ú·ù¶È, 100ΪÍêÈ«±£Áô£¬0ΪÍêÈ«²»±£Áô¡£
+
+MaskGrowÓëMaskEdgeShrinkЧ¹û¶Ô±È
+
### MaskPreview
Ô¤ÀÀmask
@@ -267,6 +290,17 @@ mask
Êä³ö:
* string
+### GetColorTone
+´ÓͼƬÖлñÈ¡Ö÷ÑÕÉ«»òƽ¾ùÉ«¡£
+
+
+½ÚµãÑ¡Ïî˵Ã÷:
+
+* mode£º ģʽ£¬ÓÐÁ½ÖÖ¿ÉÑ¡Ôñ£¬Ö÷ÑÕÉ«main_colorºÍƽ¾ùÉ«average¡£
+
+Êä³ö:
+* string
+
### ExtendCanvas
À©Õ¹»²¼¡£

@@ -280,7 +314,7 @@ mask
* right: ÓÒ²àÀ©Õ¹Öµ¡£
* color; »²¼ÑÕÉ«
-### X,Y to Percent
+### XY to Percent

½«¾ø¶Ô×ø±êת»»Îª°Ù·Ö±È×ø±ê¡£
@@ -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):