Captcha Generator with SixLabors’ libs on .NET Core 3

Tiago Godoi
Agile Content Teamwork
7 min readOct 28, 2019

--

Well, if you are reading this article I could guess that you tried to search how to generate captcha images to use in your own .NET Core application because:

  1. You aren’t able to use System.Drawing because you want it to run on any Linux-based system;
  2. You don’t wanna use any online-based-third-party service to generate it for you (I’m sorry, reCAPTCHA);
  3. You like to learn new things (and that’s a really good reason on itself).

If you have any of the reasons above, or any other else, you are welcome here!

By the way, if you don’t know what a captcha is, don’t feel intimidated by it.

A Captcha, in a brief explanation, is a way of preventing your system to be accessed by bots. It may asks the user to click and wait for a while, to click in certain images, to type some alphanumeric characters, etc.

In this article, our Captcha will be an image drawn in a rectangular shape, some multicolored characters on it, and some lines and dots to difficult reading.

So let’s start our mission!

What we’ll be doing here:

  1. Creating a bitmap
  2. Filling a bitmap
  3. Writing a char
  4. Writing a text char by char
  5. Rotating the text
  6. Adding dirt

Mostly, we’ll be using SixLabors’ libraries for our drawing operations because it’s 100% manageable, its code is portable, open source, it has a fluent interface, and its quite easy to learn how to use it. I guess we have plenty of reasons already!

Creating a bitmap

To start our adventure, we need to create a bitmap in which we can work on. We may create an Image variable like any other else: (Use this one if you want your method to return this Image variable.)

var img = new Image<Rgba32>(width, height);

Or within an using statement: (You won’t be able to use this var as a return variable, as it’ll be disposed after the using statement)

using (var img = new Image<Rgba32>(width, height))
{

}

Note that when not defining the Image variable within an using statement, it’s a good practice to .Dispose() it after working on it.

Rgba32 corresponds to a Pixel Format and may be substituted by any other Pixel Format of your choice.

So now that we have a bitmap to work on, what can we do to actually start drawing?

Filling the bitmap

backColor = Color.White;
img.Mutate(ctx => ctx.BackgroundColor(backColor));

The way we define colors here is the same way it is used in System.Drawing. As we need a white color, Color.White is the one we’ll be working with.

.Mutate() is an important method in this article, it will be used for almost everything we want to do with the image itself. Note that, when we mutate the image, we apply an action on it overwriting the “old” bitmap with the new actions. In this case we are only defining a background color.

You may also define a color as:

new Color(new Argb32(0.0f, 0.0f, 1.0f))

In this case, the three (shown) parameters are related to Red, Green and Blue color quantities, going from 0 to 1. There’s also another (optional) parameter for Alpha, which controls the transparency/opacity of the color.

Blank 200 px x 30px rectangle, but c’mon, it’s a start.

(By the way, I’ll be showing our results step by step surrounded by a black line just for visualization matters, as the background is also white.)

Writing a char

var font = SystemFonts.CreateFont("Arial", 10, FontStyle.Regular);
var location = new PointF(x, y);
img.Mutate(ctx => ctx.DrawText(c.ToString(),font, Color.Black, location));

When writing some text in a bitmap, you’re going to use DrawText() in Mutate(), which parameters are:

  • text: a string parameter, in this case I converted a char to a string;
  • font: a Font parameter, its value is defined using SystemFonts.CreateFont(<fontName as String>,<size as int>,<fontStyle as a FontStyle enum value>);
  • color: a Color variable, as explained above;
  • location: a PointF variable, requiring x and y float values to determine the start location of the drawing.

Of course you can write a full text with this method, but here we want to write char by char, modifying their location, color and font.

Now we have a blue ’A’ character!

And how to write a string text?

Well, if you want to write char by char like me, you’ll have to use a loop statement (yay!). So let’s start showing a more complete code:

using (var img = new Image<Rgba32>(width, height))
{
var fontFamilies = new string[] { "Arial", "Verdana", "Times New Roman" };
Random random = new Random();
img.Mutate(ctx => ctx.BackgroundColor(backColor)); foreach(char c in stringText)
{
var font = SystemFonts.CreateFont(fontFamilies[random.Next(0,fontFamilies.Length)], 20, FontStyle.Regular);
var location = new PointF(x+position, y);
img.Mutate(ctx => ctx.DrawText(c.ToString(), font, new Color(new Argb32(0.0f, 0.0f, 1.0f)), location)); position = position + TextMeasurer.Measure(c.ToString(), new RendererOptions(font, location)).Width;
}
}

