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
** 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)
** 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)
** 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?
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.
this code works fine, but on a hidden from return a black image, what are I missing?
ReplyDeletelocal locapturebmp as xfcbitmap
do locfile("system.app")
with _screen.system.drawing
locapturebmp = .bitmap.fromscreen(_screen.hwnd)
locapturebmp.save("z.png", .imaging.imageformat.png)
ENDWITH
correction, this code works fine, but on a hidden FORM return a black image, what are I missing?
ReplyDeletelocal locapturebmp as xfcbitmap
Nothing is missing. To capture the image, the form MUST be visible!
ReplyDeleteHi Daniel,
ReplyDeleteTo capture the image of the form, it MUST be visible !