Commit MaskMotionBlur node and SoftLight node.
The MaskMotionBlur node create motion blur on the mask. The SoftLight node create the bright highlights on the screen appear blurry.
@@ -12,6 +12,8 @@ Nodes are divided into four groups according to their functions: LayerStyle, Lay
|
||||
[中文说明点这里](./README_CN.MD)
|
||||
|
||||
## Update
|
||||
* Commit [MaskMotionBlur](#MaskMotionBlur) node.
|
||||
* Commit [SoftLight](#SoftLight) node.
|
||||
* Commit [ChannelShake](#ChannelShake) node, that is filter, can produce channel dislocation effect similar like Tiktok logo.
|
||||
* 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.
|
||||
@@ -205,6 +207,7 @@ Output:
|
||||
|
||||
### <a id="table1">MaskGrow</a>
|
||||
Grow and shrink edges and blur the mask
|
||||

|
||||
|
||||
Node options:
|
||||

|
||||
@@ -214,6 +217,7 @@ Node options:
|
||||
|
||||
### <a id="table1">MaskEdgeShrink</a>
|
||||
Smooth transition and shrink the mask edges while preserving edge details.
|
||||

|
||||
|
||||
Node options:
|
||||

|
||||
@@ -226,6 +230,15 @@ Node options:
|
||||
Comparison of MaskGrow and MaskEdgeShrink
|
||||

|
||||
|
||||
### <a id="table1">MaskMotionBlur</a>
|
||||
Create motion blur on the mask.
|
||||

|
||||
|
||||
Node options:
|
||||

|
||||
* invert_mask: Whether to reverse the mask.
|
||||
* blur: The size of blur.
|
||||
* angle: The angle of blur.
|
||||
|
||||
### <a id="table1">MaskGradient</a>
|
||||
Create a gradient for the mask from one side.
|
||||
@@ -387,6 +400,17 @@ This node allows any type of input.
|
||||

|
||||
|
||||
|
||||
### <a id="table1">SoftLight</a>
|
||||
Soft light effect, the bright highlights on the screen appear blurry.
|
||||

|
||||
|
||||
Node options:
|
||||

|
||||
* soft: Size of soft light.
|
||||
* threshold: Soft light range. the light appears from the brightest part of the picture. in lower value, the range will be larger, and in higher value, the range will be smaller.
|
||||
* opacity: Opacity of the soft light.
|
||||
|
||||
|
||||
### <a id="table1">ChannelShake</a>
|
||||
Channel misalignment. similar to the effect of Tiktok logo.
|
||||

|
||||
|
||||
@@ -9,6 +9,8 @@ ComfyUI
|
||||
* [LayerFilter](#LayerFilter)节点组提供图像效果滤镜。
|
||||
|
||||
## 更新说明
|
||||
* 添加[MaskMotionBlur](#MaskMotionBlur)节点。
|
||||
* 添加[SoftLight](#SoftLight)节点。
|
||||
* 添加[ChannelShake](#ChannelShake)节点,这是一个滤镜,能产生类似抖音logo的通道错位效果。
|
||||
* 添加[MaskGradient](#MaskGradient)节点,可使mask产生渐变。
|
||||
* 添加[GetColorTone](#GetColorTone)节点,可以获取图片的主色或平均色。添加[MaskGrow](#MaskGrow)和[MaskEdgeShrink](#MaskEdgeShrink)节点。
|
||||
@@ -198,6 +200,7 @@ ComfyUI
|
||||
|
||||
### <a id="table1">MaskGrow</a>
|
||||
对mask进行扩张收缩边缘和模糊处理
|
||||

|
||||
|
||||
节点选项说明:
|
||||

|
||||
@@ -207,6 +210,7 @@ ComfyUI
|
||||
|
||||
### <a id="table1">MaskEdgeShrink</a>
|
||||
使mask边缘平滑地过渡收缩,并保留边缘细节。
|
||||

|
||||
|
||||
节点选项说明:
|
||||

|
||||
@@ -219,6 +223,16 @@ ComfyUI
|
||||
MaskGrow与MaskEdgeShrink效果对比
|
||||

|
||||
|
||||
### <a id="table1">MaskMotionBlur</a>
|
||||
使mask产生运动模糊。
|
||||

|
||||
|
||||
节点选项说明:
|
||||

|
||||
* invert_mask: 是否反转遮罩。
|
||||
* blur: 模糊大小。
|
||||
* angle: 模糊角度。
|
||||
|
||||
### <a id="table1">MaskGradient</a>
|
||||
使mask从一侧产生渐变。
|
||||

|
||||
@@ -377,6 +391,16 @@ mask
|
||||

|
||||
|
||||
|
||||
### <a id="table1">SoftLight</a>
|
||||
柔光效果。
|
||||

|
||||
|
||||
节点选项说明:
|
||||

|
||||
* soft: 柔光大小。
|
||||
* threshold: 柔光范围。柔光从画面最明亮的部分呈现。数值越低范围越大,越高范围越小。
|
||||
* opacity: 柔光的不透明度。
|
||||
|
||||
### <a id="table1">ChannelShake</a>
|
||||
通道错位。类似抖音logo的效果。
|
||||

|
||||
@@ -421,9 +445,6 @@ mask
|
||||
|
||||
LayerMask:
|
||||
* text mask
|
||||
* mask subtract
|
||||
* mask combine
|
||||
* mask diffrent
|
||||
|
||||
LayerFilter:
|
||||
* emboss
|
||||
|
||||
|
Before Width: | Height: | Size: 168 KiB After Width: | Height: | Size: 180 KiB |
|
Before Width: | Height: | Size: 303 KiB After Width: | Height: | Size: 313 KiB |
|
After Width: | Height: | Size: 332 KiB |
|
After Width: | Height: | Size: 327 KiB |
|
After Width: | Height: | Size: 461 KiB |
|
After Width: | Height: | Size: 134 KiB |
|
After Width: | Height: | Size: 1.5 MiB |
|
After Width: | Height: | Size: 84 KiB |
@@ -14,7 +14,7 @@ class ChannelShake:
|
||||
"required": {
|
||||
"image": ("IMAGE", ), #
|
||||
"distance": ("INT", {"default": 20, "min": 1, "max": 999, "step": 1}), # 距离
|
||||
"angle": ("FLOAT", {"default": 40, "min": -360, "max": 360, "step": 0.01}), # 角度
|
||||
"angle": ("FLOAT", {"default": 40, "min": -360, "max": 360, "step": 0.1}), # 角度
|
||||
"mode": (channel_mode,), # 模式
|
||||
},
|
||||
"optional": {
|
||||
|
||||
@@ -250,6 +250,19 @@ def get_image_color_average(image:Image) -> str:
|
||||
ret_color = RGB_to_Hex(color)
|
||||
return ret_color
|
||||
|
||||
def get_image_bright_average(image:Image) -> int:
|
||||
image = image.convert('L')
|
||||
width, height = image.size
|
||||
total_bright = 0
|
||||
pixels = 0
|
||||
for y in range(height):
|
||||
for x in range(width):
|
||||
b = image.getpixel((x, y))
|
||||
if b > 1: # 排除死黑
|
||||
pixels += 1
|
||||
total_bright += b
|
||||
return int(total_bright / pixels)
|
||||
|
||||
'''Mask Functions'''
|
||||
|
||||
def expand_mask(mask:torch.Tensor, grow:int, blur:int) -> torch.Tensor:
|
||||
@@ -345,6 +358,17 @@ def max_inscribed_rect(image:Image) -> tuple:
|
||||
x1, y1, x2, y2 = min(x1, x2), min(y1, y2), max(x1, x2), max(y1, y2)
|
||||
return (x1, y1, x2 - x1, y2 - y1)
|
||||
|
||||
def gray_threshold(image:Image, thresh:int=127, otsu:bool=False) -> Image:
|
||||
cv2_image = pil2cv2(image)
|
||||
gray = cv2.cvtColor(cv2_image, cv2.COLOR_BGR2GRAY)
|
||||
if otsu:
|
||||
_, thresh = cv2.threshold(gray,0,255,cv2.THRESH_BINARY+cv2.THRESH_OTSU)
|
||||
else:
|
||||
_, thresh = cv2.threshold(gray, thresh, 255, cv2.THRESH_TOZERO)
|
||||
|
||||
return cv22pil(thresh).convert('L')
|
||||
|
||||
|
||||
'''Color Functions'''
|
||||
|
||||
def RGB_to_Hex(RGB) -> str:
|
||||
|
||||
@@ -0,0 +1,45 @@
|
||||
import copy
|
||||
|
||||
from .imagefunc import *
|
||||
|
||||
class MaskMotionBlur:
|
||||
|
||||
def __init__(self):
|
||||
pass
|
||||
|
||||
@classmethod
|
||||
def INPUT_TYPES(self):
|
||||
|
||||
return {
|
||||
"required": {
|
||||
"mask": ("MASK",),
|
||||
"invert_mask": ("BOOLEAN", {"default": True}), # 反转mask
|
||||
"blur": ("INT", {"default": 20, "min": 1, "max": 9999, "step": 1}),
|
||||
"angle": ("FLOAT", {"default": 0, "min": -360, "max": 360, "step": 0.1}),
|
||||
},
|
||||
"optional": {
|
||||
}
|
||||
}
|
||||
|
||||
RETURN_TYPES = ("MASK",)
|
||||
RETURN_NAMES = ("mask",)
|
||||
FUNCTION = 'mask_motion_blur'
|
||||
CATEGORY = '😺dzNodes/LayerMask'
|
||||
OUTPUT_NODE = True
|
||||
|
||||
def mask_motion_blur(self, mask, invert_mask, blur, angle,):
|
||||
|
||||
if invert_mask:
|
||||
mask = 1 - mask
|
||||
_mask = mask2image(mask).convert('RGB')
|
||||
_blurimage = motion_blur(_mask, angle, blur)
|
||||
ret_mask = image2mask(_blurimage)
|
||||
return (ret_mask,)
|
||||
|
||||
NODE_CLASS_MAPPINGS = {
|
||||
"LayerMask: MaskMotionBlur": MaskMotionBlur
|
||||
}
|
||||
|
||||
NODE_DISPLAY_NAME_MAPPINGS = {
|
||||
"LayerMask: MaskMotionBlur": "LayerMask: MaskMotionBlur"
|
||||
}
|
||||
@@ -0,0 +1,54 @@
|
||||
from .imagefunc import *
|
||||
|
||||
class SoftLight:
|
||||
|
||||
def __init__(self):
|
||||
pass
|
||||
|
||||
@classmethod
|
||||
def INPUT_TYPES(self):
|
||||
|
||||
return {
|
||||
"required": {
|
||||
"image": ("IMAGE", ), #
|
||||
|
||||
"soft": ("FLOAT", {"default": 1, "min": 0.2, "max": 10, "step": 0.01}), # 模糊
|
||||
"threshold": ("INT", {"default": -10, "min": -255, "max": 255, "step": 1}), # 高光阈值
|
||||
"opacity": ("INT", {"default": 100, "min": 0, "max": 100, "step": 1}), # 透明度
|
||||
},
|
||||
"optional": {
|
||||
}
|
||||
}
|
||||
|
||||
RETURN_TYPES = ("IMAGE",)
|
||||
RETURN_NAMES = ("image",)
|
||||
FUNCTION = 'soft_light'
|
||||
CATEGORY = '😺dzNodes/LayerFilter'
|
||||
OUTPUT_NODE = True
|
||||
|
||||
def soft_light(self, image, soft, threshold, opacity,):
|
||||
blend_mode = 'screen'
|
||||
_canvas = tensor2pil(image).convert('RGB')
|
||||
blur = int((_canvas.width + _canvas.height) / 200 * soft)
|
||||
_otsumask = gray_threshold(_canvas, otsu=True)
|
||||
_removebkgd = remove_background(_canvas, _otsumask, '#000000').convert('L')
|
||||
auto_threshold = get_image_bright_average(_removebkgd)
|
||||
light_mask = gray_threshold(_canvas, auto_threshold + threshold)
|
||||
highlight_mask = gray_threshold(_canvas, auto_threshold + (255 - auto_threshold) // 2 + threshold // 2)
|
||||
blurimage = gaussian_blur(_canvas, soft).convert('RGB')
|
||||
light = chop_image(_canvas, blurimage, blend_mode=blend_mode, opacity=opacity)
|
||||
highlight = chop_image(light, blurimage, blend_mode=blend_mode, opacity=opacity)
|
||||
_canvas.paste(highlight, mask=gaussian_blur(light_mask, blur * 2).convert('L'))
|
||||
_canvas.paste(highlight, mask=gaussian_blur(highlight_mask, blur).convert('L'))
|
||||
ret_image = _canvas
|
||||
log('SoftLight Processed.')
|
||||
|
||||
return (pil2tensor(ret_image),)
|
||||
|
||||
NODE_CLASS_MAPPINGS = {
|
||||
"LayerFilter: SoftLight": SoftLight
|
||||
}
|
||||
|
||||
NODE_DISPLAY_NAME_MAPPINGS = {
|
||||
"LayerFilter: SoftLight": "LayerFilter: SoftLight"
|
||||
}
|
||||