Animated Collapsible Panel in HTML Extract Office Document Properties
May 22

Printer Friendly Version

Download Source Code: CaptchaImageGenerator.zip - 19.65KB

---
  Fully customizable CAPTCHA image C# class, with simple Windows application to visually set its properties. Online and downloadable ASP.NET demo with CAPTCHA image.  
---

Overview

As largely described in Wikipedia, CAPTCHA is an acronym for Completely Automated Public Turing test to tell Computers and Humans Apart. Most frequent CAPTCHA you will find on web pages is a character recognition test with 5-6 letters randomly generated in an image, that you will have to enter in a text box.

The web server will deliver your request and send back the actual response only if the test succeeds. What CAPTCHA does is determine if the web user is human or automated crawler. In the last case, it's rare such spider has the ability to recognize and extract text from an image, and the web server will not consequently serve it.

It's not difficult to write a CAPTCHA image generator. In fact, CAPTCHA Image is such a C# implementation on CodeProject, and current project has been inspired from that code. We will simply provide some alternative design solutions for the CAPTCHA image class, where every method is a property.

CAPTCHA Image Generator
CAPTCHA Image Generator

CAPTCHA Image Generation

CAPTCHA Image Generator is our standalone Windows application, where you can customize your image, trying different property values, before using it in an ASP.NET project. For the web project, we provided a simple web page with no further encapsulation, just for simplicity.

If you want to know about different ways of encapsulating the CAPTCHA class as a web control, take a look at A CAPTCHA Server Control for ASP.NET. This second CodeProject article provides a VB.NET implementation and a standalone web control, where the image content is rendered through a HTTP Handler.

CaptchaImage is built as a stateless class. All GDI+ objects are instantiated, used and disposed each time a client application reads the Image property, at which moment a new image bitmap is generated on the fly. Stateless objects are easy to cache on the server side and you don't have to worry about disposable resources.

All other CaptchaImage members are read/write public properties, based on private fields instantiated with default values. If you want different default values, just change the fields initial values and recompile the project. These public properties make it easy to build a very simple standalone Windows application with a PropertyGrid control, and link the CaptchaImage instance to its SelectedObject property. After you change one or more properties, click on the image on the right, which shows the bitmap generated through a Image property call. When you double-click on the image, a new random text value is generated as well. With encapsulation of all the functionality in CaptchaImage class, the implementation of the actual Image Generator MainForm is trivial and self-explanatory:

/// <summary>
/// Very simple implementation of the CAPTCHA Image Generator,
/// which allows customization of properties of a CaptchaImage
/// _image instance through a propGrid PropertyGrid control.
/// The image will be passed from _image's Image property
/// in a pctImage PictureBox control, each time the user clicks
/// on it. On double-click, setting the Text property of the
/// _image instance to an empty string will actually generate
/// a new random text.
/// </summary>
public partial class MainForm : Form
{
    private CaptchaImage _image;

    public MainForm()
    {
        InitializeComponent();
    }

    private void MainForm_Load(object sender, EventArgs e)
    {
        propGrid.SelectedObject = _image = new CaptchaImage();
        pctImage.Image = _image.Image;
    }

    private void pctImage_Click(object sender, EventArgs e)
    {
        pctImage.Image = _image.Image;
    }

    private void pctImage_DoubleClick(object sender, EventArgs e)
    {
        _image.Text = "";
        pctImage.Image = _image.Image;
    }
}

CaptchaImage Class Properties

Following categories of properties of a CaptchaImage class can be customized, programmatically or from the Image Generator property grid.

CAPTCHA image without noise, lines or warp
CAPTCHA image without noise, lines or warp

(1) Image's Width and Height.

(2) Some noise factors for special effects. Noise is additional random drawing and distorted text rendering, to confuse automated programs that try to reverse engineer the image. When the factor is 0, no such special effect is applied. A maximum of 100 uses maximum distortions. More noise means more secure and harder to be read by automated programs, but also by human visitors:

  • NoiseFactor - to draw random ellipses over the text.
  • LineFactor - to draw random curve lines over the text.
  • WarpFactor - to draw the text in a distorted curve fashion.
