diff --git a/README.MD b/README.MD
index 2fcbda7..80fe11f 100644
--- a/README.MD
+++ b/README.MD
@@ -13,6 +13,9 @@ Nodes are divided into four groups according to their functions: LayerStyle, Lay
[䏿–‡è¯´æ˜Žç‚¹è¿™é‡Œ](./README_CN.MD)
## Update
+* Commit [CropByMask](#CropByMask) and [RestoreCropBox](#RestoreCropBox) nodes. The combination of these two can partially crop and redraw the image before restoring it.
+* Commit [ColorAdapter](#ColorAdapter) node, that can automatically adjust the color tone of the image.
+* Commit [MaskStroke](#MaskStroke) node, it can generate mask contour strokes.
* Add [LayerColor](#LayerColor) node group, used to adjust image color. it include [LUT Apply](#LUT), [Gamma](#Gamma), [Brightness & Contrast](#Brightness), [RGB](#RGB), [YUV](#YUV), [LAB](#LAB) adn [HSV](#HSV).
* Commit [ImageChannelSplit](#ImageChannelSplit) and [ImageChannelMerge](#ImageChannelMerge) nodes.
* Commit [MaskMotionBlur](#MaskMotionBlur) node.
@@ -144,6 +147,7 @@ Node options:
* opacity: Opacity of stroke.
* stroke_grow: Stroke expansion/contraction amplitude, positive values indicate expansion and negative values indicate contraction.
* stroke_width: Stroke width.
+* blur: Blur of stroke.
* stroke_color4: Stroke color, described in hexadecimal RGB format.
* [note](#notes)
@@ -196,6 +200,14 @@ Node options:
Open the text editing software and find the line starting with "LUT dir=", after "=", enter the custom folder path name. all .cube files in this folder will be collected and displayed in the node list during ComfyUI initialization.
If the folder set in ini is invalid, the LUT folder that comes with the plugin will be enabled.
+### ColorAdapter
+Auto adjust the color tone of the image to resemble the reference image.
+
+
+Node options:
+
+* opacity: The opacity of an image after adjusting its color tone.
+
### Gamma
Change the gamma value of the image.
@@ -320,6 +332,16 @@ Node options:
* gradient_offset: Gradient position offset.
* opacity: The opacity of the gradient.
+### MaskStroke
+Generate mask contour strokes.
+
+
+Node options:
+
+* invert_mask: Whether to reverse the mask.
+* stroke_grow: Stroke expansion/contraction amplitude, positive values indicate expansion and negative values indicate contraction.
+* stroke_width: Stroke width.
+* blur: Blur of stroke.
### MaskPreview
Preview the input mask
@@ -361,6 +383,41 @@ Node options:
* anti_aliasing: Anti aliasing, ranging from 0 to 16, the larger the value, the less obvious the aliasing. An excessively high value will significantly reduce the processing speed of the node.
* [note](#notes)
+### CropByMask
+Crop the image according to the mask range, and set the size of the surrounding borders to be retained.
+This node can be used in conjunction with the [RestoreCropBox](#RestoreCropBox) node to crop and modify parts of an image, and then paste them back in place.
+
+
+Node options:
+
+* image5: The input image.
+* mask_for_crop5: Mask of the image, it will automatically be cut according to the mask range.
+* invert_mask: Whether to reverse the mask.
+* detect: Detection method, min_bounding_rect is the minimum bounding rectangle, max_inscribed_rect is the maximum inscribed rectangle.
+* top_reserve: Cut the top to preserve size.
+* bottom_reserve: Cut the bottom to preserve size.
+* left_reserve: Cut the left to preserve size.
+* right_reserve: Cut the right to preserve size.
+* [note](#notes)
+
+Output:
+* croped_image: The image after crop.
+* croped_mask: The mask after crop.
+* crop_box: The trimmed box data is used when restoring the RestoreCropBox node.
+* box_preview: Preview image of cutting position, red represents the detected range, and green represents the cutting range after adding the reserved border.
+
+### RestoreCropBox
+Restore the cropped image to the original image by [CropByMask](#CropByMask).
+
+Node options:
+
+* background_image: The original image before cutting.
+* croped_image5: The cropped image. If the middle is enlarged, the size needs to be restored before restoration.
+* croped_mask5: The cut mask.
+* crop_box: Box data during cutting.
+* invert_mask: Whether to reverse the mask.
+* [note](#notes)
+
### ImageBlend
A simple node for composit layer image and background image, multiple blend modes are available for option, and transparency can be set.

@@ -535,25 +592,16 @@ image Some JSON workflow files in the workflow directory, that is example for Co
## The following tasks are in the planned list
- LayerUtility:
-* image channel split
-* image channel merge
-
LayerMask:
-* text mask
-* mask subtract
-* mask combine
-* mask diffrent
+* TextMask
LayerFilter:
-* emboss
-* contour
-* findedge
+* Emboss
+* Contour
+* Findedge
+* PhotoStyle
+* ColorMap
-LayerColor:
-* RGB adjust
-* HSV adjust
-* brightness & contrast adjust
## How to install
diff --git a/README_CN.MD b/README_CN.MD
index 9b6226e..8b209b0 100644
--- a/README_CN.MD
+++ b/README_CN.MD
@@ -10,6 +10,9 @@ ComfyUI
* [LayerFilter](#LayerFilter)½Úµã×éÌṩͼÏñЧ¹ûÂ˾µ¡£
## ¸üÐÂ˵Ã÷
+* Ìí¼Ó[CropByMask](#CropByMask) ºÍ [RestoreCropBox](#RestoreCropBox)½Úµã¡£´Ë¶þÕßÅäºÏ¿É½«Í¼Æ¬¾Ö²¿²ÃÇÐÖØ»æÈ»ºó»¹Ô¡£
+* Ìí¼Ó[ColorAdapter](#ColorAdapter) ½Úµã£¬¿É×Ô¶¯µ÷ÕûͼƬɫµ÷¡£
+* Ìí¼Ó[MaskStroke](#MaskStroke) ½Úµã£¬¿É²úÉúmaskÃè±ß¡£
* Ìí¼Ó[LayerColor](#LayerColor)½Úµã×飬ÓÃÓÚµ÷ÕûͼÏñÑÕÉ«¡£°üÀ¨[LUT Apply](#LUT)£¬[Gamma](#Gamma), [Brightness & Contrast](#Brightness), [RGB](#RGB), [YUV](#YUV), [LAB](#LAB)ºÍ[HSV](#HSV)¡£
* Ìí¼Ó[ImageChannelSplit](#ImageChannelSplit)ºÍ[ImageChannelMerge](#ImageChannelMerge)½Úµã¡£
* Ìí¼Ó[MaskMotionBlur](#MaskMotionBlur)½Úµã¡£
@@ -137,6 +140,7 @@ ComfyUI
* opacity: ²»Í¸Ã÷¶È¡£
* stroke_grow: Ãè±ßÀ©ÕÅ/ÊÕËõ·ù¶È£¬ÕýÖµÊÇÀ©ÕÅ£¬¸ºÖµÊÇÊÕËõ¡£
* stroke_width: Ãè±ß¿í¶È¡£
+* blur: Ãè±ßÄ£ºý¡£
* stroke_color4: Ãè±ßÑÕÉ«¡£
* [½Úµã×¢½â](#½Úµã×¢½â)
@@ -188,6 +192,14 @@ ComfyUI
*LUTÎļþ¼ÐÔÚresource_dir.iniÖж¨Ò壬Õâ¸öÎļþλÓÚ²å¼þ¸ùĿ¼Ï¡£ÓÃÎı¾±à¼Èí¼þ´ò¿ª£¬ÕÒµ½¡°LUT_dir=¡±¿ªÍ·µÄÕâÒ»ÐУ¬±à¼¡°=¡±Ö®ºóΪ×Ô¶¨ÒåÎļþ¼Ð·¾¶Ãû¡£Õâ¸öÎļþ¼ÐÀïÃæËùÓеÄ.cubeÎļþ½«ÔÚComfyUI³õʼ»¯Ê±±»ÊÕ¼¯²¢ÏÔʾÔÚ½ÚµãµÄÁбíÖС£Èç¹ûiniÖÐÉ趨µÄÎļþ¼ÐÎÞЧ£¬½«ÆôÓòå¼þ×Ô´øµÄLUTÎļþ¼Ð¡£
+### ColorAdapter
+×Ô¶¯µ÷ÕûͼƬɫµ÷£¬Ê¹Ö®Óë²Î¿¼Í¼Æ¬ÏàËÆ¡£
+
+
+½ÚµãÑ¡Ïî˵Ã÷:
+
+* opacity: ͼÏñµ÷ÕûÉ«µ÷Ö®ºóµÄ²»Í¸Ã÷¶È¡£
+
### Gamma
¸Ä±äͼÏñµÄGammaÖµ¡£
@@ -310,6 +322,17 @@ MaskGrow
* gradient_offset: ½¥±äλÖÃÆ«ÒÆ¡£
* opacity: ½¥±äµÄ²»Í¸Ã÷¶È¡£
+### MaskStroke
+²úÉúmaskÂÖÀªÃè±ß¡£
+
+
+½ÚµãÑ¡Ïî˵Ã÷:
+
+* invert_mask: ÊÇ·ñ·´×ªÕÚÕÖ¡£
+* stroke_grow: Ãè±ßÀ©ÕÅ/ÊÕËõ·ù¶È£¬ÕýÖµÊÇÀ©ÕÅ£¬¸ºÖµÊÇÊÕËõ¡£
+* stroke_width: Ãè±ß¿í¶È¡£
+* blur: Ãè±ßÄ£ºý¡£
+
### MaskPreview
Ô¤ÀÀmask

@@ -350,6 +373,40 @@ mask
* anti_aliasing: ¿¹¾â³Ý£¬·¶Î§´Ó0-16£¬ÊýÖµÔ½´ó£¬¾â³ÝÔ½²»Ã÷ÏÔ¡£¹ý¸ßµÄÊýÖµ½«ÏÔÖø½µµÍ½ÚµãµÄ´¦ÀíËÙ¶È¡£
* [½Úµã×¢½â](#½Úµã×¢½â)
+### CropByMask
+½«Í¼Æ¬°´ÕÕmask·¶Î§²ÃÇУ¬¿ÉÉèÖÃËÄÖܱ߿ò±£Áô´óС¡£Õâ¸ö½ÚµãÓë[RestoreCropBox](#RestoreCropBox)½ÚµãÅäºÏʹÓ㬿ÉÒÔ¶ÔͼƬµÄ¾Ö²¿½øÐвÃÇÐÐ޸ģ¬È»ºóÌù»ØÔ´¦¡£
+
+
+½ÚµãÑ¡Ïî˵Ã÷:
+
+* image5: ÊäÈëµÄͼÏñ¡£
+* mask_for_crop5: imageµÄÕÚÕÖ£¬½«×Ô¶¯°´ÕÕÕÚÕÖ·¶Î§½øÐвÃÇС£
+* invert_mask: ÊÇ·ñ·´×ªÕÚÕÖ¡£
+* detect: ̽²â·½·¨£¬min_bounding_rectÊÇ×îСÍâ½Ó¾ØÐÎ, max_inscribed_rectÊÇ×î´óÄÚ½Ó¾ØÐΡ£
+* top_reserve: ²ÃÇж¥¶Ë±£Áô´óС¡£
+* bottom_reserve: ²ÃÇеײ¿±£Áô´óС¡£
+* left_reserve: ²ÃÇÐ×ó²à±£Áô´óС¡£
+* right_reserve: ²ÃÇÐÓҲౣÁô´óС¡£
+* [½Úµã×¢½â](#½Úµã×¢½â)
+
+Êä³ö:
+* croped_image: ²ÃÇкóµÄͼƬ¡£
+* croped_mask: ²ÃÇкóµÄÕÚÕÖ¡£
+* crop_box: ²ÃÇÐboxÊý¾Ý£¬ÔÚRestoreCropBox½Úµã»Ö¸´Ê±Ê¹Óá£
+* box_preview: ²ÃÇÐλÖÃÔ¤ÀÀͼ£¬ºìÉ«ÊÇ̽²âµ½µÄ·¶Î§£¬ÂÌÉ«ÊǼÓÉϱ£Áô±ß¿òºó²ÃÇеķ¶Î§¡£
+
+### RestoreCropBox
+½«±»[CropByMask](#CropByMask)²ÃÇкóµÄͼƬ»Ö¸´µ½Ôͼ¡£
+
+½ÚµãÑ¡Ïî˵Ã÷:
+
+* background_image: ²ÃÇÐǰµÄÔͼ¡£
+* croped_image5: ²ÃÇкóµÄͼƬ¡£Èç¹ûÖм侹ý·Å´ó´¦Àí£¬»Ö¸´Ç°Ð轫³ß´ç»¹Ô¡£
+* croped_mask5: ²ÃÇкóµÄÕÚÕÖ¡£
+* crop_box: ²ÃÇÐʱµÄboxÊý¾Ý¡£
+* invert_mask: ÊÇ·ñ·´×ªÕÚÕÖ¡£
+* [½Úµã×¢½â](#½Úµã×¢½â)
+
### ImageBlend
Ò»¸öÓÃÓںϳÉͼ²ãµÄ¼òµ¥½Úµã£¬Ìṩ¶àÖÖ»ìºÏģʽ¹©Ñ¡Ôñ£¬¿ÉÉèÖÃ͸Ã÷¶È¡£

@@ -520,22 +577,15 @@ mask
## ÒÔϹ¤×÷¼Æ»®ÖÐ
- LayerUtility:
-* image channel split
-* image channel merge
-
LayerMask:
-* text mask
+* TextMask
LayerFilter:
-* emboss
-* contour
-* findedge
-
-LayerColor:
-* RGB adjust
-* HSV adjust
-* brightness & contrast adjust
+* Emboss
+* Contour
+* Findedge
+* PhotoStyle
+* ColorMap
## °²×°·½·¨
diff --git a/image/color_adapter_example.png b/image/color_adapter_example.png
new file mode 100644
index 0000000..cfddfcb
Binary files /dev/null and b/image/color_adapter_example.png differ
diff --git a/image/color_adapter_node.png b/image/color_adapter_node.png
new file mode 100644
index 0000000..a48b1b7
Binary files /dev/null and b/image/color_adapter_node.png differ
diff --git a/image/corp_by_mask_example.png b/image/corp_by_mask_example.png
new file mode 100644
index 0000000..3c766b0
Binary files /dev/null and b/image/corp_by_mask_example.png differ
diff --git a/image/corp_by_mask_node.png b/image/corp_by_mask_node.png
new file mode 100644
index 0000000..41f803a
Binary files /dev/null and b/image/corp_by_mask_node.png differ
diff --git a/py/color_adapter.py b/py/color_adapter.py
new file mode 100644
index 0000000..672f536
--- /dev/null
+++ b/py/color_adapter.py
@@ -0,0 +1,39 @@
+from .imagefunc import *
+
+class ColorAdapter:
+
+ def __init__(self):
+ pass
+
+ @classmethod
+ def INPUT_TYPES(self):
+
+ return {
+ "required": {
+ "image": ("IMAGE", ), #
+ "color_ref_image": ("IMAGE", ), #
+ "opacity": ("INT", {"default": 75, "min": 0, "max": 100, "step": 1}), # �明度
+ },
+ "optional": {
+ }
+ }
+
+ RETURN_TYPES = ("IMAGE",)
+ RETURN_NAMES = ("image",)
+ FUNCTION = 'color_adapter'
+ CATEGORY = '😺dzNodes/LayerColor'
+ OUTPUT_NODE = True
+
+ def color_adapter(self, image, color_ref_image, opacity):
+ _canvas = tensor2pil(image).convert('RGB')
+ ret_image = color_adapter(_canvas, tensor2pil(color_ref_image).convert('RGB'))
+ ret_image = chop_image(_canvas, ret_image, blend_mode='normal', opacity=opacity)
+ return (pil2tensor(ret_image),)
+
+NODE_CLASS_MAPPINGS = {
+ "LayerColor: ColorAdapter": ColorAdapter
+}
+
+NODE_DISPLAY_NAME_MAPPINGS = {
+ "LayerColor: ColorAdapter": "LayerColor: ColorAdapter"
+}
\ No newline at end of file
diff --git a/py/crop_by_mask.py b/py/crop_by_mask.py
new file mode 100644
index 0000000..b38c310
--- /dev/null
+++ b/py/crop_by_mask.py
@@ -0,0 +1,70 @@
+from .imagefunc import *
+
+class CropByMask:
+
+ def __init__(self):
+ pass
+
+ @classmethod
+ def INPUT_TYPES(self):
+ detect_mode = ['min_bounding_rect', 'max_inscribed_rect']
+ return {
+ "required": {
+ "image": ("IMAGE", ), #
+ "mask_for_crop": ("MASK",),
+ "invert_mask": ("BOOLEAN", {"default": False}), # �转mask#
+ "detect": (detect_mode,),
+ "top_reserve": ("INT", {"default": 20, "min": -9999, "max": 9999, "step": 1}),
+ "bottom_reserve": ("INT", {"default": 20, "min": -9999, "max": 9999, "step": 1}),
+ "left_reserve": ("INT", {"default": 20, "min": -9999, "max": 9999, "step": 1}),
+ "right_reserve": ("INT", {"default": 20, "min": -9999, "max": 9999, "step": 1}),
+ },
+ "optional": {
+ }
+ }
+
+ RETURN_TYPES = ("IMAGE", "MASK", "BOX", "IMAGE",)
+ RETURN_NAMES = ("croped_image", "croped_mask", "crop_box", "box_preview")
+ FUNCTION = 'crop_by_mask'
+ CATEGORY = '😺dzNodes/LayerUtility'
+ OUTPUT_NODE = True
+
+ def crop_by_mask(self, image, mask_for_crop, invert_mask, detect,
+ top_reserve, bottom_reserve, left_reserve, right_reserve
+ ):
+
+ _canvas = tensor2pil(image).convert('RGB')
+ if invert_mask:
+ mask_for_crop = 1 - mask_for_crop
+ _mask = mask2image(mask_for_crop)
+ bluredmask = gaussian_blur(_mask, 20).convert('L')
+ x = 0
+ y = 0
+ width = 0
+ height = 0
+ if detect == "min_bounding_rect":
+ (x, y, width, height) = min_bounding_rect(bluredmask)
+ if detect == "max_inscribed_rect":
+ (x, y, width, height) = max_inscribed_rect(bluredmask)
+
+ x1 = x - left_reserve if x - left_reserve > 0 else 0
+ y1 = y - top_reserve if y - top_reserve > 0 else 0
+ x2 = x + width + right_reserve if x + width + right_reserve < _canvas.width else _canvas.width
+ y2 = y + height + bottom_reserve if y + height + bottom_reserve < _canvas.height else _canvas.height
+ preview_image = tensor2pil(mask_for_crop).convert('RGB')
+ preview_image = draw_rect(preview_image, x, y, width, height, line_color="#F00000", line_width=(width+height)//100)
+ preview_image = draw_rect(preview_image, x1, y1, x2 - x1, y2 - y1,
+ line_color="#00F000", line_width=(width+height)//200)
+ crop_box = (x1, y1, x2, y2)
+ ret_image = _canvas.crop(crop_box)
+ ret_mask = _mask.crop(crop_box)
+
+ return (pil2tensor(ret_image), image2mask(ret_mask), list(crop_box), pil2tensor(preview_image),)
+
+NODE_CLASS_MAPPINGS = {
+ "LayerUtility: CropByMask": CropByMask
+}
+
+NODE_DISPLAY_NAME_MAPPINGS = {
+ "LayerUtility: CropByMask": "LayerUtility: CropByMask"
+}
\ No newline at end of file
diff --git a/py/imagefunc.py b/py/imagefunc.py
index 96cc981..9046185 100644
--- a/py/imagefunc.py
+++ b/py/imagefunc.py
@@ -334,7 +334,6 @@ def image_hue_offset(image:Image, offset:int) -> Image:
ret_image.putpixel((x, y), _pixel)
return ret_image
-
def gamma_trans(image:Image, gamma:float) -> Image:
cv2_image = pil2cv2(image)
gamma_table = [np.power(x/255.0,gamma)*255.0 for x in range(256)]
@@ -370,6 +369,26 @@ def lut_apply(image:Image, lut_file:str) -> Image:
_image.putpixel((x, y), new_pixel)
return _image
+
+def color_adapter(image:Image, ref_image:Image) -> Image:
+ image = pil2cv2(image)
+ ref_image = pil2cv2(ref_image)
+ image = cv2.cvtColor(image, cv2.COLOR_BGR2LAB)
+ image_mean, image_std = calculate_mean_std(image)
+ ref_image = cv2.cvtColor(ref_image, cv2.COLOR_BGR2LAB)
+ ref_image_mean, ref_image_std = calculate_mean_std(ref_image)
+ _image = ((image - image_mean) * (ref_image_std / image_std)) + ref_image_mean
+ np.putmask(_image, _image > 255, values=255)
+ np.putmask(_image, _image < 0, values=0)
+ ret_image = cv2.cvtColor(cv2.convertScaleAbs(_image), cv2.COLOR_LAB2BGR)
+ return cv22pil(ret_image)
+
+def calculate_mean_std(image:Image):
+ mean, std = cv2.meanStdDev(image)
+ mean = np.hstack(np.around(mean, decimals=2))
+ std = np.hstack(np.around(std, decimals=2))
+ return mean, std
+
'''Mask Functions'''
def expand_mask(mask:torch.Tensor, grow:int, blur:int) -> torch.Tensor:
diff --git a/py/mask_stroke.py b/py/mask_stroke.py
new file mode 100644
index 0000000..34ebe99
--- /dev/null
+++ b/py/mask_stroke.py
@@ -0,0 +1,49 @@
+from .imagefunc import *
+
+class MaskStrkoe:
+
+ def __init__(self):
+ pass
+
+ @classmethod
+ def INPUT_TYPES(self):
+
+ return {
+ "required": {
+ "mask": ("MASK", ), #
+ "invert_mask": ("BOOLEAN", {"default": True}), # �转mask
+ "stroke_grow": ("INT", {"default": 0, "min": -999, "max": 999, "step": 1}), # 收缩值
+ "stroke_width": ("INT", {"default": 20, "min": 0, "max": 999, "step": 1}), # æ‰©å¼ å€¼
+ "blur": ("INT", {"default": 6, "min": 0, "max": 100, "step": 1}), # 模糊
+ },
+ "optional": {
+ }
+ }
+
+ RETURN_TYPES = ("MASK",)
+ RETURN_NAMES = ("mask",)
+ FUNCTION = 'mask_stroke'
+ CATEGORY = '😺dzNodes/LayerMask'
+ OUTPUT_NODE = True
+
+ def mask_stroke(self, mask, invert_mask, stroke_grow, stroke_width, blur,):
+
+ if invert_mask:
+ mask = 1 - mask
+ _mask = mask2image(mask).convert('L')
+ grow_offset = int(stroke_width / 2)
+ inner_stroke = stroke_grow - grow_offset
+ outer_stroke = inner_stroke + stroke_width
+ inner_mask = expand_mask(image2mask(_mask), inner_stroke, blur)
+ outer_mask = expand_mask(image2mask(_mask), outer_stroke, blur)
+ stroke_mask = subtract_mask(outer_mask, inner_mask)
+
+ return (stroke_mask,)
+
+NODE_CLASS_MAPPINGS = {
+ "LayerMask: MaskStrkoe": MaskStrkoe
+}
+
+NODE_DISPLAY_NAME_MAPPINGS = {
+ "LayerMask: MaskStrkoe": "LayerMask: MaskStrkoe"
+}
\ No newline at end of file
diff --git a/py/restore_crop_box.py b/py/restore_crop_box.py
new file mode 100644
index 0000000..bcb67a7
--- /dev/null
+++ b/py/restore_crop_box.py
@@ -0,0 +1,52 @@
+from .imagefunc import *
+
+class RestoreCropBox:
+
+ def __init__(self):
+ pass
+
+ @classmethod
+ def INPUT_TYPES(self):
+
+ return {
+ "required": {
+ "background_image": ("IMAGE", ),
+ "croped_image": ("IMAGE",),
+ "invert_mask": ("BOOLEAN", {"default": False}), # �转mask#
+ "crop_box": ("BOX",),
+ },
+ "optional": {
+ "croped_mask": ("MASK",),
+ }
+ }
+
+ RETURN_TYPES = ("IMAGE", "MASK", )
+ RETURN_NAMES = ("image", "mask", )
+ FUNCTION = 'restore_crop_box'
+ CATEGORY = '😺dzNodes/LayerUtility'
+ OUTPUT_NODE = True
+
+ def restore_crop_box(self, background_image, croped_image, invert_mask, crop_box,
+ croped_mask=None
+ ):
+
+ _canvas = tensor2pil(background_image).convert('RGB')
+ _layer = tensor2pil(croped_image).convert('RGB')
+ _mask = Image.new('L', size=_layer.size, color='white')
+ if croped_mask is not None:
+ if invert_mask:
+ croped_mask = 1 - croped_mask
+ _mask = mask2image(croped_mask).convert('L')
+ ret_mask = Image.new('L', size=_canvas.size, color='black')
+ _canvas.paste(_layer, box=tuple(crop_box), mask=_mask)
+ ret_mask.paste(_mask, box=tuple(crop_box))
+
+ return (pil2tensor(_canvas), image2mask(ret_mask),)
+
+NODE_CLASS_MAPPINGS = {
+ "LayerUtility: RestoreCropBox": RestoreCropBox
+}
+
+NODE_DISPLAY_NAME_MAPPINGS = {
+ "LayerUtility: RestoreCropBox": "LayerUtility: RestoreCropBox"
+}
\ No newline at end of file
diff --git a/py/stroke.py b/py/stroke.py
index ff50d3b..1ad7374 100644
--- a/py/stroke.py
+++ b/py/stroke.py
@@ -58,11 +58,11 @@ class Stroke:
outer_stroke = inner_stroke + stroke_width
inner_mask = expand_mask(image2mask(_mask), inner_stroke, blur)
outer_mask = expand_mask(image2mask(_mask), outer_stroke, blur)
- strock_mask = subtract_mask(outer_mask, inner_mask)
+ stroke_mask = subtract_mask(outer_mask, inner_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))
+ _canvas.paste(blend_image, mask=tensor2pil(stroke_mask))
ret_image = _canvas
log('Stroke Processed.')
return (pil2tensor(ret_image),)
diff --git a/workflow/crop_by_mask_&_restore_crop_box_example.json b/workflow/crop_by_mask_&_restore_crop_box_example.json
new file mode 100644
index 0000000..358a3b2
--- /dev/null
+++ b/workflow/crop_by_mask_&_restore_crop_box_example.json
@@ -0,0 +1,472 @@
+{
+ "last_node_id": 34,
+ "last_link_id": 75,
+ "nodes": [
+ {
+ "id": 27,
+ "type": "PreviewImage",
+ "pos": [
+ 2360,
+ 430
+ ],
+ "size": [
+ 516.973361206055,
+ 302.7666168212892
+ ],
+ "flags": {},
+ "order": 7,
+ "mode": 0,
+ "inputs": [
+ {
+ "name": "images",
+ "type": "IMAGE",
+ "link": 56
+ }
+ ],
+ "properties": {
+ "Node name for S&R": "PreviewImage"
+ }
+ },
+ {
+ "id": 28,
+ "type": "LayerMask: MaskPreview",
+ "pos": [
+ 2360,
+ 780
+ ],
+ "size": [
+ 511.933361206055,
+ 312.2199255371095
+ ],
+ "flags": {},
+ "order": 8,
+ "mode": 0,
+ "inputs": [
+ {
+ "name": "mask",
+ "type": "MASK",
+ "link": 57
+ }
+ ],
+ "properties": {
+ "Node name for S&R": "LayerMask: MaskPreview"
+ }
+ },
+ {
+ "id": 4,
+ "type": "PreviewImage",
+ "pos": [
+ 1500,
+ 250
+ ],
+ "size": [
+ 385.7333923339845,
+ 246
+ ],
+ "flags": {},
+ "order": 3,
+ "mode": 0,
+ "inputs": [
+ {
+ "name": "images",
+ "type": "IMAGE",
+ "link": 64
+ }
+ ],
+ "properties": {
+ "Node name for S&R": "PreviewImage"
+ }
+ },
+ {
+ "id": 22,
+ "type": "LayerMask: MaskPreview",
+ "pos": [
+ 1920,
+ 250
+ ],
+ "size": [
+ 380.6532714843752,
+ 246
+ ],
+ "flags": {},
+ "order": 4,
+ "mode": 0,
+ "inputs": [
+ {
+ "name": "mask",
+ "type": "MASK",
+ "link": 65
+ }
+ ],
+ "properties": {
+ "Node name for S&R": "LayerMask: MaskPreview"
+ }
+ },
+ {
+ "id": 16,
+ "type": "Image Remove Background Rembg (mtb)",
+ "pos": [
+ 780,
+ 670
+ ],
+ "size": {
+ "0": 294,
+ "1": 230
+ },
+ "flags": {},
+ "order": 1,
+ "mode": 0,
+ "inputs": [
+ {
+ "name": "image",
+ "type": "IMAGE",
+ "link": 22
+ }
+ ],
+ "outputs": [
+ {
+ "name": "Image (rgba)",
+ "type": "IMAGE",
+ "links": null,
+ "shape": 3
+ },
+ {
+ "name": "Mask",
+ "type": "MASK",
+ "links": [
+ 59
+ ],
+ "shape": 3,
+ "slot_index": 1
+ },
+ {
+ "name": "Image",
+ "type": "IMAGE",
+ "links": [],
+ "shape": 3,
+ "slot_index": 2
+ }
+ ],
+ "properties": {
+ "Node name for S&R": "Image Remove Background Rembg (mtb)"
+ },
+ "widgets_values": [
+ false,
+ 240,
+ 10,
+ 10,
+ false,
+ "#000000"
+ ]
+ },
+ {
+ "id": 18,
+ "type": "PreviewImage",
+ "pos": [
+ 1490,
+ 810
+ ],
+ "size": [
+ 369.95327148437514,
+ 246
+ ],
+ "flags": {},
+ "order": 6,
+ "mode": 0,
+ "inputs": [
+ {
+ "name": "images",
+ "type": "IMAGE",
+ "link": 63
+ }
+ ],
+ "properties": {
+ "Node name for S&R": "PreviewImage"
+ }
+ },
+ {
+ "id": 29,
+ "type": "LayerUtility: CropByMask",
+ "pos": [
+ 1130,
+ 570
+ ],
+ "size": {
+ "0": 330,
+ "1": 238
+ },
+ "flags": {},
+ "order": 2,
+ "mode": 0,
+ "inputs": [
+ {
+ "name": "image",
+ "type": "IMAGE",
+ "link": 58
+ },
+ {
+ "name": "mask_for_crop",
+ "type": "MASK",
+ "link": 59
+ }
+ ],
+ "outputs": [
+ {
+ "name": "croped_image",
+ "type": "IMAGE",
+ "links": [
+ 64,
+ 75
+ ],
+ "shape": 3,
+ "slot_index": 0
+ },
+ {
+ "name": "croped_mask",
+ "type": "MASK",
+ "links": [
+ 61,
+ 65
+ ],
+ "shape": 3,
+ "slot_index": 1
+ },
+ {
+ "name": "crop_box",
+ "type": "BOX",
+ "links": [
+ 62
+ ],
+ "shape": 3,
+ "slot_index": 2
+ },
+ {
+ "name": "box_preview",
+ "type": "IMAGE",
+ "links": [
+ 63
+ ],
+ "shape": 3,
+ "slot_index": 3
+ }
+ ],
+ "properties": {
+ "Node name for S&R": "LayerUtility: CropByMask"
+ },
+ "widgets_values": [
+ false,
+ "min_bounding_rect",
+ 20,
+ 20,
+ 20,
+ 20
+ ]
+ },
+ {
+ "id": 26,
+ "type": "LayerUtility: RestoreCropBox",
+ "pos": [
+ 1950,
+ 560
+ ],
+ "size": {
+ "0": 315,
+ "1": 118
+ },
+ "flags": {},
+ "order": 5,
+ "mode": 0,
+ "inputs": [
+ {
+ "name": "background_image",
+ "type": "IMAGE",
+ "link": 67
+ },
+ {
+ "name": "croped_image",
+ "type": "IMAGE",
+ "link": 75
+ },
+ {
+ "name": "croped_mask",
+ "type": "MASK",
+ "link": 61
+ },
+ {
+ "name": "crop_box",
+ "type": "BOX",
+ "link": 62
+ }
+ ],
+ "outputs": [
+ {
+ "name": "image",
+ "type": "IMAGE",
+ "links": [
+ 56
+ ],
+ "shape": 3,
+ "slot_index": 0
+ },
+ {
+ "name": "mask",
+ "type": "MASK",
+ "links": [
+ 57
+ ],
+ "shape": 3,
+ "slot_index": 1
+ }
+ ],
+ "properties": {
+ "Node name for S&R": "LayerUtility: RestoreCropBox"
+ },
+ "widgets_values": [
+ false
+ ]
+ },
+ {
+ "id": 2,
+ "type": "LoadImage",
+ "pos": [
+ 410,
+ 400
+ ],
+ "size": {
+ "0": 315,
+ "1": 314
+ },
+ "flags": {},
+ "order": 0,
+ "mode": 0,
+ "outputs": [
+ {
+ "name": "IMAGE",
+ "type": "IMAGE",
+ "links": [
+ 22,
+ 58,
+ 67
+ ],
+ "shape": 3,
+ "slot_index": 0
+ },
+ {
+ "name": "MASK",
+ "type": "MASK",
+ "links": null,
+ "shape": 3
+ }
+ ],
+ "properties": {
+ "Node name for S&R": "LoadImage"
+ },
+ "widgets_values": [
+ "3840x2160car (13).jpg",
+ "image"
+ ]
+ }
+ ],
+ "links": [
+ [
+ 22,
+ 2,
+ 0,
+ 16,
+ 0,
+ "IMAGE"
+ ],
+ [
+ 56,
+ 26,
+ 0,
+ 27,
+ 0,
+ "IMAGE"
+ ],
+ [
+ 57,
+ 26,
+ 1,
+ 28,
+ 0,
+ "MASK"
+ ],
+ [
+ 58,
+ 2,
+ 0,
+ 29,
+ 0,
+ "IMAGE"
+ ],
+ [
+ 59,
+ 16,
+ 1,
+ 29,
+ 1,
+ "MASK"
+ ],
+ [
+ 61,
+ 29,
+ 1,
+ 26,
+ 2,
+ "MASK"
+ ],
+ [
+ 62,
+ 29,
+ 2,
+ 26,
+ 3,
+ "BOX"
+ ],
+ [
+ 63,
+ 29,
+ 3,
+ 18,
+ 0,
+ "IMAGE"
+ ],
+ [
+ 64,
+ 29,
+ 0,
+ 4,
+ 0,
+ "IMAGE"
+ ],
+ [
+ 65,
+ 29,
+ 1,
+ 22,
+ 0,
+ "MASK"
+ ],
+ [
+ 67,
+ 2,
+ 0,
+ 26,
+ 0,
+ "IMAGE"
+ ],
+ [
+ 75,
+ 29,
+ 0,
+ 26,
+ 1,
+ "IMAGE"
+ ]
+ ],
+ "groups": [],
+ "config": {},
+ "extra": {},
+ "version": 0.4
+}
\ No newline at end of file