Skip to content

Commit

Permalink
optimize captcha generator and tests
Browse files Browse the repository at this point in the history
  • Loading branch information
bezzad committed Jun 10, 2023
1 parent 7c83e29 commit 76459bb
Show file tree
Hide file tree
Showing 4 changed files with 52 additions and 22 deletions.
26 changes: 21 additions & 5 deletions src/Captcha.Net.Sample/Program.cs
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@

var captchaGenerator = new CaptchaGenerator();
var currentDirectory = Directory.CreateDirectory("captcha");
var count = 1000;
var count = 10000;
var times = new List<long>(count);
var stopWatch = new Stopwatch();

Expand All @@ -12,20 +12,36 @@
stopWatch.Restart();
var key = captchaGenerator.GenerateCaptchaCode();
var result = captchaGenerator.GenerateCaptchaImage(145, 56, key);
stopWatch.Stop();
Console.WriteLine($"Captcha {i}: \n");
Console.WriteLine(result.CaptchBase64Data);
Console.WriteLine();
stopWatch.Stop();
times.Add(stopWatch.ElapsedMilliseconds);
File.WriteAllBytes($"{currentDirectory.FullName}/captcha-{i}.jpg", result.CaptchaByteData);
}

Console.WriteLine($"Duration: Max({times.Max()}ms), Min({times.Min()}ms), Average({times.Average()}ms)");
Console.WriteLine($"Duration: Max({times.Max()}ms), Min({times.Min()}ms), Average({times.Average()}ms)");
for (int i = 1, q = 1; i < 10000; i += q)
{
var timeCount = times.Count(t => t <= i && t > i - q);

if (timeCount != 0)
{
Console.WriteLine($"Time[{i - q}ms ~ {i}ms]: {timeCount}");
}

if (i == 10)
q = 10;
else if (i == 100)
q = 100;
else if (i == 1000)
q = 1000;
}

// open the directory after completion
if (OperatingSystem.IsWindows())
{
Process.Start("explorer.exe", currentDirectory.FullName);
{
Process.Start("explorer.exe", currentDirectory.FullName);
}
else if (OperatingSystem.IsLinux())
{
Expand Down
42 changes: 28 additions & 14 deletions src/Captcha.Net/Captcha.cs
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,11 @@ namespace Captcha.Net
public class Captcha : ICaptchaModule
{
protected static readonly Random Rand = new Random(DateTime.Now.GetHashCode());
protected static readonly ConcurrentDictionary<int, ushort> TextMeasures = new ConcurrentDictionary<int, ushort>();
protected static readonly ConcurrentDictionary<int, Image<Rgba32>> ImagesBuffer = new ConcurrentDictionary<int, Image<Rgba32>>();
protected static readonly byte CharPadding = (byte)Rand.Next(5, 10);
protected static float? CharWidth;
protected static float? LineThickness;
protected static Color? BackgroundColor;
protected static Font _font;
protected static AffineTransformBuilder RotationBuilder;
protected readonly CaptchaOptions _options;
Expand All @@ -37,6 +41,11 @@ public Captcha(CaptchaOptions options)
}
}
}

BackgroundColor ??= _options.BackgroundColor[Rand.Next(0, _options.BackgroundColor.Length)];
LineThickness ??= Extensions.GenerateNextFloat(_options.MinLineThickness, _options.MaxLineThickness);
CharWidth ??= TextMeasurer.Measure("8", new TextOptions(_font)).Width;
RotationBuilder ??= GetRotation();
}

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

// add the dynamic image to original image
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);
using var img = GetImageBackground(text.Length);
img.Mutate(ctx => Draw(ctx, img.Width, img.Height, imgText));

using var ms = new MemoryStream();
img.Save(ms, _options.Encoder);
return ms.ToArray();
}

protected Image<Rgba32> GetImageBackground(int textLength)
{
return ImagesBuffer.GetOrAdd(textLength, len =>
{
var sampleText = "".PadRight(textLength, '8');
var size = (ushort)TextMeasurer.Measure(sampleText, new TextOptions(_font)).Width;
return new Image<Rgba32>(size + 15, _options.Height);
}).Clone();
}

