Compare commits
1
Commits
8964692448
...
cd37690d97
@@ -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 };
|
||||||
|
|||||||
Reference in new issue
Block a user