25 de febrero de 2016

Generador de imágenes Captcha con GDI+

Artículo original: Captcha Image Generator with GdiPlus-X
https://vfpimaging.blogspot.com/2012/03/captcha-image-generator-with-gdiplusx.html
Autor: Cesar Ch.
Traducido por: Ana María Bisbé York


Recientemente, estaba navegando por el Blog de Rick Stralh cuando encontré un escrito muy interesante, "A Captcha Image generator for FoxPro", en el cual el muestra cómo crear algunas imágenes "captcha".

Nota de la Traductora: CAPTCHA es el acrónimo de "Completely Automated Public Turing test to tell Computers and Humans Apart", y consiste en una verificación automatizada para determinar si el usuario que realiza la petición es humano o no.

Como ha dicho Rick Stralh en su escrito, "CAPTCHA muestra básicamente una imagen de verificación junto a un cuadro de texto que tiene que ser llenado para verificar la petición actual. No es un procedimiento infalible para la validación y tiene algunos problemas de accesibilidad; pero parece que es la solución más frecuente a este problema." La imagen generada contiene un texto aleatorio.

En este caso, Rick Stralh escogió crear una DLL en C++, y la llama desde VFP. Aquí mostraré cómo obtener los mismos resultados utilizando la nueva biblioteca GdiPlus-X.

El código que se muestra a continuación pudiera ser útil para otros propósitos también, porque se utilizaron algunas técnicas de dibujo muy interesantes.

Para obtener la cadena aleatoria, creé una función muy simple, que recibe como un parámetro de cantidad de caracteres que se crearán.

