diff --git a/.gitignore b/.gitignore index 681311e..887033c 100644 --- a/.gitignore +++ b/.gitignore @@ -1 +1,2 @@ -CLAUDE.md \ No newline at end of file +CLAUDE.md +src/ diff --git a/README.md b/README.md index 88997cd..e50fd8b 100644 --- a/README.md +++ b/README.md @@ -19,6 +19,8 @@ An example workflow is provided in `workflows/mircify_example_workflow.json`. At the moment this example is using qwen image (gguf), lightning 4 step lora with RES4LYF samplers. However any model that is decent at making pixel ansi style art can suffice. +It will save the irc and ansi data to the [png exif data](#irc-png-exporter) and also to [file](#irc-text-saver). + ## IRC Output Pasting the results of the created `.txt` file into IRC, we should be able to see the IRC art rendered in the client. @@ -44,6 +46,8 @@ or display it with `less -R` to preserve colors: less -R output/irc_art/your_ansi_file.txt ``` +![ANSI output](workflows/screenshot_ansi.jpg) + ## Features - **Universal Image Support**: Works with any image dimensions, but remember the IRC 512 byte limit per line. @@ -202,6 +206,7 @@ For more information about how IRC uses colors refer to these resources: - torch - numpy - scikit-learn +- Pillow ## Credits diff --git a/requirements.txt b/requirements.txt index c7b1ce7..5ea778b 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,3 +1,4 @@ torch>=1.9.0 numpy>=1.21.0 -scikit-learn>=1.0.0 \ No newline at end of file +scikit-learn>=1.0.0 +Pillow>=8.0.0 \ No newline at end of file diff --git a/src/nodes.py b/src/nodes.py index 04477eb..dd7249f 100644 --- a/src/nodes.py +++ b/src/nodes.py @@ -192,12 +192,13 @@ class IRCFormatter(TextFormatter): @staticmethod def format_line(line_colors: List[Union[int, Tuple[int, int]]]) -> str: - """Format a line of IRC color codes with compression.""" + """Format a line of IRC color codes with comprehensive duplicate prevention.""" if not line_colors: return "" result = [] i = 0 + last_color_code = None # Track the last color code to prevent duplicates while i < len(line_colors): color_entry = line_colors[i] @@ -207,23 +208,47 @@ class IRCFormatter(TextFormatter): top_color, bottom_color = color_entry if top_color == bottom_color: - # Compress consecutive identical blocks + # Same colors - treat as spaces with background color count = 1 while (i + count < len(line_colors) and isinstance(line_colors[i + count], tuple) and line_colors[i + count] == color_entry): count += 1 - result.append(IRCFormatter.format_color_code(top_color) + (SPACE_CHAR * count)) + color_code = IRCFormatter.format_color_code(top_color) + if color_code != last_color_code: + result.append(color_code + (SPACE_CHAR * count)) + last_color_code = color_code + else: + # Same color code as previous, just add spaces without color code + result.append(SPACE_CHAR * count) i += count else: - # Different colors - use half block character - result.append(IRCFormatter.format_color_code(top_color, bottom_color) + HALF_BLOCK_CHAR) - i += 1 + # Different colors - check for consecutive identical half-block patterns + count = 1 + while (i + count < len(line_colors) and + isinstance(line_colors[i + count], tuple) and + line_colors[i + count] == color_entry): + count += 1 + + color_code = IRCFormatter.format_color_code(top_color, bottom_color) + if color_code != last_color_code: + result.append(color_code + (HALF_BLOCK_CHAR * count)) + last_color_code = color_code + else: + # Same color code as previous, just add characters without color code + result.append(HALF_BLOCK_CHAR * count) + i += count else: - # Full-block mode + # Full-block mode - treat as spaces with background color count = IRCFormatter.count_consecutive(line_colors, i) - result.append(IRCFormatter.format_color_code(color_entry) + (SPACE_CHAR * count)) + color_code = IRCFormatter.format_color_code(color_entry) + if color_code != last_color_code: + result.append(color_code + (SPACE_CHAR * count)) + last_color_code = color_code + else: + # Same color code as previous, just add spaces without color code + result.append(SPACE_CHAR * count) i += count return ''.join(result) @@ -250,12 +275,13 @@ class ANSIFormatter(TextFormatter): return f"\033[38;5;{top_ansi};48;5;{bottom_ansi}m{HALF_BLOCK_CHAR}" def format_line(self, line_colors: List[Union[int, Tuple[int, int]]]) -> str: - """Format a line of ANSI color codes with compression.""" + """Format a line of ANSI color codes with comprehensive duplicate prevention.""" if not line_colors: return "" result = [] i = 0 + last_ansi_code = None # Track the last ANSI code to prevent duplicates while i < len(line_colors): color_entry = line_colors[i] @@ -265,7 +291,7 @@ class ANSIFormatter(TextFormatter): top_color, bottom_color = color_entry if top_color == bottom_color: - # Same color - compress + # Same colors - treat as background spaces count = 1 while (i + count < len(line_colors) and isinstance(line_colors[i + count], tuple) and @@ -273,19 +299,43 @@ class ANSIFormatter(TextFormatter): count += 1 ansi_color = self.get_ansi_color(top_color) - result.append(self.format_background(ansi_color, count)) + ansi_code = f"\033[48;5;{ansi_color}m" + if ansi_code != last_ansi_code: + result.append(ansi_code + (SPACE_CHAR * count)) + last_ansi_code = ansi_code + else: + # Same ANSI code as previous, just add spaces without code + result.append(SPACE_CHAR * count) i += count else: - # Different colors - half block + # Different colors - check for consecutive identical patterns + count = 1 + while (i + count < len(line_colors) and + isinstance(line_colors[i + count], tuple) and + line_colors[i + count] == color_entry): + count += 1 + top_ansi = self.get_ansi_color(top_color) bottom_ansi = self.get_ansi_color(bottom_color) - result.append(self.format_half_block(top_ansi, bottom_ansi)) - i += 1 + ansi_code = f"\033[38;5;{top_ansi};48;5;{bottom_ansi}m" + if ansi_code != last_ansi_code: + result.append(ansi_code + (HALF_BLOCK_CHAR * count)) + last_ansi_code = ansi_code + else: + # Same ANSI code as previous, just add characters without code + result.append(HALF_BLOCK_CHAR * count) + i += count else: - # Full-block mode + # Full-block mode - treat as background spaces count = ANSIFormatter.count_consecutive(line_colors, i) ansi_color = self.get_ansi_color(color_entry) - result.append(self.format_background(ansi_color, count)) + ansi_code = f"\033[48;5;{ansi_color}m" + if ansi_code != last_ansi_code: + result.append(ansi_code + (SPACE_CHAR * count)) + last_ansi_code = ansi_code + else: + # Same ANSI code as previous, just add spaces without code + result.append(SPACE_CHAR * count) i += count result.append(ANSI_RESET) diff --git a/workflows/mircify_example_workflow.json b/workflows/mircify_example_workflow.json index 784325c..a89ce1c 100644 --- a/workflows/mircify_example_workflow.json +++ b/workflows/mircify_example_workflow.json @@ -1,8 +1,8 @@ { "id": "6a85ef9e-440c-47f8-b6cc-6a647c18839d", "revision": 0, - "last_node_id": 93, - "last_link_id": 156, + "last_node_id": 94, + "last_link_id": 159, "nodes": [ { "id": 7, @@ -399,7 +399,7 @@ "widget_ue_connectable": {} }, "widgets_values": [ - 817468956820710, + 462218769959024, "randomize", 4, 1, @@ -408,6 +408,157 @@ 1 ] }, + { + "id": 91, + "type": "IRC Text Saver", + "pos": [ + 1386.3250732421875, + 292.4225158691406 + ], + "size": [ + 270, + 58 + ], + "flags": {}, + "order": 11, + "mode": 0, + "inputs": [ + { + "name": "irc_text", + "type": "STRING", + "link": 155 + } + ], + "outputs": [ + { + "name": "file_path", + "type": "STRING", + "links": null + } + ], + "properties": { + "aux_id": "ComfyUI-Mircify.git", + "ver": "7560f88586f8cf1c4e67e706e79f3dfc3390f308", + "Node name for S&R": "IRC Text Saver" + }, + "widgets_values": [ + "irc_art_{TIMESTAMP}.txt" + ] + }, + { + "id": 93, + "type": "IRC Text Saver", + "pos": [ + 1391.58154296875, + 407.7270812988281 + ], + "size": [ + 270, + 58 + ], + "flags": {}, + "order": 12, + "mode": 0, + "inputs": [ + { + "name": "irc_text", + "type": "STRING", + "link": 156 + } + ], + "outputs": [ + { + "name": "file_path", + "type": "STRING", + "links": null + } + ], + "properties": { + "aux_id": "ComfyUI-Mircify.git", + "ver": "7560f88586f8cf1c4e67e706e79f3dfc3390f308", + "Node name for S&R": "IRC Text Saver" + }, + "widgets_values": [ + "ansi_art_{TIMESTAMP}.txt" + ] + }, + { + "id": 76, + "type": "UnetLoaderGGUF", + "pos": [ + -125.99971008300781, + -188.51158142089844 + ], + "size": [ + 270, + 58 + ], + "flags": {}, + "order": 3, + "mode": 0, + "inputs": [], + "outputs": [ + { + "name": "MODEL", + "type": "MODEL", + "links": [ + 134 + ] + } + ], + "properties": { + "cnr_id": "ComfyUI-GGUF", + "ver": "d247022e3fa66851c5084cc251b076aab816423d", + "Node name for S&R": "UnetLoaderGGUF" + }, + "widgets_values": [ + "qwen/qwen-image-Q8_0.gguf" + ] + }, + { + "id": 94, + "type": "IRC PNG Exporter", + "pos": [ + 1399.4827880859375, + -166.5812225341797 + ], + "size": [ + 270, + 334 + ], + "flags": {}, + "order": 13, + "mode": 0, + "inputs": [ + { + "name": "image", + "type": "IMAGE", + "link": 159 + }, + { + "name": "irc_text", + "shape": 7, + "type": "STRING", + "link": 157 + }, + { + "name": "ansi_text", + "shape": 7, + "type": "STRING", + "link": 158 + } + ], + "outputs": [], + "properties": { + "aux_id": "ComfyUI-Mircify.git", + "ver": "2d649703ece193bb00fecca47f77c9a9e76dd3fc", + "Node name for S&R": "IRC PNG Exporter" + }, + "widgets_values": [ + true, + "irc_art_{TIMESTAMP}.png" + ] + }, { "id": 6, "type": "CLIPTextEncode", @@ -459,43 +610,6 @@ "color": "#232", "bgcolor": "#353" }, - { - "id": 91, - "type": "IRC Text Saver", - "pos": [ - 1386.3250732421875, - 292.4225158691406 - ], - "size": [ - 270, - 58 - ], - "flags": {}, - "order": 12, - "mode": 0, - "inputs": [ - { - "name": "irc_text", - "type": "STRING", - "link": 155 - } - ], - "outputs": [ - { - "name": "file_path", - "type": "STRING", - "links": null - } - ], - "properties": { - "aux_id": "ComfyUI-Mircify.git", - "ver": "7560f88586f8cf1c4e67e706e79f3dfc3390f308", - "Node name for S&R": "IRC Text Saver" - }, - "widgets_values": [ - "irc_art_{TIMESTAMP}.txt" - ] - }, { "id": 82, "type": "IRC Art Converter", @@ -522,21 +636,23 @@ "name": "processed_image", "type": "IMAGE", "links": [ - 142 + 159 ] }, { "name": "irc_text", "type": "STRING", "links": [ - 155 + 155, + 157 ] }, { "name": "ansi_text", "type": "STRING", "links": [ - 156 + 156, + 158 ] } ], @@ -552,105 +668,6 @@ false, "perceptual_weighted" ] - }, - { - "id": 93, - "type": "IRC Text Saver", - "pos": [ - 1391.58154296875, - 407.7270812988281 - ], - "size": [ - 270, - 58 - ], - "flags": {}, - "order": 13, - "mode": 0, - "inputs": [ - { - "name": "irc_text", - "type": "STRING", - "link": 156 - } - ], - "outputs": [ - { - "name": "file_path", - "type": "STRING", - "links": null - } - ], - "properties": { - "aux_id": "ComfyUI-Mircify.git", - "ver": "7560f88586f8cf1c4e67e706e79f3dfc3390f308", - "Node name for S&R": "IRC Text Saver" - }, - "widgets_values": [ - "ansi_art_{TIMESTAMP}.txt" - ] - }, - { - "id": 80, - "type": "PreviewImage", - "pos": [ - 1385.32958984375, - -166.24484252929688 - ], - "size": [ - 524.072998046875, - 389.5648498535156 - ], - "flags": {}, - "order": 11, - "mode": 0, - "inputs": [ - { - "name": "images", - "type": "IMAGE", - "link": 142 - } - ], - "outputs": [], - "properties": { - "cnr_id": "comfy-core", - "ver": "0.3.52", - "Node name for S&R": "PreviewImage" - }, - "widgets_values": [] - }, - { - "id": 76, - "type": "UnetLoaderGGUF", - "pos": [ - -125.99971008300781, - -188.51158142089844 - ], - "size": [ - 270, - 58 - ], - "flags": {}, - "order": 3, - "mode": 0, - "inputs": [], - "outputs": [ - { - "name": "MODEL", - "type": "MODEL", - "links": [ - 134 - ] - } - ], - "properties": { - "cnr_id": "ComfyUI-GGUF", - "ver": "d247022e3fa66851c5084cc251b076aab816423d", - "Node name for S&R": "UnetLoaderGGUF" - }, - "widgets_values": [ - "qwen/qwen-image-Q8_0.gguf" - ] } ], "links": [ @@ -734,14 +751,6 @@ 0, "MODEL" ], - [ - 142, - 82, - 0, - 80, - 0, - "IMAGE" - ], [ 150, 8, @@ -765,6 +774,30 @@ 93, 0, "STRING" + ], + [ + 157, + 82, + 1, + 94, + 1, + "STRING" + ], + [ + 158, + 82, + 2, + 94, + 2, + "STRING" + ], + [ + 159, + 82, + 0, + 94, + 0, + "IMAGE" ] ], "groups": [], @@ -773,8 +806,8 @@ "ds": { "scale": 0.8390545288824212, "offset": [ - 108.16671263739013, - 536.0446891793314 + 271.42065915743825, + 496.835589687672 ] }, "frontendVersion": "1.23.4", diff --git a/workflows/screenshot.jpg b/workflows/screenshot.jpg index 3c48935..08d79bd 100644 Binary files a/workflows/screenshot.jpg and b/workflows/screenshot.jpg differ diff --git a/workflows/screenshot_ansi.jpg b/workflows/screenshot_ansi.jpg new file mode 100644 index 0000000..d6082ad Binary files /dev/null and b/workflows/screenshot_ansi.jpg differ diff --git a/workflows/screenshot_irc.jpg b/workflows/screenshot_irc.jpg index 64ec129..9277869 100644 Binary files a/workflows/screenshot_irc.jpg and b/workflows/screenshot_irc.jpg differ