Make CAPTCHA images with warped and rotated characters 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 some scammers outsource CAPTCHA images to cheap labor 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 a large percentage of scammers.

The MakeCaptchaImage method that follows makes a Bitmap of the desired size and clears it. For each character in the message, it creates a random font and calls subroutine DrawCharacter to draw it. This example uses relatively large fonts so the characters tend to overlap in cool and interesting ways that are hard for an OCR program to decipher.

// 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;

RectangleF rectf = new RectangleF(0, 0, wid, hgt);
gr.FillRectangle(Brushes.White, rectf);

// See how much room is available for each character.
int ch_wid = (int)(wid / txt.Length);

// Draw each character.
Random rnd = new Random();
for (int i = 0; i < txt.Length; i++)
{
float font_size = rnd.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;
}

Subroutine DrawCharacter draws a single 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.
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.
Random rnd = new Random();
float x1 = (float)(X + rnd.Next(ch_wid) / 2);
float y1 = (float)(rnd.Next(hgt) / 2);
float x2 = (float)(X + ch_wid / 2 + rnd.Next(ch_wid) / 2);
float y2 = (float)(hgt / 2 + rnd.Next(hgt) / 2);
PointF[] pts = {
new PointF((float)(X + rnd.Next(ch_wid) / 4), (float)(rnd.Next(hgt) / 4)),
new PointF((float)(X + ch_wid - rnd.Next(ch_wid) / 4), (float)(rnd.Next(hgt) / 4)),
new PointF((float)(X + rnd.Next(ch_wid) / 4), (float)(hgt - rnd.Next(hgt) / 4)),
new PointF((float)(X + ch_wid - rnd.Next(ch_wid) / 4), (float)(hgt - rnd.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 = rnd.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 code creates a GraphicsPath and adds a character to it in the proper position for the bitmap. 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 representing the bitmap.

   

 

What did you think of this article?




Trackbacks
  • No trackbacks exist for this post.
Comments
  • No comments exist for this post.
Leave a comment

Submitted comments are subject to moderation before being displayed.

 Name

 Email (will not be published)

 Website

Your comment is 0 characters limited to 3000 characters.