Compare commits

...
1 Commits
+65 -18
View File
@@ -128,6 +128,48 @@ public sealed class RandomCaptchaGenerator(CaptchaKitOptions options) : ICaptcha
public sealed class SkiaTextCaptchaGenerator : ICaptchaGenerator public sealed class SkiaTextCaptchaGenerator : ICaptchaGenerator
{ {
private const string Alphabet = "ABCDEFGHJKLMNPQRSTUVWXYZ23456789"; private const string Alphabet = "ABCDEFGHJKLMNPQRSTUVWXYZ23456789";
private static readonly IReadOnlyDictionary<char, string[]> Glyphs = new Dictionary<char, string[]>
{
['0'] = ["01110", "10001", "10011", "10101", "11001", "10001", "01110"],
['1'] = ["00100", "01100", "00100", "00100", "00100", "00100", "01110"],
['2'] = ["01110", "10001", "00001", "00010", "00100", "01000", "11111"],
['3'] = ["11110", "00001", "00001", "01110", "00001", "00001", "11110"],
['4'] = ["00010", "00110", "01010", "10010", "11111", "00010", "00010"],
['5'] = ["11111", "10000", "10000", "11110", "00001", "00001", "11110"],
['6'] = ["01110", "10000", "10000", "11110", "10001", "10001", "01110"],
['7'] = ["11111", "00001", "00010", "00100", "01000", "01000", "01000"],
['8'] = ["01110", "10001", "10001", "01110", "10001", "10001", "01110"],
['9'] = ["01110", "10001", "10001", "01111", "00001", "00001", "01110"],
['A'] = ["01110", "10001", "10001", "11111", "10001", "10001", "10001"],
['B'] = ["11110", "10001", "10001", "11110", "10001", "10001", "11110"],
['C'] = ["01110", "10001", "10000", "10000", "10000", "10001", "01110"],
['D'] = ["11110", "10001", "10001", "10001", "10001", "10001", "11110"],
['E'] = ["11111", "10000", "10000", "11110", "10000", "10000", "11111"],
['F'] = ["11111", "10000", "10000", "11110", "10000", "10000", "10000"],
['G'] = ["01110", "10001", "10000", "10111", "10001", "10001", "01110"],
['H'] = ["10001", "10001", "10001", "11111", "10001", "10001", "10001"],
['J'] = ["00111", "00010", "00010", "00010", "00010", "10010", "01100"],
['K'] = ["10001", "10010", "10100", "11000", "10100", "10010", "10001"],
['L'] = ["10000", "10000", "10000", "10000", "10000", "10000", "11111"],
['M'] = ["10001", "11011", "10101", "10101", "10001", "10001", "10001"],
['N'] = ["10001", "11001", "10101", "10011", "10001", "10001", "10001"],
['P'] = ["11110", "10001", "10001", "11110", "10000", "10000", "10000"],
['Q'] = ["01110", "10001", "10001", "10001", "10101", "10010", "01101"],
['R'] = ["11110", "10001", "10001", "11110", "10100", "10010", "10001"],
['S'] = ["01111", "10000", "10000", "01110", "00001", "00001", "11110"],
['T'] = ["11111", "00100", "00100", "00100", "00100", "00100", "00100"],
['U'] = ["10001", "10001", "10001", "10001", "10001", "10001", "01110"],
['V'] = ["10001", "10001", "10001", "10001", "10001", "01010", "00100"],
['W'] = ["10001", "10001", "10001", "10101", "10101", "10101", "01010"],
['X'] = ["10001", "10001", "01010", "00100", "01010", "10001", "10001"],
['Y'] = ["10001", "10001", "01010", "00100", "00100", "00100", "00100"],
['Z'] = ["11111", "00001", "00010", "00100", "01000", "10000", "11111"],
['+'] = ["00000", "00100", "00100", "11111", "00100", "00100", "00000"],
['-'] = ["00000", "00000", "00000", "11111", "00000", "00000", "00000"],
['='] = ["00000", "11111", "00000", "11111", "00000", "00000", "00000"],
['?'] = ["01110", "10001", "00001", "00010", "00100", "00000", "00100"],
[' '] = ["00000", "00000", "00000", "00000", "00000", "00000", "00000"]
};
public GeneratedCaptcha Generate(CaptchaGenerationRequest request) public GeneratedCaptcha Generate(CaptchaGenerationRequest request)
{ {
@@ -148,33 +190,28 @@ public sealed class SkiaTextCaptchaGenerator : ICaptchaGenerator
using var canvas = new SKCanvas(bitmap); using var canvas = new SKCanvas(bitmap);
canvas.Clear(new SKColor(244, 248, 252)); canvas.Clear(new SKColor(244, 248, 252));
DrawNoise(canvas, width, 54, 20); DrawNoise(canvas, width, 54, 20);
// Use Skia's portable default typeface instead of relying on a server-installed font such as Arial.
using var font = new SKFont(SKTypeface.Default, 32) { Embolden = true };
using var outlinePaint = new SKPaint
{
IsAntialias = true,
Color = new SKColor(255, 255, 255),
Style = SKPaintStyle.Stroke,
StrokeWidth = 3
};
using var textPaint = new SKPaint using var textPaint = new SKPaint
{ {
IsAntialias = true, IsAntialias = true,
Color = new SKColor(20, 48, 80) Color = new SKColor(20, 48, 80)
}; };
var spacing = Math.Min(34, Math.Max(24, (width - 28) / displayText.Length)); var scale = Math.Min(4f, (width - 28f) / (displayText.Length * 6.5f - 1.5f));
var startX = width / 2f - (displayText.Length - 1) * spacing / 2f; var glyphWidth = scale * 5;
var metrics = font.Metrics; var glyphHeight = scale * 7;
var baseline = (54 - (metrics.Descent - metrics.Ascent)) / 2f - metrics.Ascent; var gap = scale * 1.5f;
var totalWidth = displayText.Length * glyphWidth + (displayText.Length - 1) * gap;
var x = (width - totalWidth) / 2f;
var y = (54 - glyphHeight) / 2f;
for (var index = 0; index < displayText.Length; index++) for (var index = 0; index < displayText.Length; index++)
{ {
var x = startX + index * spacing + RandomNumberGenerator.GetInt32(-2, 3); var glyph = Glyphs.GetValueOrDefault(displayText[index], Glyphs['?']);
var y = baseline + RandomNumberGenerator.GetInt32(-3, 4);
canvas.Save(); canvas.Save();
canvas.RotateDegrees(RandomNumberGenerator.GetInt32(-20, 21), x, y); canvas.Translate(x + glyphWidth / 2, y + glyphHeight / 2);
canvas.DrawText(displayText[index].ToString(), x, y, SKTextAlign.Center, font, outlinePaint); canvas.RotateDegrees(RandomNumberGenerator.GetInt32(-12, 13));
canvas.DrawText(displayText[index].ToString(), x, y, SKTextAlign.Center, font, textPaint); canvas.Translate(-glyphWidth / 2, -glyphHeight / 2);
DrawGlyph(canvas, glyph, scale, textPaint);
canvas.Restore(); canvas.Restore();
x += glyphWidth + gap;
} }
using var image = SKImage.FromBitmap(bitmap); using var image = SKImage.FromBitmap(bitmap);
using var data = image.Encode(SKEncodedImageFormat.Png, 100); using var data = image.Encode(SKEncodedImageFormat.Png, 100);
@@ -183,6 +220,16 @@ public sealed class SkiaTextCaptchaGenerator : ICaptchaGenerator
return new GeneratedCaptcha(challenge, answer); return new GeneratedCaptcha(challenge, answer);
} }
private static void DrawGlyph(SKCanvas canvas, IReadOnlyList<string> glyph, float scale, SKPaint paint)
{
for (var row = 0; row < glyph.Count; row++)
for (var column = 0; column < glyph[row].Length; column++)
{
if (glyph[row][column] == '1')
canvas.DrawRect(column * scale, row * scale, scale, scale, paint);
}
}
private static void DrawNoise(SKCanvas canvas, int width, int height, int count) private static void DrawNoise(SKCanvas canvas, int width, int height, int count)
{ {
using var paint = new SKPaint { IsAntialias = true, StrokeWidth = 1 }; using var paint = new SKPaint { IsAntialias = true, StrokeWidth = 1 };