commit HLFrequencyDetailRestore,AddGrain,MaskGrain,FilmV2 nodes
@@ -80,6 +80,10 @@ When this error has occurred, please check the network environment.
|
||||
## Update
|
||||
<font size="4">**If the dependency package error after updating, please reinstall the relevant dependency packages. </font><br />
|
||||
|
||||
|
||||
* Commit [HLFrequencyDetailRestore](#HLFrequencyDetailRestore) node, Using low-frequency filtering and high-frequency preserving to restore image details, the fusion is better.
|
||||
* Commit [AddGrain](#AddGrain) and [MaskGrain](#MaskGrain) nodes, Add noise to a picture or mask.
|
||||
* Commit [FilmV2](#FilmV2) node, The fastgrain method is added on the basis of the previous one, and the noise generation speed is 10 times faster.
|
||||
* Commit [ImageToMask](#ImageToMask) node, it can be converted image into mask. Supports converting any channel in LAB, RGBA, YUV, and HSV modes into masks, while providing color scale adjustment. Support mask optional input to obtain masks that only include valid parts.
|
||||
* The blackpoint and whitepoint options in some nodes have been changed to slider adjustment for a more intuitive display. Include [MaskEdgeUltraDetailV2](#MaskEdgeUltraDetailV2), [SegmentAnythingUltraV2](#SegmentAnythingUltraV2), [RmBgUltraV2](#RmBgUltraV2),[PersonMaskUltraV2](#PersonMaskUltraV2),[BiRefNetUltra](#BiRefNetUltra), [SegformerB2ClothesUltra](#SegformerB2ClothesUltra), [BlendIfMask](#BlendIfMask) and [Levels](#Levels).
|
||||
* [ImageScaleRestoreV2](#ImageScaleRestoreV2) and [ImageScaleByAspectRatioV2](#ImageScaleByAspectRatioV2) nodes add the ```total_pixel``` method to scale images.
|
||||
@@ -1047,6 +1051,19 @@ The V2 upgrad version of ```ImageAutoCrop```, it has made the following changes
|
||||
* scale_by_length: The value here is used as ```scale_by``` to specify the length of the edge.
|
||||
|
||||
|
||||
### <a id="table1">HLFrequencyDetailRestore</a>
|
||||
Using low frequency filtering and retaining high frequency to recover image details. Compared to [kijai's DetailTransfer](https://github.com/kijai/ComfyUI-IC-Light), this node is better integrated with the environment while retaining details.
|
||||

|
||||
|
||||
Node Options:
|
||||

|
||||
* image: Background image input.
|
||||
* detail_image: Detail image input.
|
||||
* mask: Optional input, if there is a mask input, only the details of the mask part are restored.
|
||||
* keep_high_freq: Reserved range of high frequency parts. The larger the value, the richer the retained high-frequency details.
|
||||
* erase_low_freq: The range of low frequency parts of the erasure. The larger the value, the more the low frequency range of the erasure.
|
||||
* mask_blur: Mask edge blur. Valid only if there is masked input.
|
||||
|
||||
### <a id="table1">GetImageSize</a>
|
||||

|
||||
Obtain the width and height of the image.
|
||||
@@ -1589,6 +1606,17 @@ Node options:
|
||||
* stroke_width: Stroke width.
|
||||
* blur: Blur of stroke.
|
||||
|
||||
### <a id="table1">MaskGrain</a>
|
||||
Generates noise for the mask.
|
||||

|
||||
|
||||
Node Options:
|
||||

|
||||
* grain: Noise intensity.
|
||||
* invert_mask: Whether to reverse the mask.
|
||||
|
||||
|
||||
|
||||
### <a id="table1">MaskPreview</a>
|
||||
Preview the input mask
|
||||

|
||||
@@ -1689,6 +1717,10 @@ Node options:
|
||||
* blur_focus_spread: Focus diffusion range. larger value means larger clear range.
|
||||
* focal_depth: Simulate the focal distance of defucus. 0 indicates that focus is farthest, and 1 indicates that is closest. this setting only valid when input the depth_map.
|
||||
|
||||
### <a id="table1">FilmV2</a>
|
||||
The upgraded version of the Film node adds the fastgrain method on the basis of the previous one, and the speed of generating noise is accelerated by 10 times. The code for fastgrain is from [github.com/spacepxl/ComfyUI-Image-Filters](https://github.com/spacepxl/ComfyUI-Image-Filters) BetterFilmGrain node, thanks to the original authors.
|
||||

|
||||
|
||||
### <a id="table1">LightLeak</a>
|
||||
Simulate the light leakage effect of the film. please download the [light_leak.pkl(Baidu Netdisk)](https://pan.baidu.com/s/1QY1ZyYm885krrqDB2t8lPg?pwd=yvs3) or [light_leak.pkl(Google Drive)]([light_leak.pkl(Google Drive)(https://drive.google.com/file/d/1DcH2Zkyj7W3OiAeeGpJk1eaZpdJwdCL-/view?usp=sharing)), copy the file to ```ComfyUI/models/layerstyle``` folder.
|
||||

|
||||
@@ -1726,6 +1758,17 @@ Make the image gaussian blur
|
||||
Node options:
|
||||
* blur: The size of blur.
|
||||
|
||||
### <a id="table1">AddGrain</a>
|
||||
Add noise to the picture.
|
||||

|
||||
|
||||
Node Options:
|
||||

|
||||
* grain_power: Noise intensity.
|
||||
* grain_scale: Noise size.
|
||||
* grain_sat: Color saturation of noise.
|
||||
|
||||
|
||||
## Annotation for <a id="table1">notes</a>
|
||||
<sup>1</sup> The layer_image, layer_mask and the background_image(if have input), These three items must be of the same size.
|
||||
|
||||
|
||||
@@ -80,6 +80,9 @@ git clone https://github.com/chflame163/ComfyUI_LayerStyle.git
|
||||
## 更新说明
|
||||
<font size="4">**如果本插件更新后出现依赖包错误,请重新安装相关依赖包。
|
||||
|
||||
* 添加 [HLFrequencyDetailRestore](#HLFrequencyDetailRestore)节点, 使用低频滤波加保留高频来恢复图像细节,图像融合性更好。
|
||||
* 添加 [AddGrain](#AddGrain) 和 [MaskGrain](#MaskGrain) 节点, 为图片或遮罩添加噪声。
|
||||
* 添加 [FilmV2](#FilmV2) 节点, 在之前基础上增加了fastgrain方法,生成噪点速度加快了10倍。
|
||||
* 添加 [ImageToMask](#ImageToMask) 节点,可将图片转为遮罩。支持以LAB,RGBA, YUV 和 HSV模式的任意通道转换为遮罩,同时提供色阶调整。支持mask可选输入以获取仅包括有效部分的遮罩。
|
||||
* 部分节点中blackpoint和whitepoint选项改为滑块调节,便于更直观显示。包括[MaskEdgeUltraDetailV2](#MaskEdgeUltraDetailV2), [SegmentAnythingUltraV2](#SegmentAnythingUltraV2), [RmBgUltraV2](#RmBgUltraV2),[PersonMaskUltraV2](#PersonMaskUltraV2),[BiRefNetUltra](#BiRefNetUltra), [SegformerB2ClothesUltra](#SegformerB2ClothesUltra), [BlendIfMask](#BlendIfMask) 和 [Levels](#Levels)。
|
||||
* [ImageScaleRestoreV2](#ImageScaleRestoreV2) 和[ImageScaleByAspectRatioV2](#ImageScaleByAspectRatioV2) 节点增加TotalPixel方法缩放图片。
|
||||
@@ -1035,6 +1038,19 @@ cropped_mask: 裁切后的遮罩。
|
||||
* scale_by: 允许按长边、短边、宽度或高度指定尺寸缩放。
|
||||
* scale_by_length: 这里的数值作为scale_by指定边的长度。
|
||||
|
||||
### <a id="table1">HLFrequencyDetailRestore</a>
|
||||
使用低频滤波加保留高频来恢复图像细节。相比[kijai's DetailTransfer](https://github.com/kijai/ComfyUI-IC-Light), 这个节点在保留细节的同时,与环境的融合度更好。
|
||||

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

|
||||
* image: 背景图片输入。
|
||||
* detail_image: 细节原图输入。
|
||||
* mask: 可选输入,如果有遮罩输入则仅恢复遮罩部分的细节。
|
||||
* keep_high_freq: 保留的高频部分范围。数值越大,保留的高频细节越丰富。
|
||||
* erase_low_freq: 擦除的低频部分范围。数值越大,擦除的低频范围越多。
|
||||
* mask_blur: 遮罩边缘模糊度。仅在有遮罩输入的情况下有效。
|
||||
|
||||
|
||||
### <a id="table1">GetImageSize</a>
|
||||

|
||||
@@ -1576,6 +1592,16 @@ MaskGrow与MaskEdgeShrink效果对比
|
||||
* stroke_width: 描边宽度。
|
||||
* blur: 描边模糊。
|
||||
|
||||
### <a id="table1">MaskGrain</a>
|
||||
为遮罩生成噪声。
|
||||

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

|
||||
* grain: 噪声强度。
|
||||
* invert_mask: 是否反转遮罩。
|
||||
|
||||
|
||||
### <a id="table1">MaskPreview</a>
|
||||
预览mask
|
||||

|
||||
@@ -1676,6 +1702,11 @@ mask反转
|
||||
* blur_focus_spread: 焦点扩散范围。数值越大,清晰的范围越大。
|
||||
* focal_depth: 模拟虚焦的焦点距离。0表示焦点在最远,1表示焦点在最近。此项设置只在depth_map有输入时才生效。
|
||||
|
||||
### <a id="table1">FilmV2</a>
|
||||
Film节点的升级版, 在之前基础上增加了fastgrain方法,生成噪点速度加快了10倍。fastgrain的代码来自[github.com/spacepxl/ComfyUI-Image-Filters](https://github.com/spacepxl/ComfyUI-Image-Filters)的BetterFilmGrain部分,感谢原作者。
|
||||

|
||||
|
||||
|
||||
### <a id="table1">LightLeak</a>
|
||||
模拟胶片漏光效果。请下载[light_leak.pkl(百度网盘)](https://pan.baidu.com/s/1QY1ZyYm885krrqDB2t8lPg?pwd=yvs3)或[light_leak.pkl(Google Drive)(https://drive.google.com/file/d/1DcH2Zkyj7W3OiAeeGpJk1eaZpdJwdCL-/view?usp=sharing)将文件复制到```ComfyUI/models/layerstyle``` 文件夹。
|
||||

|
||||
@@ -1712,6 +1743,17 @@ mask反转
|
||||
节点选项说明:
|
||||
* blur: 模糊大小。
|
||||
|
||||
### <a id="table1">AddGrain</a>
|
||||
给图片增加噪声。
|
||||

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

|
||||
* grain_power: 噪声强度。
|
||||
* grain_scale: 噪声的大小。
|
||||
* grain_sat: 噪声的色彩饱和度。
|
||||
|
||||
|
||||
## <a id="table1">节点注解</a>
|
||||
<sup>1</sup> image、mask和background_image(如果有输入)这三项必须是相同的尺寸。
|
||||
|
||||
|
||||
|
After Width: | Height: | Size: 138 KiB |
|
After Width: | Height: | Size: 40 KiB |
|
After Width: | Height: | Size: 84 KiB |
|
After Width: | Height: | Size: 137 KiB |
|
After Width: | Height: | Size: 42 KiB |
|
After Width: | Height: | Size: 70 KiB |
|
After Width: | Height: | Size: 34 KiB |
@@ -0,0 +1,47 @@
|
||||
from .imagefunc import *
|
||||
|
||||
NODE_NAME = 'AddGrain'
|
||||
|
||||
class AddGrain:
|
||||
|
||||
def __init__(self):
|
||||
pass
|
||||
|
||||
@classmethod
|
||||
def INPUT_TYPES(self):
|
||||
|
||||
return {
|
||||
"required": {
|
||||
"image": ("IMAGE", ), #
|
||||
"grain_power": ("FLOAT", {"default": 0.5, "min": 0, "max": 1, "step": 0.01}),
|
||||
"grain_scale": ("FLOAT", {"default": 1, "min": 0.1, "max": 10, "step": 0.1}),
|
||||
"grain_sat": ("FLOAT", {"default": 1, "min": 0, "max": 1, "step": 0.01}),
|
||||
},
|
||||
"optional": {
|
||||
}
|
||||
}
|
||||
|
||||
RETURN_TYPES = ("IMAGE",)
|
||||
RETURN_NAMES = ("image",)
|
||||
FUNCTION = 'add_grain'
|
||||
CATEGORY = '😺dzNodes/LayerFilter'
|
||||
|
||||
def add_grain(self, image, grain_power, grain_scale, grain_sat):
|
||||
|
||||
ret_images = []
|
||||
|
||||
for i in image:
|
||||
_canvas = tensor2pil(torch.unsqueeze(i, 0)).convert('RGB')
|
||||
_canvas = image_add_grain(_canvas, grain_scale, grain_power, grain_sat, toe=0, seed=int(time.time()))
|
||||
ret_images.append(pil2tensor(_canvas))
|
||||
|
||||
log(f"{NODE_NAME} Processed {len(ret_images)} image(s).", message_type='finish')
|
||||
return (torch.cat(ret_images, dim=0),)
|
||||
|
||||
NODE_CLASS_MAPPINGS = {
|
||||
"LayerFilter: AddGrain": AddGrain
|
||||
}
|
||||
|
||||
NODE_DISPLAY_NAME_MAPPINGS = {
|
||||
"LayerFilter: AddGrain": "LayerFilter: Add Grain"
|
||||
}
|
||||
@@ -0,0 +1,91 @@
|
||||
from .imagefunc import *
|
||||
|
||||
NODE_NAME = 'FilmV2'
|
||||
|
||||
class FilmV2:
|
||||
|
||||
def __init__(self):
|
||||
pass
|
||||
|
||||
@classmethod
|
||||
def INPUT_TYPES(self):
|
||||
grain_method_list = ["fastgrain", "filmgrainer", ]
|
||||
return {
|
||||
"required": {
|
||||
"image": ("IMAGE", ), #
|
||||
"center_x": ("FLOAT", {"default": 0.5, "min": 0, "max": 1, "step": 0.01}),
|
||||
"center_y": ("FLOAT", {"default": 0.5, "min": 0, "max": 1, "step": 0.01}),
|
||||
"saturation": ("FLOAT", {"default": 1, "min": 0.01, "max": 3, "step": 0.01}),
|
||||
"vignette_intensity": ("FLOAT", {"default": 0.5, "min": 0, "max": 1, "step": 0.01}),
|
||||
"grain_method": (grain_method_list,),
|
||||
"grain_power": ("FLOAT", {"default": 0.15, "min": 0, "max": 1, "step": 0.01}),
|
||||
"grain_scale": ("FLOAT", {"default": 1, "min": 0.1, "max": 10, "step": 0.1}),
|
||||
"grain_sat": ("FLOAT", {"default": 0.5, "min": 0, "max": 1, "step": 0.01}),
|
||||
"filmgrainer_shadows": ("FLOAT", {"default": 0.6, "min": 0, "max": 1, "step": 0.01}),
|
||||
"filmgrainer_highs": ("FLOAT", {"default": 0.2, "min": 0, "max": 1, "step": 0.01}),
|
||||
"blur_strength": ("INT", {"default": 90, "min": 0, "max": 256, "step": 1}),
|
||||
"blur_focus_spread": ("FLOAT", {"default": 2.2, "min": 0.1, "max": 8, "step": 0.1}),
|
||||
"focal_depth": ("FLOAT", {"default": 0.9, "min": 0.0, "max": 1, "step": 0.01}),
|
||||
},
|
||||
"optional": {
|
||||
"depth_map": ("IMAGE",), #
|
||||
}
|
||||
}
|
||||
|
||||
RETURN_TYPES = ("IMAGE",)
|
||||
RETURN_NAMES = ("image",)
|
||||
FUNCTION = 'film_v2'
|
||||
CATEGORY = '😺dzNodes/LayerFilter'
|
||||
|
||||
def film_v2(self, image, center_x, center_y, saturation, vignette_intensity,
|
||||
grain_method, grain_power, grain_scale, grain_sat, filmgrainer_shadows, filmgrainer_highs,
|
||||
blur_strength, blur_focus_spread, focal_depth,
|
||||
depth_map=None
|
||||
):
|
||||
|
||||
ret_images = []
|
||||
|
||||
for i in image:
|
||||
i = torch.unsqueeze(i, 0)
|
||||
_canvas = tensor2pil(i).convert('RGB')
|
||||
|
||||
if saturation != 1:
|
||||
color_image = ImageEnhance.Color(_canvas)
|
||||
_canvas = color_image.enhance(factor= saturation)
|
||||
|
||||
if blur_strength:
|
||||
if depth_map is not None:
|
||||
depth_map = tensor2pil(depth_map).convert('RGB')
|
||||
if depth_map.size != _canvas.size:
|
||||
depth_map.resize((_canvas.size), Image.BILINEAR)
|
||||
_canvas = depthblur_image(_canvas, depth_map, blur_strength, focal_depth, blur_focus_spread)
|
||||
else:
|
||||
_canvas = radialblur_image(_canvas, blur_strength, center_x, center_y, blur_focus_spread * 2)
|
||||
|
||||
if vignette_intensity:
|
||||
# adjust image gamma and saturation
|
||||
_canvas = gamma_trans(_canvas, 1 - vignette_intensity / 3)
|
||||
color_image = ImageEnhance.Color(_canvas)
|
||||
_canvas = color_image.enhance(factor= 1+ vignette_intensity / 3)
|
||||
# add vignette
|
||||
_canvas = vignette_image(_canvas, vignette_intensity, center_x, center_y)
|
||||
|
||||
if grain_power:
|
||||
if grain_method == "fastgrain":
|
||||
_canvas = image_add_grain(_canvas, grain_scale,grain_power, grain_sat, toe=0, seed=int(time.time()))
|
||||
elif grain_method == "filmgrainer":
|
||||
_canvas = filmgrain_image(_canvas, grain_scale, grain_power, filmgrainer_shadows, filmgrainer_highs, grain_sat)
|
||||
|
||||
ret_image = _canvas
|
||||
ret_images.append(pil2tensor(ret_image))
|
||||
|
||||
log(f"{NODE_NAME} Processed {len(ret_images)} image(s).", message_type='finish')
|
||||
return (torch.cat(ret_images, dim=0),)
|
||||
|
||||
NODE_CLASS_MAPPINGS = {
|
||||
"LayerFilter: FilmV2": FilmV2
|
||||
}
|
||||
|
||||
NODE_DISPLAY_NAME_MAPPINGS = {
|
||||
"LayerFilter: FilmV2": "LayerFilter: Film V2"
|
||||
}
|
||||
@@ -0,0 +1,85 @@
|
||||
from .imagefunc import *
|
||||
|
||||
NODE_NAME = 'HLFrequencyDetailRestore'
|
||||
|
||||
class HLFrequencyDetailRestore:
|
||||
|
||||
def __init__(self):
|
||||
pass
|
||||
|
||||
@classmethod
|
||||
def INPUT_TYPES(self):
|
||||
|
||||
return {
|
||||
"required": {
|
||||
"image": ("IMAGE",),
|
||||
"detail_image": ("IMAGE",),
|
||||
"keep_high_freq": ("INT", {"default": 64, "min": 0, "max": 1023}),
|
||||
"erase_low_freq": ("INT", {"default": 32, "min": 0, "max": 1023}),
|
||||
"mask_blur": ("INT", {"default": 16, "min": 0, "max": 1023}),
|
||||
},
|
||||
"optional": {
|
||||
"mask": ("MASK",), #
|
||||
}
|
||||
}
|
||||
|
||||
RETURN_TYPES = ("IMAGE",)
|
||||
RETURN_NAMES = ("image",)
|
||||
FUNCTION = 'hl_frequency_detail_restore'
|
||||
CATEGORY = '😺dzNodes/LayerUtility'
|
||||
|
||||
def hl_frequency_detail_restore(self, image, detail_image, keep_high_freq, erase_low_freq, mask_blur, mask=None):
|
||||
|
||||
b_images = []
|
||||
l_images = []
|
||||
l_masks = []
|
||||
ret_images = []
|
||||
for b in image:
|
||||
b_images.append(torch.unsqueeze(b, 0))
|
||||
for l in detail_image:
|
||||
l_images.append(torch.unsqueeze(l, 0))
|
||||
m = tensor2pil(l)
|
||||
if m.mode == 'RGBA':
|
||||
l_masks.append(m.split()[-1])
|
||||
else:
|
||||
l_masks.append(Image.new('L', m.size, 'white'))
|
||||
if mask is not None:
|
||||
if mask.dim() == 2:
|
||||
mask = torch.unsqueeze(mask, 0)
|
||||
l_masks = []
|
||||
for m in mask:
|
||||
l_masks.append(tensor2pil(torch.unsqueeze(m, 0)).convert('L'))
|
||||
max_batch = max(len(b_images), len(l_images), len(l_masks))
|
||||
|
||||
for i in range(max_batch):
|
||||
background_image = b_images[i] if i < len(b_images) else b_images[-1]
|
||||
background_image = tensor2pil(background_image).convert('RGB')
|
||||
detail_image = l_images[i] if i < len(l_images) else l_images[-1]
|
||||
detail_image = tensor2pil(detail_image).convert('RGB')
|
||||
_mask = l_masks[i] if i < len(l_masks) else l_masks[-1]
|
||||
|
||||
high_ferq = chop_image_v2(ImageChops.invert(detail_image),
|
||||
gaussian_blur(detail_image, keep_high_freq),
|
||||
blend_mode='normal', opacity=50)
|
||||
high_ferq = ImageChops.invert(high_ferq)
|
||||
if erase_low_freq:
|
||||
low_freq = gaussian_blur(background_image, erase_low_freq)
|
||||
else:
|
||||
low_freq = background_image.copy()
|
||||
ret_image = chop_image_v2(low_freq, high_ferq, blend_mode="linear light", opacity=100)
|
||||
_mask = ImageChops.invert(_mask)
|
||||
if mask_blur > 0:
|
||||
_mask = gaussian_blur(_mask, mask_blur)
|
||||
ret_image.paste(background_image, _mask)
|
||||
ret_images.append(pil2tensor(ret_image))
|
||||
|
||||
log(f"{NODE_NAME} Processed {len(ret_images)} image(s).", message_type='finish')
|
||||
return (torch.cat(ret_images, dim=0),)
|
||||
|
||||
NODE_CLASS_MAPPINGS = {
|
||||
"LayerUtility: HLFrequencyDetailRestore": HLFrequencyDetailRestore
|
||||
}
|
||||
|
||||
NODE_DISPLAY_NAME_MAPPINGS = {
|
||||
"LayerUtility: HLFrequencyDetailRestore": "LayerUtility: H/L Frequency Detail Restore"
|
||||
}
|
||||
@@ -446,6 +446,56 @@ def vignette_image(image:Image, intensity: float, center_x: float, center_y: flo
|
||||
vignette_image = __apply_vignette(tensor_image, vignette)
|
||||
return tensor2pil(torch.from_numpy(vignette_image).unsqueeze(0))
|
||||
|
||||
def RGB2YCbCr(t):
|
||||
YCbCr = t.detach().clone()
|
||||
YCbCr[:,:,:,0] = 0.2123 * t[:,:,:,0] + 0.7152 * t[:,:,:,1] + 0.0722 * t[:,:,:,2]
|
||||
YCbCr[:,:,:,1] = 0 - 0.1146 * t[:,:,:,0] - 0.3854 * t[:,:,:,1] + 0.5 * t[:,:,:,2]
|
||||
YCbCr[:,:,:,2] = 0.5 * t[:,:,:,0] - 0.4542 * t[:,:,:,1] - 0.0458 * t[:,:,:,2]
|
||||
return YCbCr
|
||||
|
||||
def YCbCr2RGB(t):
|
||||
RGB = t.detach().clone()
|
||||
RGB[:,:,:,0] = t[:,:,:,0] + 1.5748 * t[:,:,:,2]
|
||||
RGB[:,:,:,1] = t[:,:,:,0] - 0.1873 * t[:,:,:,1] - 0.4681 * t[:,:,:,2]
|
||||
RGB[:,:,:,2] = t[:,:,:,0] + 1.8556 * t[:,:,:,1]
|
||||
return RGB
|
||||
|
||||
# gaussian blur a tensor image batch in format [B x H x W x C] on H/W (spatial, per-image, per-channel)
|
||||
def cv_blur_tensor(images, dx, dy):
|
||||
if min(dx, dy) > 100:
|
||||
np_img = torch.nn.functional.interpolate(images.detach().clone().movedim(-1,1), scale_factor=0.1, mode='bilinear').movedim(1,-1).cpu().numpy()
|
||||
for index, image in enumerate(np_img):
|
||||
np_img[index] = cv2.GaussianBlur(image, (dx // 20 * 2 + 1, dy // 20 * 2 + 1), 0)
|
||||
return torch.nn.functional.interpolate(torch.from_numpy(np_img).movedim(-1,1), size=(images.shape[1], images.shape[2]), mode='bilinear').movedim(1,-1)
|
||||
else:
|
||||
np_img = images.detach().clone().cpu().numpy()
|
||||
for index, image in enumerate(np_img):
|
||||
np_img[index] = cv2.GaussianBlur(image, (dx, dy), 0)
|
||||
return torch.from_numpy(np_img)
|
||||
|
||||
def image_add_grain(image:Image, scale:float=0.5, strength:float=0.5, saturation:float=0.7, toe:float=0.0, seed:int=0) -> Image:
|
||||
|
||||
image = pil2tensor(image.convert("RGB"))
|
||||
t = image.detach().clone()
|
||||
torch.manual_seed(seed)
|
||||
grain = torch.rand(t.shape[0], int(t.shape[1] // scale), int(t.shape[2] // scale), 3)
|
||||
|
||||
YCbCr = RGB2YCbCr(grain)
|
||||
YCbCr[:, :, :, 0] = cv_blur_tensor(YCbCr[:, :, :, 0], 3, 3)
|
||||
YCbCr[:, :, :, 1] = cv_blur_tensor(YCbCr[:, :, :, 1], 15, 15)
|
||||
YCbCr[:, :, :, 2] = cv_blur_tensor(YCbCr[:, :, :, 2], 11, 11)
|
||||
|
||||
grain = (YCbCr2RGB(YCbCr) - 0.5) * strength
|
||||
grain[:, :, :, 0] *= 2
|
||||
grain[:, :, :, 2] *= 3
|
||||
grain += 1
|
||||
grain = grain * saturation + grain[:, :, :, 1].unsqueeze(3).repeat(1, 1, 1, 3) * (1 - saturation)
|
||||
|
||||
grain = torch.nn.functional.interpolate(grain.movedim(-1, 1), size=(t.shape[1], t.shape[2]),
|
||||
mode='bilinear').movedim(1, -1)
|
||||
t[:, :, :, :3] = torch.clip((1 - (1 - t[:, :, :, :3]) * grain) * (1 - toe) + toe, 0, 1)
|
||||
return tensor2pil(t)
|
||||
|
||||
def filmgrain_image(image:Image, scale:float, grain_power:float,
|
||||
shadows:float, highs:float, grain_sat:float,
|
||||
sharpen:int=1, grain_type:int=4, src_gamma:float=1.0,
|
||||
|
||||
@@ -0,0 +1,61 @@
|
||||
from .imagefunc import *
|
||||
|
||||
NODE_NAME = 'MaskGrain'
|
||||
|
||||
class MaskGrain:
|
||||
|
||||
def __init__(self):
|
||||
pass
|
||||
|
||||
@classmethod
|
||||
def INPUT_TYPES(self):
|
||||
|
||||
return {
|
||||
"required": {
|
||||
"mask": ("MASK", ), #
|
||||
"grain": ("INT", {"default": 6, "min": 0, "max": 127, "step": 1}),
|
||||
"invert_mask": ("BOOLEAN", {"default": False}), # 反转mask
|
||||
},
|
||||
"optional": {
|
||||
}
|
||||
}
|
||||
|
||||
RETURN_TYPES = ("MASK",)
|
||||
RETURN_NAMES = ("mask",)
|
||||
FUNCTION = 'mask_grain'
|
||||
CATEGORY = '😺dzNodes/LayerMask'
|
||||
|
||||
def mask_grain(self, mask, grain, invert_mask):
|
||||
|
||||
l_masks = []
|
||||
ret_masks = []
|
||||
|
||||
if mask.dim() == 2:
|
||||
mask = torch.unsqueeze(mask, 0)
|
||||
for m in mask:
|
||||
if invert_mask:
|
||||
m = 1 - m
|
||||
l_masks.append(tensor2pil(torch.unsqueeze(m, 0)).convert('L'))
|
||||
|
||||
for mask in l_masks:
|
||||
if grain:
|
||||
white_mask = Image.new('L', mask.size, color="white")
|
||||
inner_mask = tensor2pil(expand_mask(image2mask(mask), 0 - grain, int(grain))).convert('L')
|
||||
outter_mask = tensor2pil(expand_mask(image2mask(mask), grain, int(grain * 2))).convert('L')
|
||||
ret_mask = Image.new('L', mask.size, color="black")
|
||||
ret_mask = chop_image_v2(ret_mask, outter_mask, blend_mode="dissolve", opacity=50).convert('L')
|
||||
ret_mask.paste(white_mask, mask=inner_mask)
|
||||
ret_masks.append(image2mask(ret_mask))
|
||||
else:
|
||||
ret_masks.append(image2mask(mask))
|
||||
|
||||
log(f"{NODE_NAME} Processed {len(ret_masks)} mask(s).", message_type='finish')
|
||||
return (torch.cat(ret_masks, dim=0),)
|
||||
|
||||
NODE_CLASS_MAPPINGS = {
|
||||
"LayerMask: MaskGrain": MaskGrain
|
||||
}
|
||||
|
||||
NODE_DISPLAY_NAME_MAPPINGS = {
|
||||
"LayerMask: MaskGrain": "LayerMask: Mask Grain"
|
||||
}
|
||||
@@ -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 = "1.0.4"
|
||||
version = "1.0.5"
|
||||
license = "MIT"
|
||||
dependencies = ["numpy", "pillow", "torch", "matplotlib", "Scipy", "scikit_image", "opencv-contrib-python", "pymatting", "segment_anything", "timm", "addict", "yapf", "colour-science", "wget", "mediapipe", "loguru", "typer_config", "fastapi", "rich", "google-generativeai", "diffusers", "omegaconf", "tqdm", "transformers", "kornia", "image-reward", "ultralytics", "blend_modes", "blind-watermark", "qrcode", "pyzbar", "psd-tools"]
|
||||
|
||||
|
||||
@@ -0,0 +1,358 @@
|
||||
{
|
||||
"last_node_id": 54,
|
||||
"last_link_id": 89,
|
||||
"nodes": [
|
||||
{
|
||||
"id": 52,
|
||||
"type": "PreviewImage",
|
||||
"pos": [
|
||||
1760,
|
||||
680
|
||||
],
|
||||
"size": {
|
||||
"0": 420.5770263671875,
|
||||
"1": 279.6587219238281
|
||||
},
|
||||
"flags": {},
|
||||
"order": 5,
|
||||
"mode": 0,
|
||||
"inputs": [
|
||||
{
|
||||
"name": "images",
|
||||
"type": "IMAGE",
|
||||
"link": 79
|
||||
}
|
||||
],
|
||||
"properties": {
|
||||
"Node name for S&R": "PreviewImage"
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": 49,
|
||||
"type": "PreviewImage",
|
||||
"pos": [
|
||||
1760,
|
||||
1010
|
||||
],
|
||||
"size": {
|
||||
"0": 419.24365234375,
|
||||
"1": 281.6587219238281
|
||||
},
|
||||
"flags": {},
|
||||
"order": 6,
|
||||
"mode": 0,
|
||||
"inputs": [
|
||||
{
|
||||
"name": "images",
|
||||
"type": "IMAGE",
|
||||
"link": 71
|
||||
}
|
||||
],
|
||||
"properties": {
|
||||
"Node name for S&R": "PreviewImage"
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": 50,
|
||||
"type": "LayerMask: RmBgUltra V2",
|
||||
"pos": [
|
||||
950,
|
||||
1080
|
||||
],
|
||||
"size": {
|
||||
"0": 315,
|
||||
"1": 198
|
||||
},
|
||||
"flags": {},
|
||||
"order": 1,
|
||||
"mode": 0,
|
||||
"inputs": [
|
||||
{
|
||||
"name": "image",
|
||||
"type": "IMAGE",
|
||||
"link": 72
|
||||
}
|
||||
],
|
||||
"outputs": [
|
||||
{
|
||||
"name": "image",
|
||||
"type": "IMAGE",
|
||||
"links": null,
|
||||
"shape": 3
|
||||
},
|
||||
{
|
||||
"name": "mask",
|
||||
"type": "MASK",
|
||||
"links": [
|
||||
89
|
||||
],
|
||||
"shape": 3,
|
||||
"slot_index": 1
|
||||
}
|
||||
],
|
||||
"properties": {
|
||||
"Node name for S&R": "LayerMask: RmBgUltra V2"
|
||||
},
|
||||
"widgets_values": [
|
||||
"VITMatte",
|
||||
6,
|
||||
6,
|
||||
0.01,
|
||||
0.99,
|
||||
false
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": 20,
|
||||
"type": "LoadImage",
|
||||
"pos": [
|
||||
473,
|
||||
954
|
||||
],
|
||||
"size": [
|
||||
363.2436218261719,
|
||||
314
|
||||
],
|
||||
"flags": {},
|
||||
"order": 0,
|
||||
"mode": 0,
|
||||
"outputs": [
|
||||
{
|
||||
"name": "IMAGE",
|
||||
"type": "IMAGE",
|
||||
"links": [
|
||||
70,
|
||||
72,
|
||||
87
|
||||
],
|
||||
"shape": 3,
|
||||
"slot_index": 0
|
||||
},
|
||||
{
|
||||
"name": "MASK",
|
||||
"type": "MASK",
|
||||
"links": null,
|
||||
"shape": 3
|
||||
}
|
||||
],
|
||||
"properties": {
|
||||
"Node name for S&R": "LoadImage"
|
||||
},
|
||||
"widgets_values": [
|
||||
"1344x768_redcar.png",
|
||||
"image"
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": 48,
|
||||
"type": "LayerUtility: HLFrequencyDetailRestore",
|
||||
"pos": [
|
||||
1350,
|
||||
930
|
||||
],
|
||||
"size": {
|
||||
"0": 352.79998779296875,
|
||||
"1": 146
|
||||
},
|
||||
"flags": {},
|
||||
"order": 4,
|
||||
"mode": 0,
|
||||
"inputs": [
|
||||
{
|
||||
"name": "image",
|
||||
"type": "IMAGE",
|
||||
"link": 78
|
||||
},
|
||||
{
|
||||
"name": "detail_image",
|
||||
"type": "IMAGE",
|
||||
"link": 70
|
||||
},
|
||||
{
|
||||
"name": "mask",
|
||||
"type": "MASK",
|
||||
"link": 89
|
||||
}
|
||||
],
|
||||
"outputs": [
|
||||
{
|
||||
"name": "image",
|
||||
"type": "IMAGE",
|
||||
"links": [
|
||||
71
|
||||
],
|
||||
"shape": 3,
|
||||
"slot_index": 0
|
||||
}
|
||||
],
|
||||
"properties": {
|
||||
"Node name for S&R": "LayerUtility: HLFrequencyDetailRestore"
|
||||
},
|
||||
"widgets_values": [
|
||||
64,
|
||||
16,
|
||||
50
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": 54,
|
||||
"type": "LayerColor: RGB",
|
||||
"pos": [
|
||||
950,
|
||||
690
|
||||
],
|
||||
"size": {
|
||||
"0": 315,
|
||||
"1": 106
|
||||
},
|
||||
"flags": {},
|
||||
"order": 2,
|
||||
"mode": 0,
|
||||
"inputs": [
|
||||
{
|
||||
"name": "image",
|
||||
"type": "IMAGE",
|
||||
"link": 87
|
||||
}
|
||||
],
|
||||
"outputs": [
|
||||
{
|
||||
"name": "image",
|
||||
"type": "IMAGE",
|
||||
"links": [
|
||||
88
|
||||
],
|
||||
"shape": 3,
|
||||
"slot_index": 0
|
||||
}
|
||||
],
|
||||
"properties": {
|
||||
"Node name for S&R": "LayerColor: RGB"
|
||||
},
|
||||
"widgets_values": [
|
||||
-147,
|
||||
-46,
|
||||
-17
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": 53,
|
||||
"type": "LayerFilter: MotionBlur",
|
||||
"pos": [
|
||||
953,
|
||||
845
|
||||
],
|
||||
"size": {
|
||||
"0": 315,
|
||||
"1": 82
|
||||
},
|
||||
"flags": {},
|
||||
"order": 3,
|
||||
"mode": 0,
|
||||
"inputs": [
|
||||
{
|
||||
"name": "image",
|
||||
"type": "IMAGE",
|
||||
"link": 88
|
||||
}
|
||||
],
|
||||
"outputs": [
|
||||
{
|
||||
"name": "image",
|
||||
"type": "IMAGE",
|
||||
"links": [
|
||||
78,
|
||||
79
|
||||
],
|
||||
"shape": 3,
|
||||
"slot_index": 0
|
||||
}
|
||||
],
|
||||
"properties": {
|
||||
"Node name for S&R": "LayerFilter: MotionBlur"
|
||||
},
|
||||
"widgets_values": [
|
||||
0,
|
||||
252
|
||||
]
|
||||
}
|
||||
],
|
||||
"links": [
|
||||
[
|
||||
70,
|
||||
20,
|
||||
0,
|
||||
48,
|
||||
1,
|
||||
"IMAGE"
|
||||
],
|
||||
[
|
||||
71,
|
||||
48,
|
||||
0,
|
||||
49,
|
||||
0,
|
||||
"IMAGE"
|
||||
],
|
||||
[
|
||||
72,
|
||||
20,
|
||||
0,
|
||||
50,
|
||||
0,
|
||||
"IMAGE"
|
||||
],
|
||||
[
|
||||
78,
|
||||
53,
|
||||
0,
|
||||
48,
|
||||
0,
|
||||
"IMAGE"
|
||||
],
|
||||
[
|
||||
79,
|
||||
53,
|
||||
0,
|
||||
52,
|
||||
0,
|
||||
"IMAGE"
|
||||
],
|
||||
[
|
||||
87,
|
||||
20,
|
||||
0,
|
||||
54,
|
||||
0,
|
||||
"IMAGE"
|
||||
],
|
||||
[
|
||||
88,
|
||||
54,
|
||||
0,
|
||||
53,
|
||||
0,
|
||||
"IMAGE"
|
||||
],
|
||||
[
|
||||
89,
|
||||
50,
|
||||
1,
|
||||
48,
|
||||
2,
|
||||
"MASK"
|
||||
]
|
||||
],
|
||||
"groups": [],
|
||||
"config": {},
|
||||
"extra": {
|
||||
"ds": {
|
||||
"scale": 0.8264462809917354,
|
||||
"offset": [
|
||||
134.34514513458976,
|
||||
-171.67926369728548
|
||||
]
|
||||
}
|
||||
},
|
||||
"version": 0.4
|
||||
}
|
||||