- Se desarrolló una técnica de renderizado ASCII que preserva los contornos y las formas de la imagen, resolviendo el problema de los bordes borrosos de los métodos tradicionales
- En lugar de un mapeo simple de brillo por píxel, se utiliza un enfoque basado en vectores de alta dimensión que cuantifica y empareja la forma visual (shape) de cada carácter
- Midiendo la densidad de las zonas superior, inferior, izquierda y derecha de cada carácter, se genera un shape vector expandido de 2 a 6 dimensiones, logrando una selección de caracteres más precisa
- Para mejorar la nitidez de los bordes, se aplican algoritmos de realce de contraste global y direccional (contrast enhancement)
- Mediante aceleración por GPU, caché y búsquedas con k-d tree, se logra rendimiento de renderizado ASCII en tiempo real, con efectos visuales de alta calidad
Conversión de imagen a ASCII
- ASCII tiene 95 caracteres imprimibles, y se divide la imagen en una cuadrícula usando una fuente monoespaciada
- Se calcula el brillo de cada celda y se asigna según la densidad del carácter
- La simple interpolación por vecino más cercano (nearest-neighbor interpolation) provoca el efecto de jaggies, con bordes dentados
- Con supersampling, al tomar varias muestras dentro de la celda y calcular el brillo promedio, la imagen se suaviza, pero siguen apareciendo bordes borrosos
- El problema central es tratar los caracteres como si fueran píxeles, sin considerar su forma propia
Uso de la forma de los caracteres
- Cada carácter tiene una distribución visual de densidad distinta dentro de la celda
- Ejemplo:
T tiene más peso arriba, y L tiene más peso abajo
- Para cuantificar esto, se colocan círculos de muestreo dentro de la celda y se calcula la proporción ocupada por el carácter en cada región
- La ocupación de las zonas superior e inferior se expresa como un vector, creando un shape vector bidimensional
- Se precalcula el shape vector de cada carácter y se elige el más cercano al vector de muestreo de la imagen usando distancia euclidiana (Euclidean distance)
Expansión a un vector de forma de 6 dimensiones
- Con solo 2 dimensiones arriba/abajo es difícil representar caracteres centrados en medio o a izquierda/derecha, como
-, p o q
- La celda se expande a 6 círculos de muestreo para capturar por completo las diferencias de arriba, centro, abajo, izquierda y derecha
- El shape vector de 6 dimensiones refleja con mucha más precisión la forma del carácter y también representa bien caracteres circulares y diagonales
- Al renderizar escenas 3D, los contornos se ven nítidos, pero aparece el problema de que los límites entre superficies se vuelven borrosos
Realce de contraste
- Cada elemento del vector de muestreo se ajusta con un exponente, haciendo que los valores oscuros se vuelvan más oscuros mientras se conservan los claros
- Se normaliza el vector, se aplica el exponente y luego se restaura a su rango original
- Este proceso refuerza la separación visual de los bordes, haciendo más clara la selección de caracteres
- En zonas de brillo uniforme casi no hay cambios, por lo que se mantienen las gradaciones suaves
- Sin embargo, en algunos bordes aparece el efecto de escalonado (staircasing)
Realce de contraste direccional
- También se colocan círculos de muestreo externos fuera de cada celda para recolectar información del brillo circundante
- Los valores brillantes del vector de muestreo externo oscurecen los elementos correspondientes del vector interno, reforzando el contraste en la dirección del borde
- Al ampliar el muestreo externo para extender la influencia entre las zonas superior, media e inferior, se logra una representación de bordes suave y nítida
- Al combinarlo con el realce de contraste global, se obtiene un renderizado ASCII legible y con bordes bien definidos en escenas 3D
Optimización de rendimiento
- Como repetir una búsqueda de vecino más cercano para elegir caracteres es lento, se usa un k-d tree para hacer búsquedas rápidas en espacio multidimensional
- Mediante caché, se reutilizan los resultados de vectores de muestreo idénticos
- Cada vector se cuantiza en unidades de 5 bits para crear una clave de caché eficiente en memoria
- Al fijar el rango en 8, se mantiene un equilibrio entre calidad y uso de memoria
- La búsqueda con caché es muy rápida, y permite procesar miles de caracteres en tiempo real
- El cálculo de los vectores de muestreo se trasladó a la GPU, de modo que el muestreo interno y externo, junto con las operaciones de realce de contraste, se procesan en la tubería de shaders
- Varias veces más rendimiento que con CPU
Conclusión
- El enfoque de cuantificar y aprovechar la forma de los caracteres como vectores mejora enormemente la resolución y nitidez del renderizado ASCII
- Este método es conceptualmente similar a los word embeddings, y podría aplicarse a otros problemas visuales
- La implementación inicial era lenta, pero con aceleración por GPU, caché y búsqueda con k-d tree logra FPS fluidos incluso en móviles
- No se abordó la representación ASCII basada en color, y se menciona la posibilidad de experimentar a futuro con combinaciones más variadas de forma y contraste
- Más allá de ser un simple efecto visual, el renderizado ASCII muestra el potencial de expansión del reconocimiento de formas y la representación vectorial
1 comentarios
Comentarios en Hacker News
Si normalizas los vectores y luego calculas la distancia euclidiana, puedes obtener el mismo resultado incluso con una simple multiplicación de matrices (matmul)
porque, en vectores normalizados, la distancia euclidiana es una transformación lineal de la distancia coseno.
Si no importa el valor exacto de la distancia sino solo el ranking, también puedes omitir la operación
sqrty obtener el mismo resultado, calculándolo un poco más rápido.Me encantan este tipo de artículos. A primera vista parecen simples, pero para hacerlos realmente bien hace falta una exploración profunda.
También recomiendo el texto que escribió Lucas Pope mientras desarrollaba el sistema de dithering de Return of The Obra Dinn.
Desarrollo de Lucas Pope
Me sorprendió leer la frase “generé una imagen de Saturno con ChatGPT”.
Hay fotos reales de Saturno por todas partes en el dominio público, así que me pregunto por qué contaminar internet creando una imagen falsa a propósito.
Artículo sobre la polémica de las ‘fotos falsas de la Luna’ de Samsung
Algún día quizá ni siquiera escribamos directamente en wikis, sitios web o foros.
Si puedes generar al instante una “imagen de Saturno de alto contraste en tamaño X×Y”, eso sería un cambio mágico.
Así como la calculadora o internet no mataron la creatividad, los humanos siempre elegiremos la herramienta con menos fricción y seguiremos avanzando hacia las estrellas.
Cada vez que veía un ejemplo pensaba “está bien, pero seguro se puede mejorar más”, y me impresionó que el autor efectivamente fuera resolviendo eso.
Es un artículo realmente hermoso, y todo el blog tiene este nivel de profundidad, así que vale la pena suscribirse.
alexharri.com/blog
Cuando hice el proyecto ascii-side-of-the-moon pensé en implementar yo mismo un renderizador ASCII.
Al final usé chafa, pero tengo pensado volver a intentarlo algún día.
Me pregunto si planeas publicar esto como biblioteca, o si estaría bien tomar como referencia el código del sitio web.
Por ahora no hay planes de convertirlo en biblioteca, pero si hace falta puedes tomar libremente el código del sitio web.
Si lo hiciera, creo que haría falta mejorar la compatibilidad con una conversión de WebGL 2 → WebGL 1, y también una herramienta para precalcular los shape vectors por fuente.
Sobre la frase “no he visto ejemplos de usar shape en ASCII art”, en realidad sí existe un generador que usa shape.
Es un proyecto llamado ascii-silhouettify, que utiliza un algoritmo que elige el carácter más grande según el contorno de las regiones de color.
Acerola probó en 2024 un renderizado ASCII basado en detección de bordes.
El método superponía símbolos direccionales (
| / - \\) sobre una pasada basada en brillo.Vale la pena ver este video relacionado.
Por ejemplo, se podrían usar contornos gruesos como en el arte 2D tradicional, o probar distintas variantes para expresar contrastes suaves de luz y sombra como en el claroscuro.
La mayoría de los filtros ASCII no toman en cuenta la forma (shape) del glifo.
chafa trata cada glifo como un bitmap de 8×8, y ese enfoque me pareció impresionante.
Si miras el código fuente de chafa y la galería, se nota lo refinado que está.
Me pregunto si un enfoque centrado en la direccionalidad podría representar mejor formas más grandes.
Mirar oldschool PC fonts es como meterse en una madriguera de conejo sin fin.
En mi tiempo libre estoy experimentando con gráficos a color basados en braille.
La resolución es suficiente, pero falta precisión en la representación del color, así que después del muestreo hace falta una corrección de contraste (contrast fixup).
Creo que estaría bueno aplicar la técnica de muestreo del autor para reforzar el contraste del color.
Antes intenté aumentar el contraste con un filtro Sobel, pero no funcionó porque no se alineaba con la cuadrícula de caracteres.
Fue un enfoque técnico realmente excelente y un análisis muy profundo.
Me quedé con ganas de ver al final una versión mejorada del Cognition cube array.
Me recordó a un diseñador que hace años logró mejores favicons en YouTube usando contraste de color subpíxel.
Artículo relacionado (Web Archive)
Aun así, el artículo en sí fue excelente, y los ejemplos dinámicos fueron realmente impresionantes.