diff --git a/README.md b/README.md new file mode 100644 index 0000000..2455955 --- /dev/null +++ b/README.md @@ -0,0 +1,29 @@ +# G'MIC in ComfyUI + +![](./assets/workflow.png) +## Quick Start + +* Install custom node with: `git clone https://github.com/gemelli/ComfyUI_GMIC` +or copy [gmic.py](https://github.com/gemelli/ComfyUI_GMIC/raw/main/comfy_gmic.py) file to custom nodes folder +* Download [G'MIC Command-line interface (CLI)](https://gmic.eu/download.html) +* Put executable path in your PATH ([how to](https://windowsloop.com/how-to-add-to-windows-path/)) +* Download [G'MIC-Qt stand-alone interface](https://gmic.eu/download.html) +* Choose a filter and copy the command +![](./assets/screenshot.png) + +* Paste the command to the custom node + +--- +Tested on Windows10, Comfyui [248aa3e](https://github.com/comfyanonymous/ComfyUI/tree/248aa3e56355d75ac3d8632af769e6c700d9bfac), G'MIC cli 3.2.4 + + +**TODO**: +[ ] Batch image +[ ] Favorites drop-down for easier usage + +--- +Other Image Processing ComfyUI custom nodes: +* [ComfyUI-Allor](https://github.com/Nourepide/ComfyUI-Allor): ComfyUI plugin for image processing and work with alpha chanel +* [ComfyUI-post-processing-nodes](https://github.com/EllangoK/ComfyUI-post-processing-nodes): A collection of Post Processing Nodes for ComfyUI, which enable a variety of cool image effects +* [ComfyI2I](https://github.com/ManglerFTW/ComfyI2I): ComfyI2I is a set of custom nodes for ComfyUI that help with image 2 image functions +* [ComfyUI\_ImageProcessing](https://github.com/bvhari/ComfyUI_ImageProcessing): ComfyUI custom nodes to apply various image processing techniques diff --git a/__init__.py b/__init__.py new file mode 100644 index 0000000..7d07fe4 --- /dev/null +++ b/__init__.py @@ -0,0 +1,4 @@ +from .comfy_gmic import NODE_CLASS_MAPPINGS, NODE_DISPLAY_NAME_MAPPINGS +__all__ = ['NODE_CLASS_MAPPINGS', 'NODE_DISPLAY_NAME_MAPPINGS'] + + diff --git a/assets/screenshot.png b/assets/screenshot.png new file mode 100644 index 0000000..3d51cec Binary files /dev/null and b/assets/screenshot.png differ diff --git a/assets/workflow.json b/assets/workflow.json new file mode 100644 index 0000000..8b81f69 --- /dev/null +++ b/assets/workflow.json @@ -0,0 +1,131 @@ +{ + "last_node_id": 3, + "last_link_id": 2, + "nodes": [ + { + "id": 1, + "type": "LoadImage", + "pos": [ + 384.08492126464853, + 147.57919708251956 + ], + "size": [ + 320, + 310 + ], + "flags": {}, + "order": 0, + "mode": 0, + "outputs": [ + { + "name": "IMAGE", + "type": "IMAGE", + "links": [ + 1 + ], + "shape": 3, + "slot_index": 0 + }, + { + "name": "MASK", + "type": "MASK", + "links": null, + "shape": 3 + } + ], + "properties": { + "Node name for S&R": "LoadImage" + }, + "widgets_values": [ + "sample_gmicky.png", + "image" + ] + }, + { + "id": 3, + "type": "PreviewImage", + "pos": [ + 1080, + 160 + ], + "size": [ + 450, + 370 + ], + "flags": {}, + "order": 2, + "mode": 0, + "inputs": [ + { + "name": "images", + "type": "IMAGE", + "link": 2 + } + ], + "properties": { + "Node name for S&R": "PreviewImage" + } + }, + { + "id": 2, + "type": "GmicCliWrapper", + "pos": [ + 740, + 150 + ], + "size": { + "0": 315, + "1": 58 + }, + "flags": {}, + "order": 1, + "mode": 0, + "inputs": [ + { + "name": "images", + "type": "IMAGE", + "link": 1 + } + ], + "outputs": [ + { + "name": "IMAGE", + "type": "IMAGE", + "links": [ + 2 + ], + "shape": 3, + "slot_index": 0 + } + ], + "properties": { + "Node name for S&R": "GmicCliWrapper" + }, + "widgets_values": [ + "fx_bokeh 3,8,0,30,8,4,0.3,0.2,210,210,80,160,0.7,30,20,20,1,2,170,130,20,110,0.15,0,50,50" + ] + } + ], + "links": [ + [ + 1, + 1, + 0, + 2, + 0, + "IMAGE" + ], + [ + 2, + 2, + 0, + 3, + 0, + "IMAGE" + ] + ], + "groups": [], + "config": {}, + "extra": {}, + "version": 0.4 +} \ No newline at end of file diff --git a/assets/workflow.png b/assets/workflow.png new file mode 100644 index 0000000..cbc41fc Binary files /dev/null and b/assets/workflow.png differ diff --git a/comfy_gmic.py b/comfy_gmic.py new file mode 100644 index 0000000..2e42ea3 --- /dev/null +++ b/comfy_gmic.py @@ -0,0 +1,58 @@ +import os +import io +import random +import tempfile +from PIL import Image +from nodes import LoadImage, SaveImage +import folder_paths + + +class GmicCliWrapper: + def __init__(self): + self.output_dir = folder_paths.get_temp_directory() + self.type = "temp" + self.prefix_append = "_temp_" + ''.join(random.choice("abcdefghijklmnopqrstupvxyz") for x in range(5)) + @classmethod + def INPUT_TYPES(s): + return { + "required": { + "command": ("STRING", {"multiline": False}), + "images": ("IMAGE", ), + } + } + RETURN_TYPES = ("IMAGE",) + FUNCTION = "gmic_filter" + CATEGORY = "filters" + def gmic_filter(self, command, images): + imgtemp = SaveImage.save_images(self, images) + fname = imgtemp['ui']['images'][0]['filename'] + inpath = "{}\{}".format(self.output_dir,fname ) + + filter_id = command.split(" ")[0] + fd, outpath = tempfile.mkstemp(prefix="GMIC--{}".format(filter_id), + suffix=".png", + dir=None,) + + gmic_cli_command = 'gmic -input {} -{} -output "{}"'.format(inpath, command, outpath) + os.system(gmic_cli_command) + + + + image3 = LoadImage.load_image(self, outpath) + + + return (image3[0],) + + + + + + +NODE_CLASS_MAPPINGS = { + "GmicCliWrapper": GmicCliWrapper, +} + +NODE_DISPLAY_NAME_MAPPINGS = { + "GmicCliWrapper": "GMIC Image Processing", +} +