Click here to Skip to main content
Click here to Skip to main content

The Captcha Web Service

, 29 Dec 2004 CPOL
Rate this:
Please Sign up or sign in to vote.
WSCaptcha is a webservice that creates Captcha images. This article shows how the Captcha web service is to be used it in a registration process.

Sample Image - WSCaptcha.jpg

Introduction

A CAPTCHA is a program that can generate and grade tests that:

  • Most humans can pass, BUT
  • Current computer programs can't pass

CAPTCHA stands for "Completely Automated Public Turing test to Tell Computers and Humans Apart". See the CAPTCHA Site for more details. The concept of a CAPTCHA is motivated by a real-world problem: how to prevent "bots" automatic registrations? By requiring the user to solve a CAPTCHA, the "bots" could be screened out.

WSCaptcha is a web service that creates CAPTCHA images. To see how web service really works please visit my site.

Inside the service

To obtain a little twisted images I applied a swirl and a water filter. These filters are realized using code written by Christian Graus in his Filter class (Filter.cs).

WSCaptcha's core is Captcha class. This class expose the following properties to set main image's characteristic.

// img size
public int Width
{
     set 
     {
         img_width = value;
     }
}

public int Height
{
    set 
    {
        img_height = value;
    }
}

// img font
public string FontName
{
    set
    {
        img_fontname = value;
    }
}

public float FontSize
{
    set
    {
        img_fontsize = value;
    }
}

//
public string Keyword
{
    set
    {
        sKeyword = value;
    }
}

Captcha class also exposes the following method to create the CAPTCHA image.

///
/// makeCaptcha
///
public Bitmap makeCaptcha()
{
    Random randomGenerator = new Random();

    Bitmap bmp = new Bitmap(img_width, img_height, 
                                 PixelFormat.Format16bppRgb555);
    Rectangle rect = new Rectangle(0, 0, img_width, img_height);


    StringFormat sFormat = new StringFormat();
    sFormat.Alignment = StringAlignment.Center;
    sFormat.LineAlignment = StringAlignment.Center;

    Graphics g = Graphics.FromImage(bmp);


    // Set up the text font.
    SizeF size;
    float fontSize = img_fontsize + 1;
    Font font;

    // try to use requested font, but
    // If the named font is not installed, default to a system font.
    try
    {
        font = new Font(img_fontname, img_fontsize);
        font.Dispose();
    }
    catch (Exception ex)
    {
        img_fontname = System.Drawing.FontFamily.GenericSerif.Name;
    }

    
    // build a new string with space through the chars
    // i.e. keyword 'hello' become 'h e l l o '
    string tempKey = "";            

    for(int ii=0; ii< sKeyword.Length; ii++)
    {
        tempKey = String.Concat(tempKey, sKeyword[ii].ToString());
        tempKey = String.Concat(tempKey, " ");
    }

    // Adjust the font size until the text fits within the image.
    do
    {
        fontSize--;
        font = new Font(img_fontname, fontSize, img_fontstyle);
        size = g.MeasureString(tempKey, font);
    } while (size.Width > (0.8*bmp.Width));

    img_font = font;


    g.Clear(Color.Silver); // blank the image
    g.SmoothingMode = SmoothingMode.AntiAlias; // antialias objects

    // fill with a liner gradient
    // random colors
    g.FillRectangle(    
        new LinearGradientBrush(
            new Point(0,0), 
            new Point(bmp.Width,bmp.Height), 
            Color.FromArgb(
                255, //randomGenerator.Next(255),
                randomGenerator.Next(255),
                randomGenerator.Next(255),
                randomGenerator.Next(255)
            ),
            Color.FromArgb(
                randomGenerator.Next(100),
                randomGenerator.Next(255),
                randomGenerator.Next(255),
                randomGenerator.Next(255)
            ) ), 
        rect);

    // apply swirl filter
    if ( fDegree == 0)
    {
        BitmapFilter.Swirl(bmp, randomGenerator.NextDouble(), true);
    }
    else
    {
        BitmapFilter.Swirl(bmp, fDegree , true);
    }

    // Add some random noise.
    HatchBrush hatchBrush = new HatchBrush(
        HatchStyle.LargeConfetti,
        Color.LightGray,
        Color.DarkGray);

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


    // write keyword

    // keyword positioning
    // to space equally
    int posx;
    int posy;
    int deltax;


    deltax = Convert.ToInt32(size.Width/tempKey.Length);
    posx = Convert.ToInt32((img_width - size.Width)/2);

    // write each keyword char
    for (int l=0; l < tempKey.Length; l++ )
    {
        posy = ((int)(2.5 * (bmp.Height/5))) + 
                      (((l%2)==0)?-2:2) * ((int)(size.Height/3));
        posy = (int)((bmp.Height/2)+ (size.Height/2));
        posy += (int) ((((l%2)==0)?-2:2) * ((size.Height/3)));
        posx += deltax;
        g.DrawString(tempKey[l].ToString(),
            img_font,
            img_fontcolor,
            posx, 
            posy,
            sFormat);
    }


    // draw a curve 
    Point[] ps = new Point[nPoints];
    
    for (int ii=0; ii < nPoints; ii++)
    {
        int x,y;
        x = randomGenerator.Next(bmp.Width);
        y = randomGenerator.Next(bmp.Height);
        ps[ii] = new Point(x,y);
    }
    g.DrawCurve(new Pen( img_fontcolor, 2), ps, 
                 Convert.ToSingle(randomGenerator.NextDouble()));

    // apply water filter
    BitmapFilter.Water(bmp, nWave, false);
    
    // Clean up.
    font.Dispose();
    hatchBrush.Dispose();
    g.Dispose();

    return bmp;
}

