diff --git a/README.md b/README.md index 6716a84..29be809 100644 --- a/README.md +++ b/README.md @@ -14,45 +14,61 @@ Be aware, These are all very experimental right now so may have some bugs or iss A more comprehensive Checkpoint, Vae loader with afew other features such as clip skip value selector and initial empty latent image generation - ### Prompts Combined Positive and Negative Prompts, Just to save some room. - ### ImageTool (wip) Uses PIL for some simple image manipulation. Won't do batch images yet, work in progress. - ### Wildcards -replaces a keyword from input text if given with a random line from a text file, if no input is given it will output just the random line +Replaces a keyword from input text if given with a random line from a text file, if no input is given it will output just the random line -can now return multiple random lines from the same file if desired, but be aware you can get doubles +Can now return multiple random lines from the same file if desired, but be aware you can get doubles wildcards files (some of which i've included which also need some more work) are stored under the extras/chibi-wildcards directory. - ### LoadEmbedding Appends the embedding text to the end of the input text ", (embedding:filename.pt:weight) " - ### ConditionText 4 in 4 out, text to conditioning. - ### SaveImages (wip) -VaeDecode and SaveImage mixed together. saves to the normal output directory but uses a unix time stamp as the filename eg. 1698462650_001_.png - +VaeDecode and SaveImage mixed together. saves to the normal output directory but uses a unix time stamp as the filename eg. 1698462650*001*.png ### Textbox -A simple text box, if passthrough text is supplied it will update the textbox contents and also send it forward. +A simple text box, if passthrough text is supplied it will update the textbox contents and also send it forward. + "ImageSizeInfo" : ImageSizeInfo, + "ImageSimpleResize" : ImageSimpleResize, + "Int2String": Int2String, + +### ImageSizeInfo + +Displays the resolution of the input image, and sends it along as well as two int values for the dimensions. + +### ImageSimpleResize + +A quick way to make a large image into a size your GPU can handle. + +largest sets the biggest dimension to the set size and keeps your aspect ratio + +smallest sets the lowest dimension to the set size and keeps your aspect ratio + +all sets all dimensions to the set size + +height and width only sets the one you pick. + +### Int2String + +Takes a number, gives you a text string, for debugging i guess. ## Installation diff --git a/chibi_nodes.py b/chibi_nodes.py index dd7f921..234312e 100644 --- a/chibi_nodes.py +++ b/chibi_nodes.py @@ -122,11 +122,16 @@ class ImageTool: RETURN_TYPES = ("IMAGE",) FUNCTION = "imagetools" - CATEGORY = "Chibi-Nodes" + CATEGORY = "Chibi-Nodes/Image" def imagetools(self, image, height, width, crop, rotate, mirror, flip, bgcolor): image = Image.fromarray(np.clip(255. * image[0].cpu().numpy(),0,255).astype(np.uint8)) image = image.rotate(rotate,fillcolor=bgcolor) + + #black and white + #corrections? + #generate mask from background color (crop, rotate) + if mirror: image = ImageOps.mirror(image) @@ -142,7 +147,7 @@ class ImageTool: image = image.crop((left, top, right, bottom)) else: - image = image.resize((width,height), Image.Resampling.LANCZOS) + image = image.resize((width,height), Image.LANCZOS) image = ImageOps.exif_transpose(image) image = image.convert("RGB") @@ -286,7 +291,7 @@ class ConditionText: RETURN_TYPES = ("CLIP","CONDITIONING","CONDITIONING","CONDITIONING","CONDITIONING",) RETURN_NAMES = ("CLIP","first","second","third","fourth",) FUNCTION = "conditiontext" - CATEGORY = "Chibi-Nodes" + CATEGORY = "Chibi-Nodes/Text" def conditiontext(self, clip, first='', second='', third='', fourth='', ): emptystring = "" @@ -433,7 +438,7 @@ class Textbox: RETURN_NAMES = ("text",) OUTPUT_NODE = True FUNCTION = "textbox" - CATEGORY = "Chibi-Nodes" + CATEGORY = "Chibi-Nodes/Text" def textbox(self,text="",passthrough=""): if passthrough != "": @@ -442,7 +447,132 @@ class Textbox: else: return (text,) +class ImageSizeInfo: + def __init__(self): + pass + @classmethod + def INPUT_TYPES(s): + return { + "required":{ + "image": ("IMAGE",) + }, + "hidden":{ + "width": ("INT",), + "height": ("INT",), + }} + RETURN_TYPES = ("IMAGE","INT","INT",) + RETURN_NAMES = ("IMAGE","width","height",) + OUTPUT_NODE = True + FUNCTION = "imagesizeinfo" + CATEGORY = "Chibi-Nodes/Image" + + def imagesizeinfo(self, image, width=0, height=0): + shape = image.shape + width = shape[2] + height = shape[1] + return {"ui": {"width": [width], "height": [height]},"result": (image,width,height,)} + + +class ImageSimpleResize: + def __init__(self): + pass + + @classmethod + def INPUT_TYPES(s): + return { + "required":{ + "image": ("IMAGE",), + "size": ("INT", {"default": 512, "min": 16, "max": MAX_RESOLUTION, "step": 1}), + "edge":(["largest","smallest","all","width","height"],), + }, + "optional":{ + "size_override":("INT",{"forceInput": True}) + }} + RETURN_TYPES = ("IMAGE",) + OUTPUT_NODE = False + FUNCTION = "imagesimpleresize" + CATEGORY = "Chibi-Nodes/Image" + + def imagesimpleresize(self, image, size, edge, size_override=None): + if size_override: + size = size_override + + width = image.shape[2] + height = image.shape[1] + ratio = height / width + image = Image.fromarray(np.clip(255. * image[0].cpu().numpy(),0,255).astype(np.uint8)) + + if edge == "largest": + if width > height: + if size < width: + image = ImageOps.contain(image, (size,MAX_RESOLUTION), Image.LANCZOS) + else: + image = image.resize((round(size),round(size*ratio)), Image.LANCZOS) + if width < height: + if size < height: + image = ImageOps.contain(image, (MAX_RESOLUTION,size), Image.LANCZOS) + else: + image = image.resize((round(size/ratio),round(size)), Image.LANCZOS) + if width == height: + if size < width: + image = ImageOps.contain(image, (size,size), Image.LANCZOS) + else: + image = image.resize((round(size),round(size)), Image.LANCZOS) + + + if edge == "smallest": + if width > height: + if size < height: + image = ImageOps.contain(image, (MAX_RESOLUTION,size), Image.LANCZOS) + else: + image = image.resize((round(size/ratio),round(size)), Image.LANCZOS) + if width < height: + if size < width: + image = ImageOps.contain(image, (MAX_RESOLUTION,size), Image.LANCZOS) + else: + image = image.resize((round(size),round(size*ratio)), Image.LANCZOS) + if width == height: + if size < width: + image = ImageOps.contain(image, (size,size), Image.LANCZOS) + else: + image = image.resize((round(size),round(size)), Image.LANCZOS) + + if edge == "all": + image = image.resize((round(size),round(size)), Image.LANCZOS) + + if edge == "width": + image = image.resize((round(size),round(height)), Image.LANCZOS) + + if edge == "height": + image = image.resize((round(width),round(size)), Image.LANCZOS) + + + image = ImageOps.exif_transpose(image) + image = image.convert("RGB") + image = np.array(image).astype(np.float32) / 255.0 + image = torch.from_numpy(image)[None,] + + return(image,) + +class Int2String: + def __init__(self): + pass + + @classmethod + def INPUT_TYPES(s): + return { + "required":{ + "Int":("INT",{"forceInput": True}) + },} + RETURN_TYPES = ("STRING",) + OUTPUT_NODE = False + FUNCTION = "int2string" + CATEGORY = "Chibi-Nodes/Text" + + def int2string(self, Int): + print(Int) + return(str(Int),) NODE_CLASS_MAPPINGS = { @@ -454,5 +584,8 @@ NODE_CLASS_MAPPINGS = { "ConditionText": ConditionText, "SaveImages":SaveImages, "Textbox":Textbox, + "ImageSizeInfo" : ImageSizeInfo, + "ImageSimpleResize" : ImageSimpleResize, + "Int2String": Int2String, } diff --git a/js/chibi_nodes.js b/js/chibi_nodes.js index 3a045cc..2ba8de4 100644 --- a/js/chibi_nodes.js +++ b/js/chibi_nodes.js @@ -22,6 +22,35 @@ app.registerExtension({ } } + this.onResize?.(this.size); + }; + } + if (nodeData.name === "ImageSizeInfo") { + const onNodeCreated = nodeType.prototype.onNodeCreated; + nodeType.prototype.onNodeCreated = function () { + const r = onNodeCreated?.apply(this, arguments); + const iw = ComfyWidgets["INT"](this, "width", ["INT", {}], app).widget; + const ih = ComfyWidgets["INT"](this, "height", ["INT", {}], app).widget; + return r; + }; + + const onExecuted = nodeType.prototype.onExecuted; + nodeType.prototype.onExecuted = function (message) { + onExecuted?.apply(this, arguments); + + for (const widget of this.widgets) { + // console.log(widget.type); + // console.log(widget.name); + if (widget.name == "width") { + console.log(message); + widget.value = message.width[0]; + } + if (widget.name == "height") { + console.log(message); + widget.value = message.height[0]; + } + } + this.onResize?.(this.size); }; } diff --git a/screenshot.png b/screenshot.png index 863b83a..e7a0a29 100644 Binary files a/screenshot.png and b/screenshot.png differ diff --git a/screenshots/screenshot4.png b/screenshots/screenshot4.png new file mode 100644 index 0000000..863b83a Binary files /dev/null and b/screenshots/screenshot4.png differ