StupidGame's picture
Upload 1941 files
baa8e90
#---------------------------------------------------------------------------------------------------------------------#
# Comfyroll Nodes by RockOfFire and Akatsuzi https://github.com/RockOfFire/CR-Animation-Nodes
# for ComfyUI https://github.com/comfyanonymous/ComfyUI
#---------------------------------------------------------------------------------------------------------------------#
import os
import folder_paths
from PIL import Image, ImageFont
import torch
import numpy as np
import re
from pathlib import Path
import typing as t
from dataclasses import dataclass
from ..nodes.xygrid_functions import create_images_grid_by_columns, Annotation
from ..categories import icons
def tensor_to_pillow(image: t.Any) -> Image.Image:
return Image.fromarray(np.clip(255. * image.cpu().numpy().squeeze(), 0, 255).astype(np.uint8))
def pillow_to_tensor(image: Image.Image) -> t.Any:
return torch.from_numpy(np.array(image).astype(np.float32) / 255.0).unsqueeze(0)
def find_highest_numeric_value(directory, filename_prefix):
highest_value = -1 # Initialize with a value lower than possible numeric values
# Iterate through all files in the directory
for filename in os.listdir(directory):
if filename.startswith(filename_prefix):
try:
# Extract numeric part of the filename
numeric_part = filename[len(filename_prefix):]
numeric_str = re.search(r'\d+', numeric_part).group()
numeric_value = int(numeric_str)
# Check if the current numeric value is higher than the highest found so far
if numeric_value > highest_value:
highest_value = int(numeric_value)
except ValueError:
# If the numeric part is not a valid integer, ignore the file
continue
return highest_value
#---------------------------------------------------------------------------------------------------------------------#
# NODES
#---------------------------------------------------------------------------------------------------------------------#
# These nodes are based on https://github.com/LEv145/images-grid-comfy-plugin
#---------------------------------------------------------------------------------------------------------------------
class CR_XYZList:
@classmethod
def INPUT_TYPES(s):
return {"required":{
"index": ("INT", {"default": 0.0, "min": 0.0, "max": 9999.0, "step": 1.0,}),
"list1": ("STRING", {"multiline": True, "default": "x"}),
"x_prepend": ("STRING", {"multiline": False, "default": ""}),
"x_append": ("STRING", {"multiline": False, "default": ""}),
"x_annotation_prepend": ("STRING", {"multiline": False, "default": ""}),
"list2": ("STRING", {"multiline": True, "default": "y"}),
"y_prepend": ("STRING", {"multiline": False, "default": ""}),
"y_append": ("STRING", {"multiline": False, "default": ""}),
"y_annotation_prepend": ("STRING", {"multiline": False, "default": ""}),
"list3": ("STRING", {"multiline": True, "default": "z"}),
"z_prepend": ("STRING", {"multiline": False, "default": ""}),
"z_append": ("STRING", {"multiline": False, "default": ""}),
"z_annotation_prepend": ("STRING", {"multiline": False, "default": ""}),
}
}
RETURN_TYPES = ("STRING", "STRING", "STRING", "STRING", "STRING", "STRING", "BOOLEAN",)
RETURN_NAMES = ("X", "Y", "Z", "x_annotation", "y_annotation", "z_annotation", "trigger",)
FUNCTION = "cross_join"
CATEGORY = icons.get("Comfyroll/XY Grid")
def cross_join(self, list1, list2, list3,
x_prepend, x_append, x_annotation_prepend,
y_prepend, y_append, y_annotation_prepend,
z_prepend, z_append, z_annotation_prepend, index):
# Index values for all XY nodes start from 1
index -=1
trigger = False
listx = re.split(r',(?=(?:[^"]*"[^"]*")*[^"]*$)', list1)
listy = re.split(r',(?=(?:[^"]*"[^"]*")*[^"]*$)', list2)
listz = re.split(r',(?=(?:[^"]*"[^"]*")*[^"]*$)', list3)
listx = [item.strip() for item in listx]
listy = [item.strip() for item in listy]
listz = [item.strip() for item in listz]
lenx = len(listx)
leny = len(listy)
lenz = len(listz)
sheet_size = lenx * leny
grid_size = lenx * leny * lenz
x = index % lenx
y = int(index / lenx) % leny
z = int(index / sheet_size)
x_out = x_prepend + listx[x] + x_append
y_out = y_prepend + listy[y] + y_append
z_out = z_prepend + listz[z] + z_append
x_ann_out = ""
y_ann_out = ""
z_ann_out = ""
if index + 1 == grid_size:
x_ann_out = [x_annotation_prepend + item + ";" for item in listx]
y_ann_out = [y_annotation_prepend + item + ";" for item in listy]
z_ann_out = [z_annotation_prepend + item + ";" for item in listz]
x_ann_out = "".join([str(item) for item in x_ann_out])
y_ann_out = "".join([str(item) for item in y_ann_out])
z_ann_out = "".join([str(item) for item in z_ann_out])
trigger = True
return (x_out, y_out, z_out, x_ann_out, y_ann_out, z_ann_out, trigger, )
#---------------------------------------------------------------------------------------------------------------------
class CR_XYZInterpolate:
@classmethod
def INPUT_TYPES(s):
gradient_profiles = ["Lerp"]
return {"required": {"x_columns":("INT", {"default": 5.0, "min": 0.0, "max": 9999.0, "step": 1.0,}),
"x_start_value": ("FLOAT", {"default": 0.0, "min": 0.0, "max": 9999.0, "step": 0.01,}),
"x_step": ("FLOAT", {"default": 1.0, "min": 0.0, "max": 9999.0, "step": 0.01,}),
"x_annotation_prepend": ("STRING", {"multiline": False, "default": ""}),
"y_rows":("INT", {"default": 5.0, "min": 0.0, "max": 9999.0, "step": 1.0,}),
"y_start_value": ("FLOAT", {"default": 0.0, "min": 0.0, "max": 9999.0, "step": 0.01,}),
"y_step": ("FLOAT", {"default": 1.0, "min": 0.0, "max": 9999.0, "step": 0.01,}),
"y_annotation_prepend": ("STRING", {"multiline": False, "default": ""}),
"z_sheets":("INT", {"default": 5.0, "min": 0.0, "max": 9999.0, "step": 1.0,}),
"z_start_value": ("FLOAT", {"default": 0.0, "min": 0.0, "max": 9999.0, "step": 0.01,}),
"z_step": ("FLOAT", {"default": 1.0, "min": 0.0, "max": 9999.0, "step": 0.01,}),
"z_annotation_prepend": ("STRING", {"multiline": False, "default": ""}),
"index": ("INT", {"default": 0.0, "min": 0.0, "max": 9999.0, "step": 1.0,}),
"gradient_profile": (gradient_profiles,)
}
}
RETURN_TYPES = ("FLOAT", "FLOAT", "FLOAT", "STRING", "STRING", "STRING", "BOOLEAN", )
RETURN_NAMES = ("X", "Y", "Z", "x_annotation", "y_annotation", "z_annotation", "trigger", )
FUNCTION = "gradient"
CATEGORY = icons.get("Comfyroll/XY Grid")
def gradient(self, x_columns, x_start_value, x_step, x_annotation_prepend,
y_rows, y_start_value, y_step, y_annotation_prepend,
z_rows, z_start_value, z_step, z_annotation_prepend,
index, gradient_profile):
# Index values for all XY nodes start from 1
index -=1
trigger = False
sheet_size = x_columns * y_rows
grid_size = x_columns * y_rows * z_sheets
x = index % x_columns
y = int(index / x_columns) % y_rows
z = int(index / sheet_size)
x_float_out = round(x_start_value + x * x_step, 3)
y_float_out = round(y_start_value + y * y_step, 3)
z_float_out = round(z_start_value + z * z_step, 3)
x_ann_out = ""
y_ann_out = ""
z_ann_out = ""
if index + 1 == grid_size:
for i in range(0, x_columns):
x = index % x_columns
x_float_out = x_start_value + i * x_step
x_float_out = round(x_float_out, 3)
x_ann_out = x_ann_out + x_annotation_prepend + str(x_float_out) + "; "
for j in range(0, y_rows):
y = int(index / x_columns)
y_float_out = y_start_value + j * y_step
y_float_out = round(y_float_out, 3)
y_ann_out = y_ann_out + y_annotation_prepend + str(y_float_out) + "; "
for k in range(0, z_sheets):
z = int(index / x_columns)
z_float_out = z_start_value + k * z_step
z_float_out = round(z_float_out, 3)
z_ann_out = z_ann_out + z_annotation_prepend + str(z_float_out) + "; "
x_ann_out = x_ann_out[:-1]
y_ann_out = y_ann_out[:-1]
z_ann_out = z_ann_out[:-1]
print(x_ann_out,y_ann_out,z_ann_out)
trigger = True
return (x_float_out, y_float_out, z_float_out, x_ann_out, y_ann_out, z_ann_out, trigger)
#---------------------------------------------------------------------------------------------------------------------
class CR_XYZIndex:
@classmethod
def INPUT_TYPES(s):
gradient_profiles = ["Lerp"]
return {"required": {"x_columns":("INT", {"default": 5.0, "min": 0.0, "max": 9999.0, "step": 1.0,}),
"y_rows":("INT", {"default": 5.0, "min": 0.0, "max": 9999.0, "step": 1.0,}),
"z_sheets":("INT", {"default": 0.0, "min": 0.0, "max": 9999.0, "step": 1.0,}),
"index": ("INT", {"default": 0.0, "min": 0.0, "max": 9999.0, "step": 1.0,}),
}
}
RETURN_TYPES = ("INT", "INT", "INT",)
RETURN_NAMES = ("x", "y", "z",)
FUNCTION = "index"
CATEGORY = icons.get("Comfyroll/XY Grid")
def index(self, x_columns, y_rows, z_sheets, index):
# Index values for all XY nodes start from 1
index -=1
sheet_size = x_columns * y_rows
x = index % x_columns
y = int(index / x_columns) % y_rows
z = int(index / sheet_size)
#print (x,y,z)
return (x, y, z)
#---------------------------------------------------------------------------------------------------------------------#
class CR_LoadXYAnnotationFromFile:
@classmethod
def INPUT_TYPES(s):
return {"required": {
"input_file_path": ("STRING", {"multiline": False, "default": ""}),
"file_name": ("STRING", {"multiline": False, "default": ""}),
"file_extension": (["txt", "csv"],),
}
}
RETURN_TYPES = ("GRID_ANNOTATION", "STRING", )
RETURN_NAMES = ("GRID_ANNOTATION", "show_text", )
FUNCTION = "load"
CATEGORY = icons.get("Comfyroll/XY Grid")
def load(self, input_file_path, file_name, file_extension):
filepath = input_file_path + "\\" + file_name + "." + file_extension
print(f"CR_Load Schedule From File: Loading {filepath}")
lists = []
if file_extension == "csv":
with open(filepath, "r") as csv_file:
reader = csv.reader(csv_file)
for row in reader:
lists.append(row)
else:
with open(filepath, "r") as txt_file:
for row in txt_file:
parts = row.strip().split(",", 1)
if len(parts) >= 2:
second_part = parts[1].strip('"')
lists.append([parts[0], second_part])
print(lists)
return(lists,str(lists),)
#---------------------------------------------------------------------------------------------------------------------
class CR_XYGrid:
@classmethod
def INPUT_TYPES(cls):
return {"required": {
"images": ("IMAGE",),
"gap": ("INT", {"default": 0, "min": 0}),
"max_columns": ("INT", {"default": 1, "min": 1, "max": 10000}),
},
"optional": {
"annotation": ("GRID_ANNOTATION",),
}
}
RETURN_TYPES = ("IMAGE",)
FUNCTION = "create_image"
CATEGORY = icons.get("Comfyroll/XY Grid")
def create_image(self, images, gap, max_columns, annotation=None):
pillow_images = [tensor_to_pillow(i) for i in images]
pillow_grid = create_images_grid_by_columns(
images=pillow_images,
gap=gap,
annotation=annotation,
max_columns=max_columns,
)
tensor_grid = pillow_to_tensor(pillow_grid)
return (tensor_grid,)
#---------------------------------------------------------------------------------------------------------------------
class CR_XYSaveGridImage:
# originally based on SaveImageSequence by mtb
def __init__(self):
self.type = "output"
@classmethod
def INPUT_TYPES(cls):
output_dir = folder_paths.output_directory
output_folders = [name for name in os.listdir(output_dir) if os.path.isdir(os.path.join(output_dir,name))]
return {
"required": {"mode": (["Save", "Preview"],),
"output_folder": (sorted(output_folders), ),
"image": ("IMAGE", ),
"filename_prefix": ("STRING", {"default": "CR"}),
"file_format": (["webp", "jpg", "png", "tif"],),
},
"optional": {"output_path": ("STRING", {"default": '', "multiline": False}),
"trigger": ("BOOLEAN", {"default": False},),
}
}
RETURN_TYPES = ()
FUNCTION = "save_image"
OUTPUT_NODE = True
CATEGORY = icons.get("Comfyroll/XY Grid")
def save_image(self, mode, output_folder, image, file_format, output_path='', filename_prefix="CR", trigger=False):
if trigger == False:
return ()
output_dir = folder_paths.get_output_directory()
out_folder = os.path.join(output_dir, output_folder)
# Set the output path
if output_path != '':
if not os.path.exists(output_path):
print(f"[Warning] CR Save XY Grid Image: The input_path `{output_path}` does not exist")
return ("",)
out_path = output_path
else:
out_path = os.path.join(output_dir, out_folder)
if mode == "Preview":
out_path = folder_paths.temp_directory
print(f"[Info] CR Save XY Grid Image: Output path is `{out_path}`")
# Set the counter
counter = find_highest_numeric_value(out_path, filename_prefix) + 1
#print(f"[Debug] counter {counter}")
# Output image
output_image = image[0].cpu().numpy()
img = Image.fromarray(np.clip(output_image * 255.0, 0, 255).astype(np.uint8))
output_filename = f"{filename_prefix}_{counter:05}"
img_params = {'png': {'compress_level': 4},
'webp': {'method': 6, 'lossless': False, 'quality': 80},
'jpg': {'format': 'JPEG'},
'tif': {'format': 'TIFF'}
}
self.type = "output" if mode == "Save" else 'temp'
resolved_image_path = os.path.join(out_path, f"{output_filename}.{file_format}")
img.save(resolved_image_path, **img_params[file_format])
print(f"[Info] CR Save XY Grid Image: Saved to {output_filename}.{file_format}")
out_filename = f"{output_filename}.{file_format}"
preview = {"ui": {"images": [{"filename": out_filename,"subfolder": out_path,"type": self.type,}]}}
return preview
#---------------------------------------------------------------------------------------------------------------------
# MAPPINGS
#---------------------------------------------------------------------------------------------------------------------#
# For reference only, actual mappings are in __init__.py
# 0 nodes released
'''
NODE_CLASS_MAPPINGS = {
# XY Grid
"CR XYZ List":CR_XYZList,
"CR XYZ Index":CR_XYZIndex,
"CR XYZ Interpolate":CR_XYZInterpolate,
"CR Load XY Annotation From File":CR_LoadXYAnnotationFromFile,
"CR XY Grid":CR_XYGrid,
}
'''