commit BlendIfMask node

This commit is contained in:
chflame
2024-05-05 17:51:25 +08:00
parent 074a0e9ec7
commit 96d3e5bd07
7 changed files with 446 additions and 4 deletions
+18
View File
@@ -70,6 +70,8 @@ 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 [BlendIfMask](#BlendIfMask) node, This node cooperates with ImgaeBlendV2 or ImageBlendAdvanceV2 to achieve the same Blend If function as Photoshop.
* Commit [ColorTemperature](#ColorTemperature) and [ColorBalance](#ColorBalance) nodes, used to adjust the color temperature and color balance of the picture.
* Add new types of [Blend Mode V2](#BlendModeV2) between images. now supports up to 30 blend modes. The new blend mode is available for all V2 versions that support mixed mode nodes, including ImageBlend V2, ImageBlendAdvance V2, DropShadow V2, InnerShadow V2, OuterGlow V2, InnerGlow V2, Stroke V2, ColorOverlay V2, GradientOverlay V2.
Part of the code for BlendMode V2 is from [Virtuoso Nodes for ComfyUI](https://github.com/chrisfreilich/virtuoso-nodes). Thanks to the original authors.
@@ -1033,6 +1035,22 @@ Perform a Boolean operation on two numeric values and output the result<sup>*</s
# <a id="table1">LayerMask</a>
![image](image/layermask_nodes.png)
### <a id="table1">BlendIfMask</a>
Reproduction of Photoshop's layer Style - Blend If function. This node outputs a mask for layer composition on the ImageBlend or ImageBlendAdvance nodes.
```mask``` is an optional input, and if you enter a mask here, it will act on the output.
![image](image/blendif_mask_example.png)
Node Options:
![image](image/blendif_mask_node.png)
* invert_mask: Whether to reverse the mask.
* blend_if: Channel selection for Blend If. There are four options: ```gray``` , ```red```, ```green```, and ```blue```.
* black_point: Black point values, ranging from 0-255.
* black_range: Dark part transition range. The larger the value, the richer the transition level of the dark part mask.
* white_point: White point values, ranging from 0-255.
* white_range: Brightness transition range. The larger the value is, the richer the transition level of the bright part mask is.
### <a id="table1">MaskBoxDetect</a>
Detect the area where the mask is located and output its position and size.
![image](image/mask_box_detect_example.png)
+17 -1
View File
@@ -70,7 +70,7 @@ git clone https://github.com/chflame163/ComfyUI_LayerStyle.git
## 更新说明
<font size="4">**如果本插件更新后出现依赖包错误,请重新安装相关依赖包。
* 添加 [BlendIfMask](#BlendIfMask) 节点,该节点与 ImgaeBlendV2 或者 ImageBlendAdvanceV2 配合,可以实现与Photoshop相同的混合颜色带功能。
* 添加 [ColorTemperature](#ColorTemperature) 和 [ColorBalance](#ColorBalance) 节点,用于调整图片色温和色彩平衡。
* 更新 [混合模式](#混合模式)到V2版本,现在支持多达30种混合模式。新增的混合模式适用于所有支持混合模式节点的V2版本,包括ImageBlend V2, ImageBlendAdvance V2, DropShadow V2, InnerShadow V2, OuterGlow V2, InnerGlow V2, Stroke V2, ColorOverlay V2, GradientOverlay V2。
混合模式V2的部分代码来自[Virtuoso Nodes for ComfyUI](https://github.com/chrisfreilich/virtuoso-nodes)的```Blend Modes```节点。感谢原作者。
@@ -1027,6 +1027,22 @@ cropped_mask: 裁切后的遮罩。
# <a id="table1">LayerMask</a>
![image](image/layermask_nodes.png)
### <a id="table1">BlendIfMask</a>
Photoshop图层样式-混合颜色带功能的复现。该节点输出一个mask,用于在ImageBlend或者ImageBlendAdvance节点进行图层合成。
mask为可选输入项,如果这里输入遮罩,将作用于输出结果。
![image](image/blendif_mask_example.png)
节点选项说明:
![image](image/blendif_mask_node.png)
* invert_mask: 是否反转遮罩。
* blend_if: 混合色带的通道选择。有```gray```, ```red```, ```green```, ```blue```四个选项。
* black_point: 黑点值,取值范围从0-255。
* black_range: 暗部过渡范围。数值越大,暗部遮罩的过渡层次越丰富。
* white_point: 白点值,取值范围从0-255。
* white_range: 亮部过渡范围。数值越大,亮部遮罩的过渡层次越丰富。
### <a id="table1">MaskBoxDetect</a>
探测mask所在区域,并输出位置和大小。
![image](image/mask_box_detect_example.png)
Binary file not shown.

After

Width:  |  Height:  |  Size: 1.3 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 138 KiB

+100
View File
@@ -0,0 +1,100 @@
from .imagefunc import *
NODE_NAME = 'BlendIfMask'
def norm_value(value):
if value < 0.01:
value = 0.01
if value > 0.99:
value = 0.99
return value
class BlendIfMask:
def __init__(self):
pass
@classmethod
def INPUT_TYPES(self):
blend_if_list = ["gray", "red", "green", "blue"]
return {
"required": {
"image": ("IMAGE", ),
"invert_mask": ("BOOLEAN", {"default": True}), # 反转mask
"blend_if": (blend_if_list,),
"black_point": ("INT", {"default": 0, "min": 0, "max": 254, "step": 1}),
"black_range": ("INT", {"default": 0, "min": 0, "max": 255, "step": 1}),
"white_point": ("INT", {"default": 255, "min": 1, "max": 255, "step": 1}),
"white_range": ("INT", {"default": 0, "min": 0, "max": 255, "step": 1}),
},
"optional": {
"mask": ("MASK",), #
}
}
RETURN_TYPES = ("MASK",)
RETURN_NAMES = ("mask",)
FUNCTION = 'blend_if_mask'
CATEGORY = '😺dzNodes/LayerMask'
def blend_if_mask(self, image, invert_mask, blend_if,
black_point, black_range,
white_point, white_range,
mask=None
):
ret_masks = []
input_images = []
input_masks = []
for i in image:
input_images.append(torch.unsqueeze(i, 0))
m = tensor2pil(i)
if m.mode == 'RGBA':
input_masks.append(m.split()[-1])
else:
input_masks.append(Image.new('L', size=m.size, color='white'))
if mask is not None:
if mask.dim() == 2:
mask = torch.unsqueeze(mask, 0)
input_masks = []
for m in mask:
if invert_mask:
m = 1 - m
input_masks.append(tensor2pil(torch.unsqueeze(m, 0)).convert('L'))
max_batch = max(len(input_images), len(input_masks))
for i in range(max_batch):
_image = input_images[i] if i < len(input_images) else input_images[-1]
_image = tensor2pil(_image).convert('RGB')
if blend_if == "red":
gray = image_channel_split(_image, 'RGB')[0]
elif blend_if == "green":
gray = image_channel_split(_image, 'RGB')[1]
elif blend_if == "blue":
gray = image_channel_split(_image, 'RGB')[2]
else:
gray = _image.convert('L')
_mask = input_masks[i] if i < len(input_masks) else input_masks[-1]
gray = histogram_range(gray, black_point, black_range, white_point, white_range)
black = Image.new('L', size=_image.size, color='black')
_mask = ImageChops.invert(_mask)
gray.paste(black, mask=_mask)
ret_masks.append(image2mask(gray))
log(f"{NODE_NAME} Processed {len(ret_masks)} image(s).", message_type='finish')
return (torch.cat(ret_masks, dim=0),)
NODE_CLASS_MAPPINGS = {
"LayerMask: BlendIf Mask": BlendIfMask
}
NODE_DISPLAY_NAME_MAPPINGS = {
"LayerMask: BlendIf Mask": "LayerMask: BlendIf Mask"
}
+54 -3
View File
@@ -693,9 +693,61 @@ def draw_rect(image:Image, x:int, y:int, width:int, height:int, line_color:str,
def draw_border(image:Image, border_width:int, color:str='#FFFFFF') -> Image:
return ImageOps.expand(image, border=border_width, fill=color)
def remap_pixel(pixel:int, min_brightness:int, max_brightness:int) -> int:
return int((pixel - min_brightness) / (max_brightness - min_brightness) * 255)
def histogram_range(image:Image, black_point:int, black_range:int, white_point:int, white_range:int) -> Image:
if image.mode != 'L':
image = image.convert('L')
if black_point == 255:
black_point = 254
if white_point == 0:
white_point = 1
if black_point + black_range > 255:
black_range = 255 - black_point
if white_range > white_point:
white_range = white_point
white_image = Image.new("L", size=image.size, color="white")
black_image = Image.new("L", size=image.size, color="black")
if black_point == white_point:
return white_image
# draw white part
white_part = black_image
if white_point < 255 or white_range > 0:
for y in (range(image.height)):
for x in range(image.width):
pixel = image.getpixel((x, y))
if pixel > white_point: # put white
white_part.putpixel((x, y), 255)
elif pixel > white_point - white_range:
pixel = remap_pixel(pixel, white_point - white_range, white_point)
white_part.putpixel((x, y), pixel)
white_part = ImageChops.invert(white_part)
# draw black part
black_part = black_image
if black_point > 0 or black_range > 0:
for y in (range(image.height)):
for x in range(image.width):
pixel = image.getpixel((x, y))
if pixel < black_point: # put black
black_part.putpixel((x, y), 255)
elif pixel < black_point + black_range:
pixel = remap_pixel(pixel, black_point, black_point + black_range)
black_part.putpixel((x, y), 255 - pixel)
black_part = ImageChops.invert(black_part)
ret_image = chop_image_v2(white_part, black_part, blend_mode='darken', opacity=100)
return ret_image
def histogram_equalization(image:Image, mask:Image=None, gamma_strength=0.5) -> Image:
def remap_pixel(pixel, min_brightness, max_brightness):
return int((pixel - min_brightness) / (max_brightness - min_brightness) * 255)
if image.mode != 'L':
image = image.convert('L')
@@ -1414,7 +1466,6 @@ def Hex_to_HSV_255level(inhex:str) -> list:
HSV = colorsys.rgb_to_hsv(RGB[0] / 255.0, RGB[1] / 255.0, RGB[2] / 255.0)
return [int(x * 255) for x in HSV]
'''Value Functions'''
def step_value(start_value, end_value, total_step, step) -> float: # 按当前步数在总步数中的位置返回比例值
+257
View File
@@ -0,0 +1,257 @@
{
"last_node_id": 28,
"last_link_id": 61,
"nodes": [
{
"id": 2,
"type": "LoadImage",
"pos": [
698,
455
],
"size": {
"0": 315,
"1": 314
},
"flags": {},
"order": 0,
"mode": 0,
"outputs": [
{
"name": "IMAGE",
"type": "IMAGE",
"links": [
8
],
"shape": 3,
"slot_index": 0
},
{
"name": "MASK",
"type": "MASK",
"links": null,
"shape": 3
}
],
"properties": {
"Node name for S&R": "LoadImage"
},
"widgets_values": [
"1344x768_beach (7).png",
"image"
]
},
{
"id": 3,
"type": "LoadImage",
"pos": [
697,
829
],
"size": {
"0": 315,
"1": 314
},
"flags": {},
"order": 1,
"mode": 0,
"outputs": [
{
"name": "IMAGE",
"type": "IMAGE",
"links": [
11,
46
],
"shape": 3,
"slot_index": 0
},
{
"name": "MASK",
"type": "MASK",
"links": null,
"shape": 3,
"slot_index": 1
}
],
"properties": {
"Node name for S&R": "LoadImage"
},
"widgets_values": [
"1344x768_girl2 (1).png",
"image"
]
},
{
"id": 20,
"type": "LayerMask: BlendIf Mask",
"pos": [
1083,
836
],
"size": {
"0": 315,
"1": 198
},
"flags": {},
"order": 2,
"mode": 0,
"inputs": [
{
"name": "image",
"type": "IMAGE",
"link": 46
},
{
"name": "mask",
"type": "MASK",
"link": null
}
],
"outputs": [
{
"name": "mask",
"type": "MASK",
"links": [
54
],
"shape": 3,
"slot_index": 0
}
],
"properties": {
"Node name for S&R": "LayerMask: BlendIf Mask"
},
"widgets_values": [
true,
"red",
22,
100,
255,
0
]
},
{
"id": 8,
"type": "LayerUtility: ImageBlend V2",
"pos": [
1415,
637
],
"size": {
"0": 315,
"1": 146
},
"flags": {},
"order": 3,
"mode": 0,
"inputs": [
{
"name": "background_image",
"type": "IMAGE",
"link": 8
},
{
"name": "layer_image",
"type": "IMAGE",
"link": 11
},
{
"name": "layer_mask",
"type": "MASK",
"link": 54
}
],
"outputs": [
{
"name": "image",
"type": "IMAGE",
"links": [
9
],
"shape": 3,
"slot_index": 0
}
],
"properties": {
"Node name for S&R": "LayerUtility: ImageBlend V2"
},
"widgets_values": [
false,
"screen",
100
]
},
{
"id": 4,
"type": "PreviewImage",
"pos": [
1783,
622
],
"size": [
501.1162109375,
327.103515625
],
"flags": {},
"order": 4,
"mode": 0,
"inputs": [
{
"name": "images",
"type": "IMAGE",
"link": 9
}
],
"properties": {
"Node name for S&R": "PreviewImage"
}
}
],
"links": [
[
8,
2,
0,
8,
0,
"IMAGE"
],
[
9,
8,
0,
4,
0,
"IMAGE"
],
[
11,
3,
0,
8,
1,
"IMAGE"
],
[
46,
3,
0,
20,
0,
"IMAGE"
],
[
54,
20,
0,
8,
2,
"MASK"
]
],
"groups": [],
"config": {},
"extra": {},
"version": 0.4
}