Prólogo
Este tema aparece aquí en Stack Overflow de vez en cuando, pero generalmente se elimina porque es una pregunta mal escrita. Vi muchas preguntas de este tipo y luego silencio del OP (repetición baja habitual) cuando se solicita información adicional. De vez en cuando, si la entrada es lo suficientemente buena para mí, decido responder con una respuesta y, por lo general, recibe algunos votos positivos por día mientras está activa, pero luego de unas semanas la pregunta se quita / borra y todo comienza desde el comenzando. Así que decidí escribir estas preguntas y respuestas para poder hacer referencia a esas preguntas directamente sin volver a escribir la respuesta una y otra vez ...
Otra razón es también este meta hilo dirigido a mí, así que si recibiste información adicional, no dudes en comentar.
Pregunta
¿Cómo puedo convertir una imagen de mapa de bits a arte ASCII usando C ++ ?
Algunas limitaciones:
- imagenes de escala de grises
- utilizando fuentes monoespaciadas
- mantenerlo simple (sin usar cosas demasiado avanzadas para programadores de nivel principiante)
Aquí hay una página de Wikipedia relacionada con arte ASCII (gracias a @RogerRowland).
Aquí laberinto similar a la conversión de arte ASCII Q&A.
Respuestas:
Hay más enfoques para la conversión de imágenes a arte ASCII que se basan principalmente en el uso de fuentes monoespaciadas . Por simplicidad, me limito solo a lo básico:
Basado en intensidad de píxel / área (sombreado)
Este enfoque maneja cada píxel de un área de píxeles como un solo punto. La idea es calcular la intensidad media de la escala de grises de este punto y luego reemplazarlo con un carácter con una intensidad lo suficientemente cercana a la calculada. Para eso necesitamos una lista de caracteres utilizables, cada uno con una intensidad precalculada. Llamémoslo personaje
map
. Para elegir más rápidamente qué personaje es el mejor para qué intensidad, hay dos formas:Mapa de caracteres de intensidad distribuida linealmente
Entonces usamos solo caracteres que tienen una diferencia de intensidad con el mismo paso. En otras palabras, cuando se ordena de forma ascendente, entonces:
Además, cuando nuestro personaje
map
está ordenado, podemos calcular el personaje directamente desde la intensidad (no se necesita búsqueda)Mapa de caracteres de intensidad distribuida arbitrariamente
Entonces tenemos una variedad de caracteres utilizables y sus intensidades. Necesitamos encontrar la intensidad más cercana al
intensity_of(dot)
Entonces, nuevamente, si ordenamosmap[]
, podemos usar la búsqueda binaria, de lo contrario, necesitamos unO(n)
ciclo de búsqueda de distancia mínima o unO(1)
diccionario. A veces, por simplicidad, el caráctermap[]
se puede manejar como distribuido linealmente, lo que provoca una ligera distorsión gamma, que generalmente no se ve en el resultado a menos que sepa qué buscar.La conversión basada en intensidad también es excelente para imágenes en escala de grises (no solo en blanco y negro). Si selecciona el punto como un solo píxel, el resultado se vuelve grande (un píxel -> un solo carácter), por lo que para imágenes más grandes se selecciona un área (multiplicar el tamaño de fuente) en su lugar para preservar la relación de aspecto y no agrandar demasiado.
Cómo hacerlo:
Como personaje
map
, puede usar cualquier carácter, pero el resultado mejora si el personaje tiene píxeles distribuidos uniformemente a lo largo del área del personaje. Para empezar puedes usar:char map[10]=" .,:;ox%#@";
ordenados descendente y pretenden estar distribuidos linealmente.
Entonces, si la intensidad del píxel / área es
i = <0-255>
, el carácter de reemplazo serámap[(255-i)*10/256];
Si
i==0
entonces el píxel / área es negro, sii==127
entonces el píxel / área es gris, y sii==255
entonces el píxel / área es blanco. Puedes experimentar con diferentes personajes dentromap[]
...Aquí hay un ejemplo antiguo mío en C ++ y VCL:
Debe reemplazar / ignorar las cosas de VCL a menos que use el entorno Borland / Embarcadero .
mm_log
es la nota donde se genera el textobmp
es el mapa de bits de entradaAnsiString
es una cadena de tipo VCL indexada desde 1, no desde 0 comochar*
!!!Este es el resultado: Imagen de ejemplo de intensidad ligeramente NSFW
A la izquierda está la salida de arte ASCII (tamaño de fuente 5 píxeles) y a la derecha la imagen de entrada se amplió varias veces. Como puede ver, la salida es un píxel más grande -> carácter. Si usa áreas más grandes en lugar de píxeles, entonces el zoom es más pequeño, pero, por supuesto, la salida es menos agradable a la vista. Este enfoque es muy fácil y rápido de codificar / procesar.
Cuando agrega cosas más avanzadas como:
Entonces puede procesar imágenes más complejas con mejores resultados:
Aquí está el resultado en una proporción 1: 1 (haga zoom para ver los personajes):
Por supuesto, para el muestreo de áreas se pierden los pequeños detalles. Esta es una imagen del mismo tamaño que el primer ejemplo muestreado con áreas:
Imagen de ejemplo avanzada de intensidad levemente NSFW
Como puede ver, esto es más adecuado para imágenes más grandes.
Ajuste de caracteres (híbrido entre sombreado y arte ASCII sólido)
Este enfoque intenta reemplazar el área (no más puntos de un solo píxel) con caracteres con intensidad y forma similares. Esto conduce a mejores resultados, incluso con fuentes más grandes en comparación con el enfoque anterior. Por otro lado, este enfoque es un poco más lento, por supuesto. Hay más formas de hacer esto, pero la idea principal es calcular la diferencia (distancia) entre el área de la imagen (
dot
) y el carácter renderizado. Puede comenzar con una suma ingenua de la diferencia absoluta entre píxeles, pero eso dará lugar a resultados no muy buenos porque incluso un cambio de un píxel aumentará la distancia. En su lugar, puede utilizar correlación o métricas diferentes. El algoritmo general es casi el mismo que el enfoque anterior:Así dividir uniformemente la imagen para (escala de grises) áreas rectangulares dot 's
idealmente con la misma relación de aspecto que los caracteres de fuente renderizados (preservará la relación de aspecto. No olvide que los caracteres generalmente se superponen un poco en el eje x)
Calcule la intensidad de cada área (
dot
)Reemplácelo por un carácter del personaje
map
con la intensidad / forma más cercana¿Cómo podemos calcular la distancia entre un carácter y un punto? Esa es la parte más difícil de este enfoque. Mientras experimento, desarrollo este compromiso entre velocidad, calidad y sencillez:
Dividir el área del personaje en zonas
map
).i=(i*256)/(xs*ys)
.Procesar la imagen de origen en áreas rectangulares
Este es el resultado para el tamaño de fuente = 7 píxeles
Como puede ver, el resultado es visualmente agradable, incluso con un tamaño de fuente más grande (el ejemplo de enfoque anterior fue con un tamaño de fuente de 5 píxeles). La salida es aproximadamente del mismo tamaño que la imagen de entrada (sin zoom). Los mejores resultados se logran porque los caracteres están más cerca de la imagen original, no solo por la intensidad, sino también por la forma general, y por lo tanto puede usar fuentes más grandes y aún preservar los detalles (hasta cierto punto, por supuesto).
Aquí está el código completo para la aplicación de conversión basada en VCL:
Es una aplicación de formulario simple (
Form1
) con un soloTMemo mm_txt
en él. Carga una imagen,"pic.bmp"
y luego, de acuerdo con la resolución, elige qué enfoque usar para convertir a texto que se guarda"pic.txt"
y envía a una nota para visualizar.Para aquellos sin VCL, ignore el material VCL y reemplácelo
AnsiString
con cualquier tipo de cadena que tenga, y tambiénGraphics::TBitmap
con cualquier mapa de bits o clase de imagen que tenga a disposición con capacidad de acceso a píxeles.Una nota muy importante es que utiliza la configuración de
mm_txt->Font
, así que asegúrese de configurar:Font->Pitch = fpFixed
Font->Charset = OEM_CHARSET
Font->Name = "System"
para que esto funcione correctamente, de lo contrario, la fuente no se manejará como monoespaciada. La rueda del mouse simplemente cambia el tamaño de la fuente hacia arriba o hacia abajo para ver resultados en diferentes tamaños de fuente.
[Notas]
3x3
lugar.Comparación
Finalmente, aquí hay una comparación entre los dos enfoques en la misma entrada:
Las imágenes marcadas con puntos verdes se hacen con el enfoque n . ° 2 y las rojas con el n . ° 1 , todas en un tamaño de fuente de seis píxeles. Como puede ver en la imagen de la bombilla, el enfoque sensible a la forma es mucho mejor (incluso si el # 1 se realiza en una imagen de origen con zoom 2x).
Aplicación genial
Mientras leía las nuevas preguntas de hoy, tuve una idea de una aplicación genial que captura una región seleccionada del escritorio y la alimenta continuamente al convertidor ASCIIart y ve el resultado. Después de una hora de codificación, está hecho y estoy tan satisfecho con el resultado que simplemente debo agregarlo aquí.
De acuerdo, la aplicación consta de solo dos ventanas. La primera ventana maestra es básicamente mi antigua ventana de conversión sin la selección de imágenes y la vista previa (todo lo anterior está en ella). Solo tiene la configuración de conversión y vista previa ASCII. La segunda ventana es un formulario vacío con un interior transparente para la selección del área de agarre (sin funcionalidad alguna).
Ahora, con un temporizador, solo tomo el área seleccionada por el formulario de selección, la paso a la conversión y obtengo una vista previa del ASCIIart .
Por lo tanto, encierra un área que desea convertir en la ventana de selección y ve el resultado en la ventana maestra. Puede ser un juego, un visor, etc. Se ve así:
Así que ahora puedo ver incluso videos en ASCIIart por diversión. Algunos son realmente agradables :).
Si desea intentar implementar esto en GLSL , eche un vistazo a esto:
fuente
3x3
zonas y comparar los DCT , pero creo que eso disminuiría mucho el rendimiento.