Please remember that, to make available an image via a web service, it's necessary to send the image's byte data. So, to do that we have to use a stream as you can see in the following lines:

    .......
    Bitmap bmp = cc.makeCaptcha();

    MemoryStream ms = new MemoryStream();
    bmp.Save(ms,ImageFormat.Jpeg);
    ImageBytes = ms.GetBuffer();
    
    return ImageBytes;
    ....... 

Using the web service

WSCaptcha web service exposes three web methods.

The first one to create an image specifying size and keyword but using default font and size.

public byte[] CaptchaImage2(int nWidth, int nHeight, string sKeyword)
{

    Captcha.Captcha cc = new Captcha.Captcha();

    cc.Width = nWidth;
    cc.Height = nHeight;

    cc.Keyword = sKeyword;

    Bitmap bmp = cc.makeCaptcha();

    // Attention
    // to return the image we have to 
    // return it as an array of byte.
    // So, we have to use a stream!
    MemoryStream ms = new MemoryStream();
    bmp.Save(ms,ImageFormat.Jpeg);
    ImageBytes = ms.GetBuffer();
    
    return ImageBytes; 

}

The second one to create an image specifying all the parameters.

public byte[] CaptchaImage1(int nWidth, int nHeight, 
                        string sKeyword, string sFontName, float fFontSize )
{

    Captcha.Captcha cc = new Captcha.Captcha();

    cc.Width = nWidth;
    cc.Height = nHeight;

    cc.FontName = sFontName;
    cc.FontSize = fFontSize;
    cc.Keyword = sKeyword;

    Bitmap bmp = cc.makeCaptcha();

    MemoryStream ms = new MemoryStream();
    bmp.Save(ms,ImageFormat.Jpeg);
    ImageBytes = ms.GetBuffer();
    
    return ImageBytes; 

}

The third one to retrieve a list of available fonts, used to write keyword on the image.

public string GetFontNames()
{
    string result = "";
    FontFamily[] fntfamily =
  

     FontFamily.Families;  for
    (int i = 

      0;i <  fntfamily.Length; i++)
    {
        result +="<fontname>" + fntfamily[i].Name.ToString() + "</fontname>";
    }

    return result;
}

To use this web service you can use the proxy class downloadable by the top link or you can create your proxy class using the following WSDL utility command line:

C:\>wsdl /language:C# /out:WSCaptchaProxy.cs  
                    <A href="http://www.code4dotnet.com/WSCaptcha/CaptchaService.asmx">http://www.code4dotnet.com/WSCaptcha/CaptchaService.asmx
</A>

How to build a registration process using CAPTCHA verification code?

Certified registration data are of vital importance to be sure that the data you are receiving is coming from an actual human being. Simply adding in your registration form an image containing the CAPTCHA code and a keyword field, you are sure enough that a human has filled your form. Your user has to fill the keyword field with the code inside the CAPTCHA image.

A simple registration form

The registration form must contain code both to generate the image and to check the keyword. The CAPTCHA image will be generated using an HttpHandler so that there will not be physical image files to store. To use the HttpHandler add the following rows in the <system.web> section of Web.config

