前几篇文章,介绍了.Net 6实现的滑动验证码功能,最近把滑动验证码的ImageSharp
替换成了SkiaSharp
,其中抠图部分参考了pojianbing大神的代码。滑动验证码完成之后,心想着。做一个旋转验证码。其实旋转验证码跟滑动验证码及其类似。
先看下效果图:
旋转的实现原理与滑动验证码一样:
- 1、获取背景图
- 2、获取模板的凹槽图与滑块图
- 3、进行凹槽抠图
- 4、旋转画布
- 5、贴图到滑块图上
- 6、背景图上叠加凹槽图模板
目录
验证码实体类ImageCaptchaInfo
public class ImageCaptchaInfo
{
/// <summary>
/// 背景图宽
/// </summary>
public int BackgroundImageWidth { get; set; }
/// <summary>
/// 背景图高
/// </summary>
public int BackgroundImageHeight { get; set; }
/// <summary>
/// 背景图
/// </summary>
public string BackgroundImageBase64 { get; set; }
/// <summary>
/// 滑动块图宽
/// </summary>
public int SliderImageWidth { get; set; }
/// <summary>
/// 滑动块图高
/// </summary>
public int SliderImageHeight { get; set; }
/// <summary>
/// 滑动块图
/// </summary>
public string SliderImageBase64 { get; set; }
/// <summary>
/// 随机值
/// </summary>
public int RandomX { get; set; }
/// <summary>
/// 容错值,可以为空!
/// </summary>
public float Tolerant { get; set; }
/// <summary>
/// 验证码类型
/// </summary>
public string CaptchaType { get; set; }
public float Percentage
{
get
{
if (BackgroundImageWidth <= 0) return 0;
return 1.0F * RandomX / BackgroundImageWidth;
}
}
}
其中随机值RandomX
是要参与到滑动比例Percentage
的运算中的。Percentage
的作用后面再说。
定义旋转验证码实体RotateImageCaptchaInfo
public class RotateImageCaptchaInfo : ImageCaptchaInfo
{
/// <summary>
/// 旋转多少度
/// </summary>
public float Degree { get; set; }
public void Check()
{
//校验
if (this.Degree <= 0) throw new TaCaptchaException($"RotateImageCaptchaInfo数据异常: {nameof(Degree)}小于等于0");
if (this.RandomX <= 0) throw new TaCaptchaException($"RotateImageCaptchaInfo数据异常: {nameof(RandomX)}小于等于0");
if (this.BackgroundImageWidth <= 0) throw new TaCaptchaException($"RotateImageCaptchaInfo数据异常: {nameof(BackgroundImageWidth)}小于等于0");
if (this.BackgroundImageHeight <= 0) throw new TaCaptchaException($"RotateImageCaptchaInfo数据异常: {nameof(BackgroundImageHeight)}小于等于0");
if (this.SliderImageWidth <= 0) throw new TaCaptchaException($"RotateImageCaptchaInfo数据异常: {nameof(SliderImageWidth)}小于等于0");
if (this.SliderImageHeight <= 0) throw new TaCaptchaException($"RotateImageCaptchaInfo数据异常: {nameof(SliderImageHeight)}小于等于0");
if (string.IsNullOrWhiteSpace(this.BackgroundImageBase64)) throw new TaCaptchaException($"RotateImageCaptchaInfo数据异常: {nameof(BackgroundImageBase64)}为空");
if (string.IsNullOrWhiteSpace(this.SliderImageBase64)) throw new TaCaptchaException($"RotateImageCaptchaInfo数据异常: {nameof(SliderImageBase64)}为空");
}
}
Check
校验方法中,如果旋转的度数Degree
、随机值RandomX
等不满足条件,返回自动以错误。
定义接口IRotateCaptchaImageGenerator
定义旋转验证码接口,这个比较简单,就一个生成验证码的方法。
public interface IRotateCaptchaImageGenerator
{
Task<RotateImageCaptchaInfo> Generate(string captchaId);
}
生成验证码
public class RotateCaptchaImageGenerator : IRotateCaptchaImageGenerator
{
private readonly IResourceManager _resourceManager;
private readonly Random _random = new Random();
public RotateCaptchaImageGenerator(IResourceManager resourceManager)
{
_resourceManager = resourceManager;
}
public async Task<RotateImageCaptchaInfo> Generate(string captchaId)
{
//获取背景图
var background = await _resourceManager.RandomBackground();
//获取滑块图、凹槽图
(var slider, var notch) = await _resourceManager.RandomTemplate(CaptchaTypeConstant.ROTATE);
using var backgroundImage = SKBitmap.Decode(background);
//滑块
using var sliderTemplateImage = SKBitmap.Decode(slider);
//凹槽
using var notchTemplateImage = SKBitmap.Decode(notch);
//定义空的凹槽图与滑块图
using var notchMattingImage = new SKBitmap(notchTemplateImage.Width, notchTemplateImage.Height);
using var sliderBarImage = new SKBitmap(360,360);
//根据透明度计算凹槽图轮廓形状(形状由不透明区域形成)
var notchShape = CaptchaImageUtils.GetImageShape(notchTemplateImage);
//滑块图画布
using var sliderCanvas = new SKCanvas(notchMattingImage);
//叠加轮廓图
sliderCanvas.ClipPath(notchShape, SKClipOperation.Intersect, true);
//计算居中的位置
int x = backgroundImage.Width / 2 - notchTemplateImage.Width / 2;
int y = backgroundImage.Height / 2 - notchTemplateImage.Height / 2;
//随机旋转抠图部分
int randomX = _random.Next(notchTemplateImage.Width + 10, backgroundImage.Width - 10);
float degree = 360f - randomX / (backgroundImage.Width / 360f);
sliderCanvas.RotateDegrees(degree, notchMattingImage.Width/2, notchMattingImage.Height/2);
//绘制抠图
sliderCanvas.DrawBitmap(backgroundImage,-x,-y);
//叠加滑块模板
sliderCanvas.DrawBitmap(sliderTemplateImage, 0, 0);
int bw = 360;
int bh = 360;
int cw = notchMattingImage.Width;
int ch = notchMattingImage.Height;
int iw = (int)(bw / 2 - cw / 2);
int ih = (int)(bh / 2 - ch / 2);
using var sliderBarCanvas = new SKCanvas(sliderBarImage);
sliderBarCanvas.DrawBitmap(notchMattingImage, iw, ih);
using var backgroundCanvas = new SKCanvas(backgroundImage);
//叠加凹槽
backgroundCanvas.DrawBitmap(notchTemplateImage, x, y);
return new RotateImageCaptchaInfo
{
BackgroundImageWidth = backgroundImage.Width,
BackgroundImageHeight = backgroundImage.Height,
BackgroundImageBase64 = backgroundImage.ToBase64String(SKEncodedImageFormat.Png),
RandomX = randomX,
Degree = degree,
Tolerant = 0.03f,
SliderImageWidth = notchMattingImage.Width,
SliderImageHeight = notchMattingImage.Height,
SliderImageBase64 = sliderBarImage.ToBase64String(SKEncodedImageFormat.Png),
CaptchaType = CaptchaTypeConstant.ROTATE
};
}
}
代码中,首先获取背景图、凹槽图与滑块图,并将他们转为SKBitmap
。
定义一个空的凹槽图与滑块图。根据凹槽图,获取凹槽图的路径。
获取凹槽抠图
/// <summary>
/// 获取图形轮廓,形状由不透明的区域形成
/// </summary>
/// <param name="image">要获取轮廓的图片</param>
/// <returns></returns>
public static SKPath GetImageShape(SKBitmap image)
{
int temp = 0;
var path = new SKPath();
for(int y=0;y< image.Height;y++)
{
for(int x=0;x< image.Width;x++)
{
var pixel = image.GetPixel(x, y);
if(pixel.Alpha!=0)
{
if(temp==0)
{
temp = x;
}
}
else
{
if(temp!=0)
{
path.AddRect(new SKRect(temp, y, x, y + 1));
temp= 0;
}
}
}
}
return path;
}
方法中,定义一个路径path
,接着逐列获取每一块的像素信息,使用到了SKBitmap
的GetPixel
方法,得到一个SKColor
。SKColor
有5个属性
- Alpha
- Blue
- Green
- Hue
- Red
其中,我们只需要判断判断Alpha
不为0就可以了,也就是他不是透明的。
获得凹槽抠图后,需要定义一个SKCanvas
,我叫他为画布。名字是sliderCanvas
,需要在sliderCanvas
上绘制背景抠图。
使用ClipPath
方法先在sliderCanvas
上叠加轮廓图notchShape
,也就是我们刚刚生成的凹槽抠图。ClipPath
方法有多个重载,这里用到的参数分别是:SKPath
、SKClipOperation
与antialias
。
第一个参数是刚刚获取到的轮廓图notchShape
,第二个参数选择SKClipOperation.Intersect
,取两个区域交集,第三个参数设置为true,表示抗锯齿。
叠加好轮廓图之后,先要计算下要在背景图上扣取的位置。首先获取背景图的中心位置,减去凹槽图的中心位置。得到的x,y值就是要绘制的起点坐标。
接着根据随机值,随机旋转画布,使用RotateDegrees
方法。RotateDegrees
方法第一个参数是旋转的度数,第二个和第三个参数确定旋转的中心点。
写来就是把背景抠图部分绘制到画布上,字把滑块图模板绘制画布上。
再定义一个滑块的画布sliderBarCanvas
。把制作好的抠图绘制到滑块上。
最后,把凹槽图绘制到背景图上。
都绘制好了之后,把信息返回给前端,就可以了。代码中返回base64字符串是我自己写了一个SKBitmap
的扩展,非常简单
public static string ToBase64String(this SKBitmap source, SKEncodedImageFormat format)
{
using var img = SKImage.FromBitmap(source);
using SKData data = img.Encode(format, 100);
var array = data.ToArray();
return "data:" + format.ToString().ToLower() + ";base64," + Convert.ToBase64String(array, 0, array.Length);
}
至此。旋转验证码的代码就全部完成了。
前端代码
与之前的滑动验证码一样,需要修改的地方非常少,reset
方法中,设置滑块位置为中心点位置,设置旋转为0
$(".captcha_slider").css('left', (captchaMainWidth - captchaMainHeight)/2);
$(".captcha_slider").css("transform", "rotate(0deg)")
handleDragMoving
方法中,设置滑块为选中,而不是从左向右移动
$('#sliderSrc').css('transform',"rotate("+(_x/(206/360))+"deg)");
其他的没有变化。前端代码在这篇文章中有详细介绍
.NET 6 实现滑动验证码(十)、大结局:前端代码实战,vue3与HTML+JQuery
总结
还有其他种类的验证码,比如点选验证码、图文验证码等。会慢慢完善、实现。
点击下方公众号卡片,关注我!一起学习,一起进步!