PROCEDURE CreateString(tnLength
  LOCAL lcText, lcChar, x, n
  lcText = ""
  FOR n = 1 TO tnLength
    x = INT(RAND() * 36)
    lcChar = IIF(x < 10, TRANSFORM(x), CHR(x + 55))
    lcText = lcText + lcChar
  ENDFOR
  RETURN lcText
ENDPROC

¡ A JUGAR !

IMPORTANTE:

Todos los ejemplos que se muestran emplean la nueva biblioteca GDIPlus-X, que está aun en versión ALPHA; pero es estable y fiable para hacer la mayoría de las tareas de GDI+. Descargue la última versión estable de Codeplex:

http://www.codeplex.com/Wiki/View.aspx?ProjectName=VFPX&title=GDIPlusX

EJEMPLO 1 - CREAR UN IMAGEN SENCILLA Y DIBUJE ALGÚN TEXTO

** Generador de imágenes Captcha 
** Crea una imagen muy sencilla que contiene una cadena
** Se basa en el código de Rick Strahl expuesto en su weblog
** http://www.west-wind.com/wconnect/weblog/ShowEntry.blog?id=556
** El siguiente ejemplo muestra como crear un sencillo Generador de imágenes Captcha 
LOCAL lcText AS Character 
lcText = CreateString(8) 
_SCREEN.AddProperty("System", NEWOBJECT("xfcSystem", ;
  LOCFILE("system.vcx","vcx")))

LOCAL loBmp AS xfcBitmap
LOCAL loFont AS xfcFont
LOCAL loSizeF AS xfcSizeF
LOCAL loScreenGfx AS xfcGraphics
LOCAL loGfx AS xfcGraphics 
WITH _SCREEN.System.Drawing
  loScreenGfx = .Graphics.FromHwnd(_Screen.HWnd)
  loFont = .Font.New("Tahoma",20) && Font Name, Size
  * Obtiene la altura y el ancho necesarios para la cadena
  loSizeF = loScreenGfx.MeasureString(lcText, loFont)
  * Crea un bitmap nuevo
  loBmp = .Bitmap.New(loSizeF.Width, loSizeF.Height)
  * Obtiene un objeto Graphics para dibujar
  loGfx = .Graphics.FromImage(loBmp)
  loGfx.Clear(.Color.White) 
  * Dibuja toda la cadena
  loGfx.DrawString(lcText, loFont, .Brushes.Black, 0, 0)
  * Guarda la imagen en disco
  loBmp.Save("c:\captcha1.png", .Imaging.ImageFormat.Png)
ENDWITH 
* Muestra la imagen en el explorador
RUN /n explorer.exe c:\captcha1.png
RETURN 

PROCEDURE CreateString(tnLength)
  LOCAL lcText, lcChar, x, n
  lcText = ""
  FOR n = 1 TO tnLength
    x = INT(RAND() * 36)
    lcChar = IIF(x < 10, TRANSFORM(x), CHR(x + 55))
    lcText = lcText + lcChar
  ENDFOR
  RETURN lcText
ENDPROC 

El ejemplo de arriba crea la imagen más sencilla, que contiene una cadena aleatoria dentro. para obtener el tamaño necesario para crear el Bitmap, en el primer momento, he creado un objeto Graphics desde _Screen. Como se puede ver a continuación, este objeto tiene solamente una tarea: permitirnos llamar a la función MeasureString para obtener el tamaño necesario para nuestro bitmap.

loScreenGfx = .Graphics.FromHwnd(_Screen.HWnd)
loFont = .Font.New("Tahoma",20) && FontName, FontSize
* Obtiene la altura y el ancho necesarios para la cadena
loSizeF = loScreenGfx.MeasureString(lcText, loFont)
* Crea un bitmap nuevo
loBmp = .Bitmap.New(loSizeF.Width, loSizeF.Height) 

EJEMPLO 2 - CREA UNA IMAGEN SENCILLA Y DIBUJA UN TEXTO SOBRE UN FONDO TRAMADO.

** Generador de imágenes Captcha 
** Crea una imagen muy sencilla que contiene una cadena con un fondo tramado
** Se basa en el código de Rick Strahl expuesto en su weblog
** http://www.west-wind.com/wconnect/weblog/ShowEntry.blog?id=556
** El siguiente ejemplo muestra como crear un sencillo Generador de imágenes Captcha 
LOCAL lcText AS Character 
lcText = CreateString(8) 
_SCREEN.AddProperty("System", NEWOBJECT("xfcSystem", ;
  LOCFILE("system.vcx","vcx")))

LOCAL loBmp AS xfcBitmap
LOCAL loFont AS xfcFont
LOCAL loSizeF AS xfcSizeF
LOCAL loScreenGfx AS xfcGraphics
LOCAL loGfx AS xfcGraphics 
WITH _SCREEN.System.Drawing
  loScreenGfx = .Graphics.FromHwnd(_Screen.HWnd)
  loFont = .Font.New("Tahoma",20) && Font Name, Size
  * Obtiene la altura y el ancho necesarios para la cadena
  loSizeF = loScreenGfx.MeasureString(lcText, loFont)
  * Crea un bitmap nuevo
  loBmp = .Bitmap.New(loSizeF.Width, loSizeF.Height)
  * Obtiene un objeto Graphics para dibujar
  loGfx = .Graphics.FromImage(loBmp)
  loGfx.Clear(.Color.White) 
  liRand = INT(RAND()*52) + 1 
  * Rellena el fondo del rectángulo con una brocha tramada de forma aleatoria
  loGfx.FillRectangle( ;
  .Drawing2D.HatchBrush.New(liRand, .Color.Black, .Color.White),;
  0,0,loBmp.Width,loBmp.Height)
  * Dibuja toda la cadena
  loGfx.DrawString(lcText, loFont, .Brushes.Black, 0, 0)
  * Guarda la imagen en disco
  loBmp.Save("c:\captcha2.png", .Imaging.ImageFormat.Png)
ENDWITH 
* Muestra la imagen en el explorador
RUN /n explorer.exe c:\captcha2.png
RETURN 

PROCEDURE CreateString(tnLength)
  LOCAL lcText, lcChar, x, n
  lcText = ""
  FOR n = 1 TO tnLength
    x = INT(RAND() * 36)
    lcChar = IIF(x < 10, TRANSFORM(x), CHR(x + 55))
    lcText = lcText + lcChar
  ENDFOR
  RETURN lcText
ENDPROC 

Este es exactamente el mismo ejemplo que utilizamos antes; pero agregando un fondo tramado. GDI+ ofrece 52 plantillas diferentes para brochas con tramas. En este ejemplo, preferí utilizar uno aleatorio. Verifíquelo y seleccione el que le guste más:

* Rellena el fondo del rectángulo con una brocha tramada de forma aleatoria
loGfx.FillRectangle( ;
.Drawing2D.HatchBrush.New(liRand, .Color.Black, .Color.White),;
0,0,loBmp.Width,loBmp.Height)

EJEMPLO 3 - DIBUJAR ALGÚN TEXTO CON UN FONDO COLOREADO UTILIZANDO COLORES ALEATORIOS

** Generador de imágenes Captcha 
** Crea una imagen muy sencilla que contiene una cadena 
** Cada carácter en color aleatorio diferente con un color de fondo aleatorio
** Se basa en el código de Rick Strahl expuesto en su weblog
** http://www.west-wind.com/wconnect/weblog/ShowEntry.blog?id=556
** El siguiente ejemplo muestra como crear un sencillo Generador de imágenes Captcha 
LOCAL lcText AS Character 
lcText = CreateString(8) 
_SCREEN.AddProperty("System", NEWOBJECT("xfcSystem", ;
  LOCFILE("system.vcx","vcx")))

LOCAL loBmp AS xfcBitmap
LOCAL loFont AS xfcFont
LOCAL loSizeF AS xfcSizeF
LOCAL loScreenGfx AS xfcGraphics
LOCAL loGfx AS xfcGraphics 
WITH _SCREEN.System.Drawing
  loScreenGfx = .Graphics.FromHwnd(_Screen.HWnd)
  loFont = .Font.New("Tahoma",20) && Font Name, Size
  * Obtiene la altura y el ancho necesarios para la cadena
  loSizeF = loScreenGfx.MeasureString(lcText, loFont)
  LOCAL lnWidth, lnHeight, lnCharWidth
  lnWidth = loSizeF.Width * 1.5 && Se estrecha al 50% para garantizar que quepan los caracteres rotados
  lnHeight = loSizeF.Height
  lnCharWidth = lnWidth / LEN(lcText) 
  * Crea un bitmap nuevo
  loBmp = .Bitmap.New(lnWidth, lnHeight)
  * Obtiene un objeto Graphics para dibujar
  loGfx = .Graphics.FromImage(loBmp)
  loGfx.Clear(.Color.White) 
  * Llena el fondo del rectángulo con una brocha sólida de color aleatorio
  * Necesitamos aquí un color pastel color, por lo que haremos que cada canal sea al menos 180 
  * Dibuja el rectángulo de fondo
  loGfx.FillRectangle( ;
  .SolidBrush.New(.Color.FromARGB(255, INT(RAND() * 76)+180, ;
    INT(RAND() * 76)+180, INT(RAND() * 76)+180)), ;
    0,0,loBmp.Width,loBmp.Height)
  * Dibuja cada carácter en un color aleatorio diferente
  LOCAL n, lcChar
  LOCAL loBrush AS xfcSolidBrush
  FOR n = 0 TO LEN(lcText)
    lcChar = SUBSTR(lcText, n, 1) 
    * Crea una brocha con un color aleatorio
    loBrush = .SolidBrush.New(.Color.FromRGB(INT(RAND() * 0xFFFFFF))) 
    * Dibuja el carácter
    loGfx.DrawString(lcChar, loFont, loBrush, (n-1) * lnCharWidth, 0)
  ENDFOR 
  * Guarda la imagen en disco
  loBmp.Save("c:\captcha3.png", .Imaging.ImageFormat.Png)
ENDWITH 
* Muestra la imagen en el explorador
RUN /n explorer.exe c:\captcha3.png
RETURN 

PROCEDURE CreateString(tnLength)
  LOCAL lcText, lcChar, x, n
  lcText = ""
  FOR n = 1 TO tnLength
    x = INT(RAND() * 36)
    lcChar = IIF(x < 10, TRANSFORM(x), CHR(x + 55))
    lcText = lcText + lcChar
  ENDFOR
 RETURN lcText
ENDPROC 

Esta vez decidí crear un fondo utilizando una brocha sólida con un valor aleatorio en BackColor. Quise asegurarme de obtener un color pastel, por que utilicé:

INT(RAND() * 76) + 180 para cada canal de color RGB. Esta forma, cada canal tendrá al menos el valor 180, asegurando que obtenga un color brillante.

También muy sencillo dibujar cada color en un color aleatorio. Solamente hice un lazo de la cadena y dibujé cada caracter en su posición proporcional. Este momento, deseo tener cualquier color aleatorio, por tanto podría ser cualquier valor entre 0 (RGB[0,0,0] - negro) y 0xFFFFFF (RGB[255,255,255] - blanco).

* Crea una brocha con un color aleatorio 
loBrush = .SolidBrush.New(.Color.FromRGB(INT(RAND() * 0xFFFFFF))) 

EJEMPLO 4 - DIBUJAR ALGÚN TEXTO EN UN FONDO CON TRAMA COLOREADA UTILIZANDO COLORES ALEATORIOS CON ROTACIÓN DE CARACTERES.

** Generador de imágenes Captcha 
** Crea una imagen muy sencilla que contiene una cadena 
** Cada carácter en color aleatorio diferente y una ligera rotación
** Se basa en el código de Rick Strahl expuesto en su weblog
** http://www.west-wind.com/wconnect/weblog/ShowEntry.blog?id=556
** El siguiente ejemplo muestra como crear un sencillo Generador de imágenes Captcha 
LOCAL lcText AS Character 
lcText = CreateString(8) 
_SCREEN.AddProperty("System", NEWOBJECT("xfcSystem", ;
  LOCFILE("system.vcx","vcx")))

LOCAL loBmp AS xfcBitmap
LOCAL loFont AS xfcFont
LOCAL loSizeF AS xfcSizeF
LOCAL loScreenGfx AS xfcGraphics
LOCAL loGfx AS xfcGraphics 
WITH _SCREEN.System.Drawing
  loScreenGfx = .Graphics.FromHwnd(_Screen.HWnd)
  loFont = .Font.New("Tahoma",20) && Font Name, Size
  * Obtiene la altura y el ancho necesarios para la cadena
  loSizeF = loScreenGfx.MeasureString(lcText, loFont)
  LOCAL lnWidth, lnHeight, lnCharWidth
  lnWidth = loSizeF.Width * 1.5 && Se estrecha al 50% para garantizar que quepan los caracteres rotados
  lnHeight = loSizeF.Height
  lnCharWidth = lnWidth / LEN(lcText) 
  * Crea un bitmap nuevo
  loBmp = .Bitmap.New(lnWidth, lnHeight)
  * Obtiene un objeto Graphics para dibujar
  loGfx = .Graphics.FromImage(loBmp)
  loGfx.Clear(.Color.White) 
  * Llena el fondo del rectángulo con una brocha sólida de colores pastel aleatorios
  loGfx.FillRectangle( ;
  .Drawing2D.HatchBrush.New(INT(RAND()*52), ;
    .Color.FromARGB(255, INT(RAND() * 76)+180, INT(RAND() * 76)+180, INT(RAND() * 76)+180), ;
    .Color.FromARGB(255, INT(RAND() * 76)+180, INT(RAND() * 76)+180, INT(RAND() * 76)+180)), ;
    0,0,loBmp.Width,loBmp.Height)
  * Dibuja cada carácter en un color aleatorio diferente 
  * y un ángulo aleatorio de rotación desde -40 a +40 grados
  LOCAL n, lcChar
  LOCAL loBrush AS xfcSolidBrush
  LOCAL loMatrix AS xfcMatrix 
  FOR n = 0 TO LEN(lcText)
    lcChar = SUBSTR(lcText, n, 1) 
    * Crea una brocha con un color aleatorio
    loBrush = .SolidBrush.New(.Color.FromRGB(INT(RAND() * 0xFFFFFF))) 
    * Crea una matriz para aplicar la rotación de los caracteres
    loMatrix = _Screen.System.Drawing.Drawing2D.Matrix.New() 
    * Calcula el ángulo aleatorio
    lnAngle = INT(RAND() * 80) - 40
    * Rota al centro de cada posición de carácter
    loMatrix.RotateAt(lnAngle, ;
      .Point.New((n-1) * lnCharWidth + (lnCharWidth / 2), lnHeight / 2)) 
    * Asocia la matriz al objeto Graphics 
    loGfx.Transform = loMatrix 
    * Dibuja el carácter
    loGfx.DrawString(lcChar, loFont, loBrush, (n-1) * lnCharWidth, 0)
  ENDFOR 
  * Guarda la imagen en disco
  loBmp.Save("c:\captcha4.png", .Imaging.ImageFormat.Png)
ENDWITH 
* Muestra la imagen en el explorador
RUN /n explorer.exe c:\captcha4.png
RETURN 

PROCEDURE CreateString(tnLength)
  LOCAL lcText, lcChar, x, n
  lcText = ""
  FOR n = 1 TO tnLength
    x = INT(RAND() * 36)
    lcChar = IIF(x < 10, TRANSFORM(x), CHR(x + 55))
    lcText = lcText + lcChar
  ENDFOR
  RETURN lcText
ENDPROC

Aquí hicimos lo mismo; pero utilizando una brocha con trama para el fondo, y aplicando una ligera rotación de cada carácter. La rotación de caracteres ya se ha discutido en un escrito anterior http://weblogs.foxite.com/vfpimaging/2012/03/16/draw-rotated-strings-with-gdiplusx en cualquier caso, he aquí una corta explicación:

"System.Drawing" brinda un método que permite dibujar cualquier objeto (forma, texto y otra imagen) utilizando rotación, de cualquier ángulo. Para esto necesitamos crear un objeto Matriz, y utilizamos el método "RotateAt", pasando el punto central del objeto que se rotará. En este caso, necesito pasar el centro de la cadena.

*  Crea una matriz para aplicar la rotación de los caracteres 
loMatrix = .Drawing2D.Matrix.New() 
*  Calcula el ángulo aleatorio
lnAngle = INT(RAND() * 80) - 40
*  Rota al centro de cada posición de carácter
loMatrix.RotateAt(lnAngle, ;
  .Point.New((n-1) * lnCharWidth + (lnCharWidth / 2), lnHeight / 2)) 
*  Asocia la matriz al objeto Graphics 
loGfx.Transform = loMatrix 
* Dibuja el carácter
loGfx.DrawString(lcChar, loFont, loBrush, (n-1) * lnCharWidth, 0)

¿QUÉ MÁS?

Podemos hacer algunas otras cosas, tales como dibujar algunas líneas aleatorias en la imagen. Una forma podría ser simplemente agregando este sencillo código inmediatamente antes de guardar la imagen:

LOCAL x1, y1, x2, y2, lnColor
FOR n = 1 TO 10
  x1 = RAND() * lnWidth
  x2 = RAND() * lnWidth
  y1 = RAND() * lnHeight
  y2 = RAND() * lnHeight
  lnColor = RAND() * 0xFFFFFF
  loGfx.DrawLine(.Pen.New(.Color.FromRGB(lnColor),1),x1, y1, x2, y2)
ENDFOR

A pesar de que se conoce que estos son únicamente algunos CAPTCHA decoradores, puede ser muy útil en determinadas situaciones. Mi objetivo en este escrito es simplemente mostrar algunas técnicas para crear buenos captchas. Hay mucho que mejorar. Usted puede utilizar fuentes irregulares aleatorias, otros fondos, aplicar algunas transformaciones a caracteres, como escalar, cortes, nublados, efectos halo. ¡ Todo esto con GDI+ !

En la próxima liberación de la biblioteca, espero enviar algunos ejemplos que creen efectos interesantes en textos, tales como, algunos que se muestran debajo pudieran también ser útiles.

Manténgase en contacto.


No hay comentarios. :

Publicar un comentario

Los comentarios son moderados, por lo que pueden demorar varias horas para su publicación.