|
|
import numpy as np |
|
|
import requests |
|
|
import torch |
|
|
import torchvision.transforms.v2 as T |
|
|
|
|
|
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 |
|
|
|
|
|
from itertools import product |
|
|
|
|
|
|
|
|
|
|
|
def pil_to_opencv(image): |
|
|
open_cv_image = cv2.cvtColor(np.array(image), cv2.COLOR_RGB2BGR) |
|
|
return open_cv_image |
|
|
|
|
|
|
|
|
def opencv_to_pil(image): |
|
|
pil_image = Image.fromarray(cv2.cvtColor(image, cv2.COLOR_BGR2RGB)) |
|
|
return pil_image |
|
|
|
|
|
|
|
|
def get_files_with_extension(directory, extension): |
|
|
file_list = [] |
|
|
for root, dirs, files in os.walk(directory): |
|
|
for file in files: |
|
|
if file.endswith(extension): |
|
|
file = os.path.splitext(file)[0] |
|
|
file_path = os.path.join(root, file) |
|
|
file_name = os.path.relpath(file_path, directory) |
|
|
file_list.append(file_name) |
|
|
return file_list |
|
|
|
|
|
def composite_images(foreground, background, mask, is_multiply_blend=False, position="overall", scale=0.25): |
|
|
width, height = foreground.size |
|
|
bg_image = background |
|
|
bwidth, bheight = bg_image.size |
|
|
|
|
|
scale=max(scale,1/bwidth) |
|
|
scale=max(scale,1/bheight) |
|
|
|
|
|
def determine_scale_option(width, height): |
|
|
return 'height' if height > width else 'width' |
|
|
|
|
|
if position == "overall": |
|
|
layer = { |
|
|
"x": 0, |
|
|
"y": 0, |
|
|
"width": bwidth, |
|
|
"height": bheight, |
|
|
"z_index": 88, |
|
|
"scale_option": 'overall', |
|
|
"image": foreground, |
|
|
"mask": mask |
|
|
} |
|
|
else: |
|
|
scale_option = determine_scale_option(width, height) |
|
|
if scale_option == 'height': |
|
|
scale = int(bheight * scale) / height |
|
|
else: |
|
|
scale = int(bwidth * scale) / width |
|
|
|
|
|
new_width = int(width * scale) |
|
|
new_height = int(height * scale) |
|
|
|
|
|
if position == 'center_bottom': |
|
|
x_position = int((bwidth - new_width) * 0.5) |
|
|
y_position = bheight - new_height - 24 |
|
|
elif position == 'right_bottom': |
|
|
x_position = bwidth - new_width - 24 |
|
|
y_position = bheight - new_height - 24 |
|
|
elif position == 'center_top': |
|
|
x_position = int((bwidth - new_width) * 0.5) |
|
|
y_position = 24 |
|
|
elif position == 'right_top': |
|
|
x_position = bwidth - new_width - 24 |
|
|
y_position = 24 |
|
|
elif position == 'left_top': |
|
|
x_position = 24 |
|
|
y_position = 24 |
|
|
elif position == 'left_bottom': |
|
|
x_position = 24 |
|
|
y_position = bheight - new_height - 24 |
|
|
elif position == 'center_center': |
|
|
x_position = int((bwidth - new_width) * 0.5) |
|
|
y_position = int((bheight - new_height) * 0.5) |
|
|
|
|
|
layer = { |
|
|
"x": x_position, |
|
|
"y": y_position, |
|
|
"width": new_width, |
|
|
"height": new_height, |
|
|
"z_index": 88, |
|
|
"scale_option": scale_option, |
|
|
"image": foreground, |
|
|
"mask": mask |
|
|
} |
|
|
|
|
|
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/fonts')) |
|
|
|
|
|
|
|
|
MAX_RESOLUTION=8192 |
|
|
|
|
|
|
|
|
def tensor2pil(image): |
|
|
return Image.fromarray(np.clip(255. * image.cpu().numpy().squeeze(), 0, 255).astype(np.uint8)) |
|
|
|
|
|
|
|
|
def pil2tensor(image): |
|
|
return torch.from_numpy(np.array(image).astype(np.float32) / 255.0).unsqueeze(0) |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
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): |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
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] |
|
|
|
|
|
x = round(x) |
|
|
|
|
|
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) |
|
|
|
|
|
|
|
|
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 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
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. |
|
|
""" |
|
|
|
|
|
|
|
|
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 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
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') |
|
|
|
|
|
|
|
|
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): |
|
|
|
|
|
image_np = cv2.cvtColor(np.array(image), cv2.COLOR_RGBA2BGRA) |
|
|
|
|
|
|
|
|
rgba = cv2.split(image_np) |
|
|
alpha = rgba[3] |
|
|
|
|
|
|
|
|
_, 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("#") |
|
|
|
|
|
|
|
|
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)) |
|
|
|
|
|
|
|
|
mask = image.split()[-1] |
|
|
|
|
|
|
|
|
mask = mask.convert('L') |
|
|
|
|
|
image=image.convert('RGB') |
|
|
|
|
|
return (image, mask) |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
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) |
|
|
|
|
|
|
|
|
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) |
|
|
|
|
|
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): |
|
|
|
|
|
response = requests.get(url, timeout=timeout) |
|
|
|
|
|
content_type = response.headers.get('Content-Type') |
|
|
|
|
|
image = Image.open(BytesIO(response.content)) |
|
|
|
|
|
|
|
|
mask = image.convert('RGBA').split()[-1] |
|
|
|
|
|
|
|
|
mask = mask.convert('L') |
|
|
|
|
|
image=image.convert('RGB') |
|
|
|
|
|
return (image, mask) |
|
|
|
|
|
|
|
|
|
|
|
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.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): |
|
|
|
|
|
r, g, b = pixel_data[i, j] |
|
|
|
|
|
|
|
|
total_red += r |
|
|
total_green += g |
|
|
total_blue += b |
|
|
|
|
|
|
|
|
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) |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
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) |
|
|
|
|
|
|
|
|
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): |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
_, mask = cv2.threshold(alpha_channel, 127, 255, cv2.THRESH_BINARY) |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
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): |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
enhancer = ImageEnhance.Contrast(depth_map) |
|
|
|
|
|
|
|
|
enhanced_depth_map = enhancer.enhance(contrast) |
|
|
|
|
|
return enhanced_depth_map |
|
|
|
|
|
|
|
|
def detect_faces(image): |
|
|
|
|
|
|
|
|
image = cv2.cvtColor(np.array(image), cv2.COLOR_RGBA2BGRA) |
|
|
|
|
|
|
|
|
gray = cv2.cvtColor(image, cv2.COLOR_BGR2GRAY) |
|
|
|
|
|
|
|
|
face_cascade = cv2.CascadeClassifier(cv2.data.haarcascades + 'haarcascade_frontalface_default.xml') |
|
|
|
|
|
|
|
|
faces = face_cascade.detectMultiScale(gray, scaleFactor=1.05, minNeighbors=5, minSize=(50, 50)) |
|
|
|
|
|
|
|
|
mask = np.zeros_like(gray) |
|
|
|
|
|
|
|
|
for (x, y, w, h) in faces: |
|
|
|
|
|
cv2.rectangle(image, (x, y), (x + w, y + h), (0, 255, 0), 2) |
|
|
|
|
|
|
|
|
mask[y:y+h, x:x+w] = 255 |
|
|
|
|
|
|
|
|
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 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
import cv2 |
|
|
import numpy as np |
|
|
|
|
|
|
|
|
|
|
|
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 |
|
|
|
|
|
|
|
|
blended = (blended * 255).astype(np.uint8) |
|
|
|
|
|
blended=opencv_to_pil(blended) |
|
|
return blended |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
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") |
|
|
|
|
|
|
|
|
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), Image.NEAREST) |
|
|
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), Image.NEAREST) |
|
|
elif scale_option == "overall": |
|
|
|
|
|
layer_image = layer_image.resize((width, height), Image.NEAREST) |
|
|
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 |
|
|
|
|
|
|
|
|
nw, nh = layer_image.size |
|
|
mask = mask.resize((nw, nh), Image.NEAREST) |
|
|
|
|
|
|
|
|
layer_image = layer_image.filter(ImageFilter.SMOOTH) |
|
|
|
|
|
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)) |
|
|
|
|
|
for i in range(transparent_img.size[0]): |
|
|
for j in range(transparent_img.size[1]): |
|
|
r, g, b, a = transparent_img.getpixel((i, j)) |
|
|
if a > 0: |
|
|
transparent_img.putpixel((i, j), (r, g, b, 255)) |
|
|
|
|
|
transparent_img.paste(layer_image, (0, 0), mask) |
|
|
bg_image.paste(transparent_img, (x, y), transparent_img) |
|
|
|
|
|
|
|
|
return bg_image |
|
|
|
|
|
|
|
|
|
|
|
def resize_2(img): |
|
|
|
|
|
if img.height % 2 != 0: |
|
|
img = img.resize((img.width, img.height + 1)) |
|
|
|
|
|
|
|
|
if img.width % 2 != 0: |
|
|
img = img.resize((img.width + 1, img.height)) |
|
|
|
|
|
return img |
|
|
|
|
|
|
|
|
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 = height / original_height |
|
|
new_width = int(original_width * scale) |
|
|
layer_image = layer_image.resize((new_width, height)) |
|
|
|
|
|
elif scale_option == "width": |
|
|
|
|
|
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 == "center": |
|
|
|
|
|
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, font_path, font_size, text_color, vertical=True, stroke=False, stroke_color=(0, 0, 0), stroke_width=1, spacing=0, padding=4): |
|
|
|
|
|
lines = text.split("\n") |
|
|
|
|
|
|
|
|
font = ImageFont.truetype(font_path, font_size) |
|
|
|
|
|
|
|
|
if vertical: |
|
|
layout = "vertical" |
|
|
else: |
|
|
layout = "horizontal" |
|
|
|
|
|
|
|
|
char_coordinates = [] |
|
|
x, y = padding, padding |
|
|
max_width, max_height = 0, 0 |
|
|
|
|
|
if layout == "vertical": |
|
|
for line in lines: |
|
|
max_char_width = max(font.getsize(char)[0] for char in line) |
|
|
for char in line: |
|
|
char_width, char_height = font.getsize(char) |
|
|
char_coordinates.append((x, y)) |
|
|
y += char_height + spacing |
|
|
max_height = max(max_height, y + padding) |
|
|
x += max_char_width + spacing |
|
|
y = padding |
|
|
max_width = x |
|
|
else: |
|
|
for line in lines: |
|
|
line_width, line_height = font.getsize(line) |
|
|
for char in line: |
|
|
char_width, char_height = font.getsize(char) |
|
|
char_coordinates.append((x, y)) |
|
|
x += char_width + spacing |
|
|
max_width = max(max_width, x + padding) |
|
|
y += line_height + spacing |
|
|
x = padding |
|
|
max_height = y |
|
|
|
|
|
|
|
|
image = Image.new('RGBA', (max_width, max_height), (255, 255, 255, 0)) |
|
|
draw = ImageDraw.Draw(image) |
|
|
|
|
|
|
|
|
index = 0 |
|
|
for line in lines: |
|
|
for char in line: |
|
|
x, y = char_coordinates[index] |
|
|
if stroke: |
|
|
draw.text((x-stroke_width, y), char, font=font, fill=text_color) |
|
|
draw.text((x+stroke_width, y), char, font=font, fill=text_color) |
|
|
draw.text((x, y-stroke_width), char, font=font, fill=text_color) |
|
|
draw.text((x, y+stroke_width), char, font=font, fill=text_color) |
|
|
|
|
|
draw.text((x, y), char, font=font, fill=text_color) |
|
|
index += 1 |
|
|
|
|
|
|
|
|
alpha_channel = image.split()[3] |
|
|
|
|
|
|
|
|
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) |
|
|
|
|
|
|
|
|
image_data = base64.b64decode(base64_data) |
|
|
|
|
|
|
|
|
image_stream = io.BytesIO(image_data) |
|
|
|
|
|
|
|
|
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): |
|
|
|
|
|
print('SmoothMask',mask.shape) |
|
|
mask=tensor2pil(mask) |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
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,) |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
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" |
|
|
|
|
|
|
|
|
OUTPUT_IS_LIST = (True,True,True,) |
|
|
|
|
|
|
|
|
|
|
|
def run(self,images,masks,invert,save,filename_prefix,prompt=None, extra_pnginfo=None): |
|
|
|
|
|
|
|
|
|
|
|
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: |
|
|
|
|
|
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 |
|
|
|
|
|
|
|
|
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']) |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
return {"ui":{"images": ui_images,"image_paths":image_paths},"result": (image_paths,images_rgb,images_rgba)} |
|
|
|
|
|
|
|
|
|
|
|
class ImagesPrompt: |
|
|
@classmethod |
|
|
def INPUT_TYPES(s): |
|
|
|
|
|
|
|
|
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): |
|
|
|
|
|
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): |
|
|
|
|
|
|
|
|
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; |
|
|
''' |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
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, |
|
|
"max": 2048, |
|
|
"step": 1, |
|
|
"display": "number" |
|
|
}), |
|
|
"watcher":(["disable","enable"],), |
|
|
"result": ("WATCHER",), |
|
|
"prompt": ("PROMPT",), |
|
|
|
|
|
} |
|
|
} |
|
|
|
|
|
RETURN_TYPES = ('IMAGE','MASK','STRING','STRING',) |
|
|
RETURN_NAMES = ("IMAGE","MASK","prompt_for_FloatingVideo","filepaths",) |
|
|
|
|
|
FUNCTION = "run" |
|
|
|
|
|
CATEGORY = "♾️Mixlab/Image" |
|
|
|
|
|
|
|
|
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 |
|
|
|
|
|
|
|
|
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() |
|
|
|
|
|
|
|
|
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']) |
|
|
|
|
|
|
|
|
|
|
|
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)) |
|
|
|
|
|
|
|
|
return {"ui": {"seed": [1]}, "result":(imgs,masks,prompt,file_names,)} |
|
|
|
|
|
|
|
|
|
|
|
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): |
|
|
|
|
|
|
|
|
image=image[0] |
|
|
RGBA=RGBA[0] |
|
|
|
|
|
bf_im = tensor2pil(image) |
|
|
|
|
|
|
|
|
im=tensor2pil(RGBA) |
|
|
|
|
|
|
|
|
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) |
|
|
|
|
|
|
|
|
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, :] |
|
|
|
|
|
|
|
|
|
|
|
ori=RGBA[:,y:to_y, x:to_x, :] |
|
|
ori=tensor2pil(ori) |
|
|
|
|
|
|
|
|
|
|
|
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]): |
|
|
|
|
|
r, g, b, a = pixel_data[x, y] |
|
|
|
|
|
|
|
|
if a != 0: |
|
|
new_pixel_data[x, y] = (255, 255, 255, 255) |
|
|
else: |
|
|
new_pixel_data[x, y] = (0,0,0,0) |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
ori=new_image.convert('L') |
|
|
|
|
|
|
|
|
ori=pil2tensor(ori) |
|
|
|
|
|
|
|
|
b_image =AreaToMask_run(RGBA) |
|
|
|
|
|
|
|
|
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": (get_files_with_extension(FONT_PATH,'.ttf'),), |
|
|
"font_size": ("INT",{ |
|
|
"default":100, |
|
|
"min": 100, |
|
|
"max": 1000, |
|
|
"step": 1, |
|
|
"display": "number" |
|
|
}), |
|
|
"spacing": ("INT",{ |
|
|
"default":12, |
|
|
"min": -200, |
|
|
"max": 200, |
|
|
"step": 1, |
|
|
"display": "number" |
|
|
}), |
|
|
"padding": ("INT",{ |
|
|
"default":8, |
|
|
"min": 0, |
|
|
"max": 200, |
|
|
"step": 1, |
|
|
"display": "number" |
|
|
}), |
|
|
"text_color":("STRING",{"multiline": False,"default": "#000000","dynamicPrompts": False}), |
|
|
"vertical":("BOOLEAN", {"default": True},), |
|
|
"stroke":("BOOLEAN", {"default": False},), |
|
|
}, |
|
|
} |
|
|
|
|
|
RETURN_TYPES = ("IMAGE","MASK",) |
|
|
RETURN_NAMES = ("image","mask",) |
|
|
|
|
|
FUNCTION = "run" |
|
|
|
|
|
CATEGORY = "♾️Mixlab/Image" |
|
|
|
|
|
INPUT_IS_LIST = False |
|
|
OUTPUT_IS_LIST = (False,False,) |
|
|
|
|
|
def run(self,text,font,font_size,spacing,padding,text_color,vertical,stroke): |
|
|
|
|
|
font_path=os.path.join(FONT_PATH,font+'.ttf') |
|
|
|
|
|
if text=="": |
|
|
text=" " |
|
|
|
|
|
img,mask=generate_text_image(text,font_path,font_size,text_color,vertical,stroke,(0, 0, 0),1,spacing,padding) |
|
|
|
|
|
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): |
|
|
|
|
|
|
|
|
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): |
|
|
|
|
|
im=tensor2pil(RGBA) |
|
|
im=naive_cutout(im,im) |
|
|
x, y, w, h=get_not_transparent_area(im) |
|
|
|
|
|
im=im.convert("RGBA") |
|
|
|
|
|
img=areaToMask(x,y,w,h,im) |
|
|
img=img.convert("RGBA") |
|
|
mask=pil2tensor(img) |
|
|
|
|
|
channels = ["red", "green", "blue", "alpha"] |
|
|
|
|
|
mask = mask[:, :, :, channels.index("green")] |
|
|
|
|
|
return mask |
|
|
|
|
|
|
|
|
class AreaToMask: |
|
|
@classmethod |
|
|
def INPUT_TYPES(s): |
|
|
return {"required": { "RGBA": ("RGBA",), }, |
|
|
} |
|
|
|
|
|
RETURN_TYPES = ("MASK",) |
|
|
|
|
|
|
|
|
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",) |
|
|
|
|
|
|
|
|
FUNCTION = "run" |
|
|
|
|
|
CATEGORY = "♾️Mixlab/Mask" |
|
|
|
|
|
INPUT_IS_LIST = False |
|
|
OUTPUT_IS_LIST = (False,) |
|
|
|
|
|
def run(self,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_center","left_bottom","center_bottom","right_bottom","left_top","center_top","right_top"],), |
|
|
"scale": ("FLOAT",{ |
|
|
"default":0.35, |
|
|
"min": 0.01, |
|
|
"max": 1, |
|
|
"step": 0.01, |
|
|
"display": "number" |
|
|
}), |
|
|
} |
|
|
} |
|
|
|
|
|
RETURN_TYPES = ("IMAGE",) |
|
|
RETURN_NAMES = ("IMAGE",) |
|
|
|
|
|
FUNCTION = "run" |
|
|
|
|
|
CATEGORY = "♾️Mixlab/Layer" |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def run(self, foreground,mask,background, is_multiply_blend, position, scale): |
|
|
results = [] |
|
|
|
|
|
f1=[] |
|
|
for fg, mask in zip(foreground, mask ): |
|
|
f1.append([fg,mask]) |
|
|
|
|
|
|
|
|
for f, bg in product(f1, background): |
|
|
[fg,mask]=f |
|
|
fg_pil = tensor2pil(fg) |
|
|
mask_pil = tensor2pil(mask) |
|
|
bg_pil = tensor2pil(bg) |
|
|
res = composite_images(fg_pil, bg_pil, mask_pil, is_multiply_blend, position, scale) |
|
|
results.append(pil2tensor(res)) |
|
|
|
|
|
output_image = torch.cat(results, dim=0) |
|
|
|
|
|
return (output_image,) |
|
|
|
|
|
|
|
|
class EmptyLayer: |
|
|
@classmethod |
|
|
def INPUT_TYPES(s): |
|
|
return { |
|
|
"required": { |
|
|
"width": ("INT",{ |
|
|
"default":512, |
|
|
"min": 1, |
|
|
"max": 8192, |
|
|
"step": 1, |
|
|
"display": "number" |
|
|
}), |
|
|
"height": ("INT",{ |
|
|
"default": 512, |
|
|
"min": 1, |
|
|
"max": 8192, |
|
|
"step": 1, |
|
|
"display": "number" |
|
|
}), |
|
|
}, |
|
|
|
|
|
} |
|
|
|
|
|
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, |
|
|
"max": 8192, |
|
|
"step": 1, |
|
|
"display": "number" |
|
|
}), |
|
|
"y": ("INT",{ |
|
|
"default": 0, |
|
|
"min": -1024, |
|
|
"max": 8192, |
|
|
"step": 1, |
|
|
"display": "number" |
|
|
}), |
|
|
"width": ("INT",{ |
|
|
"default": 512, |
|
|
"min": 1, |
|
|
"max": 8192, |
|
|
"step": 1, |
|
|
"display": "number" |
|
|
}), |
|
|
"height": ("INT",{ |
|
|
"default": 512, |
|
|
"min": 1, |
|
|
"max": 8192, |
|
|
"step": 1, |
|
|
"display": "number" |
|
|
}), |
|
|
"z_index": ("INT",{ |
|
|
"default": 0, |
|
|
"min":0, |
|
|
"max": 100, |
|
|
"step": 1, |
|
|
"display": "number" |
|
|
}), |
|
|
"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): |
|
|
|
|
|
|
|
|
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() |
|
|
|
|
|
for i in range(int(x), int(x + w)): |
|
|
for j in range(int(y), int(y + h)): |
|
|
pixels[i, j] = 255 |
|
|
|
|
|
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) |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
class SplitImage: |
|
|
@classmethod |
|
|
def INPUT_TYPES(s): |
|
|
return { |
|
|
"required": { |
|
|
"image": ("IMAGE",), |
|
|
"num": ("INT",{ |
|
|
"default": 4, |
|
|
"min": 1, |
|
|
"max": 500, |
|
|
"step": 1, |
|
|
"display": "number" |
|
|
}), |
|
|
"seed": ("INT",{ |
|
|
"default": 4, |
|
|
"min": 1, |
|
|
"max": 500, |
|
|
"step": 1, |
|
|
"display": "number" |
|
|
}), |
|
|
} |
|
|
} |
|
|
|
|
|
RETURN_TYPES = ("_GRID","_GRID","MASK",) |
|
|
RETURN_NAMES = ("grids","grid","mask",) |
|
|
|
|
|
FUNCTION = "run" |
|
|
|
|
|
CATEGORY = "♾️Mixlab/Layer" |
|
|
|
|
|
INPUT_IS_LIST = False |
|
|
|
|
|
|
|
|
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, |
|
|
"max": 5000, |
|
|
"step": 1, |
|
|
"display": "number" |
|
|
}), |
|
|
"top": ("INT",{ |
|
|
"default":24, |
|
|
"min": 0, |
|
|
"max": 5000, |
|
|
"step": 1, |
|
|
"display": "number" |
|
|
}), |
|
|
"right": ("INT",{ |
|
|
"default": 24, |
|
|
"min": 0, |
|
|
"max": 5000, |
|
|
"step": 1, |
|
|
"display": "number" |
|
|
}), |
|
|
"bottom": ("INT",{ |
|
|
"default": 24, |
|
|
"min": 0, |
|
|
"max": 5000, |
|
|
"step": 1, |
|
|
"display": "number" |
|
|
}), |
|
|
} |
|
|
} |
|
|
|
|
|
RETURN_TYPES = ("_GRID","MASK",) |
|
|
RETURN_NAMES = ("grid","mask",) |
|
|
|
|
|
FUNCTION = "run" |
|
|
|
|
|
CATEGORY = "♾️Mixlab/Layer" |
|
|
|
|
|
INPUT_IS_LIST = False |
|
|
|
|
|
|
|
|
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 |
|
|
|
|
|
|
|
|
def run(self,labels,grids,image,filename_prefix): |
|
|
|
|
|
|
|
|
|
|
|
img= tensor2pil(image[0]) |
|
|
|
|
|
for grid in grids: |
|
|
draw_rectangle(img, grid, 'red',8) |
|
|
|
|
|
|
|
|
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": ()} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
class GridInput: |
|
|
@classmethod |
|
|
def INPUT_TYPES(s): |
|
|
return { |
|
|
"required": { |
|
|
"grids": ("STRING", |
|
|
{ |
|
|
"multiline": True, |
|
|
"default": "", |
|
|
"dynamicPrompts": False |
|
|
}), |
|
|
"padding":("INT",{ |
|
|
"default": 24, |
|
|
"min": -500, |
|
|
"max": 5000, |
|
|
"step": 1, |
|
|
"display": "number" |
|
|
}), |
|
|
|
|
|
}, |
|
|
"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]): |
|
|
|
|
|
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) |
|
|
|
|
|
|
|
|
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))] |
|
|
|
|
|
|
|
|
return {"ui":{ |
|
|
"json":[data["width"],data["height"],data_converted] |
|
|
},"result": (new_grids,labels,im,)} |
|
|
|
|
|
|
|
|
|
|
|
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 |
|
|
|
|
|
|
|
|
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] |
|
|
|
|
|
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, |
|
|
"max": 8192, |
|
|
"step": 1, |
|
|
"display": "number" |
|
|
}), |
|
|
"y": ("INT",{ |
|
|
"default": 0, |
|
|
"min": 0, |
|
|
"max": 8192, |
|
|
"step": 1, |
|
|
"display": "number" |
|
|
}), |
|
|
"width": ("INT",{ |
|
|
"default": 512, |
|
|
"min": 1, |
|
|
"max": 8192, |
|
|
"step": 1, |
|
|
"display": "number" |
|
|
}), |
|
|
"height": ("INT",{ |
|
|
"default": 512, |
|
|
"min": 1, |
|
|
"max": 8192, |
|
|
"step": 1, |
|
|
"display": "number" |
|
|
}), |
|
|
"z_index": ("INT",{ |
|
|
"default": 0, |
|
|
"min":0, |
|
|
"max": 100, |
|
|
"step": 1, |
|
|
"display": "number" |
|
|
}), |
|
|
"scale_option": (["width","height",'overall'],), |
|
|
|
|
|
}, |
|
|
"optional":{ |
|
|
|
|
|
"layers": ("LAYER",{"default": None}), |
|
|
} |
|
|
} |
|
|
|
|
|
RETURN_TYPES = ( ) |
|
|
RETURN_NAMES = ( ) |
|
|
|
|
|
FUNCTION = "run" |
|
|
|
|
|
CATEGORY = "♾️Mixlab/Layer" |
|
|
|
|
|
INPUT_IS_LIST = True |
|
|
|
|
|
|
|
|
def run(self,edit,x,y,width,height,z_index,scale_option,layers): |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
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 |
|
|
|
|
|
|
|
|
def run(self,layers,images,is_multiply_blend): |
|
|
|
|
|
bg_images=[] |
|
|
masks=[] |
|
|
|
|
|
is_multiply_blend=is_multiply_blend[0] |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
for img in images: |
|
|
|
|
|
for bg_image in img: |
|
|
|
|
|
bg_image=tensor2pil(bg_image) |
|
|
|
|
|
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) |
|
|
|
|
|
|
|
|
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') |
|
|
|
|
|
|
|
|
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" |
|
|
}), |
|
|
"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) |
|
|
|
|
|
|
|
|
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) |
|
|
|
|
|
|
|
|
im=pil2tensor(im) |
|
|
|
|
|
mask=pil2tensor(mask) |
|
|
|
|
|
|
|
|
|
|
|
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" |
|
|
}), |
|
|
"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) |
|
|
|
|
|
|
|
|
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) |
|
|
|
|
|
|
|
|
im=pil2tensor(im) |
|
|
|
|
|
|
|
|
|
|
|
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, |
|
|
"max": 8192, |
|
|
"step": 8, |
|
|
"display": "number" |
|
|
}), |
|
|
"height": ("INT",{ |
|
|
"default": 512, |
|
|
"min": 1, |
|
|
"max": 8192, |
|
|
"step": 8, |
|
|
"display": "number" |
|
|
}), |
|
|
"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, |
|
|
"max": 2048, |
|
|
"step": 8, |
|
|
"display": "number" |
|
|
}) |
|
|
}, |
|
|
} |
|
|
|
|
|
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 |
|
|
|
|
|
|
|
|
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 ComparingTwoFrames: |
|
|
def __init__(self): |
|
|
self.output_dir = folder_paths.get_output_directory() |
|
|
self.type = "output" |
|
|
self.prefix_append = "ComparingTwoFrames" |
|
|
self.compress_level = 4 |
|
|
|
|
|
@classmethod |
|
|
def INPUT_TYPES(s): |
|
|
return {"required": |
|
|
{"before_image": ("IMAGE", ), |
|
|
"after_image": ("IMAGE", ) |
|
|
}, |
|
|
} |
|
|
|
|
|
RETURN_TYPES = () |
|
|
FUNCTION = "comparingImages" |
|
|
|
|
|
OUTPUT_NODE = True |
|
|
|
|
|
CATEGORY = "♾️Mixlab/Output" |
|
|
|
|
|
def comparingImages(self, before_image,after_image): |
|
|
filename_prefix = self.prefix_append |
|
|
full_output_folder, filename, counter, subfolder, filename_prefix = folder_paths.get_save_image_path( |
|
|
filename_prefix, self.output_dir, after_image[0].shape[1], after_image[0].shape[0]) |
|
|
|
|
|
bresults = list() |
|
|
|
|
|
for bimage in before_image: |
|
|
i = 255. * bimage.cpu().numpy() |
|
|
img = Image.fromarray(np.clip(i, 0, 255).astype(np.uint8)) |
|
|
|
|
|
file = f"{filename}_{counter:05}_.png" |
|
|
img.save(os.path.join(full_output_folder, file), pnginfo=None, compress_level=self.compress_level) |
|
|
bresults.append({ |
|
|
"filename": file, |
|
|
"subfolder": subfolder, |
|
|
"type": self.type |
|
|
}) |
|
|
counter += 1 |
|
|
|
|
|
|
|
|
results = list() |
|
|
for aimage in after_image: |
|
|
i = 255. * aimage.cpu().numpy() |
|
|
img = Image.fromarray(np.clip(i, 0, 255).astype(np.uint8)) |
|
|
|
|
|
file = f"{filename}_{counter:05}_.png" |
|
|
img.save(os.path.join(full_output_folder, file), pnginfo=None, compress_level=self.compress_level) |
|
|
results.append({ |
|
|
"filename": file, |
|
|
"subfolder": subfolder, |
|
|
"type": self.type |
|
|
}) |
|
|
counter += 1 |
|
|
|
|
|
return { "ui": { "after_images": results,"before_images":bresults } } |
|
|
|
|
|
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" |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def run(self,source,target,weight): |
|
|
|
|
|
res=[] |
|
|
|
|
|
|
|
|
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) |
|
|
|
|
|
|
|
|
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) |
|
|
|
|
|
|
|
|
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) |
|
|
|
|
|
|
|
|
|
|
|
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(file_path) |
|
|
print("目录已创建") |
|
|
else: |
|
|
print("目录已存在") |
|
|
|
|
|
|
|
|
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 () |
|
|
|
|
|
|
|
|
class ImageBatchToList_: |
|
|
@classmethod |
|
|
def INPUT_TYPES(s): |
|
|
return {"required": {"image_batch": ("IMAGE",), }} |
|
|
|
|
|
RETURN_TYPES = ("IMAGE",) |
|
|
RETURN_NAMES = ("image_list",) |
|
|
OUTPUT_IS_LIST = (True,) |
|
|
FUNCTION = "run" |
|
|
|
|
|
CATEGORY = "♾️Mixlab/Image" |
|
|
|
|
|
def run(self, image_batch): |
|
|
images = [image_batch[i:i + 1, ...] for i in range(image_batch.shape[0])] |
|
|
return (images, ) |
|
|
|
|
|
class ImageListToBatch_: |
|
|
@classmethod |
|
|
def INPUT_TYPES(s): |
|
|
return { |
|
|
"required": { |
|
|
"images": ("IMAGE",), |
|
|
} |
|
|
} |
|
|
|
|
|
RETURN_TYPES = ("IMAGE",) |
|
|
FUNCTION = "run" |
|
|
INPUT_IS_LIST = True |
|
|
CATEGORY = "♾️Mixlab/Image" |
|
|
|
|
|
def run(self, images): |
|
|
shape = images[0].shape[1:3] |
|
|
out = [] |
|
|
|
|
|
for i in range(len(images)): |
|
|
img = images[i].permute([0,3,1,2]) |
|
|
if images[i].shape[1:3] != shape: |
|
|
transforms = T.Compose([ |
|
|
T.CenterCrop(min(img.shape[2], img.shape[3])), |
|
|
T.Resize((shape[0], shape[1]), interpolation=T.InterpolationMode.BICUBIC), |
|
|
]) |
|
|
img = transforms(img) |
|
|
out.append(img.permute([0,2,3,1])) |
|
|
|
|
|
out = torch.cat(out, dim=0) |
|
|
|
|
|
return (out,) |