diff --git a/README.MD b/README.MD index b733899..6069f0a 100644 --- a/README.MD +++ b/README.MD @@ -1,48 +1,45 @@ # ComfyUI Layer Style A set of nodes for ComfyUI that generate image like Adobe Photoshop's Layer Style. - -Currently completed Drop Shadow and OuterGlow nodes, and follow-up work is in progress. +Currently completed the Drop Shadow, OuterGlow, and some auxiliary nodes. follow-up work is in progress. [中文说明点这里](./README_CN.MD) ## Update +* Commit Stroke node. +* Commit MaskPreview node. * Commit ImageOpacity node. -* The layer_mask is not a mandatory input. without inputting will use the layer_image's alpha channel. It is allowed to use layers and masks with different shapes, but the size must be consistent. -![image](image/title.png) +* The layer_mask is not a mandatory input now. it is allowed to use layers and masks with different shapes, but the size must be consistent. * Commit ImageBlend node. -* Remove the mask channel output from the DropShadow node; Commit OuterGlow node. +* Commit OuterGlow node. * Commit DropShadow node. ## Node Description -### Drop Shadow: +### DropShadow: Generate shadow ![image](image/drop_shadow_example.png) - Node options: ![image](image/drop_shadow_node.png) * background_image1: The background image. * layer_image1: Layer image for composite. -* layer_mask1: Mask for layer_image, shadows are generated according to their shape. +* layer_mask1,2: Mask for layer_image, shadows are generated according to their shape. * invert_mask: Whether to reverse the mask. -* blend_mode: Blending method of shadows, there are **_normal, multply, screen, add, subtract, difference, darker_** and **_lighter_**. +* blend_mode3: Blending mode of shadows. * opacity: Opacity of shadow. * distance_x: Horizontal offset of shadow. * distance_y: Vertical offset of shadow. * grow: Shadow expansion amplitude. -* blur:Shadow blur level. +* blur: Shadow blur level. * shadow_color: Shadow color, described in hexadecimal RGB format. - -1 The layer_image, layer_mask and the background_image, These three items must be of the same size. Output type: * image -### OuterGlow: +### OuterGlow: Generate outer glow ![image](image/outer_glow_example.png) @@ -50,20 +47,40 @@ Node options: ![image](image/outer_glow_node.png) * background_image1: The background image. * layer_image1: Layer image for composite. -* layer_mask1: Mask for layer_image, shadows are generated according to their shape. +* layer_mask1,22: Mask for layer_image, shadows are generated according to their shape. * invert_mask: Whether to reverse the mask. -* blend_mode: Blending method of glow, there are **_normal, multply, screen, add, subtract, difference, darker_** and **_lighter_**. -* opacity: Opacity of Glow. +* blend_mode3: Blending mode of glow. +* opacity: Opacity of glow. * grow: Glow expansion amplitude. -* blur:Gkow blur level. -* glow_color: Glow color, described in hexadecimal RGB format. +* blur: Gkow blur level. +* glow_color: Glow color, described in hexadecimal RGB format. -1 The layer_image, layer_mask and the background_image, These three items must be of the same size. Output type: * image -### ImageBlend: +### Stroke: +Generate a stroke of layer。 +![image](image/stroke_example.png) + +Node options: +![image](image/stroke_node.png) +* background_image1: The background image. +* layer_image1: Layer image for composite. +* layer_mask1,22: Mask for layer_image, shadows are generated according to their shape. +* invert_mask: Whether to reverse the mask. +* blend_mode3: Blending mode of stroke. +* opacity: Opacity of stroke. +* shrink*: Stroke shrinkage amplitude. +* expansion*: Stroke expansion amplitude. +* stroke_color: Stroke color, described in hexadecimal RGB format. + +*The width of the stroke is equal to shrink plus expansion value. Setting two values separately can make it more convenient to adjust the border range. + +Output type: +* image + +### ImageBlend: Composit layer image and background image, multiple blend modes are available for option, and transparency can be set. ![image](image/image_blend_example.png) @@ -71,24 +88,21 @@ Node options: ![image](image/image_blend_node.png) * background_image1: The background image. * layer_image1: Layer image for composite. -* layer_mask1: Mask for layer_image. +* layer_mask1,2: Mask for layer_image. * invert_mask: Whether to reverse the mask. -* blend_mode: Blending method of glow, there are **_normal, multply, screen, add, subtract, difference, darker_** and **_lighter_**. +* blend_mode3: Blending mode. * opacity: Opacity of blend. - -1 The layer_image, layer_mask and the background_image, These three items must be of the same size.。 - Output type: * image -### ImageOpacity: +### ImageOpacity: Adjust image opacity ![image](image/image_opacity_example.png) Node option: -* image: Image input, supporting RGB and RGBA. if is RGB, the alpha channel of the entire image will be automatically added. -* mask: Mask input. this is not a mandatory input item. attention that if the image input has alpha channel and different masks are input simultaneously, the original alpha channel will be overwritten by the mask. +* image1: Image input, supporting RGB and RGBA. if is RGB, the alpha channel of the entire image will be automatically added. +* mask1,2 : Mask input. * invert_mask: Whether to reverse the mask. * opacity: Opacity of image. @@ -96,9 +110,38 @@ Output type: * image(RGBA) * mask + +### MaskPreview: +Preview the input mask +![image](image/mask_preview_example.png) + +input:mask + +### Annotation: +1 The layer_image, layer_mask and the background_image(if have input), These three items must be of the same size.。 + +2 The mask not a mandatory input item. the alpha channel of the image is used by default. If the image input does not include an alpha channel, the entire image's alpha channel will be automatically created. if have masks input simultaneously, the alpha channel will be overwrite by the mask. + +3 The blend mode include **_normal, multply, screen, add, subtract, difference, darker_** and **_lighter_**. + ## Example workflow image Some JSON workflow files in the workflow directory, that is example for ComfyUI. +## The following tasks are in the planned list +### Layer Style node: +* Inner Shadow +* Inner Glow +* Texture +* Color Overlay +* Gradient Overlay +* Neon +### Auxiliary node: +* invert mask +* mask soft crop +* mask motion blur +* mask radial blur + + ## How to install * Open the cmd window in the plugin directory of ComfyUI, like "ComfyUI\custom_nodes\",type```git clone https://github.com/chflame163/ComfyUI_LayerStyle.git``` diff --git a/README_CN.MD b/README_CN.MD index 0757329..8537569 100644 --- a/README_CN.MD +++ b/README_CN.MD @@ -1,14 +1,16 @@ # ComfyUI Layer Style£º ComfyUI²å¼þ£¬Í¨¹ýÒ»×é½ÚµãʵÏÖ·ÂÕÕAdobe PhotoshopµÄͼ²ãÑùʽ¡£ -µ±Ç°ÒÑÍê³ÉDrop ShadowºÍOuterGlow½Úµã£¬ºóÐø¹¤×÷½øÐÐÖС£ + +µ±Ç°ÒÑÍê³ÉDrop ShadowºÍOuterGlow½ÚµãºÍһЩ¸¨Öú½Úµã£¬ºóÐø¹¤×÷½øÐÐÖС£ ## ¸üÐÂ˵Ã÷£º +* Ìí¼ÓStroke½Úµã¡£ +* Ìí¼ÓMaskPreview½Úµã¡£ * Ìí¼ÓImageOpacity½Úµã¡£ -* layer_mask²»ÊÇÊäÈë±ØÑ¡¡£²»ÊäÈ뽫ʹÓÃlayer_imageµÄalphaͨµÀ¡£ÔÊÐíʹÓÃÐÎ×´²»Í¬µÄlayerºÍmask£¬µ«ÊÇ³ß´ç±ØÐëÒ»Ö¡£ -![image](image/title.png) -* Ìí¼ÓImageBlend½Úµã -* DropShadow½ÚµãÈ¥µôµ÷ÊÔmaskͨµÀÊä³ö£»Ìí¼ÓOuterGlow½Úµã¡£ -* DropShadow½ÚµãÉÏÏß¡£ +* layer_maskÐÞ¸ÄΪ·Ç±ØÑ¡, ĬÈÏʹÓÃlayer_imageµÄalphaͨµÀ£¬ÔÊÐíͨ¹ýÊäÈëmask¸Ä±äÖ®£¬µ«ÊÇ³ß´ç±ØÐëÒ»Ö¡£ +* Ìí¼ÓImageBlend½Úµã¡£ +* Ìí¼ÓOuterGlow½Úµã¡£ +* Ê׸ö½ÚµãDropShadowÌá½»¡£ ## ½Úµã˵Ã÷£º @@ -17,14 +19,13 @@ ComfyUI ![image](image/drop_shadow_example.png) - ½ÚµãÑ¡Ïî˵Ã÷£º ![image](image/drop_shadow_node.png) * background_image1: ±³¾°Í¼Ïñ¡£ * layer_image1: ÓÃÓںϳɵIJãͼÏñ¡£ -* layer_mask1: ²ãͼÏñµÄÕÚÕÖ£¬ÒõÓ°°´´ËÉú³É¡£ +* layer_mask1,2: ²ãͼÏñµÄÕÚÕÖ£¬ÒõÓ°°´´ËÉú³É¡£ * invert_mask: ÊÇ·ñ·´×ªÕÚÕÖ¡£ -* blend_mode: ÒõÓ°µÄ»ìºÏģʽ£¬°üÀ¨normal¡¢multply¡¢screen¡¢add¡¢subtract¡¢difference¡¢darkerºÍlighter¡£ +* blend_mode3: ÒõÓ°µÄ»ìºÏģʽ¡£ * opacity: ÒõÓ°µÄ²»Í¸Ã÷¶È¡£ * distance_x: ÒõÓ°µÄˮƽ·½ÏòÆ«ÒÆÁ¿¡£ * distance_y: ÒõÓ°µÄ´¹Ö±·½ÏòÆ«ÒÆÁ¿¡£ @@ -32,7 +33,6 @@ ComfyUI * blur£ºÒõÓ°Ä£ºý³Ì¶È¡£ * shadow_color: ÒõÓ°ÑÕÉ«£¬Ê¹ÓÃ16½øÖÆRGB¸ñʽÃèÊö¡£ -1 background_image, layer_image, layer_maskÕâÈýÏî±ØÐëÊÇÏàͬµÄ³ß´ç¡£ Êä³ö£º * image @@ -47,13 +47,34 @@ ComfyUI * layer_image1: ÓÃÓںϳɵIJãͼÏñ¡£ * layer_mask1: ²ãͼÏñµÄÕÚÕÖ£¬Íâ·¢¹â°´´ËÉú³É¡£ * invert_mask: ÊÇ·ñ·´×ªÕÚÕÖ¡£ -* blend_mode: ·¢¹âµÄ»ìºÏģʽ£¬°üÀ¨normal¡¢multply¡¢screen¡¢add¡¢subtract¡¢difference¡¢darkerºÍlighter¡£ +* blend_mode3: ·¢¹âµÄ»ìºÏģʽ¡£ * opacity: ·¢¹âµÄ²»Í¸Ã÷¶È¡£ * grow: ·¢¹âÀ©ÕÅ·ù¶È¡£ * blur£º·¢¹âÄ£ºý³Ì¶È¡£ * glow_color: ·¢¹âÑÕÉ«£¬Ê¹ÓÃ16½øÖÆRGB¸ñʽÃèÊö¡£ -1 background_image, layer_image, layer_maskÕâÈýÏî±ØÐëÊÇÏàͬµÄ³ß´ç¡£ + +Êä³ö£º +* image + +### Stroke£º +Éú³ÉÃè±ß¡£ +![image](image/stroke_example.png) + +½ÚµãÑ¡Ïî˵Ã÷£º +![image](image/stroke_node.png) +* background_image1: ±³¾°Í¼Ïñ¡£ +* layer_image1: ÓÃÓںϳɵIJãͼÏñ¡£ +* layer_mask1: ²ãͼÏñµÄÕÚÕÖ£¬Ãè±ß°´´ËÉú³É¡£ +* invert_mask: ÊÇ·ñ·´×ªÕÚÕÖ¡£ +* blend_mode3: Ãè±ßµÄ»ìºÏģʽ¡£ +* opacity: ²»Í¸Ã÷¶È¡£ +* shrink*: Ãè±ßÊÕËõ·ù¶È¡£ +* expansion*£ºÃè±ßÀ©ÕÅ·ù¶È¡£ +* stroke_color: Ãè±ßÑÕÉ«£¬Ê¹ÓÃ16½øÖÆRGB¸ñʽÃèÊö¡£ + +*Ãè±ßµÄ¿í¶ÈµÈÓÚÊÕËõÖµ¼ÓÉÏÀ©ÕÅÖµ¡£Á½¸öÖµ·Ö¿ªÉèÖÃÄܸü·½±ãµÄµ÷Õû±ß¿ò·¶Î§¡£ + Êä³ö£º * image @@ -68,22 +89,20 @@ ComfyUI * layer_image1: ÓÃÓںϳɵIJãͼÏñ¡£ * layer_mask1: ²ãͼÏñµÄÕÚÕÖ¡£ * invert_mask: ÊÇ·ñ·´×ªÕÚÕÖ¡£ -* blend_mode: ͼ²ã»ìºÏģʽ£¬°üÀ¨normal¡¢multply¡¢screen¡¢add¡¢subtract¡¢difference¡¢darkerºÍlighter¡£ +* blend_mode3: ͼ²ã»ìºÏģʽ¡£ * opacity: ²»Í¸Ã÷¶È¡£ -1 background_image, layer_image, layer_maskÕâÈýÏî±ØÐëÊÇÏàͬµÄ³ß´ç¡£ - Êä³ö£º * image -### ImageOpacity£º +### ImageOpacity: µ÷ÕûͼÏñ²»Í¸Ã÷¶È¡£ ![image](image/image_opacity_example.png) ½ÚµãÑ¡Ïî˵Ã÷£º -* image: ͼÏñÊäÈ룬֧³ÖRGBºÍRGBAÊäÈë¡£Èç¹ûÊäÈëµÄÊÇRGB£¬½«×Ô¶¯Ìí¼ÓÕû¸öͼÏñµÄalphaͨµÀ¡£ -* mask: ÕÚÕÖÊäÈ룬Õâ²»ÊDZØÐëµÄÊäÈëÏî¡£×¢ÒâÈç¹ûimageÊäÈëÓÐalphaͨµÀÇÒͬʱÊäÈëÁ˲»Í¬µÄmask£¬Ô­±¾µÄalphaͨµÀ½«±»mask¸²¸Ç¡£ +* image: ͼÏñÊäÈ룬֧³ÖRGBºÍRGBAÊäÈë¡£ +* mask: ÕÚÕÖÊäÈë¡£ * invert_mask: ÊÇ·ñ·´×ªÕÚÕÖ¡£ * opacity: ²»Í¸Ã÷¶È¡£ @@ -91,10 +110,39 @@ ComfyUI * image(RGBA) * mask +### MaskPreview: +Ô¤ÀÀmask +![image](image/mask_preview_example.png) + +ÊäÈ룺mask + +### ½ÚµãÑ¡Ïî×¢½â£º +1 image¡¢maskºÍbackground_image(Èç¹ûÓÐÊäÈë)ÕâÈýÏî±ØÐëÊÇÏàͬµÄ³ß´ç¡£ +2 mask²»ÊDZØÐëµÄÊäÈëÏĬÈÏʹÓÃimageµÄalphaͨµÀ£¬Èç¹ûimageÊäÈë²»°üº¬alphaͨµÀ½«×Ô¶¯´´½¨Õû¸öͼÏñµÄalphaͨµÀ¡£Èç¹ûÊäÈëmask£¬Ô­±¾µÄalphaͨµÀ½«±»mask¸²¸Ç¡£ +3: »ìºÏģʽ°üÀ¨normal¡¢multply¡¢screen¡¢add¡¢subtract¡¢difference¡¢darkerºÍlighter¡£ + ## ʹÓÃʾÀý£º ÔÚworkflowĿ¼ÏÂÓÐjson¸ñʽµÄ¹¤×÷Á÷ʾÀýÎļþ¡£ +## ÒÔϹ¤×÷Ôڼƻ®ÁбíÖÐ +### Layer Style½Úµã£º +* Inner Shadow +* Inner Glow +* Stroke +* Texture +* Color Overlay +* Gradient Overlay +* Neon +### ¸¨Öú½Úµã£º +* invert mask +* invert image +* load image+ +* mask soft crop +* mask motion blur +* mask radial blur + + ## °²×°·½·¨£º * ÔÚCompyUI²å¼þĿ¼(ÀýÈç¡°CompyUI\custom_nodes\¡±)Öдò¿ªcmd´°¿Ú£¬¼üÈë```git clone https://github.com/chflame163/ComfyUI_LayerStyle.git```°²×°¡£»òÕßÏÂÔØ½âѹzipÎļþ£¬½«µÃµ½µÄÎļþ¼Ð¸´ÖƵ½ ComfyUI\custom_nodes\ diff --git a/image/mask_preview_example.png b/image/mask_preview_example.png new file mode 100644 index 0000000..63c3702 Binary files /dev/null and b/image/mask_preview_example.png differ diff --git a/image/stroke_example.png b/image/stroke_example.png new file mode 100644 index 0000000..8b2eb84 Binary files /dev/null and b/image/stroke_example.png differ diff --git a/image/stroke_node.png b/image/stroke_node.png new file mode 100644 index 0000000..881fb81 Binary files /dev/null and b/image/stroke_node.png differ diff --git a/py/drop_shadow.py b/py/drop_shadow.py index 881e6fa..03471a9 100644 --- a/py/drop_shadow.py +++ b/py/drop_shadow.py @@ -52,7 +52,7 @@ class DropShadow: if distance_x != 0 or distance_y != 0: __mask = shift_image(_mask, distance_x, distance_y) # ä½�ç§» - shadow_mask = expand_mask(image2mask(__mask), grow, blur, 0) #扩张,模糊 + shadow_mask = expand_mask(image2mask(__mask), grow, blur) #扩张,模糊 # å�ˆæˆ�阴影 shadow_color = Image.new("RGB", _layer.size, color=shadow_color) diff --git a/py/image_blend.py b/py/image_blend.py index aec27b9..2ca0d72 100644 --- a/py/image_blend.py +++ b/py/image_blend.py @@ -12,7 +12,6 @@ class ImageBlend: "required": { "background_image": ("IMAGE", ), # "layer_image": ("IMAGE",), # - "invert_mask": ("BOOLEAN", {"default": True}), # å��转mask "blend_mode": (chop_mode,), # æ··å�ˆæ¨¡å¼� "opacity": ("INT", {"default": 100, "min": 0, "max": 100, "step": 1}), # é€�明度 @@ -43,11 +42,9 @@ class ImageBlend: # å�ˆæˆ�layer _comp = chop_image(_canvas, _layer, blend_mode, opacity) - _canvas.paste(_comp, mask=_mask) - - ret_image = _canvas + return (pil2tensor(ret_image),) NODE_CLASS_MAPPINGS = { diff --git a/py/image_opacity.py b/py/image_opacity.py index 2fe5e41..926e271 100644 --- a/py/image_opacity.py +++ b/py/image_opacity.py @@ -31,15 +31,14 @@ class ImageOpacity: _image = tensor2pil(image).convert('RGB') _mask = tensor2pil(image).convert('RGBA').split()[-1] + if mask is not None: _mask = mask2image(mask).convert('L') - - # 设置é€�明度 if invert_mask: _color = Image.new("L", _image.size, color=(255)) else: _color = Image.new("L", _image.size, color=(0)) - # opacity + ret_mask = _mask if opacity == 0: ret_mask = _color diff --git a/py/imagefunc.py b/py/imagefunc.py index 0fb7f47..5d5a5d3 100644 --- a/py/imagefunc.py +++ b/py/imagefunc.py @@ -1,12 +1,24 @@ +'''Image process functions for ComfyUI nodes +by chflame https://github.com/chflame163 +''' import numpy as np import torch import scipy.ndimage +import cv2 from typing import Union, List from PIL import Image, ImageFilter, ImageChops def log(message): - name = 'Layer Style' - print(f"# 😺dzNodes: {name} -> {message}") + name = 'LayerStyle' + print(f"# 😺dzNodes: {name} -> {message}") + +def cv22pil(cv2_img:np.ndarray) -> Image: + cv2_img = cv2.cvtColor(cv2_img, cv2.COLOR_BGR2RGB) + return Image.fromarray(cv2_img) + +def pil2cv2(pil_img:Image) -> np.array: + np_img_array = np.asarray(pil_img) + return cv2.cvtColor(np_img_array, cv2.COLOR_RGB2BGR) def pil2tensor(image:Image) -> torch.Tensor: return torch.from_numpy(np.array(image).astype(np.float32) / 255.0).unsqueeze(0) @@ -51,7 +63,6 @@ def shift_image(image:Image, distance_x:int, distance_y:int) -> Image: for y in range(height): if x > -distance_x and y > -distance_y: if x + distance_x < width and y + distance_y < height: - # print(f"x={x}, y={y}, distance_x={distance_x}, distance_y={distance_y}") pixel = image.getpixel((x + distance_x, y + distance_y)) ret_image.putpixel((x, y), pixel) return ret_image @@ -85,7 +96,7 @@ def chop_image(background_image:Image, layer_image:Image, blend_mode:str, opacit return ret_image -def expand_mask(mask:torch.Tensor, grow:int, blur:int, expandrate:int) -> torch.Tensor: +def expand_mask(mask:torch.Tensor, grow:int, blur:int) -> torch.Tensor: # grow c = 0 kernel = np.array([[c, 1, c], @@ -100,19 +111,23 @@ def expand_mask(mask:torch.Tensor, grow:int, blur:int, expandrate:int) -> torch. output = scipy.ndimage.grey_erosion(output, footprint=kernel) else: output = scipy.ndimage.grey_dilation(output, footprint=kernel) - if grow < 0: - grow -= abs(expandrate) - else: - grow += abs(expandrate) output = torch.from_numpy(output) out.append(output) # blur - if blur != 0: - for idx, tensor in enumerate(out): - pil_image = tensor2pil(tensor.cpu().detach()) - pil_image = pil_image.filter(ImageFilter.GaussianBlur(blur)) - out[idx] = pil2tensor(pil_image) + for idx, tensor in enumerate(out): + pil_image = tensor2pil(tensor.cpu().detach()) + pil_image = pil_image.filter(ImageFilter.GaussianBlur(blur)) + out[idx] = pil2tensor(pil_image) ret_mask = torch.cat(out, dim=0) - # ret_mask = torch.tensor([ret_mask.tolist()]) return ret_mask + +def remove_background(image:Image, mask:Image, color:str) -> Image: + width = image.width + height = image.height + ret_image = Image.new('RGB', size=(width, height), color=color) + ret_image.paste(image, mask=mask) + return ret_image + +def subtract_mask(masks_a, masks_b): + return torch.clamp(masks_a - masks_b, 0, 255) diff --git a/py/mask_preview.py b/py/mask_preview.py new file mode 100644 index 0000000..1462144 --- /dev/null +++ b/py/mask_preview.py @@ -0,0 +1,32 @@ +import random +from .imagefunc import * +from nodes import SaveImage +import folder_paths + +class MaskPreview(SaveImage): + def __init__(self): + self.output_dir = folder_paths.get_temp_directory() + self.type = "temp" + self.prefix_append = "_temp_" + ''.join(random.choice("abcdefghijklmnopqrstupvxyz1234567890") for x in range(5)) + self.compress_level = 4 + + @classmethod + def INPUT_TYPES(self): + return { + "required": {"mask": ("MASK",), }, + } + + FUNCTION = "mask_preview" + CATEGORY = '😺dzNodes' + + def mask_preview(self, mask): + preview = mask.reshape((-1, 1, mask.shape[-2], mask.shape[-1])).movedim(1, -1).expand(-1, -1, -1, 3) + return self.save_images(preview, "MaskPreview") + +NODE_CLASS_MAPPINGS = { + "LayerStyle_MaskPreview": MaskPreview +} + +NODE_DISPLAY_NAME_MAPPINGS = { + "LayerStyle_MaskPreview": "LayerStyle: MaskPreview" +} \ No newline at end of file diff --git a/py/outer_glow.py b/py/outer_glow.py index fab82fe..ed55be6 100644 --- a/py/outer_glow.py +++ b/py/outer_glow.py @@ -48,7 +48,6 @@ class OuterGlow: _mask = mask2image(layer_mask).convert('L') glow_mask = expand_mask(image2mask(_mask), grow, blur, 0) #扩张,模糊 - # å�ˆæˆ�glow glow_color = Image.new("RGB", _layer.size, color=glow_color) alpha = tensor2pil(glow_mask).convert('L') @@ -57,7 +56,6 @@ class OuterGlow: # å�ˆæˆ�layer _canvas.paste(_layer, mask=_mask) - ret_image = _canvas # ret_mask = glow_mask # return (pil2tensor(ret_image), ret_mask,) diff --git a/py/stroke.py b/py/stroke.py new file mode 100644 index 0000000..487a3ac --- /dev/null +++ b/py/stroke.py @@ -0,0 +1,64 @@ +from .imagefunc import * + +class Stroke: + + def __init__(self): + pass + + @classmethod + def INPUT_TYPES(self): + chop_mode = ['normal','multply','screen','add','subtract','difference','darker','lighter'] + return { + "required": { + "background_image": ("IMAGE", ), # + "layer_image": ("IMAGE",), # + "invert_mask": ("BOOLEAN", {"default": True}), # å��转mask + "blend_mode": (chop_mode,), # æ··å�ˆæ¨¡å¼� + "opacity": ("INT", {"default": 100, "min": 0, "max": 100, "step": 1}), # é€�明度 + "shrink": ("INT", {"default": 1, "min": 0, "max": 9999, "step": 1}), # 收缩值 + "expansion": ("INT", {"default": 1, "min": 0, "max": 9999, "step": 1}), # 扩张值 + "blur": ("INT", {"default": 5, "min": 0, "max": 100, "step": 1}), # 模糊 + "stroke_color": ("STRING", {"default": "#FF0000"}), # æ��边颜色 + }, + "optional": { + "layer_mask": ("MASK",), # + } + } + + RETURN_TYPES = ("IMAGE",) + RETURN_NAMES = ("image",) + FUNCTION = 'stroke' + CATEGORY = '😺dzNodes' + OUTPUT_NODE = True + + def stroke(self, background_image, layer_image, + invert_mask, blend_mode, opacity, + shrink, expansion, blur, stroke_color, + layer_mask=None, + ): + + _canvas = tensor2pil(background_image).convert('RGB') + _layer = tensor2pil(layer_image).convert('RGB') + _mask = tensor2pil(layer_image).convert('RGBA').split()[-1] + if layer_mask is not None: + if invert_mask: + layer_mask = 1 - layer_mask + _mask = mask2image(layer_mask).convert('L') + + shink_mask = expand_mask(image2mask(_mask), -shrink, blur) + expansion_mask = expand_mask(image2mask(_mask), expansion, blur) + strock_mask = subtract_mask(expansion_mask, shink_mask) + color_image = Image.new('RGB', size=_layer.size, color=stroke_color) + blend_image = chop_image(_layer, color_image, blend_mode, opacity) + _canvas.paste(_layer, mask=_mask) + _canvas.paste(blend_image, mask=tensor2pil(strock_mask)) + ret_image = _canvas + return (pil2tensor(ret_image),) + +NODE_CLASS_MAPPINGS = { + "LayerStyle_Stroke": Stroke +} + +NODE_DISPLAY_NAME_MAPPINGS = { + "LayerStyle_Stroke": "LayerStyle: Stroke" +} \ No newline at end of file diff --git a/workflow/stroke_example.json b/workflow/stroke_example.json new file mode 100644 index 0000000..8709cf4 --- /dev/null +++ b/workflow/stroke_example.json @@ -0,0 +1,205 @@ +{ + "last_node_id": 15, + "last_link_id": 18, + "nodes": [ + { + "id": 1, + "type": "LayerStyle_Stroke", + "pos": [ + 758, + 309 + ], + "size": [ + 315, + 242 + ], + "flags": {}, + "order": 2, + "mode": 0, + "inputs": [ + { + "name": "background_image", + "type": "IMAGE", + "link": 16 + }, + { + "name": "layer_image", + "type": "IMAGE", + "link": 10 + }, + { + "name": "layer_mask", + "type": "MASK", + "link": 3 + } + ], + "outputs": [ + { + "name": "image", + "type": "IMAGE", + "links": [ + 4 + ], + "shape": 3, + "slot_index": 0 + } + ], + "properties": { + "Node name for S&R": "LayerStyle_Stroke" + }, + "widgets_values": [ + true, + "normal", + 100, + 8, + 2, + 0, + "#DB43A2" + ] + }, + { + "id": 3, + "type": "LoadImage", + "pos": [ + 413, + 284 + ], + "size": { + "0": 315, + "1": 314 + }, + "flags": {}, + "order": 0, + "mode": 0, + "outputs": [ + { + "name": "IMAGE", + "type": "IMAGE", + "links": [ + 10 + ], + "shape": 3, + "slot_index": 0 + }, + { + "name": "MASK", + "type": "MASK", + "links": [ + 3 + ], + "shape": 3, + "slot_index": 1 + } + ], + "properties": { + "Node name for S&R": "LoadImage" + }, + "widgets_values": [ + "512x512 (10).png", + "image" + ] + }, + { + "id": 2, + "type": "LoadImage", + "pos": [ + 59, + 279 + ], + "size": { + "0": 315, + "1": 314 + }, + "flags": {}, + "order": 1, + "mode": 0, + "outputs": [ + { + "name": "IMAGE", + "type": "IMAGE", + "links": [ + 16 + ], + "shape": 3, + "slot_index": 0 + }, + { + "name": "MASK", + "type": "MASK", + "links": null, + "shape": 3 + } + ], + "properties": { + "Node name for S&R": "LoadImage" + }, + "widgets_values": [ + "512x512bkgd (7).jpg", + "image" + ] + }, + { + "id": 4, + "type": "PreviewImage", + "pos": [ + 1105, + 285 + ], + "size": [ + 299.70625443891913, + 311.19479890303114 + ], + "flags": {}, + "order": 3, + "mode": 0, + "inputs": [ + { + "name": "images", + "type": "IMAGE", + "link": 4 + } + ], + "properties": { + "Node name for S&R": "PreviewImage" + } + } + ], + "links": [ + [ + 3, + 3, + 1, + 1, + 2, + "MASK" + ], + [ + 4, + 1, + 0, + 4, + 0, + "IMAGE" + ], + [ + 10, + 3, + 0, + 1, + 1, + "IMAGE" + ], + [ + 16, + 2, + 0, + 1, + 0, + "IMAGE" + ] + ], + "groups": [], + "config": {}, + "extra": {}, + "version": 0.4 +} \ No newline at end of file