…Okay, we have a lot of stuff here, so let’s take a look at the new ones now:

  • fontFamilies: I created a array of font names so, at the time we’ll be defining the font of each character, I could use a random one;
  • position: We don’t want our characters all overdrawn on each other, so we start to manage their position. That’s a quite an easy job when we use TextMeasurer.Measure(). All you need to measure it is the text itself (the character, in this case) and a RendererOption(), that requires a font and a start location.

Given that we have our text drawn, we want to rotate it, adding an extra dificulty when reading the Captcha.

We now have a full text! But is its position ideal? Well, I’m not going to focus on calculus here because you are the one to decide where to position the text. Take this as a challenge!

Rotating the text

When rotating a bitmap, you may use:

var builder = new AffineTransformBuilder();
img.Mutate(ctx => ctx.Transform(builder.PrependRotationDegrees(angle, new PointF(rotateX, rotateY))));

Here we use a .Transform() method, which requires an AffineTransformationBuilder(). In this case, as we want to rotate the image, we use .PrependRotationDegrees().

When you add a rotation operation, you have to specify its angle and the point where the rotation is applied.

So, let’s just add the rotation operation at the end of our algorithm:

using (var img = new Image<Rgba32>(width, height))
{
...
foreach(char c in s)
{
...
}
var builder = new AffineTransformBuilder();
img.Mutate(ctx => ctx.Transform(builder.PrependRotationDegrees(angle, new PointF(rotateX, rotateY))));
}

With width = 200px and height 30px, I got:

200px x 50px image

…but actually I don’t want the size of my image changing accordingly to the rotation applied.

What just happened? Let’s make a review about what we did so far:

  1. Instantiated a bitmap
  2. Added a background color to it
  3. Wrote some text on it
  4. Rotated it

The problem is: when we rotate an image, it won’t be bound to the limits we defined before, with width and height. But we don’t actually want the whole image to rotate, only the text.

So what if we create a separate bitmap to it and, after applying the rotation operation, we draw it into another image?

var img = new Image<Rgba32>(width, height);img.Mutate(ctx => ctx.BackgroundColor(backColor));using (var imgText = new Image<Rgba32>(width, height))
{
...
foreach(char c in stringText)
{
...
}
var builder = new AffineTransformBuilder();
imgText.Mutate(ctx => ctx.Transform(builder.PrependRotationDegrees(angle, new PointF(rotateX, rotateY))));
img.Mutate(ctx => ctx.DrawImage(imgText, transparency));}return img;
200px x 30px image

So now we have an bitmap for the captcha itself and another bitmap for our text. The text is applied in our final image with the .DrawImage() method, in which you may also specify a transparency.

Note that I’m not defining our final bitmap in an using clause, because I want it to be our return value to another piece of code somewhere else.

Okay! We are starting to get things ready, but what can we do more?

Adding dirt

So now that we have our text placed, why don’t we add some stuff to make it more difficult to read?

You may add some lines:

img.Mutate(ctx => ctx.DrawLines(color, thickness, new PointF[] { new PointF(x1, y1), new PointF(x2, y2)}));

The color? Your choice. The thickness? A float of your choice. You may even specify the points (x1,y1) and (x2,y2) which define the line itself. You may add them above your text, or below it, or both, why not?

You may also add some dots:

img.Mutate(ctx => ctx.DrawLines(color, thickness, new PointF[] { new PointF(x1, y1), new PointF(x1, y1)}));

In this case I reused .DrawLines() but both the point parameters are the same.

And then I got:

And what more can I do?

I actually played a little bit with characters positions, and got something like this one:

And, well, it’s up to your imagination! You may draw curves, multicolored lines, colorful background, etc.

I recommend exploring SixLabors’ documentation (https://docs.sixlabors.com/) and also their github ( https://github.com/SixLabors) for some samples.

I hope this guide was useful to you! Keep up the good work!

--

--