.Net 6实现旋转验证码

前几篇文章,介绍了.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方法,得到一个SKColorSKColor有5个属性

  • Alpha
  • Blue
  • Green
  • Hue
  • Red
    其中,我们只需要判断判断Alpha不为0就可以了,也就是他不是透明的。

获得凹槽抠图后,需要定义一个SKCanvas,我叫他为画布。名字是sliderCanvas,需要在sliderCanvas上绘制背景抠图。
使用ClipPath方法先在sliderCanvas上叠加轮廓图notchShape,也就是我们刚刚生成的凹槽抠图。ClipPath方法有多个重载,这里用到的参数分别是:SKPathSKClipOperationantialias
第一个参数是刚刚获取到的轮廓图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

总结

还有其他种类的验证码,比如点选验证码、图文验证码等。会慢慢完善、实现。

点击下方公众号卡片,关注我!一起学习,一起进步!

猜你喜欢

转载自blog.csdn.net/sd2208464/article/details/128507887