pegar online imagen hacker copiar convertir convertidor convert arte art c++ image-processing bitmap 2d ascii-art

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:

  1. 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];

  2. 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 el map[] , podemos usar la búsqueda binaria; de lo contrario, necesitamos O(n) buscar el bucle de distancia mínima o el diccionario O(1) . A veces, por simplicidad, el map[] caracteres map[] 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:

  1. así que divida uniformemente la imagen en píxeles (escala de grises) o áreas (rectangulares)
  2. calcular la intensidad de cada píxel / área
  3. 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 se mm_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 como char* !

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:

  1. 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)
  2. calcular la intensidad de cada área ( dot )
  3. 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:

  1. 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)
  2. 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)