Recently I was navigating at
Rick Strahl's Blog, when I found a very interesting post, "
A Captcha Image generator for FoxPro", in which he shows how he created some captcha images. As Rick said in his post, "
CAPTCHA basically displays a verification image next to a textbox that has to be filled out to validate the current request. It’s not a foolproof approach for validation and it has some issues with accessibility but it seems to be a common solution to this problem". The generated image contains a random text.
In that case, Rick chose to create a DLL in C++, and call it from VFP. Here, I'll show how to obtain the same results using the new GdiPlus-X library.
The codes below might be useful for other purposes too, because some interesting drawing techniques were used there.
To obtain the random string, I created a very simple function, that receives as parameter the quantity of characters that will be created:
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
TIME TO PLAY!
IMPORTANT:
All samples below use the new GDIPlus-X library. Download the latest stable release from Codeplex:
http://www.codeplex.com/Wiki/View.aspx?ProjectName=VFPX&title=GDIPlusX
SAMPLE 1 - CREATE A SIMPLE IMAGE AND DRAW SOME TEXT
** Captcha Image Generator
** Creates a very simple image containing a string
** Based on Rick Strahl post on his weblog
** http://www.west-wind.com/wconnect/weblog/ShowEntry.blog?id=556
* The following sample demonstrates how to create a simple Captcha Generator
LOCAL lcText AS Character
lcText = CreateString(8)
DO LOCFILE("System.App")
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
* Obtain the width and height needed for the string
loSizeF = loScreenGfx.MeasureString(lcText, loFont)
* Create the new bitmap
loBmp = .Bitmap.New(loSizeF.Width, loSizeF.Height)
* Obtain a Graphics object to draw
loGfx = .Graphics.FromImage(loBmp)
loGfx.Clear(.Color.White)
* Draw the whole String
loGfx.DrawString(lcText, loFont, .Brushes.Black, 0, 0)
* Save image to disk
loBmp.Save("c:\captcha1.png", .Imaging.ImageFormat.Png)
ENDWITH
* Show image in Explorer
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
The above sample creates the most simple image, containing a random string inside. To get the size needed to create the Bitmap, in a first moment I created a Graphics object from the _Screen. This object has only one task: to allow us to call the function MeasureString to obtain the size needed for our bitmap, as you can see below:
loScreenGfx = .Graphics.FromHwnd(_Screen.HWnd)
loFont = .Font.New("Tahoma",20) && FontName, FontSize
* Obtain the width and height needed for the string
loSizeF = loScreenGfx.MeasureString(lcText, loFont)
* Create the new bitmap
loBmp = .Bitmap.New(loSizeF.Width, loSizeF.Height)
SAMPLE 2 - CREATE A SIMPLE IMAGE AND DRAW SOME TEXT ON A HATCH BACKGROUND
** Captcha Image Generator
** Creates a very simple image containing a string on a hatch background
** Based on Rick Strahl post on his weblog
** http://www.west-wind.com/wconnect/weblog/ShowEntry.blog?id=556
* The following sample demonstrates how to create a simple Captcha Generator
LOCAL lcText AS Character
lcText = CreateString(8)
DO LOCFILE("System.App")
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
* Obtain the width and height needed for the string
loSizeF = loScreenGfx.MeasureString(lcText, loFont)
* Create the new bitmap
loBmp = .Bitmap.New(loSizeF.Width, loSizeF.Height)
* Obtain a Graphics object to draw
loGfx = .Graphics.FromImage(loBmp)
loGfx.Clear(.Color.White)
liRand = INT(RAND()*52) + 1
* Fill the background of the rectangle with a random Hatch Brush
loGfx.FillRectangle( ;
.Drawing2D.HatchBrush.New(liRand, .Color.Black, .Color.White),;
0,0,loBmp.Width,loBmp.Height)
* Draw the whole string
loGfx.DrawString(lcText, loFont, .Brushes.Black, 0, 0)
* Save image to disk
loBmp.Save("c:\captcha2.png", .Imaging.ImageFormat.Png)
ENDWITH
* Show image in Explorer
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
This is exactly the same sample we used before, but adding a hatch background. Gdi+ provides 52 different preset patterns for hatch brushes. In this sample, I chose to use a random one. Test it, and choose the one that you like most:
* Fill the background of the rectangle with a random Hatch Brush
loGfx.FillRectangle( ;
.Drawing2D.HatchBrush.New(liRand, .Color.Black, .Color.White),;
0,0,loBmp.Width,loBmp.Height)
SAMPLE 3 - DRAW SOME TEXT ON A COLORED BACKGROUND USING RANDOM COLORS
** Captcha Image Generator
** Creates a simple image containing a string
** Each character in a different random color
** in a random background color
** Based on Rick Strahl post on his weblog
** http://www.west-wind.com/wconnect/weblog/ShowEntry.blog?id=556
* The following sample demonstrates how to create a simple Captcha Generator
LOCAL lcText AS Character
lcText = CreateString(8)
DO LOCFILE("System.App")
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
* Obtain the width and height needed for the string
loSizeF = loScreenGfx.MeasureString(lcText, loFont)
* Obtain the width and height needed for the string
loSizeF = loScreenGfx.MeasureString(lcText, loFont)
LOCAL lnWidth, lnHeight, lnCharWidth
lnWidth = loSizeF.Width * 1.5 && Stretch 50% to ensure rotated
&& characters will fit
lnHeight = loSizeF.Height
lnCharWidth = lnWidth / LEN(lcText)
* Create the new bitmap
loBmp = .Bitmap.New(lnWidth, lnHeight)
* Obtain a Graphics object to draw
loGfx = .Graphics.FromImage(loBmp)
loGfx.Clear(.Color.White)
* Fill the background of the rectangle with a random Solid Brush color
* We need here a pastel color, so let's make each channel will be
* at least 180
* Draw the background rectangle
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)
* Draw each character in a different random color
LOCAL n, lcChar
LOCAL loBrush AS xfcSolidBrush
FOR n = 0 TO LEN(lcText)
lcChar = SUBSTR(lcText, n, 1)
* Create a brush with a random color
loBrush = .SolidBrush.New(.Color.FromRGB(INT(RAND() * 0xFFFFFF)))
* Draw the character
loGfx.DrawString(lcChar, loFont, loBrush, (n-1) * lnCharWidth, 0)
ENDFOR
* Save image to disk
loBmp.Save("c:\captcha3.png", .Imaging.ImageFormat.Png)
ENDWITH
* Show image in Explorer
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
This time I chose to create the background using a Solid Brush with a random backColor. For that I wanted to ensure to obtain a pastel color, so I used
INT(RAND() * 76) + 180 for each RGB color channel. This way, each channel will have at least the value 180, ensuring to obtain a bright color.
To draw each color in a different random color was easy too. Just looped through the string and drawn each character in its proportional position. This time, I wanted to have ANY random color, so that could be any value between 0 (RGB[0,0,0] - black) and 0xFFFFFF (RGB[255,255,255] - white).
* Create a brush with a random color
loBrush = .SolidBrush.New(.Color.FromRGB(INT(RAND() * 0xFFFFFF)))
SAMPLE 4 - DRAW SOME TEXT ON A COLORED HATCH BACKGROUND USING RANDOM COLORS WITH CHARACTER ROTATION
** Captcha Image Generator
** Creates an image containing a string
** Each character in a different random color and a slight rotation
** Based on Rick Strahl post on his weblog
** http://www.west-wind.com/wconnect/weblog/ShowEntry.blog?id=556
* The following sample demonstrates how to create a simple Captcha Generator
LOCAL lcText AS Character
lcText = CreateString(8)
DO LOCFILE("System.App")
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
* Obtain the width and height needed for the string
loSizeF = loScreenGfx.MeasureString(lcText, loFont)
LOCAL lnWidth, lnHeight, lnCharWidth
lnWidth = loSizeF.Width * 1.5 && Stretch 50% to ensure rotated
&& characters will fit
lnHeight = loSizeF.Height
lnCharWidth = lnWidth / LEN(lcText)
* Create the new bitmap
loBmp = .Bitmap.New(lnWidth, lnHeight)
* Obtain a Graphics object to draw
loGfx = .Graphics.FromImage(loBmp)
loGfx.Clear(.Color.White)
* Fill the background rectangle using a Hatch Brush with random pastel colors
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)
* Draw each character in a different random color
* and a random rotation angle from -40 to +40 degrees
LOCAL n, lcChar
LOCAL loBrush AS xfcSolidBrush
LOCAL loMatrix AS xfcMatrix
FOR n = 0 TO LEN(lcText)
lcChar = SUBSTR(lcText, n, 1)
* Create a brush with a random color
loBrush = .SolidBrush.New(.Color.FromRGB(INT(RAND() * 0xFFFFFF)))
* Create a Matrix to apply rotation to the characters
loMatrix = _Screen.System.Drawing.Drawing2D.Matrix.New()
* Calculate random angle
lnAngle = INT(RAND() * 80) - 40
* Rotate at the center of each character position
loMatrix.RotateAt(lnAngle, ;
.Point.New((n-1) * lnCharWidth + (lnCharWidth / 2), lnHeight / 2))
* Associate the Matrix to the Graphics object
loGfx.Transform = loMatrix
* Draw the character
loGfx.DrawString(lcChar, loFont, loBrush, (n-1) * lnCharWidth, 0)
ENDFOR
* Save image to disk
loBmp.Save("c:\captcha4.png", .Imaging.ImageFormat.Png)
ENDWITH
* Show image in Explorer
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
This time we did the same, but using a random Hatch Brush for the background, and aplying a slight rotation on each character. String rotation was already discussed in a
previous post, anyway, here's a short explanation:
"System.Drawing" brings a method that permits to draw any object (shape, text, another image) using rotation, at any angle. For that we need to create a Matrix object, and use the "RotateAt" method, passing the center point of the object to be rotated. In this case, I needed to pass the center of the string.
* Create a Matrix to apply rotation to the characters
loMatrix = .Drawing2D.Matrix.New()
* Calculate random angle
lnAngle = INT(RAND() * 80) - 40
* Rotate at the center of each character position
loMatrix.RotateAt(lnAngle, ;
.Point.New((n-1) * lnCharWidth + (lnCharWidth / 2), lnHeight / 2))
* Associate the Matrix to the Graphics object
loGfx.Transform = loMatrix
* Draw the character
loGfx.DrawString(lcChar, loFont, loBrush, (n-1) * lnCharWidth, 0)
WHAT MORE?
We can do some more things, like to draw some random lines on the image. One way could be just adding this simple code immediately before saving the image:
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
Although it is known that there are already some Captcha decoders, this might be useful for some situations. My goal in this post was just to show some techniques to create good captchas. There's a lot to be improved. You can use random and irregular fonts, other backgrounds, apply some transformations to characters, like scaling, shearing, blurring, halo effects. All this with GDI+ !
In the next release of the library, I hope to send some samples that create some interesting effects on texts, like the ones below, that might be useful for this too.