diff --git a/README.md b/README.md index 2bff133..3331e72 100644 --- a/README.md +++ b/README.md @@ -6,9 +6,11 @@ A set of custom nodes for creating image grids, sequences, and batches in [Comfy * **Create Image Grid** - a node that lets you specify a grid size and then automatically queue up the full number of prompts required, outputting the current x and y position in the grid with each prompt. (It's actually a fully general `for` loop; you might find it useful for purposes other than making grids!) * **Save Image Grid** - a modified Save Image node that accumulates images until reaching the number specified in its x_size and y_size inputs, then puts them into a grid. Supports labeling the grid through the 'annotations' input (though there are currently no nodes in this extension that can provide the right output - see Recommended Resources. ) -* **Grid Floats** - Allows you to specify up to 6 floating-point numbers; the output will be selected using the 'index' input. -* **Grid Float List** - Similar to Grid Floats, but allows you to type in the list of floats manually - in case you need more than 6 or just find the selection widgets cumbersome. Commas, semicolons, colons, and whitespace are all valid delimiters - so e.g. `0.5,2.8:3.33;-2.5` is a valid input that should resolve to the list `[0.5, 2.8, 3.33, -2.5]`. -* **Float to Text** - Just converts a floating-point number to a text string. Useful for dynamically adjusting token or LoRA strengths. +* **Float/Int List** - Allows you to specify a list of up to 6 numbers; the output will be selected using the 'index' input. +* **Float/Int List from Text Field** - Similar to the basic Float/Int List nodes, but allow you to type in the list of numbers manually - in case you need more than 6 or just find the selection widgets cumbersome. Commas, semicolons, colons, and whitespace are all valid delimiters - so e.g. `0.5,2.8:3.33;-2.5 8.1` is a valid input that should resolve to the list `[0.5, 2.8, 3.33, -2.5, 8.1]`. +* **String List** - Lets you specify a list of up to 6 strings, similar to the Float/Int List nodes. +* **String List from Text Field** - Lets you enter a list of strings in a text field. Unlike the Float/Int List nodes, each entry in the list must be delimited by a new line. +* **Float/Int to Text** - Just converts a floating-point number to a text string. Useful for dynamically adjusting token or LoRA strengths, or specifying a list of seeds. * **Text Concatenator** - Transforms two strings of text into one string. Useful for adjusting your prompt according to the grid indexes. ## Future Features @@ -17,6 +19,7 @@ A set of custom nodes for creating image grids, sequences, and batches in [Comfy * **Annotations** - Labeling the output grid is currently supported...but not by any node that's part of this repository. * **String Formatting** - Even adjusting a small number of token strengths with the current nodes quickly results in an absolute spaghetti of Text Concatenators. My goal is to have a node that provides `printf`- or `str.format`-like capability, substituting input strings at specified points in the text. * **More visual feedback** - Progress bars and automatically updating counters on the Create and Save Image Grid nodes would be super helpful for knowing where you are in the loop. It might also make sense to have Save Image Grid update on the fly with the images it's received so far. +* **Z-Axis** - A1111 supports an additional axis on its image grids, represented as multiple grids on the final output. This is neat and I'd like to add something similar! ## How To Use @@ -39,7 +42,11 @@ cd /custom_nodes git clone https://github.com/shockz0rz/comfy-easy-grids.git ``` -I don't believe there are any additional dependencies beyond what's already installed with ComfyUI; please correct me if I'm wrong on that! +I don't believe there are any additional dependencies beyond what's already installed with ComfyUI; please let me know if I'm wrong about that! + +## Known Issues + +* By default, ComfyUI loads up a workflow from the last saved image on startup. Image grids from EasyGrids have their workflow saved in the metadata like any other Comfy image and can be loaded without any issues, but for some reason ComfyUI doesn't seem to automatically load from them on startup. ## Recommended Resources diff --git a/grid_types.py b/grid_types.py index 8519532..d7e82a4 100644 --- a/grid_types.py +++ b/grid_types.py @@ -1,6 +1,7 @@ from dataclasses import dataclass from PIL import ImageFont +# from ImagesGrid @dataclass class Annotation(): column_texts: list[str] diff --git a/nodes.py b/nodes.py index de0f996..2028a73 100644 --- a/nodes.py +++ b/nodes.py @@ -1,12 +1,13 @@ import re -from copy import deepcopy import folder_paths -from PIL import Image, ImageDraw import numpy as np -from comfy.cli_args import args import json -from PIL.PngImagePlugin import PngInfo import os.path + +from copy import deepcopy +from PIL import Image, ImageDraw +from comfy.cli_args import args +from PIL.PngImagePlugin import PngInfo from .grid_types import Annotation static_x = 1 @@ -19,8 +20,8 @@ class GridFloats: def INPUT_TYPES(s): return { "required": { - "index": ( "INT", {"default": 1, "min": 1, "max": 6 } ), - "float1": ("FLOAT", {"default": 1.0, "step": 0.01 }), + "index": ( "INT", {"default": 1, "min": 1 } ), + "float1": ("FLOAT", {"default": 1.0, "step": 0.01}), "float2": ("FLOAT", {"default": 1.0, "step": 0.01}), "float3": ("FLOAT", {"default": 1.0, "step": 0.01}), "float4": ("FLOAT", {"default": 1.0, "step": 0.01}), @@ -70,6 +71,115 @@ class GridFloatList: return ( self.static_out_arr[len(self.static_out_arr) - 1], ) return (self.static_out_arr[ index - 1 ],) +class GridInts: + @classmethod + def INPUT_TYPES(s): + return { + "required": { + "index": ( "INT", {"default": 1, "min": 1 }), + "int1": ("INT", {"default": 1, "step": 1 }), + "int2": ("INT", {"default": 1, "step": 1}), + "int3": ("INT", {"default": 1, "step": 1}), + "int4": ("INT", {"default": 1, "step": 1}), + "int5": ("INT", {"default": 1, "step": 1}), + "int6": ("INT", {"default": 1, "step": 1}), + }, + } + + RETURN_TYPES = ("INT",) + FUNCTION = "ReturnInt" + CATEGORY = "EasyGrids" + + def ReturnInt( self, index: int, int1 : int, int2 : int, int3 : int, int4 : int, int5 : int, int6: int ): + ret_list = [int1, int2, int3, int4, int5, int6] + if ( index > len(ret_list) ): + return ( ret_list[len(ret_list) - 1], ) + return (ret_list[ index - 1 ], ) + +class GridIntList: + @classmethod + def INPUT_TYPES(s): + return { + "required": { + "index": ( "INT", {"default": 1, "min": 1, "max": 100 } ), + "int_list": ("STRING", {"multiline": True}), + }} + + RETURN_TYPES = ("INT",) + FUNCTION = "ParseAndReturnInt" + CATEGORY = "EasyGrids" + + def __init__(self): + self.static_text = "" + self.static_out_arr = [] + + def ParseAndReturnInt( self, index: int, int_list: str ): + if int_list != self.static_text: + split_str = re.split( ",|;|\s|:", int_list ) + out_arr = [] + for val in split_str: + # let the exception happen if invalid + out_arr.append(int(val)) + self.static_text = int_list + self.static_out_arr = deepcopy( out_arr ) + if ( index > len(self.static_out_arr) ): + return ( self.static_out_arr[len(self.static_out_arr) - 1], ) + return (self.static_out_arr[ index - 1 ],) + +class GridStrings: + @classmethod + def INPUT_TYPES(s): + return { + "required": { + "index": ( "INT", {"default": 1, "min": 1 }), + "string1": ("STRING", {"default": ""}), + "string2": ("STRING", {"default": ""}), + "string3": ("STRING", {"default": ""}), + "string4": ("STRING", {"default": ""}), + "string5": ("STRING", {"default": ""}), + "string6": ("STRING", {"default": ""}), + }, + } + + RETURN_TYPES = ("STRING",) + FUNCTION = "ReturnString" + CATEGORY = "EasyGrids" + + def ReturnString( self, index: int, string1 : str, string2 : str, string3 : str, string4 : str, string5 : str, string6: str ): + ret_list = [string1, string2, string3, string4, string5, string6] + if ( index > len(ret_list) ): + return ( ret_list[len(ret_list) - 1], ) + return (ret_list[ index - 1 ], ) + +class GridStringList: + @classmethod + def INPUT_TYPES(s): + return { + "required": { + "index": ( "INT", {"default": 1, "min": 1, "max": 100 } ), + "string_list": ("STRING", {"multiline": True}), + } + } + + RETURN_TYPES = ("STRING",) + FUNCTION = "SplitAndReturnStrings" + CATEGORY = "EasyGrids" + + def __init__(self): + self.static_text = "" + self.static_out_arr = [] + + def SplitAndReturnStrings( self, index: int, string_list: str ): + if string_list != self.static_text: + # unlike the numeric list nodes, we only want to split on newlines + # TODO: support manual delimiter specification? + split_str = re.split( "\n", string_list ) + self.static_text = string_list + self.static_out_arr = deepcopy( split_str ) + if ( index > len(self.static_out_arr) ): + return ( self.static_out_arr[len(self.static_out_arr) - 1], ) + return (self.static_out_arr[ index - 1 ],) + class ImageGridCommander: @classmethod def INPUT_TYPES(s): @@ -151,7 +261,7 @@ class TextConcatenator: class FloatToText: @classmethod def INPUT_TYPES(s): - return { "required": { "float_input": ("FLOAT", {"default": 1.0}), + return { "required": { "float_input": ("FLOAT", {"default": 1.0, "step": 0.01}), "decimal_places": ("INT", {"default": 3, "min": 1, "max": 10 }), }} RETURN_TYPES = ("STRING",) @@ -163,6 +273,18 @@ class FloatToText: formatted_float = "{:.{}f}".format(float_input, decimal_places) return (formatted_float,) +class IntToText: + @classmethod + def INPUT_TYPES(s): + return { "required": { "int_input": ("INT", {"default": 1}), }} + + RETURN_TYPES = ("STRING",) + FUNCTION = "convert_to_str" + CATEGORY = "EasyGrids" + + def convert_to_str(self, int_input : int): + return (str(int_input),) + class SaveImageGrid: def __init__(self): self.output_dir = folder_paths.get_output_directory() @@ -285,18 +407,28 @@ class SaveImageGrid: NODE_CLASS_MAPPINGS = { "ImageGridCommander": ImageGridCommander, - "GridFloatList": GridFloatList, "GridFloats": GridFloats, + "GridFloatList": GridFloatList, + "GridInts": GridInts, + "GridIntList": GridIntList, + "GridStrings": GridStrings, + "GridStringList": GridStringList, "TextConcatenator": TextConcatenator, "FloatToText": FloatToText, + "IntToText": IntToText, "SaveImageGrid": SaveImageGrid, } NODE_DISPLAY_NAME_MAPPINGS = { "ImageGridCommander": "Create Image Grid", - "GridFloatList": "Float List fom Text", "GridFloats" : "Float List", + "GridFloatList": "Float List fom Text Field", + "GridInts": "Int List", + "GridIntList": "Int List from Text Field", + "GridStrings": "String List", + "GridStringList": "String List from Text Field", "TextConcatenator": "Text Concatenator", "FloatToText": "Float to Text", + "IntToText": "Int to Text", "SaveImageGrid": "Save Image Grid", } \ No newline at end of file