protected void DrawText(IImageProcessingContext imgContext, Font font, string text)
{
var position = 5f;
var charPadding = (byte)Rand.Next(5, 10);
imgContext.BackgroundColor(_options.BackgroundColor[Rand.Next(0, _options.BackgroundColor.Length)]);
imgContext.BackgroundColor(BackgroundColor.Value);

foreach (char ch in text)
{
var location = new PointF(charPadding + position, Rand.Next(6, Math.Abs(_options.Height - _options.FontSize - 5)));
var location = new PointF(CharPadding + position, Rand.Next(6, Math.Abs(_options.Height - _options.FontSize - 5)));
imgContext.DrawText(ch.ToString(), font, _options.TextColor[Rand.Next(0, _options.TextColor.Length)], location);
position += TextMeasurer.Measure(ch.ToString(), new TextOptions(font)).Width;
position += CharWidth.Value;
}

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

Expand All @@ -90,8 +106,7 @@ protected void AddNoisePoint(IImageProcessingContext ctx, int width, int height)
{
int x0 = Rand.Next(0, width);
int y0 = Rand.Next(0, height);
int colorIndex = Rand.Next(0, _options.NoiseRateColor.Length);
var color = _options.NoiseRateColor[colorIndex];
var color = _options.NoiseRateColor[Rand.Next(0, _options.NoiseRateColor.Length)];
ctx.DrawLines(color, 1F, new PointF[] { new Vector2(x0, y0), new Vector2(x0, y0) });
}

Expand All @@ -100,10 +115,9 @@ protected void DrawNoiseLines(IImageProcessingContext ctx, int width, int height
int x0 = Rand.Next(0, Rand.Next(0, Math.Min(30, width)));
int y0 = Rand.Next(Math.Min(10, height), height);
int x1 = Rand.Next(width - Rand.Next(0, (int)(width * 0.25)), width);
int y1 = Rand.Next(0, height);
var thickness = Extensions.GenerateNextFloat(_options.MinLineThickness, _options.MaxLineThickness);
int y1 = Rand.Next(0, height);
var lineColor = _options.DrawLinesColor[Rand.Next(0, _options.DrawLinesColor.Length)];
ctx.DrawLines(lineColor, thickness, new PointF[] { new PointF(x0, y0), new PointF(x1, y1) });
ctx.DrawLines(lineColor, LineThickness.Value, new PointF[] { new PointF(x0, y0), new PointF(x1, y1) });
}

protected AffineTransformBuilder GetRotation()
Expand Down
2 changes: 1 addition & 1 deletion src/Captcha.Net/CaptchaGenerator.cs
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@ public CaptchaResult GenerateCaptchaImage(ushort width, ushort height, string ca
Width = width,
Height = height,
MaxRotationDegrees = 15,
RotationDegree = 10,
RotationDegree = 7,
NoiseRate = 50,
DrawLines = 4,
FontSize = GetFontSize(width, captchaCode.Length),
Expand Down
4 changes: 2 additions & 2 deletions src/Captcha.Net/CaptchaOptions.cs
Original file line number Diff line number Diff line change
Expand Up @@ -13,13 +13,13 @@ public class CaptchaOptions
/// </summary>
public string[] FontFamilies { get; set; }
public Color[] TextColor { get; set; } = new Color[] { Color.Blue, Color.Black, Color.Black, Color.DarkGray, Color.Brown, Color.Gray, Color.Gray, Color.Green, Color.SlateGray };
public Color[] DrawLinesColor { get; set; } = new Color[] { Color.Blue, Color.Black, Color.Black, Color.Brown, Color.Gray, Color.Green, Color.SlateGray };
public Color[] DrawLinesColor { get; set; } = new Color[] { Color.Blue, Color.Black, Color.Red, Color.Brown, Color.Gray, Color.Green, Color.SlateGray };
public float MinLineThickness { get; set; } = 0.7f;
public float MaxLineThickness { get; set; } = 2.0f;
public ushort Width { get; set; } = 180;
public ushort Height { get; set; } = 50;
public ushort NoiseRate { get; set; } = 100;
public Color[] NoiseRateColor { get; set; } = new Color[] { Color.Gray };
public Color[] NoiseRateColor { get; set; } = new Color[] { Color.Gray, Color.Black, Color.Red };
public byte FontSize { get; set; } = 29;
public FontStyle FontStyle { get; set; } = FontStyle.Regular;
public EncoderTypes EncoderType { get; set; } = EncoderTypes.Jpeg;
Expand Down

0 comments on commit 76459bb

Please sign in to comment.