Click here to Skip to main content
15,890,185 members
Articles / Web Development / ASP.NET

CAPTCHA Image

Rate me:
Please Sign up or sign in to vote.
4.89/5 (232 votes)
9 Feb 2004CPOL5 min read 1.9M   32.6K   601   319
Using CAPTCHA images to prevent automated form submission

Sample Image - CaptchaImage.gif

Introduction

CAPTCHA stands for "completely automated public Turing test to tell computers and humans apart." What it means is, a program that can tell humans from machines using some type of generated test. A test most people can easily pass but a computer program cannot.

You've probably encountered such tests when signing up for an online email or forum account. The form might include an image of distorted text, like that seen above, which you are required to type into a text field.

The idea is to prevent spammers from using web bots to automatically post form data in order to create email accounts (for sending spam) or to submit feedback comments or guestbook entries containing spam messages. The text in the image is usually distorted to prevent the use of OCR (optical character reader) software to defeat the process. Hotmail, PayPal, Yahoo and a number of blog sites have employed this technique.

This article demonstrates how to create such an image and employ it within an ASP.NET web form.

Background

You can find more information on CAPTCHA at The CAPTCHA Project and read about its use in foiling purveyors of pills, pr0n and pyramid-schemes in an article from Scientific American entitled Baffling the Bots.

Before using this technique however, you should consider how it will affect your site's accessibility to the blind and other visually impaired visitors. PayPal attempts to address this problem on their sign up form by including a link to an audio file, in which a voice spells out the image text.

The code presented here produces only an image. But, if you had code to generate an audio file, you could easily integrate it.

Using the Code

The source code zip file contains the source for one class and two web forms. To use it, just create a new web project and add those items.

Files

  • CaptchaImage.cs - defines the CapchaImage object which actually creates the image
  • Default.aspx, Default.aspx.cs - a sample web form
  • JpegImage.aspx, JpegImage.aspx.cs - a web form designed to output a JPEG image rather than HTML

Let's look at each component and its purpose.

CaptchaImage.cs

The CaptchaImage object creates an image given parameters for the text to be displayed, the image dimensions and, optionally, the font to use.

The heart of the code is the GenerateImage() method, shown below, which creates a bitmap image of the specified width and height. This method is called from the CaptchaImage constructor, so the image is ready as soon as you create a new instance of the object.

To create the image, we first fill in the background using a hatched brush (the "dirtier" the image appears, the harder it is for an OCR program to read it).

To make the text fit within the image, we start with an initial font size based on the image height and use the Graphics.MeasureString() method to find the resulting dimensions of the drawn text. If the text exceeds the image dimensions, we reduce the font size and test again and again until a suitable font size is found.

C#
// ====================================================================
// Creates the bitmap image.
// ====================================================================
private void GenerateImage()
{
  // Create a new 32-bit bitmap image.
  Bitmap bitmap = new Bitmap(
    this.width,
    this.height,
    PixelFormat.Format32bppArgb);

  // Create a graphics object for drawing.
  Graphics g = Graphics.FromImage(bitmap);
  g.SmoothingMode = SmoothingMode.AntiAlias;
  Rectangle rect = new Rectangle(0, 0, this.width, this.height);

  // Fill in the background.
  HatchBrush hatchBrush = new HatchBrush(
    HatchStyle.SmallConfetti,
    Color.LightGray,
    Color.White);
  g.FillRectangle(hatchBrush, rect);

  // Set up the text font.
  SizeF size;
  float fontSize = rect.Height + 1;
  Font font;
  // Adjust the font size until the text fits within the image.
  do
  {
    fontSize--;
    font = new Font(
      this.familyName,
      fontSize,
      FontStyle.Bold);
    size = g.MeasureString(this.text, font);
  } while (size.Width > rect.Width);

  // Set up the text format.
  StringFormat format = new StringFormat();
  format.Alignment = StringAlignment.Center;
  format.LineAlignment = StringAlignment.Center;

  // Create a path using the text and warp it randomly.
  GraphicsPath path = new GraphicsPath();
  path.AddString(
    this.text,
    font.FontFamily,
    (int) font.Style,
    font.Size, rect,
    format);
  float v = 4F;
  PointF[] points =
  {
    new PointF(
      this.random.Next(rect.Width) / v,
      this.random.Next(rect.Height) / v),
    new PointF(
      rect.Width - this.random.Next(rect.Width) / v,
      this.random.Next(rect.Height) / v),
    new PointF(
      this.random.Next(rect.Width) / v,
      rect.Height - this.random.Next(rect.Height) / v),
    new PointF(
      rect.Width - this.random.Next(rect.Width) / v,
      rect.Height - this.random.Next(rect.Height) / v)
  };
  Matrix matrix = new Matrix();
  matrix.Translate(0F, 0F);
  path.Warp(points, rect, matrix, WarpMode.Perspective, 0F);

  // Draw the text.
  hatchBrush = new HatchBrush(
    HatchStyle.LargeConfetti,
    Color.LightGray,
    Color.DarkGray);
  g.FillPath(hatchBrush, path);

  // Add some random noise.
  int m = Math.Max(rect.Width, rect.Height);
  for (int i = 0; i < (int) (rect.Width * rect.Height / 30F); i++)
  {
    int x = this.random.Next(rect.Width);
    int y = this.random.Next(rect.Height);
    int w = this.random.Next(m / 50);
    int h = this.random.Next(m / 50);
    g.FillEllipse(hatchBrush, x, y, w, h);
  }

  // Clean up.
  font.Dispose();
  hatchBrush.Dispose();
  g.Dispose();

  // Set the image.
  this.image = bitmap;
}

