Complete package with Core, Creative, Vintage, Deformation, Light Effects, and Geometric categories
166 lines
5.9 KiB
Python
166 lines
5.9 KiB
Python
import numpy as np
|
|
import torch
|
|
from PIL import Image
|
|
|
|
class AsciiTextNode:
|
|
@classmethod
|
|
def INPUT_TYPES(cls):
|
|
return {
|
|
"required": {
|
|
"image": ("IMAGE",),
|
|
"ascii_width": ("INT", {"default": 80, "min": 20, "max": 200, "step": 10}),
|
|
"style": (["classic", "detailed", "minimal", "blocks", "custom"], {"default": "classic"}),
|
|
"invert": ("BOOLEAN", {"default": False}),
|
|
},
|
|
"optional": {
|
|
"contrast_boost": ("FLOAT", {"default": 2.0, "min": 1.0, "max": 4.0, "step": 0.1}),
|
|
"height_compression": ("FLOAT", {"default": 2.0, "min": 1.0, "max": 3.0, "step": 0.1}),
|
|
"custom_chars": ("STRING", {"default": " .-+*#@", "multiline": False}),
|
|
"add_border": ("BOOLEAN", {"default": False}),
|
|
"line_numbers": ("BOOLEAN", {"default": False}),
|
|
}
|
|
}
|
|
|
|
RETURN_TYPES = ("STRING",)
|
|
RETURN_NAMES = ("ascii_text",)
|
|
FUNCTION = "generate_ascii_text"
|
|
CATEGORY = "Image Effects"
|
|
OUTPUT_NODE = True
|
|
|
|
def generate_ascii_text(self, image, ascii_width, style, invert,
|
|
contrast_boost=2.0, height_compression=2.0,
|
|
custom_chars=" .-+*#@", add_border=False, line_numbers=False):
|
|
|
|
# Prendre la première image du batch
|
|
if len(image.shape) == 4:
|
|
img_tensor = image[0]
|
|
else:
|
|
img_tensor = image
|
|
|
|
# Convertir en numpy
|
|
img_np = img_tensor.cpu().numpy()
|
|
original_h, original_w, c = img_np.shape
|
|
|
|
print(f"ASCII Text: {original_w}x{original_h} → {ascii_width} chars wide")
|
|
|
|
# Convertir en niveaux de gris
|
|
gray = np.dot(img_np[..., :3], [0.2989, 0.5870, 0.1140])
|
|
|
|
# Calculer les dimensions ASCII avec compression de hauteur
|
|
ascii_height = int(ascii_width * (original_h / original_w) / height_compression)
|
|
|
|
# Redimensionner l'image pour l'analyse ASCII
|
|
gray_pil = Image.fromarray((gray * 255).astype(np.uint8))
|
|
resized = np.array(gray_pil.resize((ascii_width, ascii_height), Image.Resampling.LANCZOS))
|
|
|
|
# Améliorer le contraste
|
|
enhanced = self._enhance_contrast(resized, contrast_boost)
|
|
|
|
# Inverser si demandé
|
|
if invert:
|
|
enhanced = 255 - enhanced
|
|
|
|
# Choisir le jeu de caractères
|
|
ascii_chars = self._get_ascii_chars(style, custom_chars)
|
|
|
|
# Normaliser et mapper aux caractères
|
|
norm_pixels = (enhanced / 255) * (len(ascii_chars) - 1)
|
|
norm_pixels = norm_pixels.astype(int)
|
|
|
|
# Générer le texte ASCII
|
|
ascii_text_lines = []
|
|
for row_idx, row in enumerate(norm_pixels):
|
|
line = "".join([ascii_chars[p] for p in row])
|
|
|
|
# Ajouter les numéros de ligne si demandé
|
|
if line_numbers:
|
|
line = f"{row_idx+1:3d}: {line}"
|
|
|
|
ascii_text_lines.append(line)
|
|
|
|
# Ajouter une bordure si demandé
|
|
if add_border:
|
|
ascii_text_lines = self._add_border(ascii_text_lines, line_numbers)
|
|
|
|
# Joindre toutes les lignes
|
|
ascii_text = "\n".join(ascii_text_lines)
|
|
|
|
# Ajouter des informations d'en-tête
|
|
header = f"ASCII Art - {ascii_width}x{ascii_height} - Style: {style}\n"
|
|
header += "=" * len(header.strip()) + "\n"
|
|
|
|
final_text = header + ascii_text
|
|
|
|
print(f"ASCII généré: {len(ascii_text_lines)} lignes, {len(ascii_text)} caractères")
|
|
|
|
return (final_text,)
|
|
|
|
def _enhance_contrast(self, image_array, boost_factor):
|
|
"""Améliorer le contraste pour un meilleur rendu ASCII"""
|
|
# Normaliser
|
|
normalized = image_array.astype(np.float32) / 255.0
|
|
|
|
# Appliquer un boost de contraste
|
|
enhanced = np.power(normalized, 1.0 / boost_factor)
|
|
|
|
# Étalement d'histogramme
|
|
min_val = np.min(enhanced)
|
|
max_val = np.max(enhanced)
|
|
|
|
if max_val > min_val:
|
|
enhanced = (enhanced - min_val) / (max_val - min_val)
|
|
|
|
# Appliquer une courbe en S pour plus de contraste
|
|
enhanced = 0.5 * (1 + np.tanh(4 * (enhanced - 0.5)))
|
|
|
|
return (enhanced * 255).astype(np.uint8)
|
|
|
|
def _get_ascii_chars(self, style, custom_chars):
|
|
"""Obtenir le jeu de caractères selon le style"""
|
|
styles = {
|
|
"classic": list(" .:-=+*#%@"),
|
|
"detailed": list(" ░▒▓█"),
|
|
"minimal": list(" .-#@"),
|
|
"blocks": list(" ▁▂▃▄▅▆▇█"),
|
|
"custom": list(custom_chars)
|
|
}
|
|
|
|
chars = styles.get(style, styles["classic"])
|
|
|
|
# S'assurer qu'on a au moins 2 caractères
|
|
if len(chars) < 2:
|
|
chars = list(" @")
|
|
|
|
return chars
|
|
|
|
def _add_border(self, text_lines, has_line_numbers):
|
|
"""Ajouter une bordure autour du texte ASCII"""
|
|
if not text_lines:
|
|
return text_lines
|
|
|
|
# Calculer la largeur maximale
|
|
max_width = max(len(line) for line in text_lines)
|
|
|
|
# Caractères de bordure
|
|
top_left = "┌"
|
|
top_right = "┐"
|
|
bottom_left = "└"
|
|
bottom_right = "┘"
|
|
horizontal = "─"
|
|
vertical = "│"
|
|
|
|
# Ligne du haut
|
|
top_line = top_left + horizontal * (max_width + 2) + top_right
|
|
|
|
# Ligne du bas
|
|
bottom_line = bottom_left + horizontal * (max_width + 2) + bottom_right
|
|
|
|
# Lignes avec bordures latérales
|
|
bordered_lines = [top_line]
|
|
for line in text_lines:
|
|
padded_line = line.ljust(max_width)
|
|
bordered_lines.append(f"{vertical} {padded_line} {vertical}")
|
|
bordered_lines.append(bottom_line)
|
|
|
|
return bordered_lines
|