Commit ImageMaskScaleAs node
@@ -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)
|
||||
|
||||
# <a id="table1">LayerColor</a>
|
||||

|
||||

|
||||
|
||||
### <a id="table1">LUT</a> 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.
|
||||
|
||||
### <a id="table1">ImageMaskScaleAs</a>
|
||||
Scale the image or mask to the size of the reference image (or reference mask).
|
||||

|
||||
|
||||
Node options:
|
||||

|
||||
* scale_as<sup>*</sup>: 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.
|
||||
|
||||
<sup>*</sup>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.
|
||||
|
||||
### <a id="table1">ImageBlend</a>
|
||||
A simple node for composit layer image and background image, multiple blend modes are available for option, and transparency can be set.
|
||||

|
||||
|
||||
@@ -10,6 +10,7 @@ ComfyUI
|
||||
* [LayerFilter](#LayerFilter)节点组提供图像效果滤镜。
|
||||
|
||||
## 更新说明
|
||||
* 添加[ImageMaskScaleAs](#ImageMaskScaleAs) 节点,可根据参考图片调整图像或遮罩大小。
|
||||
* 添加[ImageScaleRestore](#ImageScaleRestore) 节点,用于配合CropByMask进行局部放大修复工作。
|
||||
* 添加[CropByMask](#CropByMask) 和 [RestoreCropBox](#RestoreCropBox)节点。此二者配合可将图片局部裁切重绘然后还原。
|
||||
* 添加[ColorAdapter](#ColorAdapter) 节点,可自动调整图片色调。
|
||||
@@ -182,6 +183,7 @@ ComfyUI
|
||||
* [节点注解](#节点注解)
|
||||
|
||||
# <a id="table1">LayerColor</a>
|
||||

|
||||

|
||||
|
||||
### <a id="table1">LUT</a> Apply
|
||||
@@ -427,6 +429,25 @@ mask
|
||||
* mask: 如果有mask输入,将输出缩放后的mask。
|
||||
* original_size: 图像的原始大小数据,用于后续节点进行恢复。
|
||||
|
||||
### <a id="table1">ImageMaskScaleAs</a>
|
||||
将图像或遮罩缩放到参考图像(或遮罩)的大小。
|
||||

|
||||
|
||||
节点选项说明:
|
||||

|
||||
* scale_as<sup>*</sup>: 参考大小。可以是图像image,也可以是遮罩mask。
|
||||
* image: 待缩放的图像。此选项为可选输入,如果没有输入将输出纯黑图片。
|
||||
* mask: 待缩放的遮罩。此选项为可选输入,如果没有输入将输出纯黑遮罩。
|
||||
* fit: 缩放画幅宽高比模式。当原图与缩放尺寸画幅宽高比例不一致时,有3种模式可以选择, letterbox模式保留完整的画幅,空白处用黑色补足;crop模式保留完整的短边,长边超出部分将被切除;fill模式不保持画幅比例,宽高各自填满画面。
|
||||
* method: 缩放的采样方法,包括lanczos、bicubic、hamming、bilinear、box和nearest。
|
||||
|
||||
<sup>*</sup>仅限输入image和mask, 如果强制接入其他类型输入,将导致节点错误。
|
||||
|
||||
输出:
|
||||
* image: 如果有image输入,将输出缩放后的图像。
|
||||
* mask: 如果有mask输入,将输出缩放后的遮罩。
|
||||
* original_size: 图像的原始大小数据,用于后续节点进行恢复。
|
||||
|
||||
### <a id="table1">ImageBlend</a>
|
||||
一个用于合成图层的简单节点,提供多种混合模式供选择,可设置透明度。
|
||||

|
||||
|
||||
|
After Width: | Height: | Size: 1.1 MiB |
|
After Width: | Height: | Size: 135 KiB |
|
Before Width: | Height: | Size: 2.3 MiB After Width: | Height: | Size: 230 KiB |
|
After Width: | Height: | Size: 2.3 MiB |
|
Before Width: | Height: | Size: 505 KiB After Width: | Height: | Size: 595 KiB |
@@ -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"
|
||||
}
|
||||
@@ -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
|
||||
@@ -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("*")
|
||||
|
||||
|
||||
@@ -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
|
||||
}
|
||||