diff --git a/README.MD b/README.MD
index 6b2ded9..5f23705 100644
--- a/README.MD
+++ b/README.MD
@@ -12,6 +12,7 @@ Nodes are divided into four groups according to their functions: LayerStyle, Lay
[䏿–‡è¯´æ˜Žç‚¹è¿™é‡Œ](./README_CN.MD)
## Update
+* Commit [MaskGradient](#MaskGradient) node, can create a gradient in the mask.
* Commit [GetColorTone](#GetColorTone) node, can obtain the main color or average color of the image.
Commit [MaskGrow](#MaskGrow) and [MaskEdgeShrink](#MaskEdgeShrink) nodes.
* Commit [MaskBoxDetect](#MaskBoxDetect) node, which can automatically detect the position through the mask and output it to the composite node.
@@ -223,6 +224,19 @@ Comparison of MaskGrow and MaskEdgeShrink

+### MaskGradient
+Create a gradient for the mask from one side.
+
+
+Node options:
+
+* invert_mask: Whether to reverse the mask.
+* gradient_side: Generate gradient from which edge. There are four directions: top, bottom, left and right.
+* gradient_scale: Gradient distance. The default value of 100 indicates that one side of the gradient is completely transparent and the other side is completely opaque. The smaller the value, the shorter the distance from transparent to opaque.
+* gradient_offset: Gradient position offset.
+* opacity: The opacity of the gradient.
+
+
### MaskPreview
Preview the input mask

diff --git a/README_CN.MD b/README_CN.MD
index 5668564..414384d 100644
--- a/README_CN.MD
+++ b/README_CN.MD
@@ -9,6 +9,7 @@ ComfyUI
* [LayerFilter](#LayerFilter)½Úµã×éÌṩͼÏñЧ¹ûÂ˾µ¡£
## ¸üÐÂ˵Ã÷
+* Ìí¼Ó[MaskGradient](#MaskGradient)½Úµã£¬¿Éʹmask²úÉú½¥±ä¡£
* Ìí¼Ó[GetColorTone](#GetColorTone)½Úµã£¬¿ÉÒÔ»ñȡͼƬµÄÖ÷É«»òƽ¾ùÉ«¡£Ìí¼Ó[MaskGrow](#MaskGrow)ºÍ[MaskEdgeShrink](#MaskEdgeShrink)½Úµã¡£
* Ìí¼Ó[MaskBoxDetect](#MaskBoxDetect)½Úµã£¬¿ÉÒÔͨ¹ýmask×Ô¶¯Ì½²âλÖò¢Êä³öµ½ºÏ³É½Úµã¡£Ìí¼Ó[XY to Percent](#Percent)½Úµã£¬½«¾ø¶Ô×ø±êת»»Îªpercent×ø±ê¡£Ìí¼Ó[GaussianBlur](#GaussianBlur)½Úµã¡£Ìí¼Ó[GetImageSize](#GetImageSize)½Úµã¡£
* Ìí¼Ó[ExtendCanvas](#ExtendCanvas)½Úµã¡£
@@ -214,6 +215,18 @@ ComfyUI
MaskGrowÓëMaskEdgeShrinkЧ¹û¶Ô±È

+### MaskGradient
+ʹmask´ÓÒ»²à²úÉú½¥±ä¡£
+
+
+½ÚµãÑ¡Ïî˵Ã÷:
+
+* invert_mask: ÊÇ·ñ·´×ªÕÚÕÖ¡£
+* gradient_side: ´ÓÄĸö±ß²úÉú½¥±ä¡£ÓÐËĸö·½Ïò£º¶¥²àtop¡¢µ×²àbottom¡¢×ó²àleft¡¢ÓÒ²àright¡£
+* gradient_scale: ½¥±ä¾àÀ롣ĬÈÏÖµ100±íʾ½¥±ä²úÉúÒ»²àÍêȫ͸Ã÷£¬ÁíÒ»²àÍêÈ«²»Í¸Ã÷¡£ÊýֵԽС£¬´Ó͸Ã÷µ½²»Í¸Ã÷µÄ¾àÀëÔ½¶Ì¡£
+* gradient_offset: ½¥±äλÖÃÆ«ÒÆ¡£
+* opacity: ½¥±äµÄ²»Í¸Ã÷¶È¡£
+
### MaskPreview
Ô¤ÀÀmask

diff --git a/image/mask_gradient_example.png b/image/mask_gradient_example.png
new file mode 100644
index 0000000..2cdcca1
Binary files /dev/null and b/image/mask_gradient_example.png differ
diff --git a/image/mask_gradient_node.png b/image/mask_gradient_node.png
new file mode 100644
index 0000000..fe3aa2d
Binary files /dev/null and b/image/mask_gradient_node.png differ
diff --git a/py/gradient_image.py b/py/gradient_image.py
index 827cf3a..46370d7 100644
--- a/py/gradient_image.py
+++ b/py/gradient_image.py
@@ -28,7 +28,7 @@ class GradientImage:
def gradient_image(self, width, height, angle, start_color, end_color, ):
- ret_image = gradint(start_color, end_color, width, height, angle)
+ ret_image = gradient(start_color, end_color, width, height, angle)
return (pil2tensor(ret_image), )
diff --git a/py/gradient_overlay.py b/py/gradient_overlay.py
index 4b04c51..e671a74 100644
--- a/py/gradient_overlay.py
+++ b/py/gradient_overlay.py
@@ -54,7 +54,7 @@ class GradientOverlay:
_mask = Image.new('L', _layer.size, 'white')
log('Warning: mask mismatch, droped!')
- _gradient = gradint(start_color, end_color, _layer.width, _layer.height, float(angle))
+ _gradient = gradient(start_color, end_color, _layer.width, _layer.height, float(angle))
# ��layer
_comp = chop_image(_layer, _gradient, blend_mode, opacity)
diff --git a/py/imagefunc.py b/py/imagefunc.py
index 84ddd79..65c4b3f 100644
--- a/py/imagefunc.py
+++ b/py/imagefunc.py
@@ -188,7 +188,7 @@ def create_gradient(start_color_inhex:str, end_color_inhex:str, width:int, heigh
draw.line((0, i, width, i), fill=color)
return ret_image
-def gradint(start_color_inhex:str, end_color_inhex:str, width:int, height:int, angle:float, ) -> Image:
+def gradient(start_color_inhex:str, end_color_inhex:str, width:int, height:int, angle:float, ) -> Image:
radius = int((width + height) / 4)
g = create_gradient(start_color_inhex, end_color_inhex, radius, radius)
_canvas = Image.new('RGB', size=(radius, radius*3), color=start_color_inhex)
diff --git a/py/mask_gradient.py b/py/mask_gradient.py
new file mode 100644
index 0000000..cf3b284
--- /dev/null
+++ b/py/mask_gradient.py
@@ -0,0 +1,133 @@
+import copy
+
+from .imagefunc import *
+
+class MaskGradient:
+
+ def __init__(self):
+ pass
+
+ @classmethod
+ def INPUT_TYPES(self):
+ side = ['top', 'bottom', 'left', 'right']
+ return {
+ "required": {
+ "mask": ("MASK",),
+ "invert_mask": ("BOOLEAN", {"default": True}), # �转mask
+ "gradient_side": (side,),
+ "gradient_scale": ("INT", {"default": 100, "min": 1, "max": 9999, "step": 1}),
+ "gradient_offset": ("INT", {"default": 0, "min": -9999, "max": 9999, "step": 1}),
+ "opacity": ("INT", {"default": 100, "min": 0, "max": 100, "step": 1}),
+ },
+ "optional": {
+ }
+ }
+
+ RETURN_TYPES = ("MASK",)
+ RETURN_NAMES = ("mask",)
+ FUNCTION = 'mask_gradient'
+ CATEGORY = '😺dzNodes/LayerMask'
+ OUTPUT_NODE = True
+
+ def mask_gradient(self, mask, invert_mask, gradient_side, gradient_scale, gradient_offset, opacity, ):
+
+ if invert_mask:
+ mask = 1 - mask
+ _mask = mask2image(mask).convert('L')
+ _canvas = copy.copy(_mask)
+ width = _mask.width
+ height = _mask.height
+ _gradient = gradient('#000000', '#FFFFFF',
+ _mask.width, _mask.height, 0)
+ (box_x, box_y, box_width, box_height) = min_bounding_rect(_mask)
+ # preview_image = mask2image(mask).convert('RGB')
+ # preview_image = draw_rect(preview_image, box_x, box_y, box_width, box_height,
+ # line_color = "#F00000", line_width = int(preview_image.height / 60))
+ if gradient_side == 'top':
+ boxsize = (width, box_height)
+ _gradient = _gradient.transpose(Image.FLIP_TOP_BOTTOM)
+ _gradient = _gradient.resize(boxsize)
+ _black = Image.new('RGB', size = boxsize, color = 'black')
+ if gradient_scale != 100:
+ _box = Image.new('RGB', size = boxsize, color = 'black')
+ _gradient = _gradient.resize((width, int(box_height * gradient_scale / 100)))
+ _box.paste(_gradient, box = (0, 0))
+ _gradient = _box
+ if gradient_offset != 0:
+ _box = Image.new('RGB', size = boxsize, color = 'black')
+ _boxwhite = Image.new('RGB', size = boxsize, color = 'white')
+ _box.paste(_gradient, box=(0, gradient_offset))
+ _box.paste(_boxwhite, box = (0, gradient_offset - _box.height))
+ _gradient = _box
+ if gradient_offset > box_height:
+ _gradient = Image.new('RGB', size = boxsize, color = 'white')
+ _canvas.paste(_black, box = (0, box_y), mask = _gradient.convert('L'))
+ elif gradient_side == 'bottom':
+ boxsize = (width, box_height)
+ _gradient = _gradient.resize((width, box_height))
+ _black = Image.new('RGB', size = boxsize, color = 'black')
+ if gradient_scale != 100:
+ _box = Image.new('RGB', size = boxsize, color = 'black')
+ _gradient = _gradient.resize((width, int(box_height * gradient_scale / 100)))
+ _box.paste(_gradient, box = (0, box_height - _gradient.height))
+ _gradient = _box
+ if gradient_offset != 0:
+ _box = Image.new('RGB', size = boxsize, color = 'black')
+ _boxwhite = Image.new('RGB', size = boxsize, color = 'white')
+ _box.paste(_gradient, box=(0, gradient_offset))
+ _box.paste(_boxwhite, box = (0, gradient_offset + _box.height))
+ _gradient = _box
+ if gradient_offset < -box_height:
+ _gradient = Image.new('RGB', size=boxsize, color='white')
+ _canvas.paste(_black, box = (0, box_y), mask = _gradient.convert('L'))
+ elif gradient_side == 'left':
+ boxsize = (box_width, height)
+ _gradient = _gradient.transpose(Image.ROTATE_270)
+ _gradient = _gradient.resize(boxsize)
+ _black = Image.new('RGB', size = boxsize, color = 'black')
+ if gradient_scale != 100:
+ _box = Image.new('RGB', size = boxsize, color = 'black')
+ _gradient = _gradient.resize((int(box_width * gradient_scale / 100), height))
+ _box.paste(_gradient, box = (0, 0))
+ _gradient = _box
+ if gradient_offset != 0:
+ _box = Image.new('RGB', size = boxsize, color = 'black')
+ _boxwhite = Image.new('RGB', size = boxsize, color = 'white')
+ _box.paste(_gradient, box=(gradient_offset, 0))
+ _box.paste(_boxwhite, box = (gradient_offset - _box.width, 0))
+ _gradient = _box
+ if gradient_offset > box_width:
+ _gradient = Image.new('RGB', size=boxsize, color='white')
+ _canvas.paste(_black, box = (box_x, 0), mask = _gradient.convert('L'))
+ elif gradient_side == 'right':
+ boxsize = (box_width, height)
+ _gradient = _gradient.transpose(Image.ROTATE_90)
+ _gradient = _gradient.resize(boxsize)
+ _black = Image.new('RGB', size = boxsize, color = 'black')
+ if gradient_scale != 100:
+ _box = Image.new('RGB', size = boxsize, color = 'black')
+ _gradient = _gradient.resize((int(box_width * gradient_scale / 100), height))
+ _box.paste(_gradient, box = (box_width - _gradient.width, 0))
+ _gradient = _box
+ if gradient_offset != 0:
+ _box = Image.new('RGB', size = boxsize, color = 'black')
+ _boxwhite = Image.new('RGB', size = boxsize, color = 'white')
+ _box.paste(_gradient, box=(gradient_offset, 0))
+ _box.paste(_boxwhite, box = (gradient_offset + _box.width, 0))
+ _gradient = _box
+ if gradient_offset < -box_width:
+ _gradient = Image.new('RGB', size=boxsize, color='white')
+ _canvas.paste(_black, box = (box_x, 0), mask = _gradient.convert('L'))
+ # opacity
+ if opacity < 100:
+ _canvas = chop_image(_mask, _canvas, 'normal', opacity)
+ ret_mask = image2mask(_canvas)
+ return (ret_mask,)
+
+NODE_CLASS_MAPPINGS = {
+ "LayerMask: MaskGradient": MaskGradient
+}
+
+NODE_DISPLAY_NAME_MAPPINGS = {
+ "LayerMask: MaskGradient": "LayerMask: MaskGradient"
+}
\ No newline at end of file
diff --git a/workflow/mask_gradient_example.json b/workflow/mask_gradient_example.json
new file mode 100644
index 0000000..5e3d2ee
--- /dev/null
+++ b/workflow/mask_gradient_example.json
@@ -0,0 +1,442 @@
+{
+ "last_node_id": 20,
+ "last_link_id": 37,
+ "nodes": [
+ {
+ "id": 12,
+ "type": "LayerUtility: ImageBlendAdvance",
+ "pos": [
+ 1709,
+ -215
+ ],
+ "size": {
+ "0": 315,
+ "1": 338
+ },
+ "flags": {},
+ "order": 5,
+ "mode": 0,
+ "inputs": [
+ {
+ "name": "background_image",
+ "type": "IMAGE",
+ "link": 35
+ },
+ {
+ "name": "layer_image",
+ "type": "IMAGE",
+ "link": 17
+ },
+ {
+ "name": "layer_mask",
+ "type": "MASK",
+ "link": 33
+ }
+ ],
+ "outputs": [
+ {
+ "name": "image",
+ "type": "IMAGE",
+ "links": [
+ 15
+ ],
+ "shape": 3,
+ "slot_index": 0
+ },
+ {
+ "name": "mask",
+ "type": "MASK",
+ "links": null,
+ "shape": 3
+ }
+ ],
+ "properties": {
+ "Node name for S&R": "LayerUtility: ImageBlendAdvance"
+ },
+ "widgets_values": [
+ false,
+ "normal",
+ 78,
+ 50,
+ 100,
+ "vertical",
+ 0.8,
+ 1,
+ 0,
+ "lanczos",
+ 0
+ ]
+ },
+ {
+ "id": 19,
+ "type": "ImageScale",
+ "pos": [
+ 1306,
+ -209
+ ],
+ "size": {
+ "0": 315,
+ "1": 130
+ },
+ "flags": {},
+ "order": 2,
+ "mode": 0,
+ "inputs": [
+ {
+ "name": "image",
+ "type": "IMAGE",
+ "link": 34
+ }
+ ],
+ "outputs": [
+ {
+ "name": "IMAGE",
+ "type": "IMAGE",
+ "links": [
+ 35
+ ],
+ "shape": 3,
+ "slot_index": 0
+ }
+ ],
+ "properties": {
+ "Node name for S&R": "ImageScale"
+ },
+ "widgets_values": [
+ "nearest-exact",
+ 1920,
+ 1080,
+ "disabled"
+ ]
+ },
+ {
+ "id": 18,
+ "type": "LayerMask: MaskGradient",
+ "pos": [
+ 1318,
+ 189
+ ],
+ "size": {
+ "0": 315,
+ "1": 154
+ },
+ "flags": {},
+ "order": 3,
+ "mode": 0,
+ "inputs": [
+ {
+ "name": "mask",
+ "type": "MASK",
+ "link": 36
+ }
+ ],
+ "outputs": [
+ {
+ "name": "mask",
+ "type": "MASK",
+ "links": [
+ 32,
+ 33
+ ],
+ "shape": 3,
+ "slot_index": 0
+ }
+ ],
+ "properties": {
+ "Node name for S&R": "LayerMask: MaskGradient"
+ },
+ "widgets_values": [
+ true,
+ "top",
+ 74,
+ 397,
+ 100
+ ]
+ },
+ {
+ "id": 9,
+ "type": "LoadImage",
+ "pos": [
+ 936,
+ -209
+ ],
+ "size": {
+ "0": 315,
+ "1": 314
+ },
+ "flags": {},
+ "order": 0,
+ "mode": 0,
+ "outputs": [
+ {
+ "name": "IMAGE",
+ "type": "IMAGE",
+ "links": [
+ 34
+ ],
+ "shape": 3,
+ "slot_index": 0
+ },
+ {
+ "name": "MASK",
+ "type": "MASK",
+ "links": [],
+ "shape": 3,
+ "slot_index": 1
+ }
+ ],
+ "properties": {
+ "Node name for S&R": "LoadImage"
+ },
+ "widgets_values": [
+ "beach (2).jpg",
+ "image"
+ ]
+ },
+ {
+ "id": 16,
+ "type": "LayerMask: MaskPreview",
+ "pos": [
+ 1709,
+ 177
+ ],
+ "size": [
+ 313.1912463378908,
+ 338.8637414550783
+ ],
+ "flags": {},
+ "order": 4,
+ "mode": 0,
+ "inputs": [
+ {
+ "name": "mask",
+ "type": "MASK",
+ "link": 32
+ }
+ ],
+ "properties": {
+ "Node name for S&R": "LayerMask: MaskPreview"
+ }
+ },
+ {
+ "id": 10,
+ "type": "LayerUtility: ImageBlendAdvance",
+ "pos": [
+ 2077,
+ -213
+ ],
+ "size": {
+ "0": 315,
+ "1": 338
+ },
+ "flags": {},
+ "order": 6,
+ "mode": 0,
+ "inputs": [
+ {
+ "name": "background_image",
+ "type": "IMAGE",
+ "link": 15
+ },
+ {
+ "name": "layer_image",
+ "type": "IMAGE",
+ "link": 10
+ },
+ {
+ "name": "layer_mask",
+ "type": "MASK",
+ "link": 37
+ }
+ ],
+ "outputs": [
+ {
+ "name": "image",
+ "type": "IMAGE",
+ "links": [
+ 18
+ ],
+ "shape": 3,
+ "slot_index": 0
+ },
+ {
+ "name": "mask",
+ "type": "MASK",
+ "links": null,
+ "shape": 3
+ }
+ ],
+ "properties": {
+ "Node name for S&R": "LayerUtility: ImageBlendAdvance"
+ },
+ "widgets_values": [
+ true,
+ "normal",
+ 100,
+ 50,
+ 40,
+ "None",
+ 0.8,
+ 1,
+ 0,
+ "lanczos",
+ 0
+ ]
+ },
+ {
+ "id": 13,
+ "type": "PreviewImage",
+ "pos": [
+ 2452,
+ -213
+ ],
+ "size": [
+ 743.0909667968749,
+ 703.6272277832031
+ ],
+ "flags": {},
+ "order": 7,
+ "mode": 0,
+ "inputs": [
+ {
+ "name": "images",
+ "type": "IMAGE",
+ "link": 18
+ }
+ ],
+ "properties": {
+ "Node name for S&R": "PreviewImage"
+ }
+ },
+ {
+ "id": 2,
+ "type": "LoadImage",
+ "pos": [
+ 931,
+ 178
+ ],
+ "size": {
+ "0": 315,
+ "1": 314
+ },
+ "flags": {},
+ "order": 1,
+ "mode": 0,
+ "outputs": [
+ {
+ "name": "IMAGE",
+ "type": "IMAGE",
+ "links": [
+ 10,
+ 17
+ ],
+ "shape": 3,
+ "slot_index": 0
+ },
+ {
+ "name": "MASK",
+ "type": "MASK",
+ "links": [
+ 36,
+ 37
+ ],
+ "shape": 3,
+ "slot_index": 1
+ }
+ ],
+ "properties": {
+ "Node name for S&R": "LoadImage"
+ },
+ "widgets_values": [
+ "erji_alpha (8).png",
+ "image"
+ ]
+ }
+ ],
+ "links": [
+ [
+ 10,
+ 2,
+ 0,
+ 10,
+ 1,
+ "IMAGE"
+ ],
+ [
+ 15,
+ 12,
+ 0,
+ 10,
+ 0,
+ "IMAGE"
+ ],
+ [
+ 17,
+ 2,
+ 0,
+ 12,
+ 1,
+ "IMAGE"
+ ],
+ [
+ 18,
+ 10,
+ 0,
+ 13,
+ 0,
+ "IMAGE"
+ ],
+ [
+ 32,
+ 18,
+ 0,
+ 16,
+ 0,
+ "MASK"
+ ],
+ [
+ 33,
+ 18,
+ 0,
+ 12,
+ 2,
+ "MASK"
+ ],
+ [
+ 34,
+ 9,
+ 0,
+ 19,
+ 0,
+ "IMAGE"
+ ],
+ [
+ 35,
+ 19,
+ 0,
+ 12,
+ 0,
+ "IMAGE"
+ ],
+ [
+ 36,
+ 2,
+ 1,
+ 18,
+ 0,
+ "MASK"
+ ],
+ [
+ 37,
+ 2,
+ 1,
+ 10,
+ 2,
+ "MASK"
+ ]
+ ],
+ "groups": [],
+ "config": {},
+ "extra": {},
+ "version": 0.4
+}
\ No newline at end of file