Once the font is set, we define a GraphicsPath() which essentially converts the text to a set of lines and curves. This can then be distorted using the GraphicsPath.Warp() method with some randomly generated values. The effect is similar to holding a cardboard sign up by opposite corners and giving it a bit of a twist. The resulting path is drawn onto the image, again using a hatch brush to give it a "dirty" appearance.

To complete the distortion, small blots are randomly painted over the image. You could experiment with other effects to thwart OCRs, but keep in mind that it should still be legible to humans, some of whom may have visual impairments.

Default.aspx

This is a very simple sample web form that contains only a few basic elements, namely an <IMG> tag for the image, a text box and a "Submit" button.

HTML
<form id="Default" method="post" runat="server">
  <img src="JpegImage.aspx"><br>
  <p>
    <strong>Enter the code shown above:</strong><br>
    <asp:TextBox id="CodeNumberTextBox" runat="server"></asp:TextBox>
    <asp:Button id="SubmitButton" runat="server" Text="Submit">
    </asp:Button><br>
  </p>
  <p>
    <em class="notice">
      (Note: If you cannot read the numbers in the above<br>
      image, reload the page to generate a new one.)</em>
  </p>
  <p><asp:Label id="MessageLabel" runat="server"></asp:Label></p>
</form>

Note that the SRC attribute of the <IMG> tag points to the web form JpegImage.aspx.

The code-behind for Default.aspx simply generates a random text string for the image and validates that this text was entered by the user when the form is submitted. The key is to store the text string in the Session object.

C#
private void Page_Load(object sender, System.EventArgs e)
{
  if (!this.IsPostBack)

    // Create a random code and store it in the Session object.
    this.Session["CaptchaImageText"] = GenerateRandomCode();

  else
  {
    // On a postback, check the user input.
    if (this.CodeNumberTextBox.Text ==
      this.Session["CaptchaImageText"].ToString())
    {
      // Display an informational message.
      this.MessageLabel.CssClass = "info";
      this.MessageLabel.Text = "Correct!";
    }
    else
    {
      // Display an error message.
      this.MessageLabel.CssClass = "error";
      this.MessageLabel.Text = "ERROR: Incorrect, try again.";

      // Clear the input and create a new random code.
      this.CodeNumberTextBox.Text = "";
      this.Session["CaptchaImageText"] = GenerateRandomCode();
    }
  }
}

The reason for storing the text string in the Session object is so that it can be accessed by JpegImage.aspx.

JpegImage.aspx

