178 lines
7.3 KiB
C#
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

using SixLabors.ImageSharp;
using SixLabors.ImageSharp.Drawing;
using SixLabors.ImageSharp.Drawing.Processing;
using SixLabors.ImageSharp.Formats.Png;
using SixLabors.ImageSharp.PixelFormats;
using SixLabors.ImageSharp.Processing;
namespace NetAdmin.Infrastructure.Utils;
/// <summary>
/// 验证码图片工具类
/// </summary>
public static class CaptchaImageHelper
{
private static readonly int[] _randRange = { 70, 100 };
/// <summary>
/// 创建一个缺口滑块验证码图片
/// </summary>
/// <param name="resAsm">包含资源的程序集</param>
/// <param name="bgPath">背景图路径</param>
/// <param name="tempPath">滑块模板小图路径</param>
/// <param name="bgIndexScope">背景图随机序号范围1-x</param>
/// <param name="tempIndexScope">模板图随机序号范围1-x</param>
/// <param name="sliderSize">滑块尺寸</param>
/// <returns> 背景图base64滑块图base64缺口坐标 </returns>
#pragma warning disable SA1414
public static async Task<(string BackgroundImage, string SliderImage, Point OffsetSaw)> CreateSawSliderImageAsync(
Assembly resAsm, string bgPath, string tempPath, (int, int) bgIndexScope, (int, int) tempIndexScope
, Size sliderSize)
#pragma warning restore SA1414
{
// 深色模板图
var templateIndex = new[] { tempIndexScope.Item1, tempIndexScope.Item2 }.Rand();
await using var bgStream = resAsm.GetManifestResourceStream(
$"{bgPath}.{new[] { bgIndexScope.Item1, bgIndexScope.Item2 }.Rand()}.jpg");
await using var darkStream = resAsm.GetManifestResourceStream($"{tempPath}._{templateIndex}.dark.png");
await using var tranStream = resAsm.GetManifestResourceStream($"{tempPath}._{templateIndex}.transparent.png");
// 底图
using var backgroundImage = await Image.LoadAsync<Rgba32>(bgStream).ConfigureAwait(false);
using var darkTemplateImage = await Image.LoadAsync<Rgba32>(darkStream).ConfigureAwait(false);
// 透明模板图
using var transparentTemplateImage = await Image.LoadAsync<Rgba32>(tranStream).ConfigureAwait(false);
// 调整模板图大小
darkTemplateImage.Mutate(x => x.Resize(sliderSize));
transparentTemplateImage.Mutate(x => x.Resize(sliderSize));
// 新建拼图
using var blockImage = new Image<Rgba32>(sliderSize.Width, sliderSize.Height);
// 新建滑块拼图
using var sliderBlockImage = new Image<Rgba32>(sliderSize.Width, backgroundImage.Height);
// 随机生成拼图坐标
var offsetRand = GeneratePoint(backgroundImage.Width, backgroundImage.Height, sliderSize.Width
, sliderSize.Height);
// 根据深色模板图计算轮廓形状
var blockShape = CalcBlockShape(darkTemplateImage);
// 生成拼图
blockImage.Mutate(x => {
// ReSharper disable once AccessToDisposedClosure
_ = x.Clip(blockShape, p => p.DrawImage(backgroundImage, new Point(-offsetRand.X, -offsetRand.Y), 1));
});
// 拼图叠加透明模板图层
// ReSharper disable once AccessToDisposedClosure
blockImage.Mutate(x => x.DrawImage(transparentTemplateImage, new Point(0, 0), 1));
// 生成滑块拼图
// ReSharper disable once AccessToDisposedClosure
sliderBlockImage.Mutate(x => x.DrawImage(blockImage, new Point(0, offsetRand.Y), 1));
var opacity = (float)(_randRange.Rand() * 0.01);
// 底图叠加深色模板图
// ReSharper disable once AccessToDisposedClosure
backgroundImage.Mutate(x => x.DrawImage(darkTemplateImage, new Point(offsetRand.X, offsetRand.Y), opacity));
// 生成干扰图坐标
var interferencePoint = GenerateInterferencePoint(backgroundImage.Width, backgroundImage.Height
, sliderSize.Width, sliderSize.Height, offsetRand.X
, offsetRand.Y);
// 底图叠加深色干扰模板图
// ReSharper disable once AccessToDisposedClosure
backgroundImage.Mutate(x => x.DrawImage(darkTemplateImage, new Point(interferencePoint.X, interferencePoint.Y)
, opacity));
return (backgroundImage.ToBase64String(PngFormat.Instance), sliderBlockImage.ToBase64String(PngFormat.Instance)
, offsetRand);
}
private static ComplexPolygon CalcBlockShape(Image<Rgba32> templateDarkImage)
{
var temp = 0;
var pathList = new List<IPath>();
templateDarkImage.ProcessPixelRows(accessor => {
for (var y = 0; y < templateDarkImage.Height; y++) {
var rowSpan = accessor.GetRowSpan(y);
for (var x = 0; x < rowSpan.Length; x++) {
ref var pixel = ref rowSpan[x];
if (pixel.A != 0) {
temp = temp switch { 0 => x, _ => temp };
}
else {
if (temp == 0) {
continue;
}
pathList.Add(new RectangularPolygon(temp, y, x - temp, 1));
temp = 0;
}
}
}
});
return new ComplexPolygon(new PathCollection(pathList));
}
/// <summary>
/// 随机生成干扰图坐标
/// </summary>
private static Point GenerateInterferencePoint(int originalWidth, int originalHeight, int templateWidth
, int templateHeight, int blockX, int blockY)
{
var x =
// 在原扣图右边插入干扰图
originalWidth - blockX - 5 > templateWidth * 2
? GetRandomInt(blockX + templateWidth + 5, originalWidth - templateWidth)
:
// 在原扣图左边插入干扰图
GetRandomInt(100, blockX - templateWidth - 5);
var y =
// 在原扣图下边插入干扰图
originalHeight - blockY - 5 > templateHeight * 2
? GetRandomInt(blockY + templateHeight + 5, originalHeight - templateHeight)
:
// 在原扣图上边插入干扰图
GetRandomInt(5, blockY - templateHeight - 5);
return new Point(x, y);
}
/// <summary>
/// 随机生成拼图坐标
/// </summary>
private static Point GeneratePoint(int originalWidth, int originalHeight, int templateWidth, int templateHeight)
{
var widthDifference = originalWidth - templateWidth;
var heightDifference = originalHeight - templateHeight;
var x = widthDifference switch {
<= 0 => 5, _ => new[] { 0, originalWidth - templateWidth - 100 }.Rand() + 100
};
var y = heightDifference switch { <= 0 => 5, _ => new[] { 0, originalHeight - templateHeight - 5 }.Rand() + 5 };
return new Point(x, y);
}
/// <summary>
/// 随机范围内数字
/// </summary>
private static int GetRandomInt(int startNum, int endNum)
{
return (endNum > startNum ? new[] { 0, endNum - startNum }.Rand() : 0) + startNum;
}
}