diff --git a/README.MD b/README.MD index cbefdd3..7c36883 100644 --- a/README.MD +++ b/README.MD @@ -7,6 +7,7 @@ The completed nodes are as follows, and further work is in progress. [中文说明点这里](./README_CN.MD) ## Update +* Add GradientOverlay and ColorOverlay nodes. Add invalid mask input judgment and ignore it when invalid mask is input. * Commit InnerGlow, InnerShadow and MotionBlur nodes. * Renaming all completed nodes, the nodes are divided into 4 groups:LayerStyle, LayerMask, LayerUtility, LayerFilter. workflows containing old version nodes need to be manually replaced with new version nodes. * OuterGlow node has undergone significant modifications by adding options for **_brightness_**, **_light_color_**, and **_glow_color_**. @@ -21,8 +22,9 @@ The completed nodes are as follows, and further work is in progress. * Commit DropShadow node. # LayerStyle +![image](image/layerstyle_nodes.png) -### DropShadow: +### DropShadow Generate shadow ![image](image/drop_shadow_example.png) @@ -41,7 +43,7 @@ Node options: * blur: Shadow blur level. * shadow_color4: Shadow color. -### OuterGlow: +### OuterGlow Generate outer glow ![image](image/outer_glow_example.png) @@ -59,7 +61,7 @@ Node options: * light_color4: Center part color of glow. * glow_color4: Edge part color of glow. -### InnerShadow: +### InnerShadow Generate inner shadow ![image](image/inner_shadow_example.png) @@ -77,7 +79,7 @@ Node options: * blur: Shadow blur level. * shadow_color4: Shadow color. -### InnerGlow: +### InnerGlow Generate inner glow ![image](image/inner_glow_example.png) @@ -96,7 +98,7 @@ Node options: * glow_color4: Edge part color of glow. -### Stroke: +### Stroke Generate a stroke of layer。 ![image](image/stroke_example.png) @@ -112,6 +114,37 @@ Node options: * stroke_width: Stroke width. * stroke_color4: Stroke color, described in hexadecimal RGB format. +### GradientOverlay +Generate gradient overlay +![image](image/gradient_overlay_example.png) + +Node options: +* background_image1: The background image. +* layer_image1: Layer image for composite. +* layer_mask1,2: Mask for layer_image. +* invert_mask: Whether to reverse the mask. +* blend_mode3: Blending mode of gradient. +* opacity: Opacity of stroke. +* start_color: Color at the beginning of the gradient. +* start_alpha: Transparency at the beginning of the gradient. +* end_color: Color at the end of the gradient. +* end_alpha: Transparency at the end of the gradient. +* angle: Gradient rotation angle. + +### ColorOverlay +Generate color overlay +![image](image/color_overlay_example.png) + +Node options: +![image](image/color_overlay_node.png) +* background_image1: The background image. +* layer_image1: Layer image for composite. +* layer_mask1,2: Mask for layer_image. +* invert_mask: Whether to reverse the mask. +* blend_mode3: Blending mode of color. +* opacity: Opacity of stroke. +* color: Color of overlay. + #### Annotation for LayerStyle nodes: 1 The layer_image, layer_mask and the background_image(if have input), These three items must be of the same size.。 @@ -123,6 +156,7 @@ Node options: 4 The RGB color described by hexadecimal RGB format, like '#FA3D86'. # LayerMask +![image](image/layermask_nodes.png) ### MaskPreview: Preview the input mask @@ -134,6 +168,7 @@ Invert the mask # LayerUtility +![image](image/layerutility_nodes.png) ### ImageBlend: Composit layer image and background image, multiple blend modes are available for option, and transparency can be set. @@ -169,6 +204,7 @@ Output Type: * string # LayerFilter +![image](image/layerfilter_nodes.png) ### MotionBlur: Make the image motion blur @@ -183,8 +219,26 @@ image Some JSON workflow files in the workflow directory, that is example for Co ## The following tasks are in the planned list -* Color Overlay -* Gradient Overlay +* LayerUtility: +imageblend advance +color image +gradient image + +* LayerMask: +text mask +gradient mask +mask grow&blur +mask subtract +mask combine +mask diffrent +mask gradient +mask softedge + +* LayerFilter: +bulr +emboss +contour +findedge ## How to install diff --git a/README_CN.MD b/README_CN.MD index 24dfed1..e7f81d1 100644 --- a/README_CN.MD +++ b/README_CN.MD @@ -1,10 +1,11 @@ -# ComfyUI Layer Style£º +# ComfyUI Layer Style ComfyUI²å¼þ£¬Í¨¹ýÒ»×é½ÚµãʵÏÖ·ÂÕÕAdobe PhotoshopµÄͼ²ãÑùʽ¡£ ÒÑÍê³ÉµÄ½ÚµãÈçÏ£¬ºóÐø¹¤×÷½øÐÐÖС£ ![image](image/title.png) -## ¸üÐÂ˵Ã÷£º +## ¸üÐÂ˵Ã÷: +* Ìí¼ÓGradientOverlay,ColorOverlay½Úµã¡£Ôö¼ÓÎÞЧmaskÊäÈëÅжϣ¬µ±ÊäÈëÎÞЧmaskʱ½«ÆäºöÂÔ¡£ * Ìí¼ÓInnerGlow, InnerShadow, MotionBlur½Úµã¡£ * ËùÓÐÒÑÍê³ÉµÄ½ÚµãÖØÐÂÃüÃû£¬½Úµã·ÖΪ4×飺LayerStyle, LayerMask, LayerUtility, LayerFilter¡£ÒòÎªÖØÐÂÃüÃû£¬°üº¬¾É°æ½ÚµãµÄ¹¤×÷Á÷ÐèÊÖ¶¯Ì滻аæ½Úµã¡£ * OuterGlow½ÚµãÐ޸ģ¬Ôö¼ÓÁÁ¶È¡¢µÆ¹âÑÕÉ«¡¢»Ô¹âÑÕɫѡÏî¡£ @@ -18,12 +19,13 @@ ComfyUI * Ê׸ö½ÚµãDropShadowÌá½»¡£ # LayerStyle +![image](image/layerstyle_nodes.png) -### DropShadow: +### DropShadow Éú³ÉÒõÓ°¡£ ![image](image/drop_shadow_example.png) -½ÚµãÑ¡Ïî˵Ã÷£º +½ÚµãÑ¡Ïî˵Ã÷: ![image](image/drop_shadow_node.png) * background_image1: ±³¾°Í¼Ïñ¡£ * layer_image1: ÓÃÓںϳɵIJãͼÏñ¡£ @@ -34,14 +36,14 @@ ComfyUI * distance_x: ÒõÓ°µÄˮƽ·½ÏòÆ«ÒÆÁ¿¡£ * distance_y: ÒõÓ°µÄ´¹Ö±·½ÏòÆ«ÒÆÁ¿¡£ * grow: ÒõÓ°À©ÕÅ·ù¶È¡£ -* blur£ºÒõÓ°Ä£ºý³Ì¶È¡£ +* blur:ÒõÓ°Ä£ºý³Ì¶È¡£ * shadow_color4: ÒõÓ°ÑÕÉ«¡£ -### OuterGlow£º +### OuterGlow Éú³ÉÍâ·¢¹â¡£ ![image](image/outer_glow_example.png) -½ÚµãÑ¡Ïî˵Ã÷£º +½ÚµãÑ¡Ïî˵Ã÷: ![image](image/outer_glow_node.png) * background_image1: ±³¾°Í¼Ïñ¡£ * layer_image1: ÓÃÓںϳɵIJãͼÏñ¡£ @@ -51,15 +53,15 @@ ComfyUI * opacity: ·¢¹âµÄ²»Í¸Ã÷¶È¡£ * brightness: ·¢¹âÁÁ¶È¡£ * glow_range: ·¢¹â·¶Î§¡£ -* blur£º·¢¹âÄ£ºý³Ì¶È¡£ +* blur:·¢¹âÄ£ºý³Ì¶È¡£ * light_color4: ·¢¹âÖÐÐÄÑÕÉ«¡£ * glow_colo4: »Ô¹âÍâΧÑÕÉ«¡£ -### InnerShadow: +### InnerShadow Éú³ÉÄÚÒõÓ°¡£ ![image](image/inner_shadow_example.png) -½ÚµãÑ¡Ïî˵Ã÷£º +½ÚµãÑ¡Ïî˵Ã÷: ![image](image/inner_shadow_node.png) * background_image1: ±³¾°Í¼Ïñ¡£ * layer_image1: ÓÃÓںϳɵIJãͼÏñ¡£ @@ -70,14 +72,14 @@ ComfyUI * distance_x: ÒõÓ°µÄˮƽ·½ÏòÆ«ÒÆÁ¿¡£ * distance_y: ÒõÓ°µÄ´¹Ö±·½ÏòÆ«ÒÆÁ¿¡£ * grow: ÒõÓ°À©ÕÅ·ù¶È¡£ -* blur£ºÒõÓ°Ä£ºý³Ì¶È¡£ +* blur:ÒõÓ°Ä£ºý³Ì¶È¡£ * shadow_color4: ÒõÓ°ÑÕÉ«¡£ -### InnerGlow£º +### InnerGlow Éú³ÉÄÚ·¢¹â¡£ ![image](image/inner_glow_example.png) -½ÚµãÑ¡Ïî˵Ã÷£º +½ÚµãÑ¡Ïî˵Ã÷: ![image](image/inner_glow_node.png) * background_image1: ±³¾°Í¼Ïñ¡£ * layer_image1: ÓÃÓںϳɵIJãͼÏñ¡£ @@ -87,15 +89,15 @@ ComfyUI * opacity: ·¢¹âµÄ²»Í¸Ã÷¶È¡£ * brightness: ·¢¹âÁÁ¶È¡£ * glow_range: ·¢¹â·¶Î§¡£ -* blur£º·¢¹âÄ£ºý³Ì¶È¡£ +* blur:·¢¹âÄ£ºý³Ì¶È¡£ * light_color4: ·¢¹âÖÐÐÄÑÕÉ«¡£ * glow_colo4: »Ô¹âÍâΧÑÕÉ«¡£ -### Stroke£º +### Stroke Éú³ÉÃè±ß¡£ ![image](image/stroke_example.png) -½ÚµãÑ¡Ïî˵Ã÷£º +½ÚµãÑ¡Ïî˵Ã÷: ![image](image/stroke_node.png) * background_image1: ±³¾°Í¼Ïñ¡£ * layer_image1: ÓÃÓںϳɵIJãͼÏñ¡£ @@ -103,18 +105,51 @@ ComfyUI * invert_mask: ÊÇ·ñ·´×ªÕÚÕÖ¡£ * blend_mode3: Ãè±ßµÄ»ìºÏģʽ¡£ * opacity: ²»Í¸Ã÷¶È¡£ -* stroke_grow: Ãè±ßÀ©ÕÅ/ÊÕËõ·ù¶È£¬ÕýÖµÊÇÀ©ÕÅ£¬¸ºÖµÊÇÊÕËõ¡£ -* stroke_width£ºÃè±ß¿í¶È¡£ +* stroke_grow: Ãè±ßÀ©ÕÅ/ÊÕËõ·ù¶È£¬ÕýÖµÊÇÀ©ÕÅ£¬¸ºÖµÊÇÊÕËõ¡£ +* stroke_width: Ãè±ß¿í¶È¡£ * stroke_color4: Ãè±ßÑÕÉ«¡£ +### GradientOverlay +½¥±ä¸²¸Ç +![image](image/gradient_overlay_example.png) -#### ½ÚµãÑ¡Ïî×¢½â£º +½ÚµãÑ¡Ïî˵Ã÷: +![image](image/gradient_overlay_node.png) +* background_image1: ±³¾°Í¼Ïñ¡£ +* layer_image1: ÓÃÓںϳɵIJãͼÏñ¡£ +* layer_mask1£¬2: ²ãͼÏñµÄÕÚÕÖ¡£ +* invert_mask: ÊÇ·ñ·´×ªÕÚÕÖ¡£ +* blend_mode3: Ãè±ßµÄ»ìºÏģʽ¡£ +* opacity: ²»Í¸Ã÷¶È¡£ +* start_color: ½¥±ä¿ªÊ¼¶ËµÄÑÕÉ«¡£ +* start_alpha: ½¥±ä¿ªÊ¼¶ËµÄ͸Ã÷¶È¡£ +* end_color: ½¥±ä½áÊø¶ËµÄÑÕÉ«¡£ +* end_alpha: ½¥±ä½áÊø¶ËµÄ͸Ã÷¶È¡£ +* angle: ½¥±äÐýת½Ç¶È¡£ + +### ColorOverlay +ÑÕÉ«¸²¸Ç +![image](image/color_overlay_example.png) + +½ÚµãÑ¡Ïî˵Ã÷: +![image](image/color_overlay_node.png) +* background_image1: ±³¾°Í¼Ïñ¡£ +* layer_image1: ÓÃÓںϳɵIJãͼÏñ¡£ +* layer_mask1£¬2: ²ãͼÏñµÄÕÚÕÖ¡£ +* invert_mask: ÊÇ·ñ·´×ªÕÚÕÖ¡£ +* blend_mode3: Ãè±ßµÄ»ìºÏģʽ¡£ +* opacity: ²»Í¸Ã÷¶È¡£ +* color: ¸²¸ÇµÄÑÕÉ«¡£ + + +#### ½ÚµãÑ¡Ïî×¢½â: 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¡£ 4: ÑÕɫʹÓÃ16½øÖÆRGB×Ö·û´®¸ñʽÃèÊö£¬ÀýÈç '#FA3D86'¡£ # LayerMask +![image](image/layermask_nodes.png) ### MaskPreview: Ô¤ÀÀmask @@ -125,12 +160,13 @@ mask ![image](image/mask_invert_node.png) # LayerUtility +![image](image/layerutility_nodes.png) ### ImageBlend£º ÓÃÓںϳÉͼ²ã£¬Ìṩ¶àÖÖ»ìºÏģʽ¹©Ñ¡Ôñ£¬¿ÉÉèÖÃ͸Ã÷¶È¡£ ![image](image/image_blend_example.png) -½ÚµãÑ¡Ïî˵Ã÷£º +½ÚµãÑ¡Ïî˵Ã÷: ![image](image/image_blend_node.png) * background_image1: ±³¾°Í¼Ïñ¡£ * layer_image1: ÓÃÓںϳɵIJãͼÏñ¡£ @@ -143,7 +179,7 @@ mask µ÷ÕûͼÏñ²»Í¸Ã÷¶È¡£ ![image](image/image_opacity_example.png) -½ÚµãÑ¡Ïî˵Ã÷£º +½ÚµãÑ¡Ïî˵Ã÷: * image: ͼÏñÊäÈ룬֧³ÖRGBºÍRGBAÊäÈë¡£ * mask: ÕÚÕÖÊäÈë¡£ * invert_mask: ÊÇ·ñ·´×ªÕÚÕÖ¡£ @@ -154,31 +190,50 @@ mask ¸Ä×Ô[mtb nodes](https://github.com/melMass/comfy_mtb)µÄweb extensions¡£ ![image](image/color_picker.png) -½ÚµãÑ¡Ïî˵Ã÷£º +½ÚµãÑ¡Ïî˵Ã÷: * mode£º Êä³ö¸ñʽ£¬¿ÉѡʮÁù½øÖÆ(HEX)»òÊ®½øÖÆ(DEC)¡£ -Êä³ö£º +Êä³ö: * string # LayerFilter +![image](image/layerfilter_nodes.png) ### MotionBlur: Ô˶¯Ä£ºý¡£ ![image](image/motion_blur_example.png) -½ÚµãÑ¡Ïî˵Ã÷£º +½ÚµãÑ¡Ïî˵Ã÷: * angle: Ä£ºý½Ç¶È¡£ * blur: Ä£ºý´óС¡£ -## ʹÓÃʾÀý£º +## ʹÓÃʾÀý: ÔÚworkflowĿ¼ÏÂÓÐjson¸ñʽµÄ¹¤×÷Á÷ʾÀýÎļþ¡£ ## ÒÔϹ¤×÷Ôڼƻ®ÁбíÖÐ -* Color Overlay -* Gradient Overlay +* LayerUtility: +imageblend advance +color image +gradient image + +* LayerMask: +text mask +gradient mask +mask grow&blur +mask subtract +mask combine +mask diffrent +mask gradient +mask softedge + +* LayerFilter: +bulr +emboss +contour +findedge ## °²×°·½·¨£º diff --git a/image/color_overlay_example.png b/image/color_overlay_example.png new file mode 100644 index 0000000..01eca7a Binary files /dev/null and b/image/color_overlay_example.png differ diff --git a/image/color_overlay_node.png b/image/color_overlay_node.png new file mode 100644 index 0000000..367dcff Binary files /dev/null and b/image/color_overlay_node.png differ diff --git a/image/gradient_overlay_example.png b/image/gradient_overlay_example.png new file mode 100644 index 0000000..a3ecc9c Binary files /dev/null and b/image/gradient_overlay_example.png differ diff --git a/image/gradient_overlay_node.png b/image/gradient_overlay_node.png new file mode 100644 index 0000000..2c7feb3 Binary files /dev/null and b/image/gradient_overlay_node.png differ diff --git a/image/layerfilter_nodes.png b/image/layerfilter_nodes.png new file mode 100644 index 0000000..ccfd8c8 Binary files /dev/null and b/image/layerfilter_nodes.png differ diff --git a/image/layermask_nodes.png b/image/layermask_nodes.png new file mode 100644 index 0000000..ff7d5d7 Binary files /dev/null and b/image/layermask_nodes.png differ diff --git a/image/layerstyle_nodes.png b/image/layerstyle_nodes.png new file mode 100644 index 0000000..040ceb3 Binary files /dev/null and b/image/layerstyle_nodes.png differ diff --git a/image/layerutility_nodes.png b/image/layerutility_nodes.png new file mode 100644 index 0000000..4db758b Binary files /dev/null and b/image/layerutility_nodes.png differ diff --git a/image/title.png b/image/title.png index 8e3c36d..20a511d 100644 Binary files a/image/title.png and b/image/title.png differ diff --git a/py/color_overlay.py b/py/color_overlay.py new file mode 100644 index 0000000..d1223d9 --- /dev/null +++ b/py/color_overlay.py @@ -0,0 +1,66 @@ +from .imagefunc import * + +class ColorOverlay: + + 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}), # é€�明度 + "color": ("STRING", {"default": "#FFBF30"}), # æ¸�å�˜å¼€å§‹é¢œè‰² + }, + "optional": { + "layer_mask": ("MASK",), # + } + } + + RETURN_TYPES = ("IMAGE",) + RETURN_NAMES = ("image",) + FUNCTION = 'color_overlay' + CATEGORY = '😺dzNodes/LayerStyle' + OUTPUT_NODE = True + + def color_overlay(self, background_image, layer_image, + invert_mask, blend_mode, opacity, color, + layer_mask=None + ): + + # preprocess + _canvas = tensor2pil(background_image).convert('RGB') + _layer = tensor2pil(layer_image) + if _layer.mode == 'RGBA': + _mask = tensor2pil(layer_image).convert('RGBA').split()[-1] + else: + _mask = Image.new('L', _layer.size, 'white') + _layer = _layer.convert('RGB') + if layer_mask is not None: + if invert_mask: + layer_mask = 1 - layer_mask + _mask = mask2image(layer_mask).convert('L') + if _mask.size != _layer.size: + _mask = Image.new('L', _layer.size, 'white') + log('Warning: mask mismatch, droped!') + + _color = Image.new('RGB', size=_layer.size, color=color) + # å�ˆæˆ�layer + _comp = chop_image(_layer, _color, blend_mode, opacity) + _canvas.paste(_comp, mask=_mask) + ret_image = _canvas + log('ColorOverlay Processed.') + return (pil2tensor(ret_image),) + +NODE_CLASS_MAPPINGS = { + "LayerStyle: ColorOverlay": ColorOverlay +} + +NODE_DISPLAY_NAME_MAPPINGS = { + "LayerStyle: ColorOverlay": "LayerStyle: ColorOverlay" +} \ No newline at end of file diff --git a/py/drop_shadow.py b/py/drop_shadow.py index e13aa49..85adb45 100644 --- a/py/drop_shadow.py +++ b/py/drop_shadow.py @@ -38,16 +38,24 @@ class DropShadow: layer_mask=None ): - distance_x = -distance_x - distance_y = -distance_y + # preprocess _canvas = tensor2pil(background_image).convert('RGB') - _layer = tensor2pil(layer_image).convert('RGB') - _mask = tensor2pil(layer_image).convert('RGBA').split()[-1] - # 处ç�†mask + _layer = tensor2pil(layer_image) + if _layer.mode == 'RGBA': + _mask = tensor2pil(layer_image).convert('RGBA').split()[-1] + else: + _mask = Image.new('L', _layer.size, 'white') + _layer = _layer.convert('RGB') if layer_mask is not None: if invert_mask: layer_mask = 1 - layer_mask _mask = mask2image(layer_mask).convert('L') + if _mask.size != _layer.size: + _mask = Image.new('L', _layer.size, 'white') + log('Warning: mask mismatch, droped!') + + distance_x = -distance_x + distance_y = -distance_y if distance_x != 0 or distance_y != 0: __mask = shift_image(_mask, distance_x, distance_y) # ä½�ç§» shadow_mask = expand_mask(image2mask(__mask), grow, blur) #扩张,模糊 diff --git a/py/gradient_overlay.py b/py/gradient_overlay.py new file mode 100644 index 0000000..4b04c51 --- /dev/null +++ b/py/gradient_overlay.py @@ -0,0 +1,79 @@ +from .imagefunc import * + +class GradientOverlay: + + 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}), # é€�明度 + "start_color": ("STRING", {"default": "#FFBF30"}), # æ¸�å�˜å¼€å§‹é¢œè‰² + "start_alpha": ("INT", {"default": 255, "min": 0, "max": 255, "step": 1}), + "end_color": ("STRING", {"default": "#FE0000"}), # æ¸�å�˜ç»“æ�Ÿé¢œè‰² + "end_alpha": ("INT", {"default": 255, "min": 0, "max": 255, "step": 1}), + "angle": ("INT", {"default": 0, "min": -180, "max": 180, "step": 1}), # æ¸�å�˜è§’度 + }, + "optional": { + "layer_mask": ("MASK",), # + } + } + + RETURN_TYPES = ("IMAGE",) + RETURN_NAMES = ("image",) + FUNCTION = 'gradient_overlay' + CATEGORY = '😺dzNodes/LayerStyle' + OUTPUT_NODE = True + + def gradient_overlay(self, background_image, layer_image, + invert_mask, blend_mode, opacity, + start_color, start_alpha, end_color, end_alpha, angle, + layer_mask=None + ): + + # preprocess + _canvas = tensor2pil(background_image).convert('RGB') + _layer = tensor2pil(layer_image) + if _layer.mode == 'RGBA': + _mask = tensor2pil(layer_image).convert('RGBA').split()[-1] + else: + _mask = Image.new('L', _layer.size, 'white') + _layer = _layer.convert('RGB') + if layer_mask is not None: + if invert_mask: + layer_mask = 1 - layer_mask + _mask = mask2image(layer_mask).convert('L') + if _mask.size != _layer.size: + _mask = Image.new('L', _layer.size, 'white') + log('Warning: mask mismatch, droped!') + + _gradient = gradint(start_color, end_color, _layer.width, _layer.height, float(angle)) + + # å�ˆæˆ�layer + _comp = chop_image(_layer, _gradient, blend_mode, opacity) + if start_alpha < 255 or end_alpha < 255: + # + start_color = RGB_to_Hex((start_alpha, start_alpha, start_alpha)) + end_color = RGB_to_Hex((end_alpha, end_alpha, end_alpha)) + comp_alpha = gradint(start_color, end_color, _layer.width, _layer.height, float(angle)) + comp_alpha = ImageChops.invert(comp_alpha).convert('L') + _comp.paste(_layer, comp_alpha) + _canvas.paste(_comp, mask=_mask) + ret_image = _canvas + log('GradientOverlay Processed.') + return (pil2tensor(ret_image),) + +NODE_CLASS_MAPPINGS = { + "LayerStyle: GradientOverlay": GradientOverlay +} + +NODE_DISPLAY_NAME_MAPPINGS = { + "LayerStyle: GradientOverlay": "LayerStyle: GradientOverlay" +} \ No newline at end of file diff --git a/py/imagefunc.py b/py/imagefunc.py index e4d8cdc..35b2963 100644 --- a/py/imagefunc.py +++ b/py/imagefunc.py @@ -6,7 +6,7 @@ import torch import scipy.ndimage import cv2 from typing import Union, List -from PIL import Image, ImageFilter, ImageChops +from PIL import Image, ImageFilter, ImageChops, ImageDraw def log(message): name = 'LayerStyle' @@ -119,9 +119,70 @@ def motion_blur(image:Image, angle:int, blur:int) -> Image: ret_image = cv22pil(blurred) return ret_image -def direction_blur(image:Image, angle:int, blur:int, color:str) -> Image: +def __rotate_expand(image:Image, angle:float, SSAA:int=0) -> Image: + images = pil2tensor(image) + expand = "true" + height, width = images[0, :, :, 0].shape - ret_image = image + def rotate_tensor(tensor): + resize_sampler = Image.LANCZOS + rotate_sampler = Image.BICUBIC + if SSAA > 1: + img = tensor.tensor_to_image() + img_us_scaled = img.resize((width * SSAA, height * SSAA), resize_sampler) + img_rotated = img_us_scaled.rotate(angle, rotate_sampler, expand == "true", fillcolor=(0, 0, 0, 0)) + img_down_scaled = img_rotated.resize((img_rotated.width // SSAA, img_rotated.height // SSAA), resize_sampler) + result = img_down_scaled.image_to_tensor() + else: + img = tensor.tensor_to_image() + img_rotated = img.rotate(angle, rotate_sampler, expand == "true", fillcolor=(0, 0, 0, 0)) + result = img_rotated.image_to_tensor() + return result + + if angle == 0.0 or angle == 360.0: + return tensor2pil(images) + else: + rotated_tensor = torch.stack([rotate_tensor(images[i]) for i in range(len(images))]) + return tensor2pil(rotated_tensor).convert('RGB') + +def image_rotate_extend_with_alpha(image:Image, alpha:Image, angle:float, SSAA:int=0) -> tuple: + _image = __rotate_expand(image.convert('RGB'), angle, SSAA) + _alpha = __rotate_expand(alpha.convert('RGB'), angle, SSAA) + R, G, B = _image.split() + A = _alpha.convert('L') + ret_image = Image.merge('RGBA', (R, G, B, A)) + return (_image, _alpha, ret_image) + + +def create_gradient(start_color_inhex:str, end_color_inhex:str, width:int, height:int) -> Image: + start_color = Hex_to_RGB(start_color_inhex) + end_color = Hex_to_RGB(end_color_inhex) + ret_image = Image.new("RGB", (width, height), start_color) + draw = ImageDraw.Draw(ret_image) + for i in range(height): + R = int(start_color[0] * (height - i) / height + end_color[0] * i / height) + G = int(start_color[1] * (height - i) / height + end_color[1] * i / height) + B = int(start_color[2] * (height - i) / height + end_color[2] * i / height) + color = (R, G, B) + 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: + 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) + top = Image.new('RGB', size=(radius, radius), color=start_color_inhex) + bottom = Image.new('RGB', size=(radius, radius),color=end_color_inhex) + _canvas.paste(top, box=(0, 0, radius, radius)) + _canvas.paste(g, box=(0, radius, radius, radius * 2)) + _canvas.paste(bottom,box=(0, radius * 2, radius, radius * 3)) + _canvas = _canvas.resize((radius * 3, radius * 3)) + _canvas = __rotate_expand(_canvas,angle) + center = int(_canvas.width / 2) + _x = int(width / 3) + _y = int(height / 3) + ret_image = _canvas.crop((center - _x, center - _y, center + _x, center + _y)) + ret_image = ret_image.resize((width, height)) return ret_image '''Mask Functions''' @@ -180,18 +241,15 @@ def step_value(start_value, end_value, total_step, step) -> float: # 按当å‰� factor = step / total_step return (end_value - start_value) * factor + start_value -def step_color(start_color, end_color, total_step, step): # 按当å‰�步数在总步数中的ä½�置返回比例颜色 - if isinstance(start_color, str): - start_color = tuple(Hex_to_RGB(start_color)) - if isinstance(end_color, str): - end_color = tuple(Hex_to_RGB(end_color)) +def step_color(start_color_inhex:str, end_color_inhex:str, total_step:int, step:int) -> str: # 按当å‰�步数在总步数中的ä½�置返回比例颜色 + start_color = tuple(Hex_to_RGB(start_color_inhex)) + end_color = tuple(Hex_to_RGB(end_color_inhex)) start_R, start_G, start_B = start_color[0], start_color[1], start_color[2] end_R, end_G, end_B = end_color[0], end_color[1], end_color[2] ret_color = (int(step_value(start_R, end_R, total_step, step)), int(step_value(start_G, end_G, total_step, step)), int(step_value(start_B, end_B, total_step, step)), ) - if isinstance(start_color, str): - return RGB_to_Hex(ret_color) - else: - return ret_color + return RGB_to_Hex(ret_color) + + diff --git a/py/inner_glow.py b/py/inner_glow.py index eee6107..e6db05e 100644 --- a/py/inner_glow.py +++ b/py/inner_glow.py @@ -38,14 +38,21 @@ class InnerGlow: layer_mask=None ): + # preprocess _canvas = tensor2pil(background_image).convert('RGB') - _layer = tensor2pil(layer_image).convert('RGB') - _mask = tensor2pil(layer_image).convert('RGBA').split()[-1] - # 处ç�†mask + _layer = tensor2pil(layer_image) + if _layer.mode == 'RGBA': + _mask = tensor2pil(layer_image).convert('RGBA').split()[-1] + else: + _mask = Image.new('L', _layer.size, 'white') + _layer = _layer.convert('RGB') if layer_mask is not None: if invert_mask: layer_mask = 1 - layer_mask _mask = mask2image(layer_mask).convert('L') + if _mask.size != _layer.size: + _mask = Image.new('L', _layer.size, 'white') + log('Warning: mask mismatch, droped!') blur_factor = blur / 20.0 grow = glow_range diff --git a/py/inner_shadow.py b/py/inner_shadow.py index c60622a..35cc20d 100644 --- a/py/inner_shadow.py +++ b/py/inner_shadow.py @@ -39,16 +39,24 @@ class InnerShadow: layer_mask=None ): - distance_x = -distance_x - distance_y = -distance_y + # preprocess _canvas = tensor2pil(background_image).convert('RGB') - _layer = tensor2pil(layer_image).convert('RGB') - _mask = tensor2pil(layer_image).convert('RGBA').split()[-1] - # 处ç�†mask + _layer = tensor2pil(layer_image) + if _layer.mode == 'RGBA': + _mask = tensor2pil(layer_image).convert('RGBA').split()[-1] + else: + _mask = Image.new('L', _layer.size, 'white') + _layer = _layer.convert('RGB') if layer_mask is not None: if invert_mask: layer_mask = 1 - layer_mask _mask = mask2image(layer_mask).convert('L') + if _mask.size != _layer.size: + _mask = Image.new('L', _layer.size, 'white') + log('Warning: mask mismatch, droped!') + + distance_x = -distance_x + distance_y = -distance_y if distance_x != 0 or distance_y != 0: __mask = shift_image(_mask, distance_x, distance_y) # ä½�ç§» shadow_mask = expand_mask(image2mask(__mask), grow, blur) #扩张,模糊 diff --git a/py/outer_glow.py b/py/outer_glow.py index b9c696e..f13b74d 100644 --- a/py/outer_glow.py +++ b/py/outer_glow.py @@ -38,14 +38,22 @@ class OuterGlow: layer_mask=None ): + # preprocess _canvas = tensor2pil(background_image).convert('RGB') - _layer = tensor2pil(layer_image).convert('RGB') - _mask = tensor2pil(layer_image).convert('RGBA').split()[-1] - # 处ç�†mask + _layer = tensor2pil(layer_image) + if _layer.mode == 'RGBA': + _mask = tensor2pil(layer_image).convert('RGBA').split()[-1] + else: + _mask = Image.new('L', _layer.size, 'white') + _layer = _layer.convert('RGB') if layer_mask is not None: if invert_mask: layer_mask = 1 - layer_mask _mask = mask2image(layer_mask).convert('L') + if _mask.size != _layer.size: + _mask = Image.new('L', _layer.size, 'white') + log('Warning: mask mismatch, droped!') + blur_factor = blur / 20.0 grow = glow_range for x in range(brightness): diff --git a/py/stroke.py b/py/stroke.py index 5c2e209..ff50d3b 100644 --- a/py/stroke.py +++ b/py/stroke.py @@ -37,13 +37,21 @@ class Stroke: layer_mask=None ): + # preprocess _canvas = tensor2pil(background_image).convert('RGB') - _layer = tensor2pil(layer_image).convert('RGB') - _mask = tensor2pil(layer_image).convert('RGBA').split()[-1] + _layer = tensor2pil(layer_image) + if _layer.mode == 'RGBA': + _mask = tensor2pil(layer_image).convert('RGBA').split()[-1] + else: + _mask = Image.new('L', _layer.size, 'white') + _layer = _layer.convert('RGB') if layer_mask is not None: if invert_mask: layer_mask = 1 - layer_mask _mask = mask2image(layer_mask).convert('L') + if _mask.size != _layer.size: + _mask = Image.new('L', _layer.size, 'white') + log('Warning: mask mismatch, droped!') grow_offset = int(stroke_width / 2) inner_stroke = stroke_grow - grow_offset diff --git a/workflow/color_overlay_example.json b/workflow/color_overlay_example.json new file mode 100644 index 0000000..4232f82 --- /dev/null +++ b/workflow/color_overlay_example.json @@ -0,0 +1,203 @@ +{ + "last_node_id": 25, + "last_link_id": 61, + "nodes": [ + { + "id": 15, + "type": "LoadImage", + "pos": [ + 659, + 386 + ], + "size": { + "0": 315, + "1": 314 + }, + "flags": {}, + "order": 0, + "mode": 0, + "outputs": [ + { + "name": "IMAGE", + "type": "IMAGE", + "links": [ + 59 + ], + "shape": 3, + "slot_index": 0 + }, + { + "name": "MASK", + "type": "MASK", + "links": [ + 60 + ], + "shape": 3, + "slot_index": 1 + } + ], + "properties": { + "Node name for S&R": "LoadImage" + }, + "widgets_values": [ + "512x512 (14).png", + "image" + ] + }, + { + "id": 2, + "type": "LoadImage", + "pos": [ + 307, + 382 + ], + "size": { + "0": 315, + "1": 314 + }, + "flags": {}, + "order": 1, + "mode": 0, + "outputs": [ + { + "name": "IMAGE", + "type": "IMAGE", + "links": [ + 58 + ], + "shape": 3, + "slot_index": 0 + }, + { + "name": "MASK", + "type": "MASK", + "links": [], + "shape": 3, + "slot_index": 1 + } + ], + "properties": { + "Node name for S&R": "LoadImage" + }, + "widgets_values": [ + "512x512bk.jpg", + "image" + ] + }, + { + "id": 24, + "type": "LayerStyle: ColorOverlay", + "pos": [ + 1008, + 440 + ], + "size": { + "0": 315, + "1": 170 + }, + "flags": {}, + "order": 2, + "mode": 0, + "inputs": [ + { + "name": "background_image", + "type": "IMAGE", + "link": 58 + }, + { + "name": "layer_image", + "type": "IMAGE", + "link": 59 + }, + { + "name": "layer_mask", + "type": "MASK", + "link": 60 + } + ], + "outputs": [ + { + "name": "image", + "type": "IMAGE", + "links": [ + 61 + ], + "shape": 3, + "slot_index": 0 + } + ], + "properties": { + "Node name for S&R": "LayerStyle: ColorOverlay" + }, + "widgets_values": [ + true, + "multply", + 66, + "#FFBF30" + ] + }, + { + "id": 4, + "type": "PreviewImage", + "pos": [ + 1358, + 386 + ], + "size": { + "0": 310.00006103515625, + "1": 308.33331298828125 + }, + "flags": {}, + "order": 3, + "mode": 0, + "inputs": [ + { + "name": "images", + "type": "IMAGE", + "link": 61 + } + ], + "properties": { + "Node name for S&R": "PreviewImage" + } + } + ], + "links": [ + [ + 58, + 2, + 0, + 24, + 0, + "IMAGE" + ], + [ + 59, + 15, + 0, + 24, + 1, + "IMAGE" + ], + [ + 60, + 15, + 1, + 24, + 2, + "MASK" + ], + [ + 61, + 24, + 0, + 4, + 0, + "IMAGE" + ] + ], + "groups": [], + "config": {}, + "extra": {}, + "version": 0.4 +} \ No newline at end of file diff --git a/workflow/gradident_overlay_example.json b/workflow/gradident_overlay_example.json new file mode 100644 index 0000000..22e3d9f --- /dev/null +++ b/workflow/gradident_overlay_example.json @@ -0,0 +1,207 @@ +{ + "last_node_id": 29, + "last_link_id": 66, + "nodes": [ + { + "id": 4, + "type": "PreviewImage", + "pos": [ + 1358, + 386 + ], + "size": { + "0": 310.00006103515625, + "1": 308.33331298828125 + }, + "flags": {}, + "order": 3, + "mode": 0, + "inputs": [ + { + "name": "images", + "type": "IMAGE", + "link": 66 + } + ], + "properties": { + "Node name for S&R": "PreviewImage" + } + }, + { + "id": 2, + "type": "LoadImage", + "pos": [ + 307, + 382 + ], + "size": { + "0": 315, + "1": 314 + }, + "flags": {}, + "order": 0, + "mode": 0, + "outputs": [ + { + "name": "IMAGE", + "type": "IMAGE", + "links": [ + 62 + ], + "shape": 3, + "slot_index": 0 + }, + { + "name": "MASK", + "type": "MASK", + "links": [], + "shape": 3, + "slot_index": 1 + } + ], + "properties": { + "Node name for S&R": "LoadImage" + }, + "widgets_values": [ + "512x512bk.jpg", + "image" + ] + }, + { + "id": 15, + "type": "LoadImage", + "pos": [ + 659, + 386 + ], + "size": { + "0": 315, + "1": 314 + }, + "flags": {}, + "order": 1, + "mode": 0, + "outputs": [ + { + "name": "IMAGE", + "type": "IMAGE", + "links": [ + 63 + ], + "shape": 3, + "slot_index": 0 + }, + { + "name": "MASK", + "type": "MASK", + "links": [ + 64 + ], + "shape": 3, + "slot_index": 1 + } + ], + "properties": { + "Node name for S&R": "LoadImage" + }, + "widgets_values": [ + "512x512 (14).png", + "image" + ] + }, + { + "id": 27, + "type": "LayerStyle: GradientOverlay", + "pos": [ + 1015, + 408 + ], + "size": { + "0": 315, + "1": 266 + }, + "flags": {}, + "order": 2, + "mode": 0, + "inputs": [ + { + "name": "background_image", + "type": "IMAGE", + "link": 62 + }, + { + "name": "layer_image", + "type": "IMAGE", + "link": 63 + }, + { + "name": "layer_mask", + "type": "MASK", + "link": 64 + } + ], + "outputs": [ + { + "name": "image", + "type": "IMAGE", + "links": [ + 66 + ], + "shape": 3, + "slot_index": 0 + } + ], + "properties": { + "Node name for S&R": "LayerStyle: GradientOverlay" + }, + "widgets_values": [ + true, + "normal", + 100, + "#FFBF30", + 60, + "#FE0000", + 255, + 0 + ] + } + ], + "links": [ + [ + 62, + 2, + 0, + 27, + 0, + "IMAGE" + ], + [ + 63, + 15, + 0, + 27, + 1, + "IMAGE" + ], + [ + 64, + 15, + 1, + 27, + 2, + "MASK" + ], + [ + 66, + 27, + 0, + 4, + 0, + "IMAGE" + ] + ], + "groups": [], + "config": {}, + "extra": {}, + "version": 0.4 +} \ No newline at end of file