For this web form, no HTML is needed (what's there is just the default code generated by Visual Studio when the file was created). Instead of HTML, the code will produce a JPEG image.

In the code-behind, we first create a CaptchaImage object, using the text retrieved from the Session object. This creates a bitmap image for us.

C#
private void Page_Load(object sender, System.EventArgs e)
{
  // Create a CAPTCHA image using the text stored in the Session object.
  CaptchaImage ci = new CaptchaImage(
    this.Session["CaptchaImageText"].ToString(),
    200, 50, "Century Schoolbook");

  // Change the response headers to output a JPEG image.
  this.Response.Clear();
  this.Response.ContentType = "image/jpeg";

  // Write the image to the response stream in JPEG format.
  ci.Image.Save(this.Response.OutputStream, ImageFormat.Jpeg);

  // Dispose of the CAPTCHA image object.
  ci.Dispose();
}

We then modify the HTTP response headers to set the Content-type to "image/jpeg" so the client browser will know we are sending an image.

The last step is to retrieve the bitmap image from CaptchaImage.Image and write it to the HTTP response output stream in JPEG format. Fortunately, the Save() method of the Bitmap object makes this simple. Any other supported image format could be used as well so long as the Content-type header is set accordingly.

Points of Interest

Because the CaptchaImage class contains a Bitmap object, and bitmaps employ unmanaged resources, a custom Dispose() method is implemented. This allows those unmanaged resources to be freed whenever a CaptchaImage is destroyed.

History

  • January 26, 2004 - version 1.0
    • Initial version
  • February 2, 2004 - version 1.01
    • Minor fix to correct parameter checking in CaptchaImage.SetDimensions()

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)


Written By
Web Developer
United States United States
This member has not yet provided a Biography. Assume it's interesting and varied, and probably something to do with programming.

Comments and Discussions

 
GeneralRe: Updated code in CS and VB Pin
ajingar1-Dec-09 4:07
ajingar1-Dec-09 4:07 
QuestionLicense for CAPTCHA Image? Pin
Dean Sauer2-Nov-09 4:35
Dean Sauer2-Nov-09 4:35 
GeneralCaptcha Pin
ikemyle30-Oct-09 13:19
ikemyle30-Oct-09 13:19 
GeneralCAPTCHA Image without the need for JpegImage PinPopular
Operation105327-Sep-09 22:49
Operation105327-Sep-09 22:49 
GeneralRe: CAPTCHA Image without the need for JpegImage [modified] Pin
NetDave2-Oct-09 20:01
NetDave2-Oct-09 20:01 
GeneralRe: CAPTCHA Image without the need for JpegImage Pin
TheMegaLoser12-Jan-10 21:28
TheMegaLoser12-Jan-10 21:28 
GeneralRe: CAPTCHA Image without the need for JpegImage Pin
Hydrogen762-Apr-10 6:57
Hydrogen762-Apr-10 6:57 
GeneralVB version of the Captcha. Pin
Operation105327-Sep-09 22:05
Operation105327-Sep-09 22:05 
<br />
Imports System<br />
Imports System.Drawing<br />
Imports System.Drawing.Drawing2D<br />
Imports System.Drawing.Imaging<br />
Imports System.Drawing.Text<br />
<br />
Namespace CaptchaImage<br />
    ' <summary><br />
    '    Summary description for CaptchaImage.<br />
    ' </summary><br />
    Public Class CaptchaImage<br />
        ' Public properties (all read-only).<br />
        Private my_text As String = ""<br />
        Private my_image As Bitmap = Nothing<br />
        Private my_width As Integer = 0<br />
        Private my_height As Integer = 0<br />
        Private my_familyName As String = ""<br />
<br />
        Public Property text() As String<br />
            Get<br />
                Return my_text<br />
            End Get<br />
            Set(ByVal value As String)<br />
                my_text = value<br />
            End Set<br />
        End Property<br />
<br />
        Public Property image() As Bitmap<br />
            Get<br />
                Return my_image<br />
            End Get<br />
            Set(ByVal value As Bitmap)<br />
                my_image = value<br />
            End Set<br />
        End Property<br />
<br />
        Public Property width() As Integer<br />
            Get<br />
                Return my_width<br />
            End Get<br />
            Set(ByVal value As Integer)<br />
                my_width = value<br />
            End Set<br />
        End Property<br />
<br />
        Public Property height() As Integer<br />
            Get<br />
                Return my_height<br />
            End Get<br />
            Set(ByVal value As Integer)<br />
                my_height = value<br />
            End Set<br />
        End Property<br />
<br />
        ' For generating random numbers.<br />
        Private random As Random = New Random()<br />
<br />
        ' ====================================================================<br />
        ' Initializes a new instance of the CaptchaImage class using the<br />
        ' specified text, width and height.<br />
        ' ====================================================================<br />
        Public Sub New(ByVal s As String, ByVal width As Integer, ByVal height As Integer)<br />
            my_text = s<br />
            SetDimensions(width, height)<br />
            GenerateImage()<br />
        End Sub<br />
