fix: proper text output and updated README.md

This commit is contained in:
Hugh Bord
2025-08-27 13:26:51 +08:00
parent 53d5a06455
commit b690cdd4e8
4 changed files with 81 additions and 23 deletions
+21 -6
View File
@@ -2,17 +2,32 @@
A ComfyUI custom node for converting images into IRC art by processing them in pixel blocks and generating both image and text outputs with optimal color selection from a 99-color IRC palette.
This node is used with [aibird](https://github.com/birdneststream/aibird) to convert images to IRC art.
This node is used with [aibird](https://github.com/birdneststream/aibird) to convert text prompts to IRC art that is scrolled in the IRC client.
![Workflow Example](workflows/screenshot.png)
## Limitations
- As we have a limited color palette of 99 colors, at times some colors may not match perfect. Overall it matches good enough. I've found the `perceptual_weighted` followed by `weighted_manhattan` to be best.
- 16 colors don't work too well, but I've added this for legacy compatibility with some IRC clients that do not support 99 colors.
- IRC has a `512 - (ident + host)` byte limit per line. If there's too many half blocks rendered, it may go over this limit and break the display of the ASCII art. Rendering the image with a solid background color can help with this, or using fullblock mode.
- I haven't tried with many widths and heights. The current `640px x 448px` is a good fit for making ASCII art that is 80 character blocks wide and 30 character blocks hight. A smaller aspect ratio would fit a lot of half blocks better.
## Workflow
An example workflow is provided in `workflows/mircify_example_workflow.json`. At the moment this example is using qwen image lightning, however any model that is decent at making pixel ansi style art can suffice.
![Workflow Example](workflows/screenshot.png)
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.
## 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.
![IRC output](workflows/screenshot_irc.png)
## Features
- **Universal Image Support**: Works with any image dimensions
- **Universal Image Support**: Works with any image dimensions, but remember the IRC 512 byte limit per line.
- **Color Transfer Integration**: Uses KMeans clustering with multiple distance methods for accurate color mapping
- **Block Processing**: Configurable block sizes (default 8x15 pixels)
- **Half Block Mode**: Option for 8x7.5 pixel blocks for finer resolution
@@ -59,7 +74,7 @@ Converts images into IRC art with both visual and text output.
- **processed_image**: Visual representation of the IRC art conversion
- **irc_text**: mIRC-formatted text with color codes ready for IRC clients
### IRC Text Saver (still not working too good with color matching)
### IRC Text Saver
**Category**: `text/output`
Saves IRC-formatted text to files for easy copying.
@@ -102,7 +117,7 @@ Saves IRC-formatted text to files for easy copying.
The node uses a two-stage approach:
1. **Color Transfer Phase**: Applies KMeans clustering with configurable distance methods to map the entire image to the 99-color IRC palette
1. **Color Transfer Phase**: Applies KMeans clustering with configurable distance methods to map the entire image to the IRC color palette
2. **Block Processing Phase**: Divides the color-corrected image into blocks using dominant color extraction and generates the final IRC art output
## IRC Color Palette
+60 -17
View File
@@ -180,21 +180,59 @@ class IRCArtConverter:
line_text = ""
i = 0
while i < len(line_colors):
color_code = line_colors[i]
count = 1
color_entry = line_colors[i]
# Count consecutive identical colors
while i + count < len(line_colors) and line_colors[i + count] == color_code:
count += 1
# Format color code
if color_code <= 9:
color_format = f"\x03{color_code},{color_code}"
# Check if this is half-block mode (tuple) or full-block mode (int)
if isinstance(color_entry, tuple):
# Half-block mode: (top_color, bottom_color)
top_color, bottom_color = color_entry
if top_color == bottom_color:
# Same color - use space with background color, check for compression
count = 1
# Count consecutive identical same-color tuples
while (i + count < len(line_colors) and
isinstance(line_colors[i + count], tuple) and
line_colors[i + count] == color_entry):
count += 1
# Format color code once
if top_color < 10:
color_format = f"\x03{top_color},{top_color}"
else:
color_format = f"\x03{top_color:02d},{top_color:02d}"
line_text += color_format + (" " * count)
i += count
else:
# Different colors - use half block character (no compression for mixed colors)
if top_color < 10 and bottom_color < 10:
color_format = f"\x03{top_color},{bottom_color}"
elif top_color < 10:
color_format = f"\x03{top_color},{bottom_color:02d}"
elif bottom_color < 10:
color_format = f"\x03{top_color:02d},{bottom_color}"
else:
color_format = f"\x03{top_color:02d},{bottom_color:02d}"
line_text += color_format + "▀"
i += 1
else:
color_format = f"\x03{color_code:02d},{color_code:02d}"
line_text += color_format + (" " * count)
i += count
# Full-block mode: single color
color_code = color_entry
count = 1
# Count consecutive identical colors
while i + count < len(line_colors) and not isinstance(line_colors[i + count], tuple) and line_colors[i + count] == color_code:
count += 1
# Format color code
if color_code < 10:
color_format = f"\x03{color_code},{color_code}"
else:
color_format = f"\x03{color_code:02d},{color_code:02d}"
line_text += color_format + (" " * count)
i += count
return line_text
@@ -257,13 +295,12 @@ class IRCArtConverter:
actual_block_height, width, height, use_16_colors, distance_method
)
# Apply color to image and store for text
# Apply color to image
irc_color = self.IRC_COLORS[color_idx]
irc_color_tensor = torch.tensor(irc_color, dtype=torch.float32) / 255.0
output[start_y:end_y, x * block_width:min((x + 1) * block_width, width), :] = irc_color_tensor
line_colors.append(color_idx + 1) # IRC codes are 1-based
# Process second half-block if it exists
# Process second half-block if it exists
if y + 1 < blocks_y:
color_idx2, start_y2, end_y2 = self.process_block(
color_transferred_img, x, y + 1, block_width, block_height,
@@ -274,6 +311,12 @@ class IRCArtConverter:
irc_color2 = self.IRC_COLORS[color_idx2]
irc_color_tensor2 = torch.tensor(irc_color2, dtype=torch.float32) / 255.0
output[start_y2:end_y2, x * block_width:min((x + 1) * block_width, width), :] = irc_color_tensor2
# Store both colors as tuple (top, bottom)
line_colors.append((color_idx, color_idx2))
else:
# Last row, only one half-block - use same color for both
line_colors.append((color_idx, color_idx))
text_lines.append(self.format_irc_line(line_colors))
else:
@@ -291,7 +334,7 @@ class IRCArtConverter:
irc_color = self.IRC_COLORS[color_idx]
irc_color_tensor = torch.tensor(irc_color, dtype=torch.float32) / 255.0
output[start_y:end_y, x * block_width:min((x + 1) * block_width, width), :] = irc_color_tensor
line_colors.append(color_idx + 1) # IRC codes are 1-based
line_colors.append(color_idx) # Use same index as image
text_lines.append(self.format_irc_line(line_colors))
Binary file not shown.

Before

Width:  |  Height:  |  Size: 93 KiB

After

Width:  |  Height:  |  Size: 101 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 7.5 KiB