<httpHandlers>
    <add verb="*" path="GetCapcthaImage.aspx" 
        type="CaptchaHttpHandler.CaptchaImgHttpHandler, capcthahttphandler"/>
</httpHandlers>

This handler defines a page named GetCaptchaImage.aspx that doesn't actually exist. Every requests for GetCaptchaImage.aspx will be intercepted and handled by the CaptchaImgHttpHandler which implements the IHttpHandler interface. This interface has the following members:

  • ProcessRequest() – this method is invoked by the runtime to handle the request.
  • IsReusable – this property indicates if multiple requests can share the same handler.

The following code shows the ProcessRequest method:

private double dTimeout = 120;
private int keylen = 7;
private int width = 350;
private int height = 200;

public void ProcessRequest(HttpContext context)
{
    // retrieve the guid identifier
    string sGuid = context.Request.QueryString["guid"].ToString();

    // Some chars was deleted (i.e. l, 0, 0) to avoid misunderstanding
    string mapcar="123456789abcdefghijkmnpqrstuvwxyzABCDEFGHJKLMNPQRSTUVWXYZ";
    Random randomGenerator = new Random();

    // generate the keyword
    string keyword = "";
    for (int i = 0; i < keylen; i++)
    {
        keyword += mapcar.Substring(randomGenerator.Next(mapcar.Length),1);
    }

    System.Byte[] imgCaptcha;

    // set the font to use
    string fontname = "Lucida Console"; 

    //
    // invoke webservice to obtain captcha images
    //
    CaptchaService CaptchaServ = new CaptchaService();
    
    imgCaptcha = CaptchaServ.CaptchaImage1(width, height, keyword.ToString(), 
               fontname.ToString(), Convert.ToSingle(45));

    MemoryStream memStream = new MemoryStream(imgCaptcha);             
    Bitmap Image=new Bitmap(memStream);

    // Store guid/keyword combination in the Cache and
    // set how many seconds it will be remain valid (dTimeout)
    HttpContext.Current.Cache.Insert(sGuid, keyword, null,
           DateTime.Now.AddSeconds(dTimeout), 
           TimeSpan.Zero, CacheItemPriority.High, null);

    // write the image to the Http output stream
    Image.Save(context.Response.OutputStream, 
           System.Drawing.Imaging.ImageFormat.Jpeg);

    context.Response.ContentType = "image/jpeg";
    context.Response.StatusCode = 200;

    context.Response.End();

}

I use a GUID identifier to store and to access keyword in the ASP.NET cache and so the registration form must contain an image tag like this:

<img id="Image1" 
      src="GetCapcthaImage.aspx?guid=72fa6e45-6dfa-46a7-8343-72d56a14f953" 
      alt="" border="0" />

It is automatically obtained by a simple code like this:

    string sGuid = Guid.NewGuid().ToString();

    Image1.ImageUrl = "GetCapcthaImage.aspx?guid=" + sGuid;

When data is posted from the registration form we have to proceed as follows:

  • Check if the Captcha keyword stored in the cache is still valid or expired.
  • Match the user keyword with the Captcha one.

That's all.

Note

Demo project contains: the web service proxy class (WSCaptchaProxy.cs), an ASP.NET demo site and the project that implements the HttpHandler.

For installation information please refer to the readme file that you can find inside the ZIP file.

Conclusion

Many thanks to BrainJar and to wumpus1 for their articles about CAPTCHA argument.

I hope you enjoyed this article.

License

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

Share

About the Author

Massimo Beatini
Web Developer
Italy Italy
No Biography provided

Comments and Discussions

 
GeneralPerfect Pinmemberbass4g0d4-Nov-05 3:38 
Questionan easier way? PinmemberDanielHac8-Feb-05 14:01 
AnswerRe: an easier way? Pinmembermbeatini8-Feb-05 22:10 
GeneralRe: an easier way? PinmemberDanielHac9-Feb-05 1:04 
GeneralGreat job! PinmemberEric Engler30-Dec-04 13:54 

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

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

| Advertise | Privacy | Mobile
Web03 | 2.8.141022.1 | Last Updated 30 Dec 2004
Article Copyright 2004 by Massimo Beatini
Everything else Copyright © CodeProject, 1999-2014
Terms of Service
Layout: fixed | fluid