CAPTCHA image with 3 digits, italic, custom font, background, colors, effects
CAPTCHA image with 3 digits, italic, custom font, background, colors, effects

(3) Two kind of Hatch Brushes are used on the bitmap: one for the text, another for the background. Each brush has customizable properties for:

  • Style - TextStyle and BackgroundStyle properties.
  • Fore Color - TextForeColor and BackgroundForeColor properties.
  • Back Color - TextBackColor and BackgroundBackColor properties.

(4) The text can be set through the Text property. When set to an empty string, it's randomly generated, according to other text-related property values:

  • Length - number of characters in a generated text.
  • CharacterSet - string with all characters used in text generation. Initially includes all digits, upper and lower cases, except some characters that can be taken one for the other: 1/I/l and 0/O. Can be manually changed to any custom set of characters. To avoid automatic generation of bad English words, a possibility is to avoid using vowels.
  • Unique - when True (default) each character is used once only in generated text, provided the Length value is smaller than the number of characters in the set.
Stylish custom CAPTCHA image, with 3 lower case letters
Stylish custom CAPTCHA image, with 3 lower case letters

(5) For text's Font, you can customize the following property values:

  • FontName - with a name of the installed fonts.
  • FontSize - size in dots.
  • FontStyle - such as Bold or Italic.

Note that, each time you change text's length, text's font or the dimensions of the image, some tests should be performed, with different auto-generated text values, prior to decide if the font size fits well within the new image size.

Implementation of the Image get property is as it follows:

[Browsable(false)]
[Category("Bitmap")]
[Description("Generated image")]
public Bitmap Image
{
    get
    {
        Bitmap bitmap = new Bitmap(_width, _height,
            PixelFormat.Format32bppPArgb);

        Graphics g = null;
        Font font = null;
        HatchBrush textBrush = null;
        HatchBrush backBrush = null;
        try
        {
            // Fill Background
            g = Graphics.FromImage(bitmap);
            g.SmoothingMode = SmoothingMode.AntiAlias;
            Rectangle rect = new Rectangle(0, 0, _width, _height);
            textBrush = new HatchBrush(_textStyle,
                _textForeColor, _textBackColor);
            backBrush = new HatchBrush(_backStyle,
                _backForeColor, _backBackColor);
            g.FillRectangle(backBrush, rect);

            // Write Text
            StringFormat stringFormat = new StringFormat();
            stringFormat.Alignment = StringAlignment.Center;
            stringFormat.LineAlignment = StringAlignment.Center;
            font = new Font(_fontName, _fontSize, _fontStyle);
            GraphicsPath path = new GraphicsPath();
            path.AddString(_text, font.FontFamily,
                (int)font.Style, font.Size, rect, stringFormat);

            // Warp Text
            if (_warp > 0)
            {
                Matrix matrix = new Matrix();
                float warp = 10 - _warp * 6 / 100;
                PointF[] points = {
	                new PointF(_random.Next(rect.Width) / warp,
                        _random.Next(rect.Height) / warp),
	                new PointF(rect.Width
                        - _random.Next(rect.Width) / warp,
                        _random.Next(rect.Height) / warp),
	                new PointF(_random.Next(rect.Width) / warp,
                        rect.Height
                        - _random.Next(rect.Height) / warp),
	                new PointF(rect.Width
                        - _random.Next(rect.Width) / warp,
                        rect.Height
                        - _random.Next(rect.Height) / warp) };
                path.Warp(points, rect, matrix,
                    WarpMode.Perspective, 0F);
            }
            g.FillPath(textBrush, path);

            // Random Noise over Text
            if (_noise > 0)
            {
                int maxDim = Math.Max(rect.Width, rect.Height);
                int radius = (int) (maxDim * _noise / 3000);
                int maxGran = (int)(rect.Width * rect.Height
                    / (100 - (_noise >= 90 ? 90 : _noise)));
                for (int i = 0; i < maxGran; i++)
                    g.FillEllipse(textBrush,
                        _random.Next(rect.Width),
                        _random.Next(rect.Height),
                        _random.Next(radius),
                        _random.Next(radius));
            }

            // Draw Lines over Text
            if (_lines > 0)
            {
                int lines = ((int) _lines/30)+1;
                using (Pen pen = new Pen(textBrush, 1))
                    for (int i=0; i<lines; i++)
                    {
                        PointF[] points
                            = new PointF[lines>2 ? lines-1 : 2];
                        for (int j=0; j<points.Length; j++)
                            points[j] = new PointF(
                                _random.Next(rect.Width),
                                _random.Next(rect.Height));
                        g.DrawCurve(pen, points, 1.75F);
                    }
            }
        }
        finally
        {
            // Dispose GDI+ objects
            if (g != null)
                g.Dispose();
            if (font != null)
                font.Dispose();
            if (textBrush != null)
                textBrush.Dispose();
            if (backBrush != null)
                backBrush.Dispose();
        }
        
        return bitmap;
    }
}

