Files

244 lines
8.4 KiB
Python

# code from https://github.com/lrzjason/Comfyui-In-Context-Lora-Utils
import torch
import numpy as np
from PIL import Image
import cv2
from .imagefunc import log, fit_resize_image, tensor2pil, pil2tensor
def resize_img(img, resolution, interpolation=cv2.INTER_CUBIC):
# print(img)
# print(resolution)
return cv2.resize(img, resolution, interpolation=interpolation)
def create_image_from_color(width, height, color=(255, 255, 255)):
# OpenCV uses BGR, so convert hex color to BGR if necessary
if isinstance(color, str) and color.startswith('#'):
color = tuple(int(color[i:i + 2], 16) for i in (5, 3, 1))[::-1]
# Create a blank image with the specified color
blank_image = np.full((height, width, 3), color, dtype=np.uint8)
return blank_image
def fit_image(image, mask=None, output_length=1536, patch_mode="auto"):
image = image.detach().cpu().numpy()
if mask is not None:
mask = mask.detach().cpu().numpy()
base_length = int(output_length / 3 * 2)
half_length = int(output_length / 2)
image_height, image_width, _ = image.shape
target_width = int(half_length)
target_height = int(base_length)
if patch_mode == "auto":
if image_width > image_height:
patch_mode = "patch_bottom"
target_width = int(base_length)
target_height = int(half_length)
else:
patch_mode = "patch_right"
elif patch_mode == "patch_bottom":
target_width = int(base_length)
target_height = int(half_length)
# 等比例缩放并填充逻辑
scale_ratio = min(target_width / image_width, target_height / image_height)
# 计算缩放后的尺寸
new_width = int(image_width * scale_ratio)
new_height = int(image_height * scale_ratio)
# 缩放图片
image = resize_img(image, (new_width, new_height))
if mask is not None:
mask = resize_img(mask, (new_width, new_height), cv2.INTER_NEAREST_EXACT)
# 计算填充的差值
diff_x = target_width - new_width
diff_y = target_height - new_height
# 计算填充上下左右的像素
pad_x = diff_x // 2
pad_y = diff_y // 2
# 添加白色填充到图片,黑色填充到掩码
resized_image = cv2.copyMakeBorder(
image,
pad_y, diff_y - pad_y,
pad_x, diff_x - pad_x,
cv2.BORDER_CONSTANT, value=(255, 255, 255)
)
if mask is not None:
resized_mask = cv2.copyMakeBorder(
mask,
pad_y, diff_y - pad_y,
pad_x, diff_x - pad_x,
cv2.BORDER_CONSTANT, value=(0, 0, 0)
)
else:
resized_mask = torch.zeros((target_width, target_height))
return resized_image, resized_mask, target_width, target_height, patch_mode
def crop_and_scale_as(image:Image, size:tuple):
target_width, target_height = size
_image = Image.new('RGB', size=size, color='black')
ret_image = fit_resize_image(image, target_width, target_height, "crop", Image.LANCZOS)
return ret_image
class ICMask_Data:
def __init__(self, x_offset, y_offset, target_width, target_height, total_width, total_height, orig_width, orig_height):
self.x_offset = x_offset
self.y_offset = y_offset
self.target_width = target_width
self.target_height = target_height
self.total_width = total_width
self.total_height = total_height
self.orig_width = orig_width
self.orig_height = orig_height
class LS_ICMask:
def __init__(self):
self.NODE_NAME = 'IC_Mask'
@classmethod
def INPUT_TYPES(s):
return {
"required": {
"first_image": ("IMAGE",),
"patch_mode": (["auto", "patch_right", "patch_bottom"], {
"default": "auto",
}),
"output_length": ("INT", {
"default": 1536,
}),
"patch_color": (["#FF0000", "#00FF00", "#0000FF", "#FFFFFF"], {
"default": "#FFFFFF",
}),
},
"optional": {
"first_mask": ("MASK",),
"second_image": ("IMAGE",),
"second_mask": ("MASK",),
}
}
RETURN_TYPES = ("IMAGE", "MASK", "ICMASK_DATA",)
RETURN_NAMES = ("image", "mask", "icmask_data",)
FUNCTION = "ic_mask"
CATEGORY = '😺dzNodes/LayerUtility'
def ic_mask(self, first_image, patch_mode, output_length, patch_color, first_mask=None, second_image=None,
second_mask=None):
orig_width = 0
orig_height = 0
if output_length % 64 != 0:
output_length = output_length - (output_length % 64)
image1 = first_image[0]
if first_mask is None:
image1_mask = torch.zeros((image1.shape[0], image1.shape[1]))
else:
image1_mask = first_mask[0]
image1, image1_mask, target_width, target_height, patch_mode = fit_image(image1, image1_mask, output_length,
patch_mode)
if second_image is not None:
image2 = second_image[0]
if second_mask is None:
image2_mask = torch.zeros((image2.shape[0], image2.shape[1]))
else:
image2_mask = second_mask[0]
orig_width = image2.shape[1]
orig_height = image2.shape[0]
image2, image2_mask, _, _, _ = fit_image(image2, image2_mask, output_length, patch_mode)
else:
image2 = create_image_from_color(target_width, target_height, color=patch_color)
image2 = torch.from_numpy(image2)
if second_mask is None:
image2_mask = torch.zeros((image2.shape[0], image2.shape[1]))
else:
image2_mask = second_mask[0]
orig_width = image2.shape[1]
orig_height = image2.shape[0]
image2, image2_mask, _, _, _ = fit_image(image2, image2_mask, output_length)
min_y = 0
min_x = 0
if second_mask is None or np.all(image2_mask == 0):
image2_mask = torch.ones((image1.shape[0], image1.shape[1]))
if patch_mode == "patch_right":
concatenated_image = np.hstack((image1, image2))
concatenated_mask = np.hstack((image1_mask, image2_mask))
min_x = 50
else:
concatenated_image = np.vstack((image1, image2))
concatenated_mask = np.vstack((image1_mask, image2_mask))
min_y = 50
min_y = int(min_y / 100.0 * concatenated_image.shape[0])
min_x = int(min_x / 100.0 * concatenated_image.shape[1])
return_masks = torch.from_numpy(concatenated_mask)[None,]
concatenated_image = np.clip(255. * concatenated_image, 0, 255).astype(np.float32) / 255.0
concatenated_image = torch.from_numpy(concatenated_image)[None,]
return_images = concatenated_image
icmask_data = ICMask_Data(min_x, min_y, target_width, target_height, concatenated_image.shape[1],
concatenated_image.shape[0], orig_width, orig_height)
return (return_images, return_masks, icmask_data)
class LS_ICMask_CropBack:
def __init__(self):
self.NODE_NAME = 'IC_Mask_Crop_Back'
@classmethod
def INPUT_TYPES(s):
return {"required": { "image": ("IMAGE",),
"icmask_data": ("ICMASK_DATA",),
}}
RETURN_TYPES = ("IMAGE",)
FUNCTION = "crop_back"
CATEGORY = '😺dzNodes/LayerUtility'
def crop_back(self, image, icmask_data):
width = icmask_data.target_width
height = icmask_data.target_height
x = icmask_data.x_offset
y = icmask_data.y_offset
orig_width = icmask_data.orig_width
orig_height = icmask_data.orig_height
x = min(x, image.shape[2] - 1)
y = min(y, image.shape[1] - 1)
to_x = width + x
to_y = height + y
img = image[:,y:to_y, x:to_x, :]
pil_image = tensor2pil(img)
ret_image = crop_and_scale_as(pil_image, (orig_width, orig_height))
return (pil2tensor(ret_image,),)
NODE_CLASS_MAPPINGS = {
"LayerUtility: ICMask": LS_ICMask,
"LayerUtility: ICMaskCropBack": LS_ICMask_CropBack,
}
NODE_DISPLAY_NAME_MAPPINGS = {
"LayerUtility: ICMask": "LayerUtility: IC Mask",
"LayerUtility: ICMaskCropBack": "LayerUtility: IC Mask Crop Back",
}