Skip to content

Commit

Permalink
improved Captcha generator algorithm
Browse files Browse the repository at this point in the history
  • Loading branch information
bezzad committed Jun 10, 2023
1 parent 7c9a3bb commit 7c83e29
Show file tree
Hide file tree
Showing 5 changed files with 37 additions and 13 deletions.
2 changes: 1 addition & 1 deletion src/Captcha.Net.Sample/Program.cs
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@
Console.WriteLine();
stopWatch.Stop();
times.Add(stopWatch.ElapsedMilliseconds);
File.WriteAllBytes($"{currentDirectory.FullName}/captcha-{i}.png", result.CaptchaByteData);
File.WriteAllBytes($"{currentDirectory.FullName}/captcha-{i}.jpg", result.CaptchaByteData);
}

Console.WriteLine($"Duration: Max({times.Max()}ms), Min({times.Min()}ms), Average({times.Average()}ms)");
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@ public void GenerateCaptchaImageTest()
// arrange
ushort width = 100;
ushort height = 50;
var jpegWhiteBalanceRatio = 0.99; // 99% like white color
var code = "012340";
var generator = new CaptchaGenerator();
var backgroundHistogram = 0;
Expand All @@ -39,7 +40,7 @@ public void GenerateCaptchaImageTest()
var pixels = image.GetPixelMemoryGroup().SelectMany(g => g.ToArray());
foreach (var pixel in pixels)
{
if (pixel == Color.White.ToPixel<Rgba32>())
if (pixel.Rgba >= Color.White.ToPixel<Rgba32>().Rgba * jpegWhiteBalanceRatio)
backgroundHistogram++;
}

Expand Down
35 changes: 27 additions & 8 deletions src/Captcha.Net/Captcha.cs
Original file line number Diff line number Diff line change
Expand Up @@ -4,22 +4,39 @@
using SixLabors.ImageSharp.PixelFormats;
using SixLabors.ImageSharp.Processing;
using System;
using System.Collections.Concurrent;
using System.IO;
using System.Numerics;

namespace Captcha.Net
{
public class Captcha : ICaptchaModule
{
protected readonly static Random Rand = new Random(DateTime.Now.GetHashCode());
protected static readonly Random Rand = new Random(DateTime.Now.GetHashCode());
protected static readonly ConcurrentDictionary<int, ushort> TextMeasures = new ConcurrentDictionary<int, ushort>();
protected static Font _font;
protected static AffineTransformBuilder RotationBuilder;
protected readonly CaptchaOptions _options;
protected Font _font;

public Captcha(CaptchaOptions options)
{
_options = options;
var fontName = _options.FontFamilies[Rand.Next(0, _options.FontFamilies.Length)];
_font = SystemFonts.CreateFont(fontName, _options.FontSize, _options.FontStyle);
if (_font == null)
{
try
{
var fontName = _options.FontFamilies[0];
_font = SystemFonts.CreateFont(fontName, _options.FontSize, _options.FontStyle);
}
catch
{
if (_options.FontFamilies.Length > 1)
{
var fontName = _options.FontFamilies[1];
_font = SystemFonts.CreateFont(fontName, _options.FontSize, _options.FontStyle);
}
}
}
}

public byte[] Generate(string text)
Expand All @@ -28,7 +45,7 @@ public byte[] Generate(string text)
imgText.Mutate(ctx => DrawText(ctx, _font, text));

// add the dynamic image to original image
var size = (ushort)TextMeasurer.Measure(text, new TextOptions(_font)).Width;
var size = TextMeasures.GetOrAdd(text.Length, len => (ushort)TextMeasurer.Measure(text, new TextOptions(_font)).Width);
using var img = new Image<Rgba32>(size + 15, _options.Height);
img.Mutate(ctx => Draw(ctx, img.Width, img.Height, imgText));

Expand All @@ -51,8 +68,8 @@ protected void DrawText(IImageProcessingContext imgContext, Font font, string te
}

// add rotation
AffineTransformBuilder rotation = GetRotation();
imgContext.Transform(rotation);
RotationBuilder ??= GetRotation();
imgContext.Transform(RotationBuilder);
}

protected void Draw(IImageProcessingContext imgContext, int width, int height, Image<Rgba32> imgText)
Expand Down Expand Up @@ -94,7 +111,9 @@ protected AffineTransformBuilder GetRotation()
var width = Rand.Next(Math.Min((ushort)10u, _options.Width), _options.Width);
var height = Rand.Next(Math.Min((ushort)10u, _options.Height), _options.Height);
var pointF = new PointF(width, height);
var rotationDegrees = Rand.Next(0, _options.MaxRotationDegrees);
var rotationDegrees = _options.RotationDegree.HasValue && _options.RotationDegree <= _options.MaxRotationDegrees
? _options.RotationDegree.Value
: Rand.Next(0, _options.MaxRotationDegrees);
var result = GetRotation(rotationDegrees, pointF);
return result;
}
Expand Down
5 changes: 4 additions & 1 deletion src/Captcha.Net/CaptchaGenerator.cs
Original file line number Diff line number Diff line change
Expand Up @@ -22,8 +22,11 @@ public CaptchaResult GenerateCaptchaImage(ushort width, ushort height, string ca
Width = width,
Height = height,
MaxRotationDegrees = 15,
RotationDegree = 10,
NoiseRate = 50,
DrawLines = 4,
FontSize = GetFontSize(width, captchaCode.Length),
EncoderType = EncoderTypes.Png
EncoderType = EncoderTypes.Jpeg
}));

var captchaCodeBytes = captcha.Generate(captchaCode);
Expand Down
5 changes: 3 additions & 2 deletions src/Captcha.Net/CaptchaOptions.cs
Original file line number Diff line number Diff line change
Expand Up @@ -18,15 +18,16 @@ public class CaptchaOptions
public float MaxLineThickness { get; set; } = 2.0f;
public ushort Width { get; set; } = 180;
public ushort Height { get; set; } = 50;
public ushort NoiseRate { get; set; } = 300;
public ushort NoiseRate { get; set; } = 100;
public Color[] NoiseRateColor { get; set; } = new Color[] { Color.Gray };
public byte FontSize { get; set; } = 29;
public FontStyle FontStyle { get; set; } = FontStyle.Regular;
public EncoderTypes EncoderType { get; set; } = EncoderTypes.Png;
public EncoderTypes EncoderType { get; set; } = EncoderTypes.Jpeg;
public IImageEncoder Encoder => Extensions.GetEncoder(EncoderType);
public byte DrawLines { get; set; } = 5;
public byte MaxRotationDegrees { get; set; } = 5;
public Color[] BackgroundColor { get; set; } = new Color[] { Color.White };
public float? RotationDegree { get; set; } = 3;

public CaptchaOptions()
{
Expand Down

0 comments on commit 7c83e29

Please sign in to comment.