c++ - online - Imagen a la conversión de arte ASCII
convertir imagen a ascii online (1)
Hay más enfoques para la conversión de imágenes a imágenes ASCII que se basan principalmente en el uso de fuentes monoespaciadas por simplicidad. Me limito solo a lo básico:
Intensidad de píxeles / área basada (sombreado)
Este enfoque maneja cada píxel del área de píxeles como un solo punto.
La idea es calcular la intensidad promedio 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 calculada previamente, vamos a llamarlo
map
caracteres.
Para elegir más rápidamente qué personaje es el mejor para qué intensidad hay dos maneras:
-
mapa de caracteres de intensidad distribuida linealmente
Por lo tanto, usamos solo caracteres que tienen diferencia de intensidad con el mismo paso. En otras palabras, cuando se ordena ascendente entonces:
intensity_of(map[i])=intensity_of(map[i-1])+constant;
Además, cuando nuestro
map
caracteres está ordenado, podemos calcular el personaje directamente a partir de la intensidad (no se necesita búsqueda)character=map[intensity_of(dot)/constant];
-
mapa de caracteres de intensidad distribuida arbitraria
Así que tenemos una variedad de personajes utilizables y sus intensidades. Necesitamos encontrar la intensidad más cercana a la
intensity_of(dot)
Entonces, nuevamente, si ordenamos elmap[]
, podemos usar la búsqueda binaria; de lo contrario, necesitamosO(n)
buscar el bucle de distancia mínima o el diccionarioO(1)
. A veces, por simplicidad, elmap[]
caracteresmap[]
se puede manejar como distribuido linealmente, causando 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 es excelente también 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 agranda (1 píxel -> carácter único), por lo que para imágenes más grandes se selecciona un área (multiplicación del tamaño de fuente) para preservar la relación de aspecto y no ampliar demasiado.
Cómo hacerlo:
- así que divida uniformemente la imagen en píxeles (escala de grises) o áreas (rectangulares)
- calcular la intensidad de cada píxel / área
- reemplazarlo por el personaje del mapa de personajes con la intensidad más cercana
Como
map
caracteres, puede usar cualquier carácter, pero el resultado mejora si el carácter tiene píxeles dispersos uniformemente a lo largo del área de caracteres.
Para empezar puedes usar:
-
char map[10]=" .,:;ox%#@";
ordenados descendentes y pretenden estar distribuidos linealmente.
Entonces, si la intensidad de píxel / área es
i = <0-255>
entonces el carácter de reemplazo será
-
map[(255-i)*10/256];
si
i==0
entonces el píxel / área es negro, si
i==127
entonces el píxel / área es gris y si
i==255
entonces el píxel / área es blanco.
Puedes experimentar con diferentes personajes dentro del
map[]
...
Aquí un antiguo ejemplo mío en C ++ y VCL:
AnsiString m=" .,:;ox%#@";
Graphics::TBitmap *bmp=new Graphics::TBitmap;
bmp->LoadFromFile("pic.bmp");
bmp->HandleType=bmDIB;
bmp->PixelFormat=pf24bit;
int x,y,i,c,l;
BYTE *p;
AnsiString s,endl;
endl=char(13); endl+=char(10);
l=m.Length();
s="";
for (y=0;y<bmp->Height;y++)
{
p=(BYTE*)bmp->ScanLine[y];
for (x=0;x<bmp->Width;x++)
{
i =p[x+x+x+0];
i+=p[x+x+x+1];
i+=p[x+x+x+2];
i=(i*l)/768;
s+=m[l-i];
}
s+=endl;
}
mm_log->Lines->Text=s;
mm_log->Lines->SaveToFile("pic.txt");
delete bmp;
necesita reemplazar / ignorar cosas de VCL a menos que use el entorno Borland / Embarcadero
-
mm_log
es una nota donde semm_log
el texto -
bmp
es un mapa de bits de entrada -
AnsiString
es una cadena de tipo VCL indexada en forma 1, no de 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 5px), y en la imagen de entrada a la derecha se amplía varias veces. Como puede ver, el resultado es un píxel más grande -> carácter. Si utiliza áreas más grandes en lugar de píxeles, el zoom es más pequeño, pero, por supuesto, la salida es menos agradable visualmente. Este enfoque es muy fácil y rápido de codificar / procesar.
Cuando agrega cosas más avanzadas como:
- cálculos de mapas automatizados
- selección automática de tamaño de píxel / área
- correcciones de relación de aspecto
Entonces puede procesar imágenes más complejas con mejores resultados:
aquí da como resultado una relación 1: 1 (haga zoom para ver los caracteres):
Por supuesto, para el muestreo de área se pierden los pequeños detalles. Esta es una imagen del mismo tamaño que el primer ejemplo muestreado con áreas:
Imagen de ejemplo avanzado de intensidad ligeramente 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 utilizadas 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 representado.
Puede comenzar con una ingenua suma de diferencia de abs entre píxeles, pero eso conducirá a resultados no muy buenos porque incluso un desplazamiento de 1 píxel hará que la distancia sea grande, en su lugar puede usar correlación o diferentes métricas.
El algoritmo general es casi el mismo que el enfoque anterior:
-
dividir uniformemente la imagen en áreas rectangulares (escala de grises)
- idealmente con la misma relación de aspecto que los caracteres de fuente representados (conservará la relación de aspecto, no olvide que los caracteres generalmente se superponen un poco en el eje x)
-
calcular la intensidad de cada área (
dot
) -
reemplazarlo por el personaje del
map
personajes con la intensidad / forma más cercana
¿Cómo calcular la distancia entre el personaje y el punto? Esa es la parte más difícil de este enfoque. Mientras experimento, desarrollo este compromiso entre velocidad, calidad y sencillez:
-
Divide el área del personaje en zonas
-
calcule la intensidad separada para la zona izquierda, derecha, arriba, abajo y centro de cada carácter de su alfabeto de conversión (
map
) -
Normalizar todas las intensidades para que sean independientes del tamaño del área
i=(i*256)/(xs*ys)
-
calcule la intensidad separada para la zona izquierda, derecha, arriba, abajo y centro de cada carácter de su alfabeto de conversión (
-
procesar la imagen de origen en áreas rectangulares
- (con la misma relación de aspecto que la fuente de destino)
- para cada área calcule la intensidad de la misma manera que en la viñeta 1
- encuentra la coincidencia más cercana de las intensidades en el alfabeto de conversión
- carácter ajustado de salida
Este es el resultado para el tamaño de fuente = 7px
Como puede ver, la salida es visualmente agradable incluso con un tamaño de fuente más grande utilizado (el ejemplo de enfoque anterior fue con un tamaño de fuente de 5px). 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 un punto grueso).
Aquí complete el código para la aplicación de conversión basada en VCL:
//---------------------------------------------------------------------------
#include <vcl.h>
#pragma hdrstop
#include "win_main.h"
//---------------------------------------------------------------------------
#pragma package(smart_init)
#pragma resource "*.dfm"
TForm1 *Form1;
Graphics::TBitmap *bmp=new Graphics::TBitmap;
//---------------------------------------------------------------------------
class intensity
{
public:
char c; // character
int il,ir,iu,id,ic; // intensity of part: left,right,up,down,center
intensity() { c=0; reset(); }
void reset() { il=0; ir=0; iu=0; id=0; ic=0; }
void compute(DWORD **p,int xs,int ys,int xx,int yy) // p source image, (xs,ys) area size, (xx,yy) area position
{
int x0=xs>>2,y0=ys>>2;
int x1=xs-x0,y1=ys-y0;
int x,y,i;
reset();
for (y=0;y<ys;y++)
for (x=0;x<xs;x++)
{
i=(p[yy+y][xx+x]&255);
if (x<=x0) il+=i;
if (x>=x1) ir+=i;
if (y<=x0) iu+=i;
if (y>=x1) id+=i;
if ((x>=x0)&&(x<=x1)
&&(y>=y0)&&(y<=y1)) ic+=i;
}
// normalize
i=xs*ys;
il=(il<<8)/i;
ir=(ir<<8)/i;
iu=(iu<<8)/i;
id=(id<<8)/i;
ic=(ic<<8)/i;
}
};
//---------------------------------------------------------------------------
AnsiString bmp2txt_big(Graphics::TBitmap *bmp,TFont *font) // charcter sized areas
{
int i,i0,d,d0;
int xs,ys,xf,yf,x,xx,y,yy;
DWORD **p=NULL,**q=NULL; // bitmap direct pixel access
Graphics::TBitmap *tmp; // temp bitmap for single character
AnsiString txt=""; // output ASCII art text
AnsiString eol="/r/n"; // end of line sequence
intensity map[97]; // character map
intensity gfx;
// input image size
xs=bmp->Width;
ys=bmp->Height;
// output font size
xf=font->Size; if (xf<0) xf=-xf;
yf=font->Height; if (yf<0) yf=-yf;
for (;;) // loop to simplify the dynamic allocation error handling
{
// allocate and init buffers
tmp=new Graphics::TBitmap; if (tmp==NULL) break;
// allow 32bit pixel access as DWORD/int pointer
tmp->HandleType=bmDIB; bmp->HandleType=bmDIB;
tmp->PixelFormat=pf32bit; bmp->PixelFormat=pf32bit;
// copy target font properties to tmp
tmp->Canvas->Font->Assign(font);
tmp->SetSize(xf,yf);
tmp->Canvas->Font ->Color=clBlack;
tmp->Canvas->Pen ->Color=clWhite;
tmp->Canvas->Brush->Color=clWhite;
xf=tmp->Width;
yf=tmp->Height;
// direct pixel access to bitmaps
p =new DWORD*[ys]; if (p ==NULL) break; for (y=0;y<ys;y++) p[y]=(DWORD*)bmp->ScanLine[y];
q =new DWORD*[yf]; if (q ==NULL) break; for (y=0;y<yf;y++) q[y]=(DWORD*)tmp->ScanLine[y];
// create character map
for (x=0,d=32;d<128;d++,x++)
{
map[x].c=char(DWORD(d));
// clear tmp
tmp->Canvas->FillRect(TRect(0,0,xf,yf));
// render tested character to tmp
tmp->Canvas->TextOutA(0,0,map[x].c);
// compute intensity
map[x].compute(q,xf,yf,0,0);
} map[x].c=0;
// loop through image by zoomed character size step
xf-=xf/3; // characters are usually overlaping by 1/3
xs-=xs%xf;
ys-=ys%yf;
for (y=0;y<ys;y+=yf,txt+=eol)
for (x=0;x<xs;x+=xf)
{
// compute intensity
gfx.compute(p,xf,yf,x,y);
// find closest match in map[]
i0=0; d0=-1;
for (i=0;map[i].c;i++)
{
d=abs(map[i].il-gfx.il)
+abs(map[i].ir-gfx.ir)
+abs(map[i].iu-gfx.iu)
+abs(map[i].id-gfx.id)
+abs(map[i].ic-gfx.ic);
if ((d0<0)||(d0>d)) { d0=d; i0=i; }
}
// add fitted character to output
txt+=map[i0].c;
}
break;
}
// free buffers
if (tmp) delete tmp;
if (p ) delete[] p;
return txt;
}
//---------------------------------------------------------------------------
AnsiString bmp2txt_small(Graphics::TBitmap *bmp) // pixel sized areas
{
AnsiString m=" `''.,:;i+o*%&$#@"; // constant character map
int x,y,i,c,l;
BYTE *p;
AnsiString txt="",eol="/r/n";
l=m.Length();
bmp->HandleType=bmDIB;
bmp->PixelFormat=pf32bit;
for (y=0;y<bmp->Height;y++)
{
p=(BYTE*)bmp->ScanLine[y];
for (x=0;x<bmp->Width;x++)
{
i =p[(x<<2)+0];
i+=p[(x<<2)+1];
i+=p[(x<<2)+2];
i=(i*l)/768;
txt+=m[l-i];
}
txt+=eol;
}
return txt;
}
//---------------------------------------------------------------------------
void update()
{
int x0,x1,y0,y1,i,l;
x0=bmp->Width;
y0=bmp->Height;
if ((x0<64)||(y0<64)) Form1->mm_txt->Text=bmp2txt_small(bmp);
else Form1->mm_txt->Text=bmp2txt_big (bmp,Form1->mm_txt->Font);
Form1->mm_txt->Lines->SaveToFile("pic.txt");
for (x1=0,i=1,l=Form1->mm_txt->Text.Length();i<=l;i++) if (Form1->mm_txt->Text[i]==13) { x1=i-1; break; }
for (y1=0,i=1,l=Form1->mm_txt->Text.Length();i<=l;i++) if (Form1->mm_txt->Text[i]==13) y1++;
x1*=abs(Form1->mm_txt->Font->Size);
y1*=abs(Form1->mm_txt->Font->Height);
if (y0<y1) y0=y1; x0+=x1+48;
Form1->ClientWidth=x0;
Form1->ClientHeight=y0;
Form1->Caption=AnsiString().sprintf("Picture -> Text ( Font %ix%i )",abs(Form1->mm_txt->Font->Size),abs(Form1->mm_txt->Font->Height));
}
//---------------------------------------------------------------------------
void draw()
{
Form1->ptb_gfx->Canvas->Draw(0,0,bmp);
}
//---------------------------------------------------------------------------
void load(AnsiString name)
{
bmp->LoadFromFile(name);
bmp->HandleType=bmDIB;
bmp->PixelFormat=pf32bit;
Form1->ptb_gfx->Width=bmp->Width;
Form1->ClientHeight=bmp->Height;
Form1->ClientWidth=(bmp->Width<<1)+32;
}
//---------------------------------------------------------------------------
__fastcall TForm1::TForm1(TComponent* Owner):TForm(Owner)
{
load("pic.bmp");
update();
}
//---------------------------------------------------------------------------
void __fastcall TForm1::FormDestroy(TObject *Sender)
{
delete bmp;
}
//---------------------------------------------------------------------------
void __fastcall TForm1::FormPaint(TObject *Sender)
{
draw();
}
//---------------------------------------------------------------------------
void __fastcall TForm1::FormMouseWheel(TObject *Sender, TShiftState Shift,int WheelDelta, TPoint &MousePos, bool &Handled)
{
int s=abs(mm_txt->Font->Size);
if (WheelDelta<0) s--;
if (WheelDelta>0) s++;
mm_txt->Font->Size=s;
update();
}
//---------------------------------------------------------------------------
Es una aplicación de formulario simple (
Form1
) con
TMemo mm_txt
único en ella.
Carga la imagen
"pic.bmp"
, luego, según la resolución, elija qué enfoque usar para convertir a texto que se guarda en
"pic.txt"
y se envía a la nota para visualizar.
Para aquellos sin VCL, ignore las cosas de VCL y reemplace
AnsiString
con cualquier tipo de cadena que tenga, y también
Graphics::TBitmap
con cualquier mapa de bits o clase de imagen que tenga a disposición con capacidad de acceso de píxeles.
Una
nota
muy importante
es que esto usa la configuración de
mm_txt->Font
así que asegúrese de establecer:
-
Font->Pitch=fpFixed
-
Font->Charset=OEM_CHARSET
-
Font->Name="System"
para que esto funcione correctamente, de lo contrario, la fuente no se manejará como monoespaciado. La rueda del mouse solo cambia el tamaño de fuente hacia arriba / abajo para ver resultados en diferentes tamaños
[Notas]
- ver visualización de retratos de palabras
- utilizar lenguaje con mapa de bits / acceso a archivos y capacidades de salida de texto
- Recomiendo encarecidamente comenzar con el primer enfoque, ya que es muy fácil avanzar hacia adelante y simple, y solo luego pasar al segundo (lo que se puede hacer como modificación del primero, por lo que la mayor parte del código permanece igual)
- Es una buena idea calcular con intensidad invertida (los píxeles negros son el valor máximo) porque la vista previa de texto estándar está sobre fondo blanco y, por lo tanto, ofrece resultados mucho mejores.
-
puede experimentar con el tamaño, el recuento y el diseño de las zonas de subdivisión o usar una cuadrícula como
3x3
lugar.
[Editar1] comparación
Finalmente, aquí hay una comparación entre los dos enfoques en la misma entrada:
Las imágenes marcadas con puntos verdes se realizan con el enfoque n
. ° 2
y las rojas con el n
. ° 1,
todo en un tamaño de fuente de
6
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).
[Edit2] aplicación genial
Mientras leía las nuevas preguntas de hoy, tuve una idea de una aplicación genial que toma la región seleccionada del escritorio y la alimenta continuamente al convertidor ASCIIart y ve el resultado. Después de una hora de codificación ya está hecho y estoy tan satisfecho con el resultado que simplemente debo agregarlo aquí.
OK, la aplicación consta de solo 2 ventanas. La primera ventana maestra es básicamente mi ventana de convertidor anterior sin la selección de imagen y la vista previa (todo lo anterior está en ella). Tiene solo la vista previa ASCII y la configuración de conversión. La segunda ventana es un formulario vacío con un interior transparente para la selección del área de captura (sin funcionalidad alguna).
Ahora, en el temporizador, simplemente tomo el área seleccionada mediante el formulario de selección, la paso a la conversión y previsualizo el ASCIIart .
Por lo tanto, encierra el área que desea convertir mediante la ventana de selección y ve el resultado en la ventana maestra. Puede ser un juego, un visor, ... Se ve así:
Así que ahora puedo ver incluso videos en ASCIIart por diversión. Algunos son realmente agradables :).
[Editar3]
Si desea intentar implementar esto en GLSL, eche un vistazo a esto:
- ¿Convertir números de coma flotante a dígitos decimales en GLSL?
Prólogo
Este tema aparece aquí en SO de vez en cuando, pero generalmente se elimina por ser una pregunta mal escrita. Vi muchas de esas preguntas y luego silencio desde el OP (habitual baja rep) cuando se solicita información adicional. De vez en cuando, si el aporte es lo suficientemente bueno para mí, decido responder con una respuesta y, por lo general, recibe algunos votos por día mientras está activo, pero luego de algunas semanas la pregunta se elimina / elimina y todo comienza desde el principio . Así que decidí escribir estas preguntas y respuestas para poder hacer referencia a esas preguntas directamente sin tener que volver a escribir la respuesta una y otra vez ...
Otra razón es también este hilo META dirigido a mí, por lo que si tiene una entrada adicional, no dude en comentar.
Pregunta
¿Cómo convertir una imagen de mapa de bits a arte ASCII usando C ++ ?
Algunas restricciones:
- imágenes en escala de grises
- utilizando fuentes monoespaciadas
- manteniéndolo simple (no usar cosas demasiado avanzadas para programadores de nivel principiante)
Aquí hay una página Wiki relacionada con el arte ASCII (gracias a @RogerRowland)