fix: optimized output for irc and ansi, readme and workflow update

This commit is contained in:
Hugh Bord
2025-08-28 11:48:04 +08:00
parent 2d649703ec
commit beb27fa98f
8 changed files with 260 additions and 170 deletions
+2 -1
View File
@@ -1 +1,2 @@
CLAUDE.md
CLAUDE.md
src/
+5
View File
@@ -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
+2 -1
View File
@@ -1,3 +1,4 @@
torch>=1.9.0
numpy>=1.21.0
scikit-learn>=1.0.0
scikit-learn>=1.0.0
Pillow>=8.0.0
+66 -16
View File
@@ -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)
+185 -152
View File
@@ -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",
Binary file not shown.

Before

Width:  |  Height:  |  Size: 91 KiB

After

Width:  |  Height:  |  Size: 73 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 31 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 27 KiB

After

Width:  |  Height:  |  Size: 32 KiB