ASP.NET Demo

The demo project has been deliberately kept as simple as possible, with a single Default.aspx web page. Within the HTML code, you'll find the following sequence, that you may likely customize and include in each FORM you want to use with CAPTCHA:

<div><table border="0" bgcolor="LightGrey"><tr>
<td valign="top"><img width="100" height="60" border="0"
 src="Default.aspx?captcha=true" /></td>
<td valign="top">Anti-Spam Code:
<br/><asp:TextBox ID="CaptchaCode" runat="server" />
<br />(Reload page for new code)
</td></tr></table></div>

The CAPTCHA HTML code portion contains a table with two cells: one for the image, rendered in a IMG element, the other one for the verification code entry. The source of the image is rendered here by the same page, when "captcha=true" is present in the query string. In our web page, this is performed by the GetCaptchaImage method. Otherwise, the Page_Load handler determines if this is a PostBack, in which case it will get the cached CaptchaImage instance, check the validation code and follow with eventual redirect or custom processing, depending on whether or not the code was valid.

To expose a CAPTCHA on client's page, CreateCaptchaImage method instantiates a new CaptchaImage objects and hides it at the Session level. The last part of the method can be filled with customization of the object, setting property values as they appear in the CAPTCHA Image Generator screen. Here is the full code-behind sequence of the web page:

public partial class _Default : System.Web.UI.Page
{
    private const string CAPTCHA_ID = "__CaptchaImage__";
    public string _result = "";

    protected void Page_Load(object sender, EventArgs e)
    {
        if (Request.QueryString["captcha"] == "true")
            GetCaptchaImage();
        else
        {
            if (IsPostBack)
                _result = (IsValidCaptcha()
                    ? "Valid Code" : "Invalid Code");
            CreateCaptchaImage();
        }
    }

    /// <summary>
    /// Renders IMG SRC content for the client-side CAPTCHA image
    /// </summary>
    private void GetCaptchaImage()
    {
        CaptchaImage image = Session[CAPTCHA_ID] as CaptchaImage;
        Debug.Assert(image != null);
        Response.Clear();
        Response.ContentType = "image/jpeg";
        image.Image.Save(Response.OutputStream, ImageFormat.Jpeg);
        Response.End();
    }

    // Generate a new CaptchaImage instance, with custom values
    private void CreateCaptchaImage()
    {
        CaptchaImage image = new CaptchaImage();
        Session[CAPTCHA_ID] = image;

        // customize CaptchaImage instance here
        image.CharacterSet = "BCDFGHJKLMNPQRSTVWXYZ";
    }

    // Returns true if valid entered code (case insensitive!)
    private bool IsValidCaptcha()
    {
        CaptchaImage image = Session[CAPTCHA_ID] as CaptchaImage;
        return (String.Compare(image.Text,
            CaptchaCode.Text, true) == 0);
    }
}

Remark that our sample shows only upper case consonants, and text validation is case insensitive. Go to next page to try the exact same demo online.

Continue reading »

Subscribe and Share: Subscribe using any feed reader Bookmark and Share

Leave a Reply