3151 lines
93 KiB
Python
3151 lines
93 KiB
Python
import numpy as np
|
||
import requests
|
||
import torch
|
||
# from PIL import Image, ImageDraw
|
||
from PIL import Image, ImageOps,ImageFilter,ImageEnhance,ImageDraw,ImageSequence, ImageFont
|
||
from PIL.PngImagePlugin import PngInfo
|
||
import base64,os,random
|
||
from io import BytesIO
|
||
import folder_paths
|
||
import json,io
|
||
import comfy.utils
|
||
from comfy.cli_args import args
|
||
import cv2
|
||
import string
|
||
import math,glob
|
||
from .Watcher import FolderWatcher
|
||
import hashlib
|
||
|
||
|
||
|
||
# 将PIL图片转换为OpenCV格式
|
||
def pil_to_opencv(image):
|
||
open_cv_image = cv2.cvtColor(np.array(image), cv2.COLOR_RGB2BGR)
|
||
return open_cv_image
|
||
|
||
# 将OpenCV格式图片转换为PIL格式
|
||
def opencv_to_pil(image):
|
||
pil_image = Image.fromarray(cv2.cvtColor(image, cv2.COLOR_BGR2RGB))
|
||
return pil_image
|
||
|
||
|
||
def composite_images(foreground, background, mask,is_multiply_blend=False,position="overall"):
|
||
width,height=foreground.size
|
||
|
||
bg_image=background
|
||
|
||
bwidth,bheight=bg_image.size
|
||
|
||
# 按z-index排序
|
||
if position=="overall":
|
||
layer = {
|
||
"x":0,
|
||
"y":0,
|
||
"width":bwidth,
|
||
"height":bheight,
|
||
"z_index":88,
|
||
"scale_option":'overall',
|
||
"image":foreground,
|
||
"mask":mask
|
||
}
|
||
|
||
elif position=='center_bottom':
|
||
|
||
scale = int(bwidth*0.25) / width
|
||
new_height = int(height * scale)
|
||
|
||
layer = {
|
||
"x":int(bwidth*0.75*0.5),
|
||
"y":bheight-new_height-24,
|
||
"width":int(bwidth*0.25),
|
||
"height":int(bheight*0.25),
|
||
"z_index":88,
|
||
"scale_option":'width',
|
||
"image":foreground,
|
||
"mask":mask
|
||
}
|
||
|
||
elif position=='right_bottom':
|
||
|
||
scale = int(bwidth*0.25) / width
|
||
new_height = int(height * scale)
|
||
|
||
layer = {
|
||
"x":bwidth-int(bwidth*0.25)-24,
|
||
"y":bheight-new_height-24,
|
||
"width":int(bwidth*0.25),
|
||
"height":int(bheight*0.25),
|
||
"z_index":88,
|
||
"scale_option":'width',
|
||
"image":foreground,
|
||
"mask":mask
|
||
}
|
||
|
||
|
||
elif position=='center_top':
|
||
|
||
scale = int(bwidth*0.25) / width
|
||
new_height = int(height * scale)
|
||
|
||
layer = {
|
||
"x":int( bwidth*0.75*0.5),
|
||
"y":24,
|
||
"width":int(bwidth*0.25),
|
||
"height":int(bheight*0.25),
|
||
"z_index":88,
|
||
"scale_option":'width',
|
||
"image":foreground,
|
||
"mask":mask
|
||
}
|
||
|
||
elif position=='right_top':
|
||
|
||
scale = int(bwidth*0.25) / width
|
||
new_height = int(height * scale)
|
||
|
||
layer = {
|
||
"x":bwidth-int(bwidth*0.25)-24,
|
||
"y":24,
|
||
"width":int(bwidth*0.25),
|
||
"height":int(bheight*0.25),
|
||
"z_index":88,
|
||
"scale_option":'width',
|
||
"image":foreground,
|
||
"mask":mask
|
||
}
|
||
elif position=='left_top':
|
||
|
||
scale = int(bwidth*0.25) / width
|
||
new_height = int(height * scale)
|
||
|
||
layer = {
|
||
"x":24,
|
||
"y":24,
|
||
"width":int(bwidth*0.25),
|
||
"height":int(bheight*0.25),
|
||
"z_index":88,
|
||
"scale_option":'width',
|
||
"image":foreground,
|
||
"mask":mask
|
||
}
|
||
elif position=='left_bottom':
|
||
|
||
scale = int(bwidth*0.25) / width
|
||
new_height = int(height * scale)
|
||
|
||
layer = {
|
||
"x":24,
|
||
"y":bheight-new_height-24,
|
||
"width":int(bwidth*0.25),
|
||
"height":int(bheight*0.25),
|
||
"z_index":88,
|
||
"scale_option":'width',
|
||
"image":foreground,
|
||
"mask":mask
|
||
}
|
||
|
||
# width, height = bg_image.size
|
||
|
||
layer_image=layer['image']
|
||
layer_mask=layer['mask']
|
||
|
||
bg_image=merge_images(bg_image,
|
||
layer_image,
|
||
layer_mask,
|
||
layer['x'],
|
||
layer['y'],
|
||
layer['width'],
|
||
layer['height'],
|
||
layer['scale_option'],
|
||
is_multiply_blend )
|
||
|
||
bg_image=bg_image.convert('RGB')
|
||
|
||
return bg_image
|
||
|
||
|
||
def count_files_in_directory(directory):
|
||
file_count = 0
|
||
for _, _, files in os.walk(directory):
|
||
file_count += len(files)
|
||
return file_count
|
||
|
||
def save_json_to_file(data, file_path):
|
||
with open(file_path, 'w') as file:
|
||
json.dump(data, file)
|
||
|
||
def draw_rectangle(image, grid, color,width):
|
||
x, y, w, h = grid
|
||
draw = ImageDraw.Draw(image)
|
||
draw.rectangle([(x, y), (x+w, y+h)], outline=color,width=width)
|
||
|
||
def generate_random_string(length):
|
||
letters = string.ascii_letters + string.digits
|
||
return ''.join(random.choice(letters) for _ in range(length))
|
||
|
||
def padding_rectangle(grid, padding):
|
||
x, y, w, h = grid
|
||
x -= padding
|
||
y -= padding
|
||
w += 2 * padding
|
||
h += 2 * padding
|
||
return (x, y, w, h)
|
||
|
||
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
|
||
|
||
any_type = AnyType("*")
|
||
|
||
|
||
FONT_PATH= os.path.abspath(os.path.join(os.path.dirname(__file__),'../assets/王汉宗颜楷体繁.ttf'))
|
||
|
||
MAX_RESOLUTION=8192
|
||
|
||
# Tensor to PIL
|
||
def tensor2pil(image):
|
||
return Image.fromarray(np.clip(255. * image.cpu().numpy().squeeze(), 0, 255).astype(np.uint8))
|
||
|
||
# Convert PIL to Tensor
|
||
def pil2tensor(image):
|
||
return torch.from_numpy(np.array(image).astype(np.float32) / 255.0).unsqueeze(0)
|
||
|
||
|
||
# 颜色迁移
|
||
# Color-Transfer-between-Images https://github.com/chia56028/Color-Transfer-between-Images/blob/master/color_transfer.py
|
||
|
||
def get_mean_and_std(x):
|
||
x_mean, x_std = cv2.meanStdDev(x)
|
||
x_mean = np.hstack(np.around(x_mean,2))
|
||
x_std = np.hstack(np.around(x_std,2))
|
||
return x_mean, x_std
|
||
|
||
def color_transfer(source,target):
|
||
# sources = ['s1','s2','s3','s4','s5','s6']
|
||
# targets = ['t1','t2','t3','t4','t5','t6']
|
||
|
||
# 将PIL的Image类型转换为OpenCV的numpy数组
|
||
source = cv2.cvtColor(np.array(source), cv2.COLOR_RGB2LAB)
|
||
target = cv2.cvtColor(np.array(target), cv2.COLOR_RGB2LAB)
|
||
|
||
s_mean, s_std = get_mean_and_std(source)
|
||
t_mean, t_std = get_mean_and_std(target)
|
||
|
||
height, width, channel = source.shape
|
||
|
||
for i in range(0,height):
|
||
for j in range(0,width):
|
||
for k in range(0,channel):
|
||
x = source[i,j,k]
|
||
x = ((x-s_mean[k])*(t_std[k]/s_std[k]))+t_mean[k]
|
||
# round or +0.5
|
||
x = round(x)
|
||
# boundary check
|
||
x = 0 if x<0 else x
|
||
x = 255 if x>255 else x
|
||
source[i,j,k] = x
|
||
|
||
source = cv2.cvtColor(source,cv2.COLOR_LAB2RGB)
|
||
|
||
# 创建PIL图像对象
|
||
image_pil = Image.fromarray(source)
|
||
|
||
return image_pil
|
||
|
||
|
||
|
||
# 组合
|
||
def create_big_image(image_folder, image_count):
|
||
# 计算行数和列数
|
||
rows = math.ceil(math.sqrt(image_count))
|
||
cols = math.ceil(image_count / rows)
|
||
|
||
# 获取每个小图的尺寸
|
||
small_width = 100
|
||
small_height = 100
|
||
|
||
# 计算大图的尺寸
|
||
big_width = small_width * cols
|
||
big_height = small_height * rows
|
||
|
||
# 创建一个新的大图
|
||
big_image = Image.new('RGB', (big_width, big_height))
|
||
|
||
# 获取所有图片文件的路径
|
||
image_files = [f for f in os.listdir(image_folder) if os.path.isfile(os.path.join(image_folder, f))]
|
||
|
||
# 遍历所有图片文件
|
||
for i, image_file in enumerate(image_files):
|
||
# 打开图片并调整大小
|
||
image = Image.open(os.path.join(image_folder, image_file))
|
||
image = image.resize((small_width, small_height))
|
||
|
||
# 计算当前小图的位置
|
||
row = i // cols
|
||
col = i % cols
|
||
x = col * small_width
|
||
y = row * small_height
|
||
|
||
# 将小图粘贴到大图上
|
||
big_image.paste(image, (x, y))
|
||
|
||
return big_image
|
||
|
||
# # 调用方法并保存大图
|
||
# image_folder = 'path/to/folder/containing/images'
|
||
# image_count = 100
|
||
# big_image = create_big_image(image_folder, image_count)
|
||
# big_image.save('path/to/save/big_image.jpg')
|
||
|
||
|
||
|
||
def naive_cutout(img, mask,invert=True):
|
||
"""
|
||
Perform a simple cutout operation on an image using a mask.
|
||
|
||
This function takes a PIL image `img` and a PIL image `mask` as input.
|
||
It uses the mask to create a new image where the pixels from `img` are
|
||
cut out based on the mask.
|
||
|
||
The function returns a PIL image representing the cutout of the original
|
||
image using the mask.
|
||
"""
|
||
|
||
# img=img.convert("RGBA")
|
||
mask=mask.convert("RGBA")
|
||
|
||
empty = Image.new("RGBA", (mask.size), 0)
|
||
|
||
red, green, blue, alpha = mask.split()
|
||
|
||
mask = mask.convert('L')
|
||
# 黑白,要可调
|
||
if invert==True:
|
||
mask = mask.point(lambda x: 255 if x > 128 else 0)
|
||
else:
|
||
mask = mask.point(lambda x: 255 if x < 128 else 0)
|
||
|
||
new_image = Image.merge('RGBA', (red, green, blue, mask))
|
||
|
||
cutout = Image.composite(img.convert("RGBA"), empty,new_image)
|
||
|
||
return cutout
|
||
|
||
|
||
# (h,w)
|
||
# (1072, 512) -- > [(536, 512),(536, 512)]
|
||
def split_mask_by_new_height(masks,new_height):
|
||
split_masks = torch.split(masks, new_height, dim=0)
|
||
return split_masks
|
||
|
||
|
||
def doMask(image,mask,save_image=False,filename_prefix="Mixlab",invert="yes",save_mask=False,prompt=None, extra_pnginfo=None):
|
||
|
||
output_dir = (
|
||
folder_paths.get_output_directory()
|
||
if save_image
|
||
else folder_paths.get_temp_directory()
|
||
)
|
||
|
||
(
|
||
full_output_folder,
|
||
filename,
|
||
counter,
|
||
subfolder,
|
||
_,
|
||
) = folder_paths.get_save_image_path(filename_prefix, output_dir)
|
||
|
||
|
||
|
||
image=tensor2pil(image)
|
||
|
||
mask = mask.reshape((-1, 1, mask.shape[-2], mask.shape[-1])).movedim(1, -1).expand(-1, -1, -1, 3)
|
||
|
||
mask=tensor2pil(mask)
|
||
|
||
im=naive_cutout(image, mask,invert=='yes')
|
||
|
||
# format="image/png",
|
||
end="1" if invert=='yes' else ""
|
||
image_file = f"{filename}_{counter:05}_{end}.png"
|
||
mask_file = f"{filename}_{counter:05}_{end}_mask.png"
|
||
|
||
image_path=os.path.join(full_output_folder, image_file)
|
||
|
||
metadata = None
|
||
if not args.disable_metadata:
|
||
metadata = PngInfo()
|
||
if prompt is not None:
|
||
metadata.add_text("prompt", json.dumps(prompt))
|
||
if extra_pnginfo is not None:
|
||
for x in extra_pnginfo:
|
||
metadata.add_text(x, json.dumps(extra_pnginfo[x]))
|
||
|
||
im.save(image_path,pnginfo=metadata, compress_level=4)
|
||
|
||
result= [{
|
||
"filename": image_file,
|
||
"subfolder": subfolder,
|
||
"type": "output" if save_image else "temp"
|
||
}]
|
||
|
||
if save_mask:
|
||
mask_path=os.path.join(full_output_folder, mask_file)
|
||
mask.save(mask_path,
|
||
compress_level=4)
|
||
|
||
result.append({
|
||
"filename": mask_file,
|
||
"subfolder": subfolder,
|
||
"type": "output" if save_image else "temp"
|
||
})
|
||
|
||
|
||
return {
|
||
"result":result,
|
||
"image_path":image_path,
|
||
"im_tensor":pil2tensor(im.convert('RGB')),
|
||
"im_rgba_tensor":pil2tensor(im)
|
||
}
|
||
|
||
|
||
# 提取不透明部分
|
||
def get_not_transparent_area(image):
|
||
# 将PIL的Image类型转换为OpenCV的numpy数组
|
||
image_np = cv2.cvtColor(np.array(image), cv2.COLOR_RGBA2BGRA)
|
||
|
||
# 分离图像的RGBA通道
|
||
rgba = cv2.split(image_np)
|
||
alpha = rgba[3]
|
||
|
||
# 使用阈值将非透明部分转换为纯白色(255),透明部分转换为纯黑色(0)
|
||
_, mask = cv2.threshold(alpha, 1, 255, cv2.THRESH_BINARY)
|
||
|
||
# 获取非透明区域的边界框
|
||
coords = cv2.findNonZero(mask)
|
||
x, y, w, h = cv2.boundingRect(coords)
|
||
|
||
return (x, y, w, h)
|
||
|
||
|
||
|
||
|
||
def generate_gradient_image(width, height, start_color_hex, end_color_hex):
|
||
image = Image.new('RGBA', (width, height))
|
||
draw = ImageDraw.Draw(image)
|
||
|
||
if len(start_color_hex) == 7:
|
||
start_color_hex += "FF"
|
||
if len(end_color_hex) == 7:
|
||
end_color_hex += "FF"
|
||
|
||
start_color_hex = start_color_hex.lstrip("#")
|
||
end_color_hex = end_color_hex.lstrip("#")
|
||
|
||
# 将十六进制颜色代码转换为RGBA元组,包括透明度
|
||
start_color = tuple(int(start_color_hex[i:i+2], 16) for i in (0, 2, 4, 6))
|
||
end_color = tuple(int(end_color_hex[i:i+2], 16) for i in (0, 2, 4, 6))
|
||
|
||
for y in range(height):
|
||
# 计算当前行的颜色
|
||
r = int(start_color[0] + (end_color[0] - start_color[0]) * y / height)
|
||
g = int(start_color[1] + (end_color[1] - start_color[1]) * y / height)
|
||
b = int(start_color[2] + (end_color[2] - start_color[2]) * y / height)
|
||
a = int(start_color[3] + (end_color[3] - start_color[3]) * y / height)
|
||
|
||
# 绘制当前行的渐变色
|
||
draw.line((0, y, width, y), fill=(r, g, b, a))
|
||
|
||
# Create a mask from the image's alpha channel
|
||
mask = image.split()[-1]
|
||
|
||
# Convert the mask to a black and white image
|
||
mask = mask.convert('L')
|
||
|
||
image=image.convert('RGB')
|
||
|
||
return (image, mask)
|
||
|
||
# 示例用法
|
||
# width = 500
|
||
# height = 200
|
||
# start_color_hex = 'FF0000FF' # 红色,完全不透明
|
||
# end_color_hex = '0000FFFF' # 蓝色,完全不透明
|
||
|
||
# gradient_image = generate_gradient_image(width, height, start_color_hex, end_color_hex)
|
||
# gradient_image.save('gradient_image.png')
|
||
|
||
def rgb_to_hex(rgb):
|
||
r, g, b = rgb
|
||
hex_color = "#{:02x}{:02x}{:02x}".format(r, g, b)
|
||
return hex_color
|
||
|
||
|
||
# 读取不了分层
|
||
def load_psd(image):
|
||
layers=[]
|
||
print('load_psd',image.format)
|
||
if image.format=='PSD':
|
||
layers = [frame.copy() for frame in ImageSequence.Iterator(image)]
|
||
print('#PSD',len(layers))
|
||
else:
|
||
image = ImageOps.exif_transpose(image) #校对方向
|
||
layers.append(image)
|
||
return layers
|
||
|
||
|
||
def load_image(fp,white_bg=False):
|
||
im = Image.open(fp)
|
||
|
||
# ims=load_psd(im)
|
||
im = ImageOps.exif_transpose(im) #校对方向
|
||
ims=[im]
|
||
|
||
images=[]
|
||
|
||
for i in ims:
|
||
image = i.convert("RGB")
|
||
image = np.array(image).astype(np.float32) / 255.0
|
||
image = torch.from_numpy(image)[None,]
|
||
if 'A' in i.getbands():
|
||
mask = np.array(i.getchannel('A')).astype(np.float32) / 255.0
|
||
mask = 1. - torch.from_numpy(mask)
|
||
if white_bg==True:
|
||
nw = mask.unsqueeze(0).unsqueeze(-1).repeat(1, 1, 1, 3)
|
||
# 将mask的黑色部分对image进行白色处理
|
||
image[nw == 1] = 1.0
|
||
else:
|
||
mask = torch.zeros((64,64), dtype=torch.float32, device="cpu")
|
||
|
||
images.append({
|
||
"image":image,
|
||
"mask":mask
|
||
})
|
||
|
||
return images
|
||
|
||
def load_image_and_mask_from_url(url, timeout=10):
|
||
# Load the image from the URL
|
||
response = requests.get(url, timeout=timeout)
|
||
|
||
content_type = response.headers.get('Content-Type')
|
||
|
||
image = Image.open(BytesIO(response.content))
|
||
|
||
# Create a mask from the image's alpha channel
|
||
mask = image.convert('RGBA').split()[-1]
|
||
|
||
# Convert the mask to a black and white image
|
||
mask = mask.convert('L')
|
||
|
||
image=image.convert('RGB')
|
||
|
||
return (image, mask)
|
||
|
||
|
||
# 获取图片s
|
||
def get_images_filepath(f,white_bg=False):
|
||
images = []
|
||
|
||
if os.path.isdir(f):
|
||
for root, dirs, files in os.walk(f):
|
||
for file in files:
|
||
file_path = os.path.join(root, file)
|
||
file_name=os.path.basename(file_path)
|
||
try:
|
||
imgs=load_image(file_path,white_bg)
|
||
for img in imgs:
|
||
images.append({
|
||
"image":img['image'],
|
||
"mask":img['mask'],
|
||
"file_path":file_path,
|
||
"file_name":file_name,
|
||
"psd":len(imgs)>1
|
||
})
|
||
except:
|
||
print('非图片',file_path)
|
||
|
||
elif os.path.isfile(f):
|
||
try:
|
||
file_path = os.path.join(root, f)
|
||
file_name=os.path.basename(file_path)
|
||
imgs=load_image(f,white_bg)
|
||
for img in imgs:
|
||
images.append({
|
||
"image":img['image'],
|
||
"mask":img['mask'],
|
||
"file_path":file_path,
|
||
"file_name":file_name,
|
||
"psd":len(imgs)>1
|
||
})
|
||
except:
|
||
print('非图片',f)
|
||
else:
|
||
print('路径不存在或无效',f)
|
||
|
||
return images
|
||
|
||
|
||
|
||
def get_average_color_image(image):
|
||
# 打开图片
|
||
# image = Image.open(image_path)
|
||
|
||
# 将图片转换为RGB模式
|
||
image = image.convert("RGB")
|
||
|
||
# 获取图片的像素值
|
||
pixel_data = image.load()
|
||
|
||
# 初始化颜色总和和像素数量
|
||
total_red = 0
|
||
total_green = 0
|
||
total_blue = 0
|
||
pixel_count = 0
|
||
|
||
# 遍历图片的每个像素
|
||
for i in range(image.width):
|
||
for j in range(image.height):
|
||
# 获取像素的RGB值
|
||
r, g, b = pixel_data[i, j]
|
||
|
||
# 累加颜色值
|
||
total_red += r
|
||
total_green += g
|
||
total_blue += b
|
||
|
||
# 像素数量加1
|
||
pixel_count += 1
|
||
|
||
# 计算平均颜色值
|
||
average_red = int(total_red // pixel_count)
|
||
average_green = int(total_green // pixel_count)
|
||
average_blue = int(total_blue // pixel_count)
|
||
|
||
# 返回平均颜色值
|
||
|
||
im = Image.new("RGB", (image.width, image.height), (average_red, average_green, average_blue))
|
||
|
||
hex=rgb_to_hex((average_red, average_green, average_blue))
|
||
return (im,hex)
|
||
|
||
|
||
|
||
# 创建噪声图像
|
||
def create_noisy_image(width, height, mode="RGB", noise_level=128, background_color="#FFFFFF"):
|
||
|
||
background_rgb = tuple(int(background_color[i:i+2], 16) for i in (1, 3, 5))
|
||
image = Image.new(mode, (width, height), background_rgb)
|
||
|
||
# 创建空白图像
|
||
# image = Image.new(mode, (width, height))
|
||
|
||
# 遍历每个像素,并随机设置像素值
|
||
pixels = image.load()
|
||
for i in range(width):
|
||
for j in range(height):
|
||
# 随机生成噪声值
|
||
noise_r = random.randint(-noise_level, noise_level)
|
||
noise_g = random.randint(-noise_level, noise_level)
|
||
noise_b = random.randint(-noise_level, noise_level)
|
||
|
||
# 像素值加上噪声值,并限制在0-255的范围内
|
||
r = max(0, min(pixels[i, j][0] + noise_r, 255))
|
||
g = max(0, min(pixels[i, j][1] + noise_g, 255))
|
||
b = max(0, min(pixels[i, j][2] + noise_b, 255))
|
||
|
||
# 设置像素值
|
||
pixels[i, j] = (r, g, b)
|
||
|
||
image=image.convert(mode)
|
||
return image
|
||
|
||
|
||
# 对轮廓进行平滑
|
||
def smooth_edges(alpha_channel, smoothness):
|
||
|
||
# 将图像中的不透明物体提取出来
|
||
# alpha_channel = image_rgba[:, :, 3]
|
||
# 0:表示设定的阈值,即像素值小于或等于这个阈值的像素将被设置为0。
|
||
# 255:表示设置的最大值,即像素值大于阈值的像素将被设置为255。
|
||
_, mask = cv2.threshold(alpha_channel, 127, 255, cv2.THRESH_BINARY)
|
||
|
||
# 对提取的不透明物体进行边缘检测
|
||
# edges = cv2.Canny(mask, 100, 200)
|
||
|
||
|
||
# 将一个整数变成最接近的奇数
|
||
smoothness = smoothness if smoothness % 2 != 0 else smoothness + 1
|
||
# 进行光滑处理
|
||
smoothed_mask = cv2.GaussianBlur(mask, (smoothness, smoothness), 0)
|
||
|
||
return smoothed_mask
|
||
|
||
|
||
def enhance_depth_map(depth_map, contrast):
|
||
# 打开深度图像
|
||
# depth_map = Image.open(im)
|
||
|
||
# 创建对比度增强对象
|
||
enhancer = ImageEnhance.Contrast(depth_map)
|
||
|
||
# 对深度图像进行对比度增强
|
||
enhanced_depth_map = enhancer.enhance(contrast)
|
||
|
||
return enhanced_depth_map
|
||
|
||
|
||
def detect_faces(image):
|
||
# Read the image
|
||
# image = cv2.imread('people1.jpg')
|
||
image = cv2.cvtColor(np.array(image), cv2.COLOR_RGBA2BGRA)
|
||
|
||
# Convert the image to grayscale
|
||
gray = cv2.cvtColor(image, cv2.COLOR_BGR2GRAY)
|
||
|
||
# Load the pre-trained face detector
|
||
face_cascade = cv2.CascadeClassifier(cv2.data.haarcascades + 'haarcascade_frontalface_default.xml')
|
||
|
||
# Detect faces in the image
|
||
faces = face_cascade.detectMultiScale(gray, scaleFactor=1.05, minNeighbors=5, minSize=(50, 50))
|
||
|
||
# Create a black and white mask image
|
||
mask = np.zeros_like(gray)
|
||
|
||
# Loop over all detected faces
|
||
for (x, y, w, h) in faces:
|
||
# Draw rectangles around the detected faces
|
||
cv2.rectangle(image, (x, y), (x + w, y + h), (0, 255, 0), 2)
|
||
|
||
# Set the corresponding region in the mask image to white
|
||
mask[y:y+h, x:x+w] = 255
|
||
|
||
# Display the number of faces detected
|
||
print('Faces Detected:', len(faces))
|
||
|
||
mask = Image.fromarray(cv2.cvtColor(mask, cv2.COLOR_BGRA2RGBA))
|
||
|
||
return mask
|
||
|
||
|
||
def areaToMask(x,y,w,h,image):
|
||
# 创建一个与原图片大小相同的空白图片
|
||
mask = Image.new('L', image.size)
|
||
|
||
# 创建一个可用于绘制的对象
|
||
draw = ImageDraw.Draw(mask)
|
||
|
||
# 在空白图片上绘制一个矩形,表示要处理的区域
|
||
draw.rectangle((x, y, x+w, y+h), fill=255)
|
||
|
||
# 将处理区域之外的部分填充为黑色
|
||
draw.rectangle((0, 0, image.width, y), fill=0)
|
||
draw.rectangle((0, y+h, image.width, image.height), fill=0)
|
||
draw.rectangle((0, y, x, y+h), fill=0)
|
||
draw.rectangle((x+w, y, image.width, y+h), fill=0)
|
||
return mask
|
||
|
||
|
||
# def merge_images(bg_image, layer_image,mask, x, y, width, height):
|
||
# # 打开底图
|
||
# # bg_image = Image.open(background)
|
||
# bg_image=bg_image.convert("RGBA")
|
||
|
||
# # 打开图层
|
||
# layer_image=layer_image.convert("RGBA")
|
||
# layer_image = layer_image.resize((width, height))
|
||
# # mask = Image.new("L", layer_image.size, 255)
|
||
# mask = mask.resize((width, height))
|
||
# # 在底图上粘贴图层
|
||
# bg_image.paste(layer_image, (x, y), mask=mask)
|
||
|
||
# # 输出合成后的图片
|
||
# # bg_image.save("output.jpg")
|
||
# return bg_image
|
||
|
||
|
||
import cv2
|
||
import numpy as np
|
||
|
||
# ps的正片叠底
|
||
# 可以基于https://www.cnblogs.com/jsxyhelu/p/16947810.html ,用gpt写python代码
|
||
def multiply_blend(image1, image2):
|
||
image1=pil_to_opencv(image1)
|
||
image2=pil_to_opencv(image2)
|
||
# 将图像转换为浮点型
|
||
image1 = image1.astype(float)
|
||
image2 = image2.astype(float)
|
||
if image1.shape != image2.shape:
|
||
image1 = cv2.resize(image1, (image2.shape[1], image2.shape[0]))
|
||
|
||
# 归一化图像
|
||
image1 /= 255.0
|
||
image2 /= 255.0
|
||
|
||
# 正片叠底混合
|
||
blended = image1 * image2
|
||
|
||
# 将图像还原为8位无符号整数
|
||
blended = (blended * 255).astype(np.uint8)
|
||
|
||
blended=opencv_to_pil(blended)
|
||
return blended
|
||
|
||
# # 读取图像
|
||
# image1 = cv2.imread('1.png')
|
||
# image2 = cv2.imread('3.png')
|
||
|
||
# # 进行正片叠底混合
|
||
# result = multiply_blend(image1, image2)
|
||
|
||
# cv2.imwrite('result.jpg', result)
|
||
|
||
|
||
def merge_images(bg_image, layer_image, mask, x, y, width, height, scale_option,is_multiply_blend=False):
|
||
# 打开底图
|
||
bg_image = bg_image.convert("RGBA")
|
||
|
||
# 打开图层
|
||
layer_image = layer_image.convert("RGBA")
|
||
# layer_image = layer_image.resize((width, height))
|
||
|
||
# 根据缩放选项调整图像大小
|
||
if scale_option == "height":
|
||
# 按照高度比例缩放
|
||
original_width, original_height = layer_image.size
|
||
scale = height / original_height
|
||
new_width = int(original_width * scale)
|
||
layer_image = layer_image.resize((new_width, height))
|
||
elif scale_option == "width":
|
||
# 按照宽度比例缩放
|
||
original_width, original_height = layer_image.size
|
||
scale = width / original_width
|
||
new_height = int(original_height * scale)
|
||
layer_image = layer_image.resize((width, new_height))
|
||
elif scale_option == "overall":
|
||
# 整体缩放
|
||
layer_image = layer_image.resize((width, height))
|
||
|
||
elif scale_option == "longest":
|
||
original_width, original_height = layer_image.size
|
||
if original_width > original_height:
|
||
new_width=width
|
||
scale = width / original_width
|
||
new_height = int(original_height * scale)
|
||
x=0
|
||
y=int((height-new_height)*0.5)
|
||
else:
|
||
new_height=height
|
||
scale = height / original_height
|
||
new_width = int(original_height * scale)
|
||
x=int((width-new_width)*0.5)
|
||
y=0
|
||
# elif side == "shortest":
|
||
# if width < height:
|
||
#
|
||
# else:
|
||
#
|
||
|
||
|
||
# 调整mask的大小
|
||
nw, nh = layer_image.size
|
||
mask = mask.resize((nw, nh))
|
||
|
||
# # 分离出a通道
|
||
# r, g, b, alpha = layer_image.split()
|
||
# alpha = ImageOps.invert(alpha)
|
||
# # 创建一个新的RGB图像
|
||
# new_rgb_image = Image.new("RGB", layer_image.size)
|
||
# # 将透明通道粘贴到新的RGB图像上
|
||
# new_rgb_image.paste(layer_image, (0, 0), mask=alpha)
|
||
|
||
# new_rgb_image.paste(layer_image, (x, y), mask=mask)
|
||
# mask=new_rgb_image.convert('L')
|
||
# mask = ImageOps.invert(mask)
|
||
|
||
if is_multiply_blend:
|
||
bg_image_white=Image.new("RGB", bg_image.size,(255, 255, 255))
|
||
|
||
bg_image_white.paste(layer_image, (x, y), mask=mask)
|
||
bg_image=multiply_blend(bg_image_white,bg_image)
|
||
bg_image=bg_image.convert("RGBA")
|
||
else:
|
||
transparent_img = Image.new("RGBA",layer_image.size, (255, 255, 255, 0))
|
||
transparent_img.paste(layer_image,(0, 0), mask)
|
||
# transparent_img.save('test.png')
|
||
bg_image.paste(transparent_img, (x, y), transparent_img)
|
||
|
||
|
||
# 输出合成后的图片
|
||
return bg_image
|
||
|
||
|
||
def resize_2(img):
|
||
# 检查图像的高度是否是2的倍数,如果不是,则调整高度
|
||
if img.height % 2 != 0:
|
||
img = img.resize((img.width, img.height + 1))
|
||
|
||
# 检查图像的宽度是否是2的倍数,如果不是,则调整宽度
|
||
if img.width % 2 != 0:
|
||
img = img.resize((img.width + 1, img.height))
|
||
|
||
return img
|
||
|
||
# TODO 几个像素点的底
|
||
def resize_image(layer_image, scale_option, width, height,color="white"):
|
||
layer_image = layer_image.convert("RGB")
|
||
original_width, original_height = layer_image.size
|
||
|
||
if scale_option == "height":
|
||
# Scale image based on height
|
||
scale = height / original_height
|
||
new_width = int(original_width * scale)
|
||
layer_image = layer_image.resize((new_width, height))
|
||
|
||
elif scale_option == "width":
|
||
# Scale image based on width
|
||
scale = width / original_width
|
||
new_height = int(original_height * scale)
|
||
layer_image = layer_image.resize((width, new_height))
|
||
|
||
elif scale_option == "overall":
|
||
# Scale image overall
|
||
layer_image = layer_image.resize((width, height))
|
||
|
||
elif scale_option == "center":
|
||
# Scale image to minimum of width and height, center it, and fill extra area with black
|
||
scale = min(width / original_width, height / original_height)
|
||
new_width = math.ceil(original_width * scale)
|
||
new_height = math.ceil(original_height * scale)
|
||
resized_image = Image.new("RGB", (width, height), color=color)
|
||
resized_image.paste(layer_image.resize((new_width, new_height)), ((width - new_width) // 2, (height - new_height) // 2))
|
||
resized_image = resized_image.convert("RGB")
|
||
resized_image=resize_2(resized_image)
|
||
return resized_image
|
||
elif scale_option == "longest":
|
||
#暂时不用,
|
||
if original_width > original_height:
|
||
new_width=width
|
||
scale = width / original_width
|
||
new_height = int(original_height * scale)
|
||
x=0
|
||
y=int((new_height-height)*0.5)
|
||
resized_image = Image.new("RGB", (new_width, new_height), color=color)
|
||
resized_image.paste(layer_image.resize((new_width, new_height)), (x,y))
|
||
resized_image = resized_image.convert("RGB")
|
||
resized_image=resize_2(resized_image)
|
||
return resized_image
|
||
else:
|
||
new_height=height
|
||
scale = height / original_height
|
||
new_width = int(original_height * scale)
|
||
x=int((new_width-width)*0.5)
|
||
y=0
|
||
resized_image = Image.new("RGB", (new_width, new_height), color=color)
|
||
resized_image.paste(layer_image.resize((new_width, new_height)), (x,y))
|
||
resized_image = resized_image.convert("RGB")
|
||
resized_image=resize_2(resized_image)
|
||
return resized_image
|
||
|
||
|
||
layer_image=resize_2(layer_image)
|
||
return layer_image
|
||
|
||
|
||
|
||
|
||
|
||
|
||
# def generate_text_image(text_list, font_path, font_size, text_color, vertical=True, spacing=0):
|
||
# # Load Chinese font
|
||
# font = ImageFont.truetype(font_path, font_size)
|
||
|
||
# # Calculate image size based on the number of characters and orientation
|
||
# if vertical:
|
||
# width = font_size + 100
|
||
# height = font_size * len(text_list) + (len(text_list) - 1) * spacing + 100
|
||
# else:
|
||
# width = font_size * len(text_list) + (len(text_list) - 1) * spacing + 100
|
||
# height = font_size + 100
|
||
|
||
# # Create a blank image
|
||
# image = Image.new('RGBA', (width, height), (255, 255, 255,0))
|
||
# draw = ImageDraw.Draw(image)
|
||
|
||
# # Draw text
|
||
# if vertical:
|
||
# for i, char in enumerate(text_list):
|
||
# char_position = (50, 50 + i * font_size)
|
||
# draw.text(char_position, char, font=font, fill=text_color)
|
||
# else:
|
||
# for i, char in enumerate(text_list):
|
||
# char_position = (50 + i * (font_size + spacing), 50)
|
||
# draw.text(char_position, char, font=font, fill=text_color)
|
||
|
||
# # Save the image
|
||
# # image.save(output_image_path)
|
||
|
||
# # 分离alpha通道
|
||
# alpha_channel = image.split()[3]
|
||
|
||
# # 创建一个只有alpha通道的新图像
|
||
# alpha_image = Image.new('L', image.size)
|
||
# alpha_image.putdata(alpha_channel.getdata())
|
||
|
||
# image=image.convert('RGB')
|
||
|
||
# return (image,alpha_image)
|
||
def generate_text_image(text, font_path, font_size, text_color, vertical=True, stroke=False, stroke_color=(0, 0, 0), stroke_width=1, spacing=0):
|
||
# Split text into lines based on line breaks
|
||
lines = text.split("\n")
|
||
|
||
# 1. Determine layout direction
|
||
if vertical:
|
||
layout = "vertical"
|
||
else:
|
||
layout = "horizontal"
|
||
|
||
# 2. Calculate absolute coordinates for each character
|
||
char_coordinates = []
|
||
if layout == "vertical":
|
||
x = 0
|
||
y = 0
|
||
for i in range(len(lines)):
|
||
line = lines[i]
|
||
for char in line:
|
||
char_coordinates.append((x, y))
|
||
y += font_size + spacing
|
||
x += font_size + spacing
|
||
y = 0
|
||
else:
|
||
x = 0
|
||
y = 0
|
||
for line in lines:
|
||
for char in line:
|
||
char_coordinates.append((x, y))
|
||
x += font_size + spacing
|
||
y += font_size + spacing
|
||
x = 0
|
||
|
||
# 3. Calculate image width and height
|
||
if layout == "vertical":
|
||
width = (len(lines) * (font_size + spacing)) - spacing
|
||
height = ((len(max(lines, key=len)) + 1) * (font_size + spacing)) + spacing
|
||
else:
|
||
width = (len(max(lines, key=len)) * (font_size + spacing)) - spacing
|
||
height = ((len(lines) - 1) * (font_size + spacing)) + font_size
|
||
|
||
# 4. Draw each character on the image
|
||
image = Image.new('RGBA', (width, height), (255, 255, 255, 0))
|
||
draw = ImageDraw.Draw(image)
|
||
font = ImageFont.truetype(font_path, font_size)
|
||
|
||
index = 0
|
||
for i, line in enumerate(lines):
|
||
for j, char in enumerate(line):
|
||
x, y = char_coordinates[index]
|
||
|
||
if stroke:
|
||
draw.text((x-stroke_width, y), char, font=font, fill=stroke_color)
|
||
draw.text((x+stroke_width, y), char, font=font, fill=stroke_color)
|
||
draw.text((x, y-stroke_width), char, font=font, fill=stroke_color)
|
||
draw.text((x, y+stroke_width), char, font=font, fill=stroke_color)
|
||
|
||
draw.text((x, y), char, font=font, fill=text_color)
|
||
index += 1
|
||
|
||
# Separate alpha channel
|
||
alpha_channel = image.split()[3]
|
||
|
||
# Create a new image with only the alpha channel
|
||
alpha_image = Image.new('L', image.size)
|
||
alpha_image.putdata(alpha_channel.getdata())
|
||
|
||
image = image.convert('RGB')
|
||
|
||
return (image, alpha_image)
|
||
|
||
|
||
|
||
def base64_to_image(base64_string):
|
||
# 去除前缀
|
||
prefix, base64_data = base64_string.split(",", 1)
|
||
|
||
# 从base64字符串中解码图像数据
|
||
image_data = base64.b64decode(base64_data)
|
||
|
||
# 创建一个内存流对象
|
||
image_stream = io.BytesIO(image_data)
|
||
|
||
# 使用PIL的Image模块打开图像数据
|
||
image = Image.open(image_stream)
|
||
|
||
return image
|
||
|
||
|
||
def create_temp_file(image):
|
||
output_dir = folder_paths.get_temp_directory()
|
||
|
||
(
|
||
full_output_folder,
|
||
filename,
|
||
counter,
|
||
subfolder,
|
||
_,
|
||
) = folder_paths.get_save_image_path('material', output_dir)
|
||
|
||
|
||
image=tensor2pil(image)
|
||
|
||
image_file = f"{filename}_{counter:05}.png"
|
||
|
||
image_path=os.path.join(full_output_folder, image_file)
|
||
|
||
image.save(image_path,compress_level=4)
|
||
|
||
return [{
|
||
"filename": image_file,
|
||
"subfolder": subfolder,
|
||
"type": "temp"
|
||
}]
|
||
|
||
|
||
class SmoothMask:
|
||
@classmethod
|
||
def INPUT_TYPES(s):
|
||
return {
|
||
"required": {
|
||
"mask": ("MASK",),
|
||
"smoothness":("INT", {"default": 1,
|
||
"min":0,
|
||
"max": 150,
|
||
"step": 1,
|
||
"display": "slider"})
|
||
}
|
||
}
|
||
|
||
RETURN_TYPES = ('MASK',)
|
||
|
||
FUNCTION = "run"
|
||
|
||
CATEGORY = "♾️Mixlab/Mask"
|
||
|
||
INPUT_IS_LIST = False
|
||
|
||
OUTPUT_IS_LIST = (False,)
|
||
|
||
# 运行的函数
|
||
def run(self,mask,smoothness):
|
||
# result = mask.reshape((-1, 1, mask.shape[-2], mask.shape[-1])).movedim(1, -1).expand(-1, -1, -1, 3)
|
||
print('SmoothMask',mask.shape)
|
||
mask=tensor2pil(mask)
|
||
|
||
# 打开图像并将其转换为黑白图
|
||
# image = mask.convert('L')
|
||
|
||
# 应用羽化效果
|
||
feathered_image = mask.filter(ImageFilter.GaussianBlur(smoothness))
|
||
|
||
mask=pil2tensor(feathered_image)
|
||
|
||
return (mask,)
|
||
|
||
|
||
|
||
|
||
|
||
|
||
class SplitLongMask:
|
||
|
||
@classmethod
|
||
def INPUT_TYPES(s):
|
||
return {
|
||
"required": {
|
||
"long_mask": ("MASK",),
|
||
"count":("INT", {"default": 1, "min": 1, "max": 1024, "step": 1})
|
||
}
|
||
}
|
||
|
||
RETURN_TYPES = ('MASK',)
|
||
|
||
FUNCTION = "run"
|
||
|
||
CATEGORY = "♾️Mixlab/Mask"
|
||
|
||
OUTPUT_IS_LIST = (True,)
|
||
|
||
# 运行的函数
|
||
def run(self,long_mask,count):
|
||
masks=[]
|
||
nh=long_mask.shape[0]//count
|
||
|
||
if nh*count==long_mask.shape[0]:
|
||
masks=split_mask_by_new_height(long_mask,nh)
|
||
else:
|
||
masks=split_mask_by_new_height(long_mask,long_mask.shape[0])
|
||
|
||
return (masks,)
|
||
|
||
|
||
|
||
# 一个batch传进来 INPUT_IS_LIST = False
|
||
# mask始终会被拍平,([2, 568, 512]) -- > ([1136, 512])
|
||
# 原因是一个batch传来的
|
||
class TransparentImage:
|
||
@classmethod
|
||
def INPUT_TYPES(s):
|
||
return {
|
||
"required": {
|
||
"images": ("IMAGE",),
|
||
"masks": ("MASK",),
|
||
"invert": (["yes", "no"],),
|
||
"save": (["yes", "no"],),
|
||
},
|
||
"optional":{
|
||
"filename_prefix":("STRING", {"multiline": False,"default": "Mixlab_save"})
|
||
},
|
||
"hidden": {"prompt": "PROMPT", "extra_pnginfo": "EXTRA_PNGINFO"}
|
||
}
|
||
|
||
RETURN_TYPES = ('STRING','IMAGE','RGBA')
|
||
RETURN_NAMES = ("file_path","IMAGE","RGBA",)
|
||
|
||
OUTPUT_NODE = True
|
||
|
||
FUNCTION = "run"
|
||
|
||
CATEGORY = "♾️Mixlab/Image"
|
||
|
||
# INPUT_IS_LIST = True, 一个batch传进来
|
||
OUTPUT_IS_LIST = (True,True,True,)
|
||
# OUTPUT_NODE = True
|
||
|
||
# 运行的函数
|
||
def run(self,images,masks,invert,save,filename_prefix,prompt=None, extra_pnginfo=None):
|
||
# print('TransparentImage',images.shape,images.size(),masks.shape,masks.size())
|
||
# print(masks.shape,masks.size())
|
||
|
||
ui_images=[]
|
||
image_paths=[]
|
||
|
||
count=images.shape[0]
|
||
masks_new=[]
|
||
nh=masks.shape[0]//count
|
||
|
||
masks_new=masks
|
||
|
||
if images.shape[0]==masks.shape[0] and images.shape[1]==masks.shape[1] and images.shape[2]==masks.shape[2]:
|
||
print('TransparentImage',images.shape,images.size(),masks.shape,masks.size())
|
||
else:
|
||
#INPUT_IS_LIST = False, 一个batch传进来
|
||
if nh*count==masks.shape[0]:
|
||
masks_new=split_mask_by_new_height(masks,nh)
|
||
else:
|
||
masks_new=split_mask_by_new_height(masks,masks.shape[0])
|
||
|
||
|
||
is_save=True if save=='yes' else False
|
||
# filename_prefix += self.prefix_append
|
||
|
||
images_rgb=[]
|
||
images_rgba=[]
|
||
|
||
for i in range(len(images)):
|
||
image=images[i]
|
||
mask=masks_new[i]
|
||
|
||
result=doMask(image,mask,is_save,filename_prefix,invert,not is_save,prompt, extra_pnginfo)
|
||
|
||
for item in result["result"]:
|
||
ui_images.append(item)
|
||
|
||
image_paths.append(result['image_path'])
|
||
|
||
images_rgb.append(result['im_tensor'])
|
||
images_rgba.append(result['im_rgba_tensor'])
|
||
|
||
# ui.images 节点里显示图片,和 传参,image_path自定义的数据,需要写节点的自定义ui
|
||
# result 里输出给下个节点的数据
|
||
# print('TransparentImage',len(images_rgb))
|
||
|
||
return {"ui":{"images": ui_images,"image_paths":image_paths},"result": (image_paths,images_rgb,images_rgba)}
|
||
|
||
|
||
|
||
class ImagesPrompt:
|
||
@classmethod
|
||
def INPUT_TYPES(s):
|
||
# input_dir = folder_paths.get_input_directory()
|
||
# files = [f for f in os.listdir(input_dir) if os.path.isfile(os.path.join(input_dir, f))]
|
||
return {
|
||
"required": {
|
||
"image_base64": ("STRING",{"multiline": False,"default": "","dynamicPrompts": False}),
|
||
"text": ("STRING",{"multiline": True,"default": "","dynamicPrompts": True}),
|
||
}
|
||
}
|
||
|
||
RETURN_TYPES = ("IMAGE","STRING",)
|
||
RETURN_NAMES = ("image","text",)
|
||
|
||
FUNCTION = "run"
|
||
|
||
CATEGORY = "♾️Mixlab/Input"
|
||
|
||
INPUT_IS_LIST = False
|
||
OUTPUT_IS_LIST = (False,False,)
|
||
OUTPUT_NODE = False
|
||
|
||
# 运行的函数
|
||
def run(self,image_base64,text):
|
||
image = base64_to_image(image_base64)
|
||
image=image.convert('RGB')
|
||
image=pil2tensor(image)
|
||
return (image,text,)
|
||
|
||
|
||
class EnhanceImage:
|
||
@classmethod
|
||
def INPUT_TYPES(s):
|
||
return {
|
||
"required": {
|
||
"image": ("IMAGE",),
|
||
"contrast":("FLOAT", {"default": 0.5,
|
||
"min":0,
|
||
"max": 10,
|
||
"step": 0.01,
|
||
"display": "slider"})
|
||
}
|
||
}
|
||
|
||
RETURN_TYPES = ('IMAGE',)
|
||
|
||
FUNCTION = "run"
|
||
|
||
CATEGORY = "♾️Mixlab/Image"
|
||
|
||
INPUT_IS_LIST = True
|
||
|
||
OUTPUT_IS_LIST = (True,)
|
||
|
||
# 运行的函数
|
||
def run(self,image,contrast):
|
||
# print('EnhanceImage',len(image),image[0].shape)
|
||
contrast=contrast[0]
|
||
res=[]
|
||
for ims in image:
|
||
for im in ims:
|
||
|
||
image=tensor2pil(im)
|
||
|
||
image=enhance_depth_map(image,contrast)
|
||
|
||
image=pil2tensor(image)
|
||
|
||
res.append(image)
|
||
|
||
return (res,)
|
||
|
||
|
||
|
||
|
||
class LoadImages_:
|
||
@classmethod
|
||
def INPUT_TYPES(s):
|
||
|
||
return {"required":
|
||
{"images": ("IMAGEBASE64",),
|
||
},
|
||
}
|
||
|
||
CATEGORY = "♾️Mixlab/Image"
|
||
|
||
INPUT_IS_LIST = False
|
||
OUTPUT_IS_LIST = (False,)
|
||
|
||
RETURN_TYPES = ("IMAGE",)
|
||
FUNCTION = "load_image"
|
||
def load_image(self, images):
|
||
|
||
# print(images)
|
||
ims=[]
|
||
for im in images['base64']:
|
||
image = base64_to_image(im)
|
||
image=image.convert('RGB')
|
||
image=pil2tensor(image)
|
||
ims.append(image)
|
||
|
||
image1 = ims[0]
|
||
for image2 in ims[1:]:
|
||
if image1.shape[1:] != image2.shape[1:]:
|
||
image2 = comfy.utils.common_upscale(image2.movedim(-1, 1), image1.shape[2], image1.shape[1], "bilinear", "center").movedim(1, -1)
|
||
image1 = torch.cat((image1, image2), dim=0)
|
||
return (image1,)
|
||
|
||
|
||
|
||
|
||
'''
|
||
("STRING",{"multiline": False,"default": "Hello World!"})
|
||
对应 widgets.js 里:
|
||
const defaultVal = inputData[1].default || "";
|
||
const multiline = !!inputData[1].multiline;
|
||
'''
|
||
|
||
# 支持按照时间排序
|
||
# 支持输出1张
|
||
#
|
||
class LoadImagesFromPath:
|
||
|
||
@classmethod
|
||
def INPUT_TYPES(s):
|
||
return {
|
||
"required": {
|
||
"file_path": ("STRING",{"multiline": False,"default": "","dynamicPrompts": False}),
|
||
},
|
||
"optional":{
|
||
"white_bg": (["disable","enable"],),
|
||
"newest_files": (["enable", "disable"],),
|
||
"index_variable":("INT", {
|
||
"default": 0,
|
||
"min": -1, #Minimum value
|
||
"max": 2048, #Maximum value
|
||
"step": 1, #Slider's step
|
||
"display": "number" # Cosmetic only: display as "number" or "slider"
|
||
}),
|
||
"watcher":(["disable","enable"],),
|
||
"result": ("WATCHER",),#为了激活本节点运行
|
||
"prompt": ("PROMPT",),
|
||
# "seed": ("INT", {"default": 0, "min": 0, "max": 0xffffffffffffffff}),
|
||
}
|
||
}
|
||
|
||
RETURN_TYPES = ('IMAGE','MASK','STRING','STRING',)
|
||
RETURN_NAMES = ("IMAGE","MASK","prompt_for_FloatingVideo","filepaths",)
|
||
|
||
FUNCTION = "run"
|
||
|
||
CATEGORY = "♾️Mixlab/Image"
|
||
|
||
# INPUT_IS_LIST = True
|
||
OUTPUT_IS_LIST = (True,True,False,True,)
|
||
|
||
global watcher_folder
|
||
watcher_folder=None
|
||
|
||
# 运行的函数
|
||
def run(self,file_path,white_bg,newest_files,index_variable,watcher,result,prompt):
|
||
global watcher_folder
|
||
# print('###监听:',watcher_folder,watcher,file_path,result)
|
||
|
||
if watcher_folder==None:
|
||
watcher_folder = FolderWatcher(file_path)
|
||
|
||
watcher_folder.set_folder_path(file_path)
|
||
|
||
if watcher=='enable':
|
||
# 在这里可以进行其他操作,监听会在后台持续
|
||
watcher_folder.set_folder_path(file_path)
|
||
watcher_folder.start()
|
||
else:
|
||
if watcher_folder!=None:
|
||
watcher_folder.stop()
|
||
|
||
#TODO 修bug: ps6477. tmp
|
||
images=get_images_filepath(file_path,white_bg=='enable')
|
||
|
||
# 当开启了监听,则取最新的,第一个文件
|
||
if watcher=='enable':
|
||
index_variable=0
|
||
newest_files='enable'
|
||
|
||
# 排序
|
||
sorted_files = sorted(images, key=lambda x: os.path.getmtime(x['file_path']), reverse=(newest_files=='enable'))
|
||
|
||
imgs=[]
|
||
masks=[]
|
||
file_names=[]
|
||
|
||
for im in sorted_files:
|
||
imgs.append(im['image'])
|
||
masks.append(im['mask'])
|
||
file_names.append(im['file_name'])
|
||
|
||
# print('index_variable',index_variable)
|
||
|
||
try:
|
||
if index_variable!=-1:
|
||
imgs=[imgs[index_variable]] if index_variable < len(imgs) else None
|
||
masks=[masks[index_variable]] if index_variable < len(masks) else None
|
||
file_names=[file_names[index_variable]] if index_variable < len(file_names) else None
|
||
except Exception as e:
|
||
print("发生了一个未知的错误:", str(e))
|
||
|
||
# print('#prompt::::',prompt)
|
||
return {"ui": {"seed": [1]}, "result":(imgs,masks,prompt,file_names,)}
|
||
|
||
|
||
# TODO 扩大选区的功能,重新输出mask
|
||
class ImageCropByAlpha:
|
||
@classmethod
|
||
def INPUT_TYPES(s):
|
||
return {"required": { "image": ("IMAGE",),
|
||
"RGBA": ("RGBA",), },
|
||
}
|
||
|
||
RETURN_TYPES = ("IMAGE","MASK","MASK","INT","INT","INT","INT",)
|
||
RETURN_NAMES = ("IMAGE","MASK","AREA_MASK","x","y","width","height",)
|
||
|
||
FUNCTION = "run"
|
||
|
||
CATEGORY = "♾️Mixlab/Image"
|
||
|
||
INPUT_IS_LIST = True
|
||
OUTPUT_IS_LIST = (True,True,True,True,True,True,True,)
|
||
|
||
def run(self,image,RGBA):
|
||
# print(image.shape,RGBA.shape)
|
||
|
||
image=image[0]
|
||
RGBA=RGBA[0]
|
||
|
||
bf_im = tensor2pil(image)
|
||
|
||
# print(RGBA)
|
||
im=tensor2pil(RGBA)
|
||
|
||
# 要把im的alpha通道转为mask
|
||
im=im.convert('RGBA')
|
||
red, green, blue, alpha = im.split()
|
||
|
||
im=naive_cutout(bf_im,alpha)
|
||
x, y, w, h=get_not_transparent_area(im)
|
||
# print('#ForImageCrop:',w, h,x, y,)
|
||
|
||
x = min(x, image.shape[2] - 1)
|
||
y = min(y, image.shape[1] - 1)
|
||
to_x = w + x
|
||
to_y = h + y
|
||
|
||
x_1=x
|
||
y_1=y
|
||
width_1=w
|
||
height_1=h
|
||
|
||
img = image[:,y:to_y, x:to_x, :]
|
||
# tensor2pil(img).save('test2.png')
|
||
|
||
# 原图的mask
|
||
ori=RGBA[:,y:to_y, x:to_x, :]
|
||
ori=tensor2pil(ori)
|
||
# ori.save('test.png')
|
||
|
||
# 创建一个新的图像对象,大小和模式与原始图像相同
|
||
new_image = Image.new("RGBA", ori.size)
|
||
|
||
# 获取原始图像的像素数据
|
||
pixel_data = ori.load()
|
||
|
||
# 获取新图像的像素数据
|
||
new_pixel_data = new_image.load()
|
||
|
||
# 遍历图像的每个像素
|
||
for y in range(ori.size[1]):
|
||
for x in range(ori.size[0]):
|
||
# 获取当前像素的RGBA值
|
||
r, g, b, a = pixel_data[x, y]
|
||
|
||
# 如果a通道不为0(不透明),将当前像素设置为白色
|
||
if a != 0:
|
||
new_pixel_data[x, y] = (255, 255, 255, 255)
|
||
else:
|
||
new_pixel_data[x, y] = (0,0,0,0)
|
||
|
||
# 保存修改后的图像
|
||
# new_image.save("output.png")
|
||
|
||
ori=new_image.convert('L')
|
||
# threshold = 128
|
||
# ori = ori.point(lambda x: 0 if x < threshold else 255, '1')
|
||
ori=pil2tensor(ori)
|
||
|
||
# 矩形区域,mask
|
||
b_image =AreaToMask_run(RGBA)
|
||
# img=None
|
||
# b_image=None
|
||
return ([img],[ori],[b_image],[x_1],[y_1],[width_1],[height_1],)
|
||
|
||
|
||
|
||
|
||
|
||
class TextImage:
|
||
@classmethod
|
||
def INPUT_TYPES(s):
|
||
return {"required": {
|
||
|
||
"text": ("STRING",{"multiline": True,"default": "龍馬精神迎新歲","dynamicPrompts": False}),
|
||
"font_path": ("STRING",{"multiline": False,"default": FONT_PATH,"dynamicPrompts": False}),
|
||
"font_size": ("INT",{
|
||
"default":100,
|
||
"min": 100, #Minimum value
|
||
"max": 1000, #Maximum value
|
||
"step": 1, #Slider's step
|
||
"display": "number" # Cosmetic only: display as "number" or "slider"
|
||
}),
|
||
"spacing": ("INT",{
|
||
"default":12,
|
||
"min": -200, #Minimum value
|
||
"max": 200, #Maximum value
|
||
"step": 1, #Slider's step
|
||
"display": "number" # Cosmetic only: display as "number" or "slider"
|
||
}),
|
||
"text_color":("STRING",{"multiline": False,"default": "#000000","dynamicPrompts": False}),
|
||
"vertical":("BOOLEAN", {"default": True},),
|
||
"stroke":("BOOLEAN", {"default": False},),
|
||
},
|
||
}
|
||
|
||
RETURN_TYPES = ("IMAGE","MASK",)
|
||
# RETURN_NAMES = ("WIDTH","HEIGHT","X","Y",)
|
||
|
||
FUNCTION = "run"
|
||
|
||
CATEGORY = "♾️Mixlab/Image"
|
||
|
||
INPUT_IS_LIST = False
|
||
OUTPUT_IS_LIST = (False,False,)
|
||
|
||
def run(self,text,font_path,font_size,spacing,text_color,vertical,stroke):
|
||
|
||
# text_list=list(text)
|
||
# stroke=False, stroke_color=(0, 0, 0), stroke_width=1, spacing=0
|
||
img,mask=generate_text_image(text,font_path,font_size,text_color,vertical,stroke,(0, 0, 0),1,spacing)
|
||
|
||
img=pil2tensor(img)
|
||
mask=pil2tensor(mask)
|
||
|
||
return (img,mask,)
|
||
|
||
class LoadImagesFromURL:
|
||
@classmethod
|
||
def INPUT_TYPES(s):
|
||
return {"required": {
|
||
"url": ("STRING",{"multiline": True,"default": "https://","dynamicPrompts": False}),
|
||
},
|
||
"optional":{
|
||
"seed": (any_type, {"default": 0, "min": 0, "max": 0xffffffffffffffff}),
|
||
}
|
||
}
|
||
|
||
RETURN_TYPES = ("IMAGE","MASK",)
|
||
RETURN_NAMES = ("images","masks",)
|
||
|
||
FUNCTION = "run"
|
||
|
||
CATEGORY = "♾️Mixlab/Image"
|
||
|
||
INPUT_IS_LIST = False
|
||
OUTPUT_IS_LIST = (True,True,)
|
||
|
||
|
||
global urls_image
|
||
urls_image={}
|
||
|
||
def run(self,url,seed=0):
|
||
global urls_image
|
||
print(urls_image)
|
||
def filter_http_urls(urls):
|
||
filtered_urls = []
|
||
for url in urls.split('\n'):
|
||
if url.startswith('http'):
|
||
filtered_urls.append(url)
|
||
return filtered_urls
|
||
|
||
filtered_urls = filter_http_urls(url)
|
||
|
||
images=[]
|
||
masks=[]
|
||
|
||
for img_url in filtered_urls:
|
||
try:
|
||
if img_url in urls_image:
|
||
img,mask=urls_image[img_url]
|
||
else:
|
||
img,mask=load_image_and_mask_from_url(img_url)
|
||
urls_image[img_url]=(img,mask)
|
||
|
||
img1=pil2tensor(img)
|
||
mask1=pil2tensor(mask)
|
||
|
||
images.append(img1)
|
||
masks.append(mask1)
|
||
except Exception as e:
|
||
print("发生了一个未知的错误:", str(e))
|
||
|
||
return (images,masks,)
|
||
|
||
|
||
|
||
|
||
class SvgImage:
|
||
@classmethod
|
||
def INPUT_TYPES(s):
|
||
return {"required": {
|
||
"upload":("SVG",),},
|
||
}
|
||
|
||
RETURN_TYPES = ("IMAGE","LAYER")
|
||
RETURN_NAMES = ("IMAGE","layers",)
|
||
|
||
FUNCTION = "run"
|
||
|
||
CATEGORY = "♾️Mixlab/Image"
|
||
|
||
INPUT_IS_LIST = False
|
||
OUTPUT_IS_LIST = (False,True,)
|
||
|
||
def run(self,upload):
|
||
layers=[]
|
||
|
||
image = base64_to_image(upload['image'])
|
||
image=image.convert('RGB')
|
||
image=pil2tensor(image)
|
||
|
||
for layer in upload['data']:
|
||
layers.append(layer)
|
||
|
||
return (image,layers,)
|
||
|
||
|
||
|
||
class Image3D:
|
||
@classmethod
|
||
def INPUT_TYPES(s):
|
||
return {"required": {
|
||
"upload":("THREED",),},
|
||
"optional":{
|
||
"material": ("IMAGE",),
|
||
}
|
||
}
|
||
|
||
RETURN_TYPES = ("IMAGE","MASK","IMAGE","IMAGE",)
|
||
RETURN_NAMES = ("IMAGE","MASK","BG_IMAGE","MATERIAL",)
|
||
|
||
FUNCTION = "run"
|
||
|
||
CATEGORY = "♾️Mixlab/3D"
|
||
|
||
INPUT_IS_LIST = False
|
||
OUTPUT_IS_LIST = (False,False,False,False,)
|
||
OUTPUT_NODE = True
|
||
|
||
def run(self,upload,material=None):
|
||
# print('material',material)
|
||
# print(upload )
|
||
image = base64_to_image(upload['image'])
|
||
|
||
mat=None
|
||
if 'material' in upload and upload['material']:
|
||
mat=base64_to_image(upload['material'])
|
||
mat=mat.convert('RGB')
|
||
mat=pil2tensor(mat)
|
||
|
||
mask = image.split()[3]
|
||
image=image.convert('RGB')
|
||
|
||
mask=mask.convert('L')
|
||
|
||
bg_image=None
|
||
if 'bg_image' in upload and upload['bg_image']:
|
||
bg_image = base64_to_image(upload['bg_image'])
|
||
bg_image=bg_image.convert('RGB')
|
||
bg_image=pil2tensor(bg_image)
|
||
|
||
|
||
mask=pil2tensor(mask)
|
||
image=pil2tensor(image)
|
||
|
||
m=[]
|
||
if not material is None:
|
||
m=create_temp_file(material[0])
|
||
|
||
return {"ui":{"material": m},"result": (image,mask,bg_image,mat,)}
|
||
|
||
|
||
|
||
def AreaToMask_run(RGBA):
|
||
# print(RGBA)
|
||
im=tensor2pil(RGBA)
|
||
im=naive_cutout(im,im)
|
||
x, y, w, h=get_not_transparent_area(im)
|
||
|
||
im=im.convert("RGBA")
|
||
# print('#AreaToMask:',im)
|
||
img=areaToMask(x,y,w,h,im)
|
||
img=img.convert("RGBA")
|
||
mask=pil2tensor(img)
|
||
|
||
channels = ["red", "green", "blue", "alpha"]
|
||
# print(mask,mask.shape)
|
||
mask = mask[:, :, :, channels.index("green")]
|
||
|
||
return mask
|
||
|
||
|
||
class AreaToMask:
|
||
@classmethod
|
||
def INPUT_TYPES(s):
|
||
return {"required": { "RGBA": ("RGBA",), },
|
||
}
|
||
|
||
RETURN_TYPES = ("MASK",)
|
||
# RETURN_NAMES = ("WIDTH","HEIGHT","X","Y",)
|
||
|
||
FUNCTION = "run"
|
||
|
||
CATEGORY = "♾️Mixlab/Mask"
|
||
|
||
INPUT_IS_LIST = False
|
||
OUTPUT_IS_LIST = (False,)
|
||
|
||
def run(self,RGBA):
|
||
|
||
mask =AreaToMask_run(RGBA)
|
||
|
||
return (mask,)
|
||
|
||
|
||
class FaceToMask:
|
||
@classmethod
|
||
def INPUT_TYPES(s):
|
||
return {"required": { "image": ("IMAGE",)},
|
||
}
|
||
|
||
RETURN_TYPES = ("MASK",)
|
||
# RETURN_NAMES = ("WIDTH","HEIGHT","X","Y",)
|
||
|
||
FUNCTION = "run"
|
||
|
||
CATEGORY = "♾️Mixlab/Mask"
|
||
|
||
INPUT_IS_LIST = False
|
||
OUTPUT_IS_LIST = (False,)
|
||
|
||
def run(self,image):
|
||
# print(image)
|
||
im=tensor2pil(image)
|
||
mask=detect_faces(im)
|
||
|
||
mask=pil2tensor(mask)
|
||
channels = ["red", "green", "blue", "alpha"]
|
||
mask = mask[:, :, :, channels.index("green")]
|
||
|
||
return (mask,)
|
||
|
||
|
||
class CompositeImages:
|
||
@classmethod
|
||
def INPUT_TYPES(s):
|
||
return {
|
||
"required": {
|
||
"foreground": (any_type,),
|
||
"mask":("MASK",),
|
||
"background": ("IMAGE",),
|
||
},
|
||
"optional":{
|
||
|
||
"is_multiply_blend": ("BOOLEAN", {"default": False}),
|
||
"position": (['overall',"center_bottom","center_top","right_bottom","left_bottom","right_top","left_top"],),
|
||
}
|
||
}
|
||
|
||
RETURN_TYPES = ("IMAGE",)
|
||
RETURN_NAMES = ("IMAGE",)
|
||
|
||
FUNCTION = "run"
|
||
|
||
CATEGORY = "♾️Mixlab/Layer"
|
||
|
||
# OUTPUT_IS_LIST = (True,)
|
||
|
||
def run(self, foreground,mask,background,is_multiply_blend,position):
|
||
foreground= tensor2pil(foreground)
|
||
mask= tensor2pil(mask)
|
||
background= tensor2pil(background)
|
||
res=composite_images(foreground,background,mask,is_multiply_blend,position)
|
||
|
||
return (pil2tensor(res),)
|
||
|
||
|
||
|
||
|
||
class EmptyLayer:
|
||
@classmethod
|
||
def INPUT_TYPES(s):
|
||
return {
|
||
"required": {
|
||
"width": ("INT",{
|
||
"default":512,
|
||
"min": 1, #Minimum value
|
||
"max": 8192, #Maximum value
|
||
"step": 1, #Slider's step
|
||
"display": "number" # Cosmetic only: display as "number" or "slider"
|
||
}),
|
||
"height": ("INT",{
|
||
"default": 512,
|
||
"min": 1, #Minimum value
|
||
"max": 8192, #Maximum value
|
||
"step": 1, #Slider's step
|
||
"display": "number" # Cosmetic only: display as "number" or "slider"
|
||
}),
|
||
},
|
||
|
||
}
|
||
|
||
RETURN_TYPES = ("LAYER",)
|
||
RETURN_NAMES = ("layers",)
|
||
|
||
FUNCTION = "run"
|
||
|
||
CATEGORY = "♾️Mixlab/Layer"
|
||
|
||
OUTPUT_IS_LIST = (True,)
|
||
|
||
def run(self, width,height):
|
||
blank_image = Image.new("RGB", (width, height))
|
||
|
||
mask=blank_image.convert('L')
|
||
|
||
blank_image=pil2tensor(blank_image)
|
||
mask=pil2tensor(mask)
|
||
|
||
layer_n=[{
|
||
"x":0,
|
||
"y":0,
|
||
"width":width,
|
||
"height":height,
|
||
"z_index":0,
|
||
"scale_option":'width',
|
||
"image":blank_image,
|
||
"mask":mask
|
||
}]
|
||
return (layer_n,)
|
||
|
||
|
||
class NewLayer:
|
||
@classmethod
|
||
def INPUT_TYPES(s):
|
||
return {
|
||
|
||
"required": {
|
||
"x": ("INT",{
|
||
"default": 0,
|
||
"min": -1024, #Minimum value
|
||
"max": 8192, #Maximum value
|
||
"step": 1, #Slider's step
|
||
"display": "number" # Cosmetic only: display as "number" or "slider"
|
||
}),
|
||
"y": ("INT",{
|
||
"default": 0,
|
||
"min": -1024, #Minimum value
|
||
"max": 8192, #Maximum value
|
||
"step": 1, #Slider's step
|
||
"display": "number" # Cosmetic only: display as "number" or "slider"
|
||
}),
|
||
"width": ("INT",{
|
||
"default": 512,
|
||
"min": 1, #Minimum value
|
||
"max": 8192, #Maximum value
|
||
"step": 1, #Slider's step
|
||
"display": "number" # Cosmetic only: display as "number" or "slider"
|
||
}),
|
||
"height": ("INT",{
|
||
"default": 512,
|
||
"min": 1, #Minimum value
|
||
"max": 8192, #Maximum value
|
||
"step": 1, #Slider's step
|
||
"display": "number" # Cosmetic only: display as "number" or "slider"
|
||
}),
|
||
"z_index": ("INT",{
|
||
"default": 0,
|
||
"min":0, #Minimum value
|
||
"max": 100, #Maximum value
|
||
"step": 1, #Slider's step
|
||
"display": "number" # Cosmetic only: display as "number" or "slider"
|
||
}),
|
||
"scale_option": (["width","height",'overall'],),
|
||
"image": (any_type,),
|
||
},
|
||
"optional":{
|
||
"mask": ("MASK",{"default": None}),
|
||
"layers": ("LAYER",{"default": None}),
|
||
"canvas": ("IMAGE",{"default": None}),
|
||
}
|
||
}
|
||
|
||
RETURN_TYPES = ("LAYER",)
|
||
RETURN_NAMES = ("layers",)
|
||
|
||
FUNCTION = "run"
|
||
|
||
CATEGORY = "♾️Mixlab/Layer"
|
||
|
||
INPUT_IS_LIST = True
|
||
OUTPUT_IS_LIST = (True,)
|
||
|
||
def run(self,x,y,width,height,z_index,scale_option,image,mask=None,layers=None,canvas=None):
|
||
# print(x,y,width,height,z_index,image,mask)
|
||
|
||
if mask==None:
|
||
im=tensor2pil(image[0])
|
||
mask=im.convert('L')
|
||
mask=pil2tensor(mask)
|
||
else:
|
||
mask=mask[0]
|
||
|
||
layer_n=[{
|
||
"x":x[0],
|
||
"y":y[0],
|
||
"width":width[0],
|
||
"height":height[0],
|
||
"z_index":z_index[0],
|
||
"scale_option":scale_option[0],
|
||
"image":image[0],
|
||
"mask":mask
|
||
}]
|
||
|
||
if layers!=None:
|
||
layer_n=layer_n+layers
|
||
|
||
return (layer_n,)
|
||
|
||
|
||
|
||
def createMask(image,x,y,w,h):
|
||
mask = Image.new("L", image.size)
|
||
pixels = mask.load()
|
||
# 遍历指定区域的像素,将其设置为黑色(0 表示黑色)
|
||
for i in range(int(x), int(x + w)):
|
||
for j in range(int(y), int(y + h)):
|
||
pixels[i, j] = 255
|
||
# mask.save("mask.png")
|
||
return mask
|
||
|
||
def splitImage(image, num):
|
||
width, height = image.size
|
||
|
||
num_rows = int(num ** 0.5)
|
||
num_cols = int(num / num_rows)
|
||
|
||
grid_width = int(width // num_cols)
|
||
grid_height = int(height // num_rows)
|
||
|
||
grid_coordinates = []
|
||
for i in range(num_rows):
|
||
for j in range(num_cols):
|
||
x = int(j * grid_width)
|
||
y = int(i * grid_height)
|
||
grid_coordinates.append((x, y, grid_width, grid_height))
|
||
|
||
return grid_coordinates
|
||
|
||
|
||
def centerImage(margin,canvas):
|
||
w,h=canvas.size
|
||
|
||
l,t,r,b=margin
|
||
|
||
x=l
|
||
y=t
|
||
width=w-r-l
|
||
height=h-t-b
|
||
|
||
return (x,y,width,height)
|
||
|
||
# # 读取图片
|
||
# image = Image.open("path_to_your_image.jpg")
|
||
|
||
# # 定义要切割的区域数量
|
||
# num = 9
|
||
|
||
# # 切割图片
|
||
# grid_coordinates = splitImage(image, num)
|
||
|
||
# # 输出切割区域坐标
|
||
# for i, coordinates in enumerate(grid_coordinates):
|
||
# print(f"Region {i + 1}: x={coordinates[0]}, y={coordinates[1]}, width={coordinates[2]}, height={coordinates[3]}")
|
||
|
||
|
||
class SplitImage:
|
||
@classmethod
|
||
def INPUT_TYPES(s):
|
||
return {
|
||
"required": {
|
||
"image": ("IMAGE",),
|
||
"num": ("INT",{
|
||
"default": 4,
|
||
"min": 1, #Minimum value
|
||
"max": 500, #Maximum value
|
||
"step": 1, #Slider's step
|
||
"display": "number" # Cosmetic only: display as "number" or "slider"
|
||
}),
|
||
"seed": ("INT",{
|
||
"default": 4,
|
||
"min": 1, #Minimum value
|
||
"max": 500, #Maximum value
|
||
"step": 1, #Slider's step
|
||
"display": "number" # Cosmetic only: display as "number" or "slider"
|
||
}),
|
||
}
|
||
}
|
||
|
||
RETURN_TYPES = ("_GRID","_GRID","MASK",)
|
||
RETURN_NAMES = ("grids","grid","mask",)
|
||
|
||
FUNCTION = "run"
|
||
|
||
CATEGORY = "♾️Mixlab/Layer"
|
||
|
||
INPUT_IS_LIST = False
|
||
# OUTPUT_IS_LIST = (True,)
|
||
|
||
def run(self,image,num,seed):
|
||
|
||
if type(seed) == list and len(seed)==1:
|
||
seed=seed[0]
|
||
|
||
image=tensor2pil(image)
|
||
|
||
grids=splitImage(image,num)
|
||
|
||
if seed>num:
|
||
num=seed % (num + 1)
|
||
else:
|
||
num=seed-1
|
||
|
||
print('#SplitImage',seed)
|
||
|
||
num=max(0,num)
|
||
num=min(num,len(grids)-1)
|
||
|
||
g=grids[num]
|
||
|
||
x,y,w,h=g
|
||
mask=createMask(image, x,y,w,h)
|
||
mask=pil2tensor(mask)
|
||
|
||
return (grids,g,mask,)
|
||
|
||
|
||
|
||
class CenterImage:
|
||
@classmethod
|
||
def INPUT_TYPES(s):
|
||
return {
|
||
"required": {
|
||
"canvas": ("IMAGE",),
|
||
"left": ("INT",{
|
||
"default":24,
|
||
"min": 0, #Minimum value
|
||
"max": 5000, #Maximum value
|
||
"step": 1, #Slider's step
|
||
"display": "number" # Cosmetic only: display as "number" or "slider"
|
||
}),
|
||
"top": ("INT",{
|
||
"default":24,
|
||
"min": 0, #Minimum value
|
||
"max": 5000, #Maximum value
|
||
"step": 1, #Slider's step
|
||
"display": "number" # Cosmetic only: display as "number" or "slider"
|
||
}),
|
||
"right": ("INT",{
|
||
"default": 24,
|
||
"min": 0, #Minimum value
|
||
"max": 5000, #Maximum value
|
||
"step": 1, #Slider's step
|
||
"display": "number" # Cosmetic only: display as "number" or "slider"
|
||
}),
|
||
"bottom": ("INT",{
|
||
"default": 24,
|
||
"min": 0, #Minimum value
|
||
"max": 5000, #Maximum value
|
||
"step": 1, #Slider's step
|
||
"display": "number" # Cosmetic only: display as "number" or "slider"
|
||
}),
|
||
}
|
||
}
|
||
|
||
RETURN_TYPES = ("_GRID","MASK",)
|
||
RETURN_NAMES = ("grid","mask",)
|
||
|
||
FUNCTION = "run"
|
||
|
||
CATEGORY = "♾️Mixlab/Layer"
|
||
|
||
INPUT_IS_LIST = False
|
||
# OUTPUT_IS_LIST = (True,)
|
||
|
||
def run(self,canvas,left,top,right,bottom):
|
||
canvas=tensor2pil(canvas)
|
||
|
||
grid=centerImage((left,top,right,bottom),canvas)
|
||
|
||
mask=createMask(canvas,left,top,canvas.width-left-right,canvas.height-top-bottom)
|
||
|
||
return (grid,pil2tensor(mask),)
|
||
|
||
class GridDisplayAndSave:
|
||
@classmethod
|
||
def INPUT_TYPES(s):
|
||
return {
|
||
"required": {
|
||
"labels": ("STRING",
|
||
{
|
||
"multiline": True,
|
||
"default": "",
|
||
"forceInput": True,
|
||
"dynamicPrompts": False
|
||
}),
|
||
"grids": ("_GRID",),
|
||
|
||
"image": ("IMAGE",),
|
||
"filename_prefix": ("STRING", {"default": "mixlab/grids"})
|
||
}
|
||
}
|
||
|
||
RETURN_TYPES = ( )
|
||
RETURN_NAMES = ( )
|
||
|
||
FUNCTION = "run"
|
||
|
||
CATEGORY = "♾️Mixlab/Layer"
|
||
|
||
INPUT_IS_LIST = True
|
||
OUTPUT_NODE = True
|
||
# OUTPUT_IS_LIST = (True,)
|
||
|
||
def run(self,labels,grids,image,filename_prefix):
|
||
|
||
# print(image.shape)
|
||
|
||
img= tensor2pil(image[0])
|
||
|
||
for grid in grids:
|
||
draw_rectangle(img, grid, 'red',8)
|
||
|
||
#获取临时目录:temp
|
||
output_dir = folder_paths.get_temp_directory()
|
||
|
||
(
|
||
full_output_folder,
|
||
filename,
|
||
counter,
|
||
subfolder,
|
||
_,
|
||
) = folder_paths.get_save_image_path('tmp_', output_dir)
|
||
|
||
image_file = f"{filename}_{counter:05}.png"
|
||
|
||
image_path=os.path.join(full_output_folder, image_file)
|
||
# 保存图片
|
||
img.save(image_path,compress_level=6)
|
||
width, height = img.size
|
||
|
||
(
|
||
full_output_folder,
|
||
filename,
|
||
counter,
|
||
_,
|
||
_,
|
||
) = folder_paths.get_save_image_path(filename_prefix[0], output_dir)
|
||
|
||
|
||
data_converted = [{
|
||
"label":labels[i],
|
||
"grid":[float(grids[i][0]),
|
||
float(grids[i][1]),
|
||
float(grids[i][2]),
|
||
float(grids[i][3])
|
||
]
|
||
} for i in range(len(grids))]
|
||
|
||
data={
|
||
"width":int(width),
|
||
"height":int(height),
|
||
"grids":data_converted
|
||
}
|
||
|
||
save_json_to_file(data,os.path.join(full_output_folder,f"${filename}_{counter:05}.json"))
|
||
|
||
return {"ui":{"image": [{
|
||
"filename": image_file,
|
||
"subfolder": subfolder,
|
||
"type":"temp"
|
||
}],
|
||
"json":[data["width"],data['height'],data["grids"]]
|
||
},"result": ()}
|
||
# return {"ui":{"image": [ ],
|
||
|
||
# },"result": ()}
|
||
|
||
class GridInput:
|
||
@classmethod
|
||
def INPUT_TYPES(s):
|
||
return {
|
||
"required": {
|
||
"grids": ("STRING",
|
||
{
|
||
"multiline": True,
|
||
"default": "",
|
||
"dynamicPrompts": False
|
||
}),
|
||
"padding":("INT",{
|
||
"default": 24,
|
||
"min": -500, #Minimum value
|
||
"max": 5000, #Maximum value
|
||
"step": 1, #Slider's step
|
||
"display": "number" # Cosmetic only: display as "number" or "slider"
|
||
}),
|
||
|
||
},
|
||
"optional":{
|
||
"width":("INT",{
|
||
"forceInput": True,
|
||
}),
|
||
"height":("INT",{
|
||
"forceInput": True,
|
||
}),
|
||
}
|
||
|
||
}
|
||
|
||
RETURN_TYPES = ("_GRID","STRING","IMAGE",)
|
||
RETURN_NAMES = ("grids","labels","image",)
|
||
|
||
FUNCTION = "run"
|
||
|
||
CATEGORY = "♾️Mixlab/Input"
|
||
|
||
INPUT_IS_LIST = True
|
||
OUTPUT_IS_LIST = (True,True,False,)
|
||
OUTPUT_NODE = True
|
||
|
||
def run(self,grids,padding,width=[-1],height=[-1]):
|
||
# print(padding[0],grids[0])
|
||
width=width[0]
|
||
height=height[0]
|
||
|
||
grids=grids[0]
|
||
data=json.loads(grids)
|
||
grids=data['grids']
|
||
|
||
if width>-1:
|
||
data['width']=width
|
||
if height>-1:
|
||
data['height']=height
|
||
|
||
new_grids=[]
|
||
labels=[]
|
||
|
||
for g in grids:
|
||
labels.append(g['label'])
|
||
new_grids.append(padding_rectangle(g['grid'],padding[0]))
|
||
|
||
image = Image.new("RGB", (int(data['width']),int(data["height"])), "white")
|
||
im=pil2tensor(image)
|
||
# image=create_temp_file(im)
|
||
|
||
data_converted = [{
|
||
"label":labels[i],
|
||
"grid":[float(new_grids[i][0]),
|
||
float(new_grids[i][1]),
|
||
float(new_grids[i][2]),
|
||
float(new_grids[i][3])
|
||
]
|
||
} for i in range(len(new_grids))]
|
||
|
||
# 传递到前端节点的数据 报错,需要处理成 key:[x,x,x,x]
|
||
return {"ui":{
|
||
"json":[data["width"],data["height"],data_converted]
|
||
},"result": (new_grids,labels,im,)}
|
||
|
||
# return (new_grids,labels,pil2tensor(image),)
|
||
|
||
class GridOutput:
|
||
@classmethod
|
||
def INPUT_TYPES(s):
|
||
return {
|
||
"required": {
|
||
"grid": ("_GRID",),
|
||
|
||
},
|
||
"optional":{
|
||
"bg_image":("IMAGE",)
|
||
}
|
||
}
|
||
|
||
RETURN_TYPES = ("INT","INT","INT","INT","MASK",)
|
||
RETURN_NAMES = ("x","y","width","height","mask",)
|
||
|
||
FUNCTION = "run"
|
||
|
||
CATEGORY = "♾️Mixlab/Layer"
|
||
|
||
INPUT_IS_LIST = False
|
||
# OUTPUT_IS_LIST = (True,)
|
||
|
||
def run(self,grid,bg_image=None):
|
||
x,y,w,h=grid
|
||
x=int(x)
|
||
y=int(y)
|
||
w=int(w)
|
||
h=int(h)
|
||
|
||
masks=[]
|
||
if bg_image!=None:
|
||
for i in range(len(bg_image)):
|
||
im=bg_image[i]
|
||
#增加输出mask
|
||
im=tensor2pil(im)
|
||
mask=areaToMask(x,y,w,h,im)
|
||
mask=pil2tensor(mask)
|
||
masks.append(mask)
|
||
out=None
|
||
if len(masks)>0:
|
||
out = torch.cat(masks, dim=0)
|
||
return (x,y,w,h,out,)
|
||
|
||
|
||
|
||
|
||
class ShowLayer:
|
||
@classmethod
|
||
def INPUT_TYPES(s):
|
||
return {
|
||
|
||
"required": {
|
||
"edit": ("EDIT",),
|
||
|
||
"x": ("INT",{
|
||
"default": 0,
|
||
"min": -100, #Minimum value
|
||
"max": 8192, #Maximum value
|
||
"step": 1, #Slider's step
|
||
"display": "number" # Cosmetic only: display as "number" or "slider"
|
||
}),
|
||
"y": ("INT",{
|
||
"default": 0,
|
||
"min": 0, #Minimum value
|
||
"max": 8192, #Maximum value
|
||
"step": 1, #Slider's step
|
||
"display": "number" # Cosmetic only: display as "number" or "slider"
|
||
}),
|
||
"width": ("INT",{
|
||
"default": 512,
|
||
"min": 1, #Minimum value
|
||
"max": 8192, #Maximum value
|
||
"step": 1, #Slider's step
|
||
"display": "number" # Cosmetic only: display as "number" or "slider"
|
||
}),
|
||
"height": ("INT",{
|
||
"default": 512,
|
||
"min": 1, #Minimum value
|
||
"max": 8192, #Maximum value
|
||
"step": 1, #Slider's step
|
||
"display": "number" # Cosmetic only: display as "number" or "slider"
|
||
}),
|
||
"z_index": ("INT",{
|
||
"default": 0,
|
||
"min":0, #Minimum value
|
||
"max": 100, #Maximum value
|
||
"step": 1, #Slider's step
|
||
"display": "number" # Cosmetic only: display as "number" or "slider"
|
||
}),
|
||
"scale_option": (["width","height",'overall'],),
|
||
# "image": ("IMAGE",),
|
||
},
|
||
"optional":{
|
||
# "mask": ("MASK",{"default": None}),
|
||
"layers": ("LAYER",{"default": None}),
|
||
}
|
||
}
|
||
|
||
RETURN_TYPES = ( )
|
||
RETURN_NAMES = ( )
|
||
|
||
FUNCTION = "run"
|
||
|
||
CATEGORY = "♾️Mixlab/Layer"
|
||
|
||
INPUT_IS_LIST = True
|
||
# OUTPUT_IS_LIST = (True,)
|
||
|
||
def run(self,edit,x,y,width,height,z_index,scale_option,layers):
|
||
# print(x,y,width,height,z_index,image,mask)
|
||
|
||
# if mask==None:
|
||
# im=tensor2pil(image)
|
||
# mask=im.convert('L')
|
||
# mask=pil2tensor(mask)
|
||
# else:
|
||
# mask=mask[0]
|
||
|
||
# layers[edit[0]]={
|
||
# "x":x[0],
|
||
# "y":y[0],
|
||
# "width":width[0],
|
||
# "height":height[0],
|
||
# "z_index":z_index[0],
|
||
# "scale_option":scale_option[0],
|
||
# "image":image[0],
|
||
# "mask":mask
|
||
# }
|
||
|
||
return ( )
|
||
|
||
|
||
class MergeLayers:
|
||
@classmethod
|
||
def INPUT_TYPES(s):
|
||
return {"required": {
|
||
"layers": ("LAYER",),
|
||
"images": ("IMAGE",),
|
||
},
|
||
"optional":{
|
||
|
||
"is_multiply_blend": ("BOOLEAN", {"default": False}),
|
||
|
||
}
|
||
}
|
||
|
||
RETURN_TYPES = ("IMAGE","MASK",)
|
||
RETURN_NAMES = ("IMAGE","MASK",)
|
||
|
||
FUNCTION = "run"
|
||
|
||
CATEGORY = "♾️Mixlab/Layer"
|
||
|
||
INPUT_IS_LIST = True
|
||
# OUTPUT_IS_LIST = (False,)
|
||
|
||
def run(self,layers,images,is_multiply_blend):
|
||
|
||
bg_images=[]
|
||
masks=[]
|
||
|
||
is_multiply_blend=is_multiply_blend[0]
|
||
# print(len(images),images[0].shape)
|
||
# 1 torch.Size([2, 512, 512, 3])
|
||
# 4 torch.Size([1, 1024, 768, 3])
|
||
|
||
for img in images:
|
||
|
||
for bg_image in img:
|
||
# bg_image=image[0]
|
||
bg_image=tensor2pil(bg_image)
|
||
# 按z-index排序
|
||
layers_new = sorted(layers, key=lambda x: x["z_index"])
|
||
|
||
width, height = bg_image.size
|
||
final_mask= Image.new('L', (width, height), 0)
|
||
|
||
for layer in layers_new:
|
||
image=layer['image']
|
||
mask=layer['mask']
|
||
if 'type' in layer and layer['type']=='base64' and type(image) == str:
|
||
im=base64_to_image(image)
|
||
im=im.convert('RGB')
|
||
image=pil2tensor(im)
|
||
|
||
mask=base64_to_image(mask)
|
||
mask=mask.convert('L')
|
||
mask=pil2tensor(mask)
|
||
|
||
|
||
layer_image=tensor2pil(image)
|
||
layer_mask=tensor2pil(mask)
|
||
# t=layer_image.convert("RGBA")
|
||
# t.save('test.png') 如果layerimage传入的是rgba,则是透明的
|
||
bg_image=merge_images(bg_image,
|
||
layer_image,
|
||
layer_mask,
|
||
layer['x'],
|
||
layer['y'],
|
||
layer['width'],
|
||
layer['height'],
|
||
layer['scale_option'],
|
||
is_multiply_blend
|
||
)
|
||
|
||
final_mask=merge_images(final_mask,
|
||
layer_mask.convert('RGB'),
|
||
layer_mask,
|
||
layer['x'],
|
||
layer['y'],
|
||
layer['width'],
|
||
layer['height'],
|
||
layer['scale_option']
|
||
)
|
||
|
||
final_mask=final_mask.convert('L')
|
||
|
||
# mask=bg_image.convert('RGBA')
|
||
final_mask=pil2tensor(final_mask)
|
||
|
||
bg_image=bg_image.convert('RGB')
|
||
bg_image=pil2tensor(bg_image)
|
||
|
||
bg_images.append(bg_image)
|
||
masks.append(final_mask)
|
||
|
||
bg_images=torch.cat(bg_images, dim=0)
|
||
masks=torch.cat(masks, dim=0)
|
||
return (bg_images,masks,)
|
||
|
||
|
||
class GradientImage:
|
||
@classmethod
|
||
def INPUT_TYPES(s):
|
||
return {"required": {
|
||
"width": ("INT",{
|
||
"default": 512,
|
||
"min": 1, # 最小值
|
||
"max": 8192, # 最大值
|
||
"step": 1, # 间隔
|
||
"display": "number" # 控件类型: 输入框 number、滑块 slider
|
||
}),
|
||
"height": ("INT",{
|
||
"default": 512,
|
||
"min": 1,
|
||
"max": 8192,
|
||
"step": 1,
|
||
"display": "number"
|
||
}),
|
||
"start_color_hex": ("STRING",{"multiline": False,"default": "#FFFFFF","dynamicPrompts": False}),
|
||
"end_color_hex": ("STRING",{"multiline": False,"default": "#000000","dynamicPrompts": False}),
|
||
},
|
||
}
|
||
|
||
# 输出的数据类型
|
||
RETURN_TYPES = ("IMAGE","MASK",)
|
||
|
||
# 运行时方法名称
|
||
FUNCTION = "run"
|
||
|
||
# 右键菜单目录
|
||
CATEGORY = "♾️Mixlab/Image"
|
||
|
||
# 输入是否为列表
|
||
INPUT_IS_LIST = False
|
||
|
||
# 输出是否为列表
|
||
OUTPUT_IS_LIST = (False,False,)
|
||
|
||
def run(self,width,height,start_color_hex, end_color_hex):
|
||
|
||
im,mask=generate_gradient_image(width, height, start_color_hex, end_color_hex)
|
||
|
||
#获取临时目录:temp
|
||
output_dir = folder_paths.get_temp_directory()
|
||
|
||
(
|
||
full_output_folder,
|
||
filename,
|
||
counter,
|
||
subfolder,
|
||
_,
|
||
) = folder_paths.get_save_image_path('tmp_', output_dir)
|
||
|
||
image_file = f"{filename}_{counter:05}.png"
|
||
|
||
image_path=os.path.join(full_output_folder, image_file)
|
||
# 保存图片
|
||
im.save(image_path,compress_level=6)
|
||
|
||
# 把PIL数据类型转为tensor
|
||
im=pil2tensor(im)
|
||
|
||
mask=pil2tensor(mask)
|
||
|
||
# 定义ui字段,数据将回传到web前端的 nodeType.prototype.onExecuted
|
||
# result是节点的输出
|
||
return {"ui":{"images": [{
|
||
"filename": image_file,
|
||
"subfolder": subfolder,
|
||
"type":"temp"
|
||
}]},"result": (im,mask,)}
|
||
|
||
|
||
|
||
class NoiseImage:
|
||
@classmethod
|
||
def INPUT_TYPES(s):
|
||
return {"required": {
|
||
"width": ("INT",{
|
||
"default": 512,
|
||
"min": 1, # 最小值
|
||
"max": 8192, # 最大值
|
||
"step": 1, # 间隔
|
||
"display": "number" # 控件类型: 输入框 number、滑块 slider
|
||
}),
|
||
"height": ("INT",{
|
||
"default": 512,
|
||
"min": 1,
|
||
"max": 8192,
|
||
"step": 1,
|
||
"display": "number"
|
||
}),
|
||
"noise_level": ("INT",{
|
||
"default": 128,
|
||
"min": 0,
|
||
"max": 8192,
|
||
"step": 1,
|
||
"display": "slider"
|
||
}),
|
||
"color_hex": ("STRING",{"multiline": False,"default": "#FFFFFF","dynamicPrompts": False}),
|
||
},
|
||
}
|
||
|
||
# 输出的数据类型
|
||
RETURN_TYPES = ("IMAGE",)
|
||
|
||
# 运行时方法名称
|
||
FUNCTION = "run"
|
||
|
||
# 右键菜单目录
|
||
CATEGORY = "♾️Mixlab/Image"
|
||
|
||
# 输入是否为列表
|
||
INPUT_IS_LIST = False
|
||
|
||
# 输出是否为列表
|
||
OUTPUT_IS_LIST = (False,)
|
||
|
||
def run(self,width,height,noise_level,color_hex):
|
||
# 创建噪声图像
|
||
im=create_noisy_image(width,height,"RGB",noise_level,color_hex)
|
||
|
||
#获取临时目录:temp
|
||
output_dir = folder_paths.get_temp_directory()
|
||
|
||
(
|
||
full_output_folder,
|
||
filename,
|
||
counter,
|
||
subfolder,
|
||
_,
|
||
) = folder_paths.get_save_image_path('tmp_', output_dir)
|
||
|
||
image_file = f"{filename}_{counter:05}.png"
|
||
|
||
image_path=os.path.join(full_output_folder, image_file)
|
||
# 保存图片
|
||
im.save(image_path,compress_level=6)
|
||
|
||
# 把PIL数据类型转为tensor
|
||
im=pil2tensor(im)
|
||
|
||
# 定义ui字段,数据将回传到web前端的 nodeType.prototype.onExecuted
|
||
# result是节点的输出
|
||
return {"ui":{"images": [{
|
||
"filename": image_file,
|
||
"subfolder": subfolder,
|
||
"type":"temp"
|
||
}]},"result": (im,)}
|
||
|
||
|
||
class ResizeImage:
|
||
@classmethod
|
||
def INPUT_TYPES(s):
|
||
return {"required": {
|
||
"width": ("INT",{
|
||
"default": 512,
|
||
"min": 1, #Minimum value
|
||
"max": 8192, #Maximum value
|
||
"step": 8, #Slider's step
|
||
"display": "number" # Cosmetic only: display as "number" or "slider"
|
||
}),
|
||
"height": ("INT",{
|
||
"default": 512,
|
||
"min": 1, #Minimum value
|
||
"max": 8192, #Maximum value
|
||
"step": 8, #Slider's step
|
||
"display": "number" # Cosmetic only: display as "number" or "slider"
|
||
}),
|
||
"scale_option": (["width","height",'overall','center'],),
|
||
|
||
},
|
||
|
||
"optional":{
|
||
"image": ("IMAGE",),
|
||
"average_color": (["on",'off'],),
|
||
"fill_color":("STRING",{"multiline": False,"default": "#FFFFFF","dynamicPrompts": False}),
|
||
"mask": ("MASK",),
|
||
}
|
||
}
|
||
|
||
RETURN_TYPES = ("IMAGE","IMAGE","STRING","MASK",)
|
||
RETURN_NAMES = ("image","average_image","average_hex","mask",)
|
||
|
||
FUNCTION = "run"
|
||
|
||
CATEGORY = "♾️Mixlab/Image"
|
||
|
||
INPUT_IS_LIST = True
|
||
OUTPUT_IS_LIST = (True,True,True,True,)
|
||
|
||
def run(self,width,height,scale_option,image=None,average_color=['on'],fill_color=["#FFFFFF"],mask=None):
|
||
|
||
w=width[0]
|
||
h=height[0]
|
||
scale_option=scale_option[0]
|
||
average_color=average_color[0]
|
||
fill_color=fill_color[0]
|
||
|
||
imgs=[]
|
||
masks=[]
|
||
average_images=[]
|
||
hexs=[]
|
||
|
||
if image==None:
|
||
im=create_noisy_image(w,h,"RGB")
|
||
a_im,hex=get_average_color_image(im)
|
||
|
||
im=pil2tensor(im)
|
||
imgs.append(im)
|
||
|
||
a_im=pil2tensor(a_im)
|
||
average_images.append(a_im)
|
||
hexs.append(hex)
|
||
else:
|
||
for ims in image:
|
||
for im in ims:
|
||
im=tensor2pil(im)
|
||
|
||
im=im.convert('RGB')
|
||
a_im,hex=get_average_color_image(im)
|
||
|
||
if average_color=='on':
|
||
fill_color=hex
|
||
|
||
im=resize_image(im,scale_option,w,h,fill_color)
|
||
|
||
im=pil2tensor(im)
|
||
imgs.append(im)
|
||
|
||
a_im=pil2tensor(a_im)
|
||
average_images.append(a_im)
|
||
hexs.append(hex)
|
||
|
||
try:
|
||
for mas in mask:
|
||
for ma in mas:
|
||
ma=tensor2pil(ma)
|
||
ma=ma.convert('RGB')
|
||
ma=resize_image(ma,scale_option,w,h,fill_color)
|
||
ma=ma.convert('L')
|
||
ma=pil2tensor(ma)
|
||
masks.append(ma)
|
||
except:
|
||
print('')
|
||
|
||
return (imgs,average_images,hexs,masks,)
|
||
|
||
|
||
class MirroredImage:
|
||
@classmethod
|
||
def INPUT_TYPES(s):
|
||
return {"required": {
|
||
"image": ("IMAGE",),
|
||
},
|
||
}
|
||
|
||
# 输出的数据类型
|
||
RETURN_TYPES = ("IMAGE",)
|
||
|
||
# 运行时方法名称
|
||
FUNCTION = "run"
|
||
|
||
# 右键菜单目录
|
||
CATEGORY = "♾️Mixlab/Image"
|
||
|
||
# 输入是否为列表
|
||
INPUT_IS_LIST = True
|
||
|
||
# 输出是否为列表
|
||
OUTPUT_IS_LIST = (True,)
|
||
|
||
def run(self,image):
|
||
res=[]
|
||
for ims in image:
|
||
for im in ims:
|
||
img=tensor2pil(im)
|
||
mirrored_image = img.transpose(Image.FLIP_LEFT_RIGHT)
|
||
img=pil2tensor(mirrored_image)
|
||
res.append(img)
|
||
return (res,)
|
||
|
||
|
||
|
||
class GetImageSize_:
|
||
@classmethod
|
||
def INPUT_TYPES(cls):
|
||
return {
|
||
"required": {
|
||
"image": ("IMAGE",),
|
||
},
|
||
"optional":{
|
||
"min_width":("INT", {
|
||
"default": 512,
|
||
"min":1, #Minimum value
|
||
"max": 2048, #Maximum value
|
||
"step": 8, #Slider's step
|
||
"display": "number" # Cosmetic only: display as "number" or "slider"
|
||
})
|
||
},
|
||
}
|
||
|
||
RETURN_TYPES = ("INT", "INT","INT", "INT",)
|
||
RETURN_NAMES = ("width", "height","min_width", "min_height",)
|
||
|
||
FUNCTION = "get_size"
|
||
|
||
CATEGORY = "♾️Mixlab/Image"
|
||
|
||
def get_size(self, image,min_width):
|
||
_, height, width, _ = image.shape
|
||
|
||
# 如果比min_widht,还小,则输出 min width
|
||
if min_width>width:
|
||
im=tensor2pil(image)
|
||
im=resize_image(im,'width',min_width,min_width,"white")
|
||
im=im.convert('RGB')
|
||
|
||
min_width,min_height=im.size
|
||
|
||
else:
|
||
min_width=width
|
||
min_height=height
|
||
|
||
return (width, height,min_width,min_height,)
|
||
|
||
class SaveImageAndMetadata:
|
||
def __init__(self):
|
||
self.output_dir = folder_paths.get_output_directory()
|
||
self.type = "output"
|
||
self.prefix_append = ""
|
||
self.compress_level = 4
|
||
|
||
@classmethod
|
||
def INPUT_TYPES(s):
|
||
return {"required":
|
||
{"images": ("IMAGE", ),
|
||
"filename_prefix": ("STRING", {"default": "Mixlab"}),
|
||
"metadata": (["disable","enable"],),
|
||
},
|
||
"hidden": {"prompt": "PROMPT", "extra_pnginfo": "EXTRA_PNGINFO"},
|
||
}
|
||
|
||
RETURN_TYPES = ()
|
||
FUNCTION = "save_images"
|
||
|
||
OUTPUT_NODE = True
|
||
|
||
CATEGORY = "♾️Mixlab/Output"
|
||
|
||
def save_images(self, images, filename_prefix="Mixlab",metadata="disable", prompt=None, extra_pnginfo=None):
|
||
filename_prefix += self.prefix_append
|
||
full_output_folder, filename, counter, subfolder, filename_prefix = folder_paths.get_save_image_path(filename_prefix, self.output_dir, images[0].shape[1], images[0].shape[0])
|
||
results = list()
|
||
for image in images:
|
||
i = 255. * image.cpu().numpy()
|
||
img = Image.fromarray(np.clip(i, 0, 255).astype(np.uint8))
|
||
metadata = None
|
||
if (not args.disable_metadata) and (metadata=="enable"):
|
||
print('##enable_metadata')
|
||
metadata = PngInfo()
|
||
if prompt is not None:
|
||
metadata.add_text("prompt", json.dumps(prompt))
|
||
if extra_pnginfo is not None:
|
||
for x in extra_pnginfo:
|
||
metadata.add_text(x, json.dumps(extra_pnginfo[x]))
|
||
|
||
file = f"{filename}_{counter:05}_.png"
|
||
img.save(os.path.join(full_output_folder, file), pnginfo=metadata, compress_level=self.compress_level)
|
||
results.append({
|
||
"filename": file,
|
||
"subfolder": subfolder,
|
||
"type": self.type
|
||
})
|
||
counter += 1
|
||
|
||
return { "ui": { "images": results } }
|
||
|
||
class ImageColorTransfer:
|
||
@classmethod
|
||
def INPUT_TYPES(s):
|
||
return {"required": {
|
||
"source": ("IMAGE",),
|
||
"target": ("IMAGE",),
|
||
"weight": ("FLOAT", {"default": 0.5, "min": 0.0, "max": 1.0, "step": 0.01}),
|
||
},
|
||
}
|
||
|
||
# 输出的数据类型
|
||
RETURN_TYPES = ("IMAGE",)
|
||
|
||
# 运行时方法名称
|
||
FUNCTION = "run"
|
||
|
||
# 右键菜单目录
|
||
CATEGORY = "♾️Mixlab/Color"
|
||
|
||
# 输入是否为列表
|
||
# INPUT_IS_LIST = True
|
||
|
||
# 输出是否为列表
|
||
# OUTPUT_IS_LIST = (True,)
|
||
|
||
def run(self,source,target,weight):
|
||
|
||
res=[]
|
||
|
||
#batch-list
|
||
source_list = [source[i:i + 1, ...] for i in range(source.shape[0])]
|
||
target_list = [target[i:i + 1, ...] for i in range(target.shape[0])]
|
||
|
||
# 长度纠正为相等
|
||
if len(target_list) != len(source_list):
|
||
target_list = target_list * (len(source_list) // len(target_list)) + target_list[:len(source_list) % len(target_list)]
|
||
|
||
for i in range(len(source_list)):
|
||
target=target_list[i]
|
||
source=source_list[i]
|
||
target=tensor2pil(target)
|
||
|
||
image=tensor2pil(source)
|
||
|
||
image_res=color_transfer(image,target)
|
||
|
||
# weight Blend image # contributors:@ning
|
||
blend_mask = Image.new(mode="L", size=image.size,
|
||
color=(round(weight * 255)))
|
||
blend_mask = ImageOps.invert(blend_mask)
|
||
img_result = Image.composite(image, image_res, blend_mask)
|
||
del image, image_res, blend_mask
|
||
|
||
img_result=pil2tensor(img_result)
|
||
|
||
res.append(img_result)
|
||
|
||
# list - batch
|
||
res=torch.cat(res, dim=0)
|
||
|
||
return (res,)
|
||
|
||
|
||
|
||
class SaveImageToLocal:
|
||
def __init__(self):
|
||
self.output_dir = folder_paths.get_output_directory()
|
||
self.type = "output"
|
||
self.compress_level = 4
|
||
|
||
@classmethod
|
||
def INPUT_TYPES(s):
|
||
return {"required":
|
||
{"images": ("IMAGE", ),
|
||
"file_path": ("STRING",{"multiline": True,"default": "","dynamicPrompts": False}),
|
||
},
|
||
"hidden": {"prompt": "PROMPT", "extra_pnginfo": "EXTRA_PNGINFO"},
|
||
|
||
}
|
||
|
||
RETURN_TYPES = ()
|
||
FUNCTION = "save_images"
|
||
|
||
OUTPUT_NODE = True
|
||
|
||
CATEGORY = "♾️Mixlab/Output"
|
||
|
||
def save_images(self, images,file_path , prompt=None, extra_pnginfo=None):
|
||
filename_prefix = os.path.basename(file_path)
|
||
if file_path=='':
|
||
filename_prefix="ComfyUI"
|
||
|
||
filename_prefix, _ = os.path.splitext(filename_prefix)
|
||
|
||
_, extension = os.path.splitext(file_path)
|
||
|
||
if extension:
|
||
# 是文件名,需要处理
|
||
file_path=os.path.dirname(file_path)
|
||
# filename_prefix=
|
||
|
||
|
||
full_output_folder, filename, counter, subfolder, filename_prefix = folder_paths.get_save_image_path(filename_prefix, self.output_dir, images[0].shape[1], images[0].shape[0])
|
||
|
||
|
||
if not os.path.exists(file_path):
|
||
# 使用os.makedirs函数创建新目录
|
||
os.makedirs(file_path)
|
||
print("目录已创建")
|
||
else:
|
||
print("目录已存在")
|
||
|
||
# 使用glob模块获取当前目录下的所有文件
|
||
if file_path=="":
|
||
files = glob.glob(full_output_folder + '/*')
|
||
else:
|
||
files = glob.glob(file_path + '/*')
|
||
# 统计文件数量
|
||
file_count = len(files)
|
||
counter+=file_count
|
||
print('统计文件数量',file_count,counter)
|
||
|
||
results = list()
|
||
for image in images:
|
||
i = 255. * image.cpu().numpy()
|
||
img = Image.fromarray(np.clip(i, 0, 255).astype(np.uint8))
|
||
metadata = None
|
||
if not args.disable_metadata:
|
||
metadata = PngInfo()
|
||
if prompt is not None:
|
||
metadata.add_text("prompt", json.dumps(prompt))
|
||
if extra_pnginfo is not None:
|
||
for x in extra_pnginfo:
|
||
metadata.add_text(x, json.dumps(extra_pnginfo[x]))
|
||
|
||
file = f"{filename}_{counter:05}_.png"
|
||
|
||
if file_path=="":
|
||
fp=os.path.join(full_output_folder, file)
|
||
if os.path.exists(fp):
|
||
file = f"{filename}_{counter:05}_{generate_random_string(8)}.png"
|
||
fp=os.path.join(full_output_folder, file)
|
||
img.save(fp, pnginfo=metadata, compress_level=self.compress_level)
|
||
results.append({
|
||
"filename": file,
|
||
"subfolder": subfolder,
|
||
"type": self.type
|
||
})
|
||
|
||
else:
|
||
|
||
fp=os.path.join(file_path, file)
|
||
if os.path.exists(fp):
|
||
file = f"{filename}_{counter:05}_{generate_random_string(8)}.png"
|
||
fp=os.path.join(file_path, file)
|
||
|
||
img.save(os.path.join(file_path, file), pnginfo=metadata, compress_level=self.compress_level)
|
||
results.append({
|
||
"filename": file,
|
||
"subfolder": file_path,
|
||
"type": self.type
|
||
})
|
||
counter += 1
|
||
|
||
return ()
|