diff --git a/README.MD b/README.MD
index f9ec258..48499d2 100644
--- a/README.MD
+++ b/README.MD
@@ -13,6 +13,7 @@ Nodes are divided into four groups according to their functions: LayerStyle, Lay
[䏿–‡è¯´æ˜Žç‚¹è¿™é‡Œ](./README_CN.MD)
## Update
+* Commit [ImageMaskScaleAs](#ImageMaskScaleAs) node to adjust the image or mask size based on the reference image.
* Commit [ImageScaleRestore](#ImageScaleRestore) node to work with CropByMask for local upscale and repair works.
* 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.
@@ -188,6 +189,7 @@ Node options:
* [note](#notes)
# LayerColor
+

### LUT Apply
@@ -437,6 +439,28 @@ Outputs:
* mask: If have mask input, the scaled mask will be output.
* original_size: The original size data of the image is used for subsequent node recovery.
+### ImageMaskScaleAs
+Scale the image or mask to the size of the reference image (or reference mask).
+
+
+Node options:
+
+* scale_as*: Reference size. It can be an image or a mask.
+* image: Image to be scaled. this option is optional input. if there is no input, a black image will be output.
+* mask: Mask to be scaled. this option is optional input. if there is no input, a black mask will be output.
+* fit: Scale aspect ratio mode. when the width to height ratio of the original image does not match the scaled size, there are three modes to choose from,
+The _letterbox_ mode retains the complete frame and fills in the blank spaces with black;
+The _crop_ mode retains the complete short edge, and any excess of the long edge will be cut off;
+The _fill_ mode does not maintain frame ratio and fills the screen with width and height.
+* method: Scaling sampling methods, including lanczos, bicubic, hamming, bilinear, box, and nearest.
+
+*Only limited to input images and masks. forcing the integration of other types of inputs will result in node errors.
+
+Outputs:
+* image: If there is an image input, the scaled image will be output.
+* mask: If there is a mask input, the scaled mask will be output.
+* original_size: The original size data of the image is used for subsequent node recovery.
+
### ImageBlend
A simple node for composit layer image and background image, multiple blend modes are available for option, and transparency can be set.

diff --git a/README_CN.MD b/README_CN.MD
index 60087d5..7fee911 100644
--- a/README_CN.MD
+++ b/README_CN.MD
@@ -10,6 +10,7 @@ ComfyUI
* [LayerFilter](#LayerFilter)½Úµã×éÌṩͼÏñЧ¹ûÂ˾µ¡£
## ¸üÐÂ˵Ã÷
+* Ìí¼Ó[ImageMaskScaleAs](#ImageMaskScaleAs) ½Úµã£¬¿É¸ù¾Ý²Î¿¼Í¼Æ¬µ÷ÕûͼÏñ»òÕÚÕÖ´óС¡£
* Ìí¼Ó[ImageScaleRestore](#ImageScaleRestore) ½Úµã£¬ÓÃÓÚÅäºÏCropByMask½øÐоֲ¿·Å´óÐÞ¸´¹¤×÷¡£
* Ìí¼Ó[CropByMask](#CropByMask) ºÍ [RestoreCropBox](#RestoreCropBox)½Úµã¡£´Ë¶þÕßÅäºÏ¿É½«Í¼Æ¬¾Ö²¿²ÃÇÐÖØ»æÈ»ºó»¹Ô¡£
* Ìí¼Ó[ColorAdapter](#ColorAdapter) ½Úµã£¬¿É×Ô¶¯µ÷ÕûͼƬɫµ÷¡£
@@ -182,6 +183,7 @@ ComfyUI
* [½Úµã×¢½â](#½Úµã×¢½â)
# LayerColor
+

### LUT Apply
@@ -427,6 +429,25 @@ mask
* mask: Èç¹ûÓÐmaskÊäÈ룬½«Êä³öËõ·ÅºóµÄmask¡£
* original_size: ͼÏñµÄÔʼ´óСÊý¾Ý£¬ÓÃÓÚºóÐø½Úµã½øÐлָ´¡£
+### ImageMaskScaleAs
+½«Í¼Ïñ»òÕÚÕÖËõ·Åµ½²Î¿¼Í¼Ïñ£¨»òÕÚÕÖ£©µÄ´óС¡£
+
+
+½ÚµãÑ¡Ïî˵Ã÷:
+
+* scale_as*: ²Î¿¼´óС¡£¿ÉÒÔÊÇͼÏñimage£¬Ò²¿ÉÒÔÊÇÕÚÕÖmask¡£
+* image: ´ýËõ·ÅµÄͼÏñ¡£´ËÑ¡ÏîΪ¿ÉÑ¡ÊäÈ룬Èç¹ûûÓÐÊäÈ뽫Êä³ö´¿ºÚͼƬ¡£
+* mask: ´ýËõ·ÅµÄÕÚÕÖ¡£´ËÑ¡ÏîΪ¿ÉÑ¡ÊäÈ룬Èç¹ûûÓÐÊäÈ뽫Êä³ö´¿ºÚÕÚÕÖ¡£
+* fit: Ëõ·Å»·ù¿í¸ß±Èģʽ¡£µ±ÔͼÓëËõ·Å³ß´ç»·ù¿í¸ß±ÈÀý²»Ò»ÖÂʱ£¬ÓÐ3ÖÖģʽ¿ÉÒÔÑ¡Ôñ, letterboxģʽ±£ÁôÍêÕûµÄ»·ù£¬¿Õ°×´¦ÓúÚÉ«²¹×㣻cropģʽ±£ÁôÍêÕûµÄ¶Ì±ß£¬³¤±ß³¬³ö²¿·Ö½«±»Çгý£»fillģʽ²»±£³Ö»·ù±ÈÀý£¬¿í¸ß¸÷×ÔÌîÂú»Ãæ¡£
+* method: Ëõ·ÅµÄ²ÉÑù·½·¨£¬°üÀ¨lanczos¡¢bicubic¡¢hamming¡¢bilinear¡¢boxºÍnearest¡£
+
+*½öÏÞÊäÈëimageºÍmask, Èç¹ûÇ¿ÖÆ½ÓÈëÆäËûÀàÐÍÊäÈ룬½«µ¼Ö½ڵã´íÎó¡£
+
+Êä³ö:
+* image: Èç¹ûÓÐimageÊäÈ룬½«Êä³öËõ·ÅºóµÄͼÏñ¡£
+* mask: Èç¹ûÓÐmaskÊäÈ룬½«Êä³öËõ·ÅºóµÄÕÚÕÖ¡£
+* original_size: ͼÏñµÄÔʼ´óСÊý¾Ý£¬ÓÃÓÚºóÐø½Úµã½øÐлָ´¡£
+
### ImageBlend
Ò»¸öÓÃÓںϳÉͼ²ãµÄ¼òµ¥½Úµã£¬Ìṩ¶àÖÖ»ìºÏģʽ¹©Ñ¡Ôñ£¬¿ÉÉèÖÃ͸Ã÷¶È¡£

diff --git a/image/image_mask_scale_as_example.png b/image/image_mask_scale_as_example.png
new file mode 100644
index 0000000..d53426b
Binary files /dev/null and b/image/image_mask_scale_as_example.png differ
diff --git a/image/image_mask_scale_as_node.png b/image/image_mask_scale_as_node.png
new file mode 100644
index 0000000..17ed6a5
Binary files /dev/null and b/image/image_mask_scale_as_node.png differ
diff --git a/image/layercolor_nodes.png b/image/layercolor_nodes.png
index 5941142..3e7604b 100644
Binary files a/image/layercolor_nodes.png and b/image/layercolor_nodes.png differ
diff --git a/image/layercolor_title.png b/image/layercolor_title.png
new file mode 100644
index 0000000..5941142
Binary files /dev/null and b/image/layercolor_title.png differ
diff --git a/image/layerutility_nodes.png b/image/layerutility_nodes.png
index 548e815..b6476c8 100644
Binary files a/image/layerutility_nodes.png and b/image/layerutility_nodes.png differ
diff --git a/py/image_mask_scale_as.py b/py/image_mask_scale_as.py
new file mode 100644
index 0000000..78317fe
--- /dev/null
+++ b/py/image_mask_scale_as.py
@@ -0,0 +1,74 @@
+from .imagefunc import *
+
+any = AnyType("*")
+
+class ImageMaskScaleAs:
+
+ def __init__(self):
+ pass
+
+ @classmethod
+ def INPUT_TYPES(self):
+
+ fit_mode = ['letterbox', 'crop', 'fill']
+ method_mode = ['lanczos', 'bicubic', 'hamming', 'bilinear', 'box', 'nearest']
+
+ return {
+ "required": {
+ "scale_as": (any, {}),
+ "fit": (fit_mode,),
+ "method": (method_mode,),
+ },
+ "optional": {
+ "image": ("IMAGE",), #
+ "mask": ("MASK",), #
+ }
+ }
+
+ RETURN_TYPES = ("IMAGE", "MASK", "BOX",)
+ RETURN_NAMES = ("image", "mask", "original_size")
+ FUNCTION = 'image_mask_scale_as'
+ CATEGORY = '😺dzNodes/LayerUtility'
+ OUTPUT_NODE = True
+
+ def image_mask_scale_as(self, scale_as, fit, method,
+ image=None, mask = None,
+ ):
+
+ _asimage = tensor2pil(scale_as)
+ target_width, target_height = _asimage.size
+ _mask = Image.new('L', size=_asimage.size, color='black')
+ _image = Image.new('RGB', size=_asimage.size, color='black')
+ orig_width = 4
+ orig_height = 4
+ if mask is not None:
+ _mask = tensor2pil(mask).convert('L')
+ orig_width, orig_height = _mask.size
+ if image is not None:
+ _image = tensor2pil(image).convert('RGB')
+ orig_width, orig_height = _image.size
+ resize_sampler = Image.LANCZOS
+ if method == "bicubic":
+ resize_sampler = Image.BICUBIC
+ elif method == "hamming":
+ resize_sampler = Image.HAMMING
+ elif method == "bilinear":
+ resize_sampler = Image.BILINEAR
+ elif method == "box":
+ resize_sampler = Image.BOX
+ elif method == "nearest":
+ resize_sampler = Image.NEAREST
+ if image is not None:
+ _image = fit_resize_image(_image, target_width, target_height, fit, resize_sampler)
+ if mask is not None:
+ _mask = fit_resize_image(_mask, target_width, target_height, fit, resize_sampler).convert('L')
+
+ return (pil2tensor(_image), image2mask(_mask), [orig_width, orig_height],)
+
+NODE_CLASS_MAPPINGS = {
+ "LayerUtility: ImageMaskScaleAs": ImageMaskScaleAs
+}
+
+NODE_DISPLAY_NAME_MAPPINGS = {
+ "LayerUtility: ImageMaskScaleAs": "LayerUtility: ImageMaskScaleAs"
+}
\ No newline at end of file
diff --git a/py/imagefunc.py b/py/imagefunc.py
index 6167c04..c378662 100644
--- a/py/imagefunc.py
+++ b/py/imagefunc.py
@@ -127,6 +127,33 @@ def motion_blur(image:Image, angle:int, blur:int) -> Image:
ret_image = cv22pil(blurred)
return ret_image
+def fit_resize_image(image:Image, target_width:int, target_height:int, fit:str, resize_sampler:str) -> Image:
+ image = image.convert('RGB')
+ orig_width, orig_height = image.size
+ if image is not None:
+ if fit == 'letterbox':
+ if orig_width / orig_height > target_width / target_height: # 更宽,上下留黑
+ fit_width = target_width
+ fit_height = int(target_width / orig_width * orig_height)
+ else: # 更瘦,左�留黑
+ fit_height = target_height
+ fit_width = int(target_height / orig_height * orig_width)
+ fit_image = image.resize((fit_width, fit_height), resize_sampler)
+ ret_image = Image.new('RGB', size=(target_width, target_height), color='black')
+ ret_image.paste(fit_image, box=((target_width - fit_width)//2, (target_height - fit_height)//2))
+ elif fit == 'crop':
+ if orig_width / orig_height > target_width / target_height: # 更宽,�左�
+ fit_width = int(orig_height * target_width / target_height)
+ fit_image = image.crop(
+ ((orig_width - fit_width)//2, 0, (orig_width - fit_width)//2 + fit_width, orig_height))
+ else: # 更瘦,�上下
+ fit_height = int(orig_width * target_height / target_width)
+ fit_image = image.crop(
+ (0, (orig_height-fit_height)//2, orig_width, (orig_height-fit_height)//2 + fit_height))
+ ret_image = fit_image.resize((target_width, target_height), resize_sampler)
+ else:
+ ret_image = image.resize((target_width, target_height), resize_sampler)
+ return ret_image
def __rotate_expand(image:Image, angle:float, SSAA:int=0, method:str="lanczos") -> Image:
images = pil2tensor(image)
@@ -536,3 +563,8 @@ def has_letters(string:str) -> bool:
return True
else:
return False
+
+class AnyType(str):
+ """A special class that is always equal in not equal comparisons. Credit to pythongosssss"""
+ def __ne__(self, __value: object) -> bool:
+ return False
\ No newline at end of file
diff --git a/py/print_info.py b/py/print_info.py
index f76a724..797288c 100644
--- a/py/print_info.py
+++ b/py/print_info.py
@@ -1,11 +1,7 @@
import json
import torch
from .imagefunc import tensor2pil, log
-
-class AnyType(str):
- """A special class that is always equal in not equal comparisons. Credit to pythongosssss"""
- def __ne__(self, __value: object) -> bool:
- return False
+from .imagefunc import AnyType
any = AnyType("*")
diff --git a/workflow/image_mask_scale_as_example.json b/workflow/image_mask_scale_as_example.json
new file mode 100644
index 0000000..d691a8f
--- /dev/null
+++ b/workflow/image_mask_scale_as_example.json
@@ -0,0 +1,313 @@
+{
+ "last_node_id": 65,
+ "last_link_id": 146,
+ "nodes": [
+ {
+ "id": 42,
+ "type": "LoadImage",
+ "pos": [
+ 133,
+ 364
+ ],
+ "size": {
+ "0": 315,
+ "1": 314
+ },
+ "flags": {},
+ "order": 0,
+ "mode": 0,
+ "outputs": [
+ {
+ "name": "IMAGE",
+ "type": "IMAGE",
+ "links": [
+ 137
+ ],
+ "shape": 3,
+ "slot_index": 0
+ },
+ {
+ "name": "MASK",
+ "type": "MASK",
+ "links": [],
+ "shape": 3,
+ "slot_index": 1
+ }
+ ],
+ "properties": {
+ "Node name for S&R": "LoadImage"
+ },
+ "widgets_values": [
+ "D04QPvc (26).jpg",
+ "image"
+ ]
+ },
+ {
+ "id": 59,
+ "type": "LayerMask: MaskPreview",
+ "pos": [
+ 1543,
+ 510
+ ],
+ "size": {
+ "0": 359.6678771972656,
+ "1": 408.945556640625
+ },
+ "flags": {},
+ "order": 5,
+ "mode": 0,
+ "inputs": [
+ {
+ "name": "mask",
+ "type": "MASK",
+ "link": 142
+ }
+ ],
+ "properties": {
+ "Node name for S&R": "LayerMask: MaskPreview"
+ }
+ },
+ {
+ "id": 16,
+ "type": "Image Remove Background Rembg (mtb)",
+ "pos": [
+ 485,
+ 735
+ ],
+ "size": {
+ "0": 294,
+ "1": 230
+ },
+ "flags": {},
+ "order": 2,
+ "mode": 0,
+ "inputs": [
+ {
+ "name": "image",
+ "type": "IMAGE",
+ "link": 22
+ }
+ ],
+ "outputs": [
+ {
+ "name": "Image (rgba)",
+ "type": "IMAGE",
+ "links": null,
+ "shape": 3
+ },
+ {
+ "name": "Mask",
+ "type": "MASK",
+ "links": [
+ 145
+ ],
+ "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": 58,
+ "type": "PreviewImage",
+ "pos": [
+ 1143,
+ 506
+ ],
+ "size": {
+ "0": 372.0478820800781,
+ "1": 417.78558349609375
+ },
+ "flags": {},
+ "order": 4,
+ "mode": 0,
+ "inputs": [
+ {
+ "name": "images",
+ "type": "IMAGE",
+ "link": 140
+ }
+ ],
+ "properties": {
+ "Node name for S&R": "PreviewImage"
+ }
+ },
+ {
+ "id": 64,
+ "type": "LayerUtility: ImageMaskScaleAs",
+ "pos": [
+ 783,
+ 507
+ ],
+ "size": {
+ "0": 315,
+ "1": 122
+ },
+ "flags": {},
+ "order": 3,
+ "mode": 0,
+ "inputs": [
+ {
+ "name": "scale_as",
+ "type": "*",
+ "link": 137
+ },
+ {
+ "name": "image",
+ "type": "IMAGE",
+ "link": 144
+ },
+ {
+ "name": "mask",
+ "type": "MASK",
+ "link": 145
+ }
+ ],
+ "outputs": [
+ {
+ "name": "image",
+ "type": "IMAGE",
+ "links": [
+ 140
+ ],
+ "shape": 3,
+ "slot_index": 0
+ },
+ {
+ "name": "mask",
+ "type": "MASK",
+ "links": [
+ 142
+ ],
+ "shape": 3,
+ "slot_index": 1
+ },
+ {
+ "name": "original_size",
+ "type": "BOX",
+ "links": [],
+ "shape": 3,
+ "slot_index": 2
+ }
+ ],
+ "properties": {
+ "Node name for S&R": "LayerUtility: ImageMaskScaleAs"
+ },
+ "widgets_values": [
+ "letterbox",
+ "lanczos"
+ ]
+ },
+ {
+ "id": 2,
+ "type": "LoadImage",
+ "pos": [
+ 131,
+ 726
+ ],
+ "size": {
+ "0": 315,
+ "1": 314
+ },
+ "flags": {},
+ "order": 1,
+ "mode": 0,
+ "outputs": [
+ {
+ "name": "IMAGE",
+ "type": "IMAGE",
+ "links": [
+ 22,
+ 144
+ ],
+ "shape": 3,
+ "slot_index": 0
+ },
+ {
+ "name": "MASK",
+ "type": "MASK",
+ "links": null,
+ "shape": 3
+ }
+ ],
+ "properties": {
+ "Node name for S&R": "LoadImage"
+ },
+ "widgets_values": [
+ "512x512with_background (15).png",
+ "image"
+ ]
+ }
+ ],
+ "links": [
+ [
+ 22,
+ 2,
+ 0,
+ 16,
+ 0,
+ "IMAGE"
+ ],
+ [
+ 137,
+ 42,
+ 0,
+ 64,
+ 0,
+ "*"
+ ],
+ [
+ 140,
+ 64,
+ 0,
+ 58,
+ 0,
+ "IMAGE"
+ ],
+ [
+ 142,
+ 64,
+ 1,
+ 59,
+ 0,
+ "MASK"
+ ],
+ [
+ 144,
+ 2,
+ 0,
+ 64,
+ 1,
+ "IMAGE"
+ ],
+ [
+ 145,
+ 16,
+ 1,
+ 64,
+ 2,
+ "MASK"
+ ]
+ ],
+ "groups": [],
+ "config": {},
+ "extra": {},
+ "version": 0.4
+}
\ No newline at end of file