Compare commits
3
Commits
2880b32c92
..
master
No files matched your search
@@ -15,7 +15,7 @@
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\CaptchaKit.Core\CaptchaKit.Core.csproj" />
|
||||
<FrameworkReference Include="Microsoft.AspNetCore.App" />
|
||||
<PackageReference Include="SkiaSharp" Version="4.152.0" />
|
||||
<PackageReference Include="SkiaSharp" Version="4.152.1" />
|
||||
<None Include="..\..\README.md" Pack="true" PackagePath="\" />
|
||||
<None Include="..\..\LICENSE" Pack="true" PackagePath="\" />
|
||||
</ItemGroup>
|
||||
|
||||
@@ -128,6 +128,48 @@ public sealed class RandomCaptchaGenerator(CaptchaKitOptions options) : ICaptcha
|
||||
public sealed class SkiaTextCaptchaGenerator : ICaptchaGenerator
|
||||
{
|
||||
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)
|
||||
{
|
||||
@@ -147,21 +189,30 @@ public sealed class SkiaTextCaptchaGenerator : ICaptchaGenerator
|
||||
using var bitmap = new SKBitmap(width, 54);
|
||||
using var canvas = new SKCanvas(bitmap);
|
||||
canvas.Clear(new SKColor(244, 248, 252));
|
||||
DrawNoise(canvas, width, 54, 16);
|
||||
using var paint = new SKPaint { IsAntialias = true };
|
||||
using var font = new SKFont(SKTypeface.FromFamilyName("Arial", SKFontStyle.Bold), 30);
|
||||
var spacing = Math.Max(22, (width - 24) / displayText.Length);
|
||||
DrawNoise(canvas, width, 54, 20);
|
||||
using var textPaint = new SKPaint
|
||||
{
|
||||
IsAntialias = true,
|
||||
Color = new SKColor(20, 48, 80)
|
||||
};
|
||||
var scale = Math.Min(4f, (width - 28f) / (displayText.Length * 6.5f - 1.5f));
|
||||
var glyphWidth = scale * 5;
|
||||
var glyphHeight = scale * 7;
|
||||
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++)
|
||||
{
|
||||
var x = 12 + index * spacing + RandomNumberGenerator.GetInt32(-2, 3);
|
||||
var y = 38 + RandomNumberGenerator.GetInt32(-4, 5);
|
||||
paint.Color = RandomColor(35, 145);
|
||||
var glyph = Glyphs.GetValueOrDefault(displayText[index], Glyphs['?']);
|
||||
canvas.Save();
|
||||
canvas.RotateDegrees(RandomNumberGenerator.GetInt32(-20, 21), x, y);
|
||||
canvas.DrawText(displayText[index].ToString(), x, y, SKTextAlign.Left, font, paint);
|
||||
canvas.Translate(x + glyphWidth / 2, y + glyphHeight / 2);
|
||||
canvas.RotateDegrees(RandomNumberGenerator.GetInt32(-12, 13));
|
||||
canvas.Translate(-glyphWidth / 2, -glyphHeight / 2);
|
||||
DrawGlyph(canvas, glyph, scale, textPaint);
|
||||
canvas.Restore();
|
||||
x += glyphWidth + gap;
|
||||
}
|
||||
DrawNoise(canvas, width, 54, 10);
|
||||
using var image = SKImage.FromBitmap(bitmap);
|
||||
using var data = image.Encode(SKEncodedImageFormat.Png, 100);
|
||||
var challenge = new CaptchaChallenge(request.Id, type, request.ExpiresAt,
|
||||
@@ -169,6 +220,16 @@ public sealed class SkiaTextCaptchaGenerator : ICaptchaGenerator
|
||||
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)
|
||||
{
|
||||
using var paint = new SKPaint { IsAntialias = true, StrokeWidth = 1 };
|
||||
@@ -179,10 +240,6 @@ public sealed class SkiaTextCaptchaGenerator : ICaptchaGenerator
|
||||
}
|
||||
}
|
||||
|
||||
private static SKColor RandomColor(int min, int max) => new(
|
||||
(byte)RandomNumberGenerator.GetInt32(min, max),
|
||||
(byte)RandomNumberGenerator.GetInt32(min, max),
|
||||
(byte)RandomNumberGenerator.GetInt32(min, max));
|
||||
private static byte RandomByte() => RandomNumberGenerator.GetBytes(1)[0];
|
||||
}
|
||||
|
||||
|
||||
Reference in new issue
Block a user