Title: Make CAPTCHA images in C#
CAPTCHA (Completely Automated Public Turing test to tell Computers and Humans Apart) images are those distorted pictures of words that some Web sites make you enter to prove you are a human and not an automated process. The idea is to distort the characters in the image so it would be hard for an optical character recognition (OCR) application to read them, but so it would still be easy for a person to read them. (Note that this form of CAPTCHA has fallen out of favor lately. The "click the part of the picture that contains crosswalks" style test is more popular these days, and even that is becoming less common.)
This example uses relatively large fonts so the characters tend to overlap in interesting ways that are hard for an OCR program to decipher.
Note that some scammers outsource CAPTCHA images to cheap laborers who get paid around $0.75 per thousand images, so this isn't a foolproof technique. However, even that low level of cost is enough to weed out some percentage of scammers.
The following MakeCaptchaImage method makes a CAPTCHA image.
private Random Rand = new Random();
// Make a captcha image for the text.
private Bitmap MakeCaptchaImge(string txt,
int min_size, int max_size, int wid, int hgt)
{
// Make the bitmap and associated Graphics object.
Bitmap bm = new Bitmap(wid, hgt);
using (Graphics gr = Graphics.FromImage(bm))
{
gr.SmoothingMode = SmoothingMode.HighQuality;
gr.Clear(Color.White);
// See how much room is available for each character.
int ch_wid = (int)(wid / txt.Length);
// Draw each character.
for (int i = 0; i < txt.Length; i++)
{
float font_size = Rand.Next(min_size, max_size);
using (Font the_font = new Font("Times New Roman",
font_size, FontStyle.Bold))
{
DrawCharacter(txt.Substring(i, 1), gr,
the_font, i * ch_wid, ch_wid, wid, hgt);
}
}
}
return bm;
}
This method first creates a Bitmap of the desired size, sets its SmoothingMode property, and clears it. The method then calculates the width it can allow each character. The code then loops over the message characters. For each character, it generates a random font size and creates a font of that size. Finally, the code calls the following DrawCharacter method to draw the character.
// Draw a deformed character at this position.
private int PreviousAngle = 0;
private void DrawCharacter(string txt, Graphics gr,
Font the_font, int X, int ch_wid, int wid, int hgt)
{
// Center the text.
using (StringFormat string_format = new StringFormat())
{
string_format.Alignment = StringAlignment.Center;
string_format.LineAlignment = StringAlignment.Center;
RectangleF rectf = new RectangleF(X, 0, ch_wid, hgt);
// Convert the text into a path.
using (GraphicsPath graphics_path = new GraphicsPath())
{
graphics_path.AddString(txt,
the_font.FontFamily, (int)(Font.Style),
the_font.Size, rectf, string_format);
// Make random warping parameters.
float x1 = (float)(X + Rand.Next(ch_wid) / 2);
float y1 = (float)(Rand.Next(hgt) / 2);
float x2 = (float)(X + ch_wid / 2 +
Rand.Next(ch_wid) / 2);
float y2 = (float)(hgt / 2 + Rand.Next(hgt) / 2);
PointF[] pts = {
new PointF(
(float)(X + Rand.Next(ch_wid) / 4),
(float)(Rand.Next(hgt) / 4)),
new PointF(
(float)(X + ch_wid - Rand.Next(ch_wid) / 4),
(float)(Rand.Next(hgt) / 4)),
new PointF(
(float)(X + Rand.Next(ch_wid) / 4),
(float)(hgt - Rand.Next(hgt) / 4)),
new PointF(
(float)(X + ch_wid - Rand.Next(ch_wid) / 4),
(float)(hgt - Rand.Next(hgt) / 4))
};
Matrix mat = new Matrix();
graphics_path.Warp(pts, rectf, mat,
WarpMode.Perspective, 0);
// Rotate a bit randomly.
float dx = (float)(X + ch_wid / 2);
float dy = (float)(hgt / 2);
gr.TranslateTransform(-dx, -dy, MatrixOrder.Append);
int angle = PreviousAngle;
do
{
angle = Rand.Next(-30, 30);
} while (Math.Abs(angle - PreviousAngle) < 20);
PreviousAngle = angle;
gr.RotateTransform(angle, MatrixOrder.Append);
gr.TranslateTransform(dx, dy, MatrixOrder.Append);
// Draw the text.
gr.FillPath(Brushes.Blue, graphics_path);
gr.ResetTransform();
}
}
}
This method makes a StringFormat object to center a character in a rectangle. It then builds the rectangle that will hold the character.
Next the code creates a GraphicsPath object and adds a character to it in the proper position. It randomly picks some points in the character's area and uses the GraphicsPath object's Warp method to warp the character's bounding rectangle onto those points, distorting the character's image.
Next the code applies a transformation to the Graphics object to rotate the character around its center by a random angle. In tests, I was seeing a lot of characters with similar rotations, so I added a static variable and a loop to ensure that each character's rotation differs from the rotation of the previous character by at least 20 degrees.
Finally the subroutine draws the warped and rotated character onto the Graphics object.
Download the example to experiment with it and to see additional details.
|