From dfaab3e3d5f1267e2580444714a0d243d7285d39 Mon Sep 17 00:00:00 2001 From: Joseph Parker Date: Mon, 14 Oct 2024 21:49:21 +0100 Subject: [PATCH 1/8] Initial commit --- __init__.py | 3 +++ nodes.py | 58 +++++++++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 61 insertions(+) create mode 100644 __init__.py create mode 100644 nodes.py diff --git a/__init__.py b/__init__.py new file mode 100644 index 0000000..2e96bd6 --- /dev/null +++ b/__init__.py @@ -0,0 +1,3 @@ +from .nodes import NODE_CLASS_MAPPINGS, NODE_DISPLAY_NAME_MAPPINGS + +__all__ = ["NODE_CLASS_MAPPINGS", "NODE_DISPLAY_NAME_MAPPINGS"] \ No newline at end of file diff --git a/nodes.py b/nodes.py new file mode 100644 index 0000000..f40b8a4 --- /dev/null +++ b/nodes.py @@ -0,0 +1,58 @@ +import math + +class DrawThingsWrapper: + def __init__(self): + pass + + @classmethod + def INPUT_TYPES(s): + return { + "required": { + "model_type": (["SD","SDXL"],), + "aspect_ratio_width": ("INT",{ + "default": 1, + "step":1, + "display": "number" + }), + "aspect_ratio_height": ("INT",{ + "default": 1, + "step":1, + "display": "number" + }) + } + } + + RETURN_TYPES = ("INT","INT") + RETURN_NAMES = ("Width", "Height") + + FUNCTION = "run" + + CATEGORY = "DrawThingsWrapper" + + def run(self, model_type, aspect_ratio_width, aspect_ratio_height): + # Define the total pixel counts for SD and SDXL + total_pixels = { + 'SD': 512 * 512, + 'SDXL': 1024 * 1024 + } + + # Calculate the number of total pixels based on model type + pixels = total_pixels.get(model_type, 0) + + # Calculate the aspect ratio decimal + aspect_ratio_decimal = aspect_ratio_width / aspect_ratio_height + + # Calculate width and height + width = math.sqrt(pixels * aspect_ratio_decimal) + height = pixels / width + + # Return the width and height as a tuple of integers + return (int(round(width)), int(round(height))) + +NODE_CLASS_MAPPINGS = { + "DrawThingsWrapper": DrawThingsWrapper +} + +NODE_DISPLAY_NAME_MAPPINGS = { + "DrawThingsWrapper": "Draw Things Wrapper" +} From f09d9e5e43f5f8cfc83408eb0bde91028996c193 Mon Sep 17 00:00:00 2001 From: Joseph Parker Date: Mon, 14 Oct 2024 22:23:05 +0100 Subject: [PATCH 2/8] Generate images --- nodes.py | 78 +++++++++++++++++++++++++++----------------------------- 1 file changed, 38 insertions(+), 40 deletions(-) diff --git a/nodes.py b/nodes.py index f40b8a4..a2774cb 100644 --- a/nodes.py +++ b/nodes.py @@ -1,53 +1,51 @@ import math +import requests +from PIL import Image +import io class DrawThingsWrapper: def __init__(self): pass - @classmethod - def INPUT_TYPES(s): - return { - "required": { - "model_type": (["SD","SDXL"],), - "aspect_ratio_width": ("INT",{ - "default": 1, - "step":1, - "display": "number" - }), - "aspect_ratio_height": ("INT",{ - "default": 1, - "step":1, - "display": "number" - }) - } - } - - RETURN_TYPES = ("INT","INT") - RETURN_NAMES = ("Width", "Height") - - FUNCTION = "run" CATEGORY = "DrawThingsWrapper" - def run(self, model_type, aspect_ratio_width, aspect_ratio_height): - # Define the total pixel counts for SD and SDXL - total_pixels = { - 'SD': 512 * 512, - 'SDXL': 1024 * 1024 + @classmethod + def INPUT_TYPES(cls): + return { + "required": { + "prompt": ("STRING", {"default": ""}), + "seed": ("INT", {"default": 42}), + "width": ("INT", {"default": 512}), + "height": ("INT", {"default": 512}), + } } - - # Calculate the number of total pixels based on model type - pixels = total_pixels.get(model_type, 0) - - # Calculate the aspect ratio decimal - aspect_ratio_decimal = aspect_ratio_width / aspect_ratio_height - - # Calculate width and height - width = math.sqrt(pixels * aspect_ratio_decimal) - height = pixels / width - - # Return the width and height as a tuple of integers - return (int(round(width)), int(round(height))) + + RETURN_TYPES = ("IMAGE",) + FUNCTION = "generate_image" + + def generate_image(self, prompt, seed, width, height): + # Call the Draw Things API + api_url = "http://127.0.0.1:7860//sdapi/v1/txt2img" + + payload = { + "prompt": prompt, + "seed": seed, + "width": width, + "height": height + } + + response = requests.post(api_url, json=payload) + + if response.status_code == 200: + image_data = response.content # Assuming the API returns raw image data + image = Image.open(io.BytesIO(image_data)) # Convert raw data to an image object + return (image,) # Return as a tuple (ComfyUI expects output in tuple form) + else: + raise Exception(f"Failed to generate image: {response.text}") + +# Register the node (depending on ComfyUI's mechanism, adjust accordingly) + NODE_CLASS_MAPPINGS = { "DrawThingsWrapper": DrawThingsWrapper From e7eaa0fe18cec606112d6262b7e5b62c1e109b98 Mon Sep 17 00:00:00 2001 From: Joseph Parker Date: Tue, 15 Oct 2024 09:57:13 +0100 Subject: [PATCH 3/8] Working txt2img generator --- nodes.py | 28 ++++++++++++++++++++-------- 1 file changed, 20 insertions(+), 8 deletions(-) diff --git a/nodes.py b/nodes.py index a2774cb..ce454a0 100644 --- a/nodes.py +++ b/nodes.py @@ -1,7 +1,10 @@ +import base64 +import numpy as np import math import requests from PIL import Image import io +import torch class DrawThingsWrapper: def __init__(self): @@ -22,11 +25,12 @@ class DrawThingsWrapper: } RETURN_TYPES = ("IMAGE",) + RETURN_NAMES = ("generated_image",) FUNCTION = "generate_image" def generate_image(self, prompt, seed, width, height): # Call the Draw Things API - api_url = "http://127.0.0.1:7860//sdapi/v1/txt2img" + api_url = "http://127.0.0.1:7860/sdapi/v1/txt2img" payload = { "prompt": prompt, @@ -37,15 +41,23 @@ class DrawThingsWrapper: response = requests.post(api_url, json=payload) - if response.status_code == 200: - image_data = response.content # Assuming the API returns raw image data - image = Image.open(io.BytesIO(image_data)) # Convert raw data to an image object - return (image,) # Return as a tuple (ComfyUI expects output in tuple form) - else: - raise Exception(f"Failed to generate image: {response.text}") + # Raise an error if the request failed + response.raise_for_status() -# Register the node (depending on ComfyUI's mechanism, adjust accordingly) + # Parse the JSON response + data = response.json() + # Process the images (assuming they are base64 encoded or raw binary data) + images = [] + for img_data in data['images']: + image_bytes = base64.b64decode(img_data) + # Convert the image data to a Pillow Image object + image = Image.open(io.BytesIO(image_bytes)) + image_np = np.array(image) + # Convert to float32 tensor and normalize + tensor_image = torch.from_numpy(image_np.astype(np.float32) / 255.0) + images.append(tensor_image) + return(torch.stack(images),) NODE_CLASS_MAPPINGS = { "DrawThingsWrapper": DrawThingsWrapper From 4bad865833ad8f5a7eacbe0ea2aa9b34d6c2799e Mon Sep 17 00:00:00 2001 From: Joseph Parker Date: Tue, 15 Oct 2024 13:10:31 +0100 Subject: [PATCH 4/8] Add guidance scale --- nodes.py | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/nodes.py b/nodes.py index ce454a0..c03ed47 100644 --- a/nodes.py +++ b/nodes.py @@ -21,6 +21,7 @@ class DrawThingsWrapper: "seed": ("INT", {"default": 42}), "width": ("INT", {"default": 512}), "height": ("INT", {"default": 512}), + "guidance_scale": ("FLOAT", {"default": 3.5}), } } @@ -28,7 +29,7 @@ class DrawThingsWrapper: RETURN_NAMES = ("generated_image",) FUNCTION = "generate_image" - def generate_image(self, prompt, seed, width, height): + def generate_image(self, prompt, seed, width, height, guidance_scale): # Call the Draw Things API api_url = "http://127.0.0.1:7860/sdapi/v1/txt2img" @@ -36,7 +37,8 @@ class DrawThingsWrapper: "prompt": prompt, "seed": seed, "width": width, - "height": height + "height": height, + "guidance_scale": guidance_scale } response = requests.post(api_url, json=payload) From 79b81cb6981067c472737e232526680e5bc76854 Mon Sep 17 00:00:00 2001 From: Joseph Parker Date: Tue, 15 Oct 2024 22:29:56 +0100 Subject: [PATCH 5/8] Add steps --- nodes.py | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/nodes.py b/nodes.py index c03ed47..f537b90 100644 --- a/nodes.py +++ b/nodes.py @@ -22,6 +22,7 @@ class DrawThingsWrapper: "width": ("INT", {"default": 512}), "height": ("INT", {"default": 512}), "guidance_scale": ("FLOAT", {"default": 3.5}), + "steps": ("INT", {"default": 20}), } } @@ -29,7 +30,7 @@ class DrawThingsWrapper: RETURN_NAMES = ("generated_image",) FUNCTION = "generate_image" - def generate_image(self, prompt, seed, width, height, guidance_scale): + def generate_image(self, prompt, seed, width, height, guidance_scale, steps): # Call the Draw Things API api_url = "http://127.0.0.1:7860/sdapi/v1/txt2img" @@ -38,7 +39,8 @@ class DrawThingsWrapper: "seed": seed, "width": width, "height": height, - "guidance_scale": guidance_scale + "guidance_scale": guidance_scale, + "steps": steps } response = requests.post(api_url, json=payload) From a5e2dd73b2ddc5fb41c64c2b1f9f97e3d30659f8 Mon Sep 17 00:00:00 2001 From: Joseph Parker Date: Wed, 16 Oct 2024 10:27:11 +0100 Subject: [PATCH 6/8] Tidy nodes.py source --- nodes.py | 27 ++++++++++++++------------- 1 file changed, 14 insertions(+), 13 deletions(-) diff --git a/nodes.py b/nodes.py index f537b90..f7e4755 100644 --- a/nodes.py +++ b/nodes.py @@ -1,16 +1,20 @@ +#!/usr/bin/env python3 +""" +Wrapper nodes for calling Draw Things from ComfyUI +""" + import base64 import numpy as np -import math import requests from PIL import Image import io -import torch +import torch + class DrawThingsWrapper: def __init__(self): pass - CATEGORY = "DrawThingsWrapper" @classmethod @@ -40,7 +44,7 @@ class DrawThingsWrapper: "width": width, "height": height, "guidance_scale": guidance_scale, - "steps": steps + "steps": steps, } response = requests.post(api_url, json=payload) @@ -53,7 +57,7 @@ class DrawThingsWrapper: # Process the images (assuming they are base64 encoded or raw binary data) images = [] - for img_data in data['images']: + for img_data in data["images"]: image_bytes = base64.b64decode(img_data) # Convert the image data to a Pillow Image object image = Image.open(io.BytesIO(image_bytes)) @@ -61,12 +65,9 @@ class DrawThingsWrapper: # Convert to float32 tensor and normalize tensor_image = torch.from_numpy(image_np.astype(np.float32) / 255.0) images.append(tensor_image) - return(torch.stack(images),) - -NODE_CLASS_MAPPINGS = { - "DrawThingsWrapper": DrawThingsWrapper -} + return (torch.stack(images),) -NODE_DISPLAY_NAME_MAPPINGS = { - "DrawThingsWrapper": "Draw Things Wrapper" -} + +NODE_CLASS_MAPPINGS = {"DrawThingsWrapper": DrawThingsWrapper} + +NODE_DISPLAY_NAME_MAPPINGS = {"DrawThingsWrapper": "Draw Things Wrapper"} From 7aeab4cb3dcd4ee6a63c797f32f4f890a879af02 Mon Sep 17 00:00:00 2001 From: Joseph Parker Date: Wed, 16 Oct 2024 10:28:52 +0100 Subject: [PATCH 7/8] Add requirements file --- requirements.txt | 4 ++++ 1 file changed, 4 insertions(+) create mode 100644 requirements.txt diff --git a/requirements.txt b/requirements.txt new file mode 100644 index 0000000..7b07bf9 --- /dev/null +++ b/requirements.txt @@ -0,0 +1,4 @@ +numpy +PIL +requests +torch From 02b8bb4a889f0ebe49e21711c6da2ee7535b7b55 Mon Sep 17 00:00:00 2001 From: Joseph Parker Date: Wed, 16 Oct 2024 13:18:55 +0100 Subject: [PATCH 8/8] Add readme --- README.md | 23 +++++++++++++++++++++++ 1 file changed, 23 insertions(+) create mode 100644 README.md diff --git a/README.md b/README.md new file mode 100644 index 0000000..3f4eab6 --- /dev/null +++ b/README.md @@ -0,0 +1,23 @@ +# Draw Things ComfyUI Wrapper + +These nodes provide a wrapper for calling [Draw Things](https://drawthings.ai/) image generations from [ComfyUI](https://github.com/comfyanonymous/ComfyUI). + +**Wait, why?** The Draw Things app has been optimized for Apple hardware and runs roughly x3 faster than ComfyUI generations. But ComfyUI is a flexible and powerful tools, and has some features - like queuing and face swapping - that haven't been implemented in Draw Things. + +This simple node calls a local instance of Draw Things through its API and returns the resulting image to ComfyUI. + +# Set up + +Start Draw Things, and in the "Advanced" tab on the left-hand side, enable "API Server" with settings Protocol HTTP, Port 7860 and IP localhost only. ComfyUI jobs can now generate via Draw Things. The generations will also display in the Draw Things as if they had been triggered natively. + +# Limitations + +This node treats Draw Things as a black box, and can only change the settings available through the [Draw Things API](https://docs.drawthings.ai/documentation/documentation/8.scripts). Settings changed through the API automatically change the values in the Draw Things UI. Other settings can be set manually inside the UI. These manual settings _do_ take effect for jobs initiated by ComfyUI, but are _not_ recorded by ComfyUI, so such ComfyUI workflows are not reproducible (without also ensuring the Draw Things settings are the same). + +# Disclaimer + +This repo is 100% unofficial and unaffiliated with either Draw Things or ComfyUI. + + + +