<br />
        ' ====================================================================<br />
        ' Initializes a new instance of the CaptchaImage class using the<br />
        ' specified text, width, height and font family.<br />
        ' ====================================================================<br />
        Public Sub New(ByVal s As String, ByVal width As Integer, ByVal height As Integer, ByVal familyName As String)<br />
            my_text = s<br />
            SetDimensions(width, height)<br />
            SetFamilyName(familyName)<br />
            GenerateImage()<br />
        End Sub<br />
<br />
        ' ====================================================================<br />
        ' This member overrides Object.Finalize.<br />
        ' ====================================================================<br />
        Protected Overrides Sub Finalize()<br />
            Dispose(False)<br />
            MyBase.Finalize()<br />
        End Sub<br />
<br />
        ' ====================================================================<br />
        ' Releases all resources used by this object.<br />
        ' ====================================================================<br />
        Public Sub Dispose()<br />
            GC.SuppressFinalize(Me)<br />
            Dispose(True)<br />
        End Sub<br />
<br />
        ' ====================================================================<br />
        ' Custom Dispose method to clean up unmanaged resources.<br />
        ' ====================================================================<br />
        Protected Overridable Sub Dispose(ByVal disposing As Boolean)<br />
            If (disposing) Then<br />
                ' Dispose of the bitmap.<br />
                image.Dispose()<br />
            End If<br />
        End Sub<br />
<br />
        ' ====================================================================<br />
        ' Sets the image width and height.<br />
        ' ====================================================================<br />
        Private Sub SetDimensions(ByVal width2 As Integer, ByVal height2 As Integer)<br />
            ' Check the width and height.<br />
            If (width2 <= 0) Then<br />
                Throw New ArgumentOutOfRangeException("width", width2, "Argument out of range, must be greater than zero.")<br />
            End If<br />
            If (height2 <= 0) Then<br />
                Throw New ArgumentOutOfRangeException("height", height2, "Argument out of range, must be greater than zero.")<br />
            End If<br />
<br />
            my_width = width2<br />
            my_height = height2<br />
        End Sub<br />
<br />
        ' ====================================================================<br />
        ' Sets the font used for the image text.<br />
        ' ====================================================================<br />
        Private Sub SetFamilyName(ByVal familyName As String)<br />
            ' If the named font is not installed, default to a system font.<br />
            Try<br />
                Dim font As Font = New Font(familyName, 12.0F)<br />
                my_familyName = familyName<br />
                font.Dispose()<br />
            Catch ex As Exception<br />
                my_familyName = System.Drawing.FontFamily.GenericSerif.Name<br />
            End Try<br />
        End Sub<br />
<br />
        ' ====================================================================<br />
        ' Creates the bitmap image.<br />
        ' ====================================================================<br />
        Private Sub GenerateImage()<br />
            ' Create a new 32-bit bitmap image.<br />
            Dim bitmap As Bitmap = New Bitmap( _<br />
                                               my_width, _<br />
                                               my_height, _<br />
                                               PixelFormat.Format32bppArgb _<br />
                                             )<br />
<br />
            ' Create a graphics object for drawing.<br />
            Dim g As Graphics = Graphics.FromImage(bitmap)<br />
            g.SmoothingMode = SmoothingMode.AntiAlias<br />
            Dim rect As Rectangle = New Rectangle(0, 0, Me.width, Me.height)<br />
<br />
            ' Fill in the background.<br />
            Dim hatchBrush As HatchBrush = New HatchBrush(HatchStyle.SmallConfetti, Color.LightGray, Color.White)<br />
            g.FillRectangle(hatchBrush, rect)<br />
<br />
            ' Set up the text font.<br />
            Dim size As SizeF<br />
            Dim fontSize As Single = rect.Height + 1<br />
            Dim font As Font<br />
            ' Adjust the font size until the text fits within the image.<br />
            Do<br />
                fontSize = fontSize - 1<br />
                font = New Font(my_familyName, fontSize, FontStyle.Bold)<br />
                size = g.MeasureString(Me.text, font)<br />
            Loop While (size.Width > rect.Width)<br />
<br />
            ' Set up the text format.<br />
            Dim format As StringFormat = New StringFormat()<br />
            format.Alignment = StringAlignment.Center<br />
            format.LineAlignment = StringAlignment.Center<br />
<br />
            ' Create a path using the text and warp it randomly.<br />
            Dim path As GraphicsPath = New GraphicsPath()<br />
            path.AddString(my_text, font.FontFamily, font.Style, font.Size, rect, format)<br />
            Dim v As Single = 4.0F<br />
            Dim points() As PointF = { _<br />
                                        New PointF(random.Next(rect.Width) / v, random.Next(rect.Height) / v), New PointF(rect.Width - random.Next(rect.Width) / v, random.Next(rect.Height) / v), _<br />
                                        New PointF(random.Next(rect.Width) / v, rect.Height - random.Next(rect.Height) / v), _<br />
                                        New PointF(rect.Width - random.Next(rect.Width) / v, rect.Height - random.Next(rect.Height) / v) _<br />
                                     }<br />
            Dim matrix As Matrix = New Matrix<br />
            matrix.Translate(0.0F, 0.0F)<br />
            path.Warp(points, rect, matrix, WarpMode.Perspective, 0.0F)<br />
<br />
            ' Draw the text.<br />
            hatchBrush = New HatchBrush(HatchStyle.LargeConfetti, Color.LightGray, Color.DarkGray)<br />
            g.FillPath(hatchBrush, path)<br />
<br />
            ' Add some random noise.<br />
            Dim m As Integer = Math.Max(rect.Width, rect.Height)<br />
            For i As Integer = 0 To i < (rect.Width * rect.Height / 30.0F) Step 1<br />
                Dim x As Integer = random.Next(rect.Height)<br />
                Dim y As Integer = random.Next(rect.Height)<br />
                Dim w As Integer = random.Next(m / 50)<br />
                Dim h As Integer = random.Next(m / 50)<br />
                g.FillEllipse(hatchBrush, x, y, w, h)<br />
            Next<br />
            '  Clean up.<br />
            font.Dispose()<br />
            hatchBrush.Dispose()<br />
            g.Dispose()<br />
<br />
            ' Set the image.<br />
            my_image = bitmap<br />
        End Sub<br />
    End Class<br />
End Namespace<br />

GeneralCode not working Pin
rub-IL29-Jul-09 6:04
rub-IL29-Jul-09 6:04 
GeneralRe: Code not working Pin
Operation105327-Sep-09 22:56
Operation105327-Sep-09 22:56 
GeneralRe: Code not working Pin
jeato12-Oct-09 20:23
jeato12-Oct-09 20:23 
GeneralRe: Code not working Pin
asdqwewqe29-Oct-09 7:15
asdqwewqe29-Oct-09 7:15 
GeneralRe: Code not working Pin
Rumbly22-Jun-10 1:11
Rumbly22-Jun-10 1:11 
GeneralLet Me Know The Error Pin
Milton_win24-Jun-09 19:14
Milton_win24-Jun-09 19:14 
AnswerHere Is The Solution : if it doesn't appear / show captcha image when you publish /deploy the site Pin
serkanapul17-Jun-09 22:57
serkanapul17-Jun-09 22:57 
GeneralMy vote of 1 Pin
http2007-Jun-09 22:22
http2007-Jun-09 22:22 
GeneralRe: My vote of 1 Pin
Mortikhi1-Oct-09 8:30
Mortikhi1-Oct-09 8:30 
GeneralRe: My vote of 1 Pin
asdqwewqe29-Oct-09 7:10
asdqwewqe29-Oct-09 7:10 
QuestionHow Mismatch Message Can Be Displayed In Java Script Alert Pin
mshariq19-May-09 2:14
mshariq19-May-09 2:14 
AnswerRe: How Mismatch Message Can Be Displayed In Java Script Alert [modified] Pin
StiGMaTa_Dev29-Oct-09 2:40
StiGMaTa_Dev29-Oct-09 2:40 
Generalvery nice! i would add protection against null in the jpegimage.aspx.cs file Pin
shabya18-Apr-09 0:27
shabya18-Apr-09 0:27 
GeneralRe: very nice! i would add protection against null in the jpegimage.aspx.cs file Pin
devaqeel17-Dec-09 20:52
devaqeel17-Dec-09 20:52 
GeneralMissing GenerateRandomCode() Pin
marine8828-Mar-09 14:13
marine8828-Mar-09 14:13 
GeneralBrilliant Pin
il Picci9-Mar-09 3:19
il Picci9-Mar-09 3:19 
GeneralRe: Brilliant Pin
mthiessen23-Oct-09 4:29
mthiessen23-Oct-09 4:29 

General General    News News    Suggestion Suggestion    Question Question    Bug Bug    Answer Answer    Joke Joke    Praise Praise    Rant Rant    Admin Admin   

Use Ctrl+Left/Right to switch messages, Ctrl+Up/Down to switch threads, Ctrl+Shift+Left/Right to switch pages.