.NET 6结合SkiaSharp实现拼接验证码功能

news2025/1/21 18:49:17

从最初的滑动验证码,到实现旋转验证码!不光实践了SkiaSharp的使用,也学到了很多东西。在网上看到一个拼接验证码功能,手痒了起来,结合前面实现的两种验证码,我们来学习一下如何实现拼接验证码功能!

目录

  • 效果预览
  • 实现原理
  • 代码部分
    • 获取背景图
    • 计算间隔
    • 定义随机值
    • 将背景图切成上下两部分
    • 将上半部分图片切割成左右两部分
    • 拼接图片
  • 总结

效果预览

在这里插入图片描述

实现原理

其实拼接验证码实现起来比滑动验证码与选择验证码要简单的多,因为拼接验证码不涉及凹槽与滑块模板。只需要一张背景图片就可以了!
获取图片:首先获取背景图片。将背景图片转换成SKBitmap
获取一个随机值:根据图片的宽和高,设定一个固定的X坐标间距spacingX与一个Y坐标间距spacingY,再定义一个X坐标的随机值randomX与Y坐标随机值randomY
横着切图:根据得到的随机值randomY,横着把背景图切成2块,使其成为上下两部分。
在这里插入图片描述
竖着切图:根据得到的随机值randomX,将上半部分竖着切成两块!
在这里插入图片描述
这样,我们就得到了4张图片。
将上半部分左右两张图。位置调换,组成一张新图
在这里插入图片描述
将组合好的新图,与下半部分组合成一张图。
在这里插入图片描述
这样,我们就完成了拼接验证码的图片。将他传到前端。
前端根据服务端传递的数据,将滑块定位到上半部分。然后控制上半部分图片的移动来达到拼接验证码的效果!

代码部分

获取背景图片部分,前面讲过了。
可参考:NET 6 实现滑动验证码(六)、验证码背景图、滑块图与凹槽图的生成

获取背景图

//拼接验证码没有模板,只需要背景图
//获取背景图
var background = await _resourceManager.RandomBackground();

将背景图转为SKBitmap

 using var backgroundImage = SKBitmap.Decode(background);

计算间隔

//X坐标间距
int spacingX = backgroundImage.Width / 8;
//Y坐标间距
int spacingY = backgroundImage.Height / 4;

定义间隔主要是为了生成X轴和Y轴的随机值。

定义随机值

int randomX = _random.Next(spacingX, backgroundImage.Width - backgroundImage.Width /5);
int randomY = _random.Next(spacingY, backgroundImage.Height - spacingY);

定义X轴随机值randomX 与Y轴随机值randomY
**randomY **:randomY主要作用是随机将背景图片切成上下两部分使用的。
**randomX **主要作用是随机将背景图切成左右两部分使用的。

将背景图切成上下两部分

首先定义一个方法,这个方法的作用是根据传入的内容,将指定图片切成两部分。

public static List<byte[]> SplitImage(int pos,bool direction,SKBitmap img)
        {
            int StartImageWidth,
                StartImageHeight,
                EndImageWidth,
                EndImageHeight;
            int EndScanX, EndScanY;
            int ImageWidth = img.Width;
            int ImageHeight = img.Height;

            StartImageWidth = direction ? ImageWidth : pos;
            StartImageHeight = direction ? ImageHeight - pos : ImageHeight;
            EndImageWidth = direction ? ImageWidth : ImageWidth - StartImageWidth;
            EndImageHeight = direction ? pos : ImageHeight;
            EndScanX = direction ? 0 : pos;
            EndScanY = direction ? StartImageHeight : 0;


            using var StartImage = new SKBitmap(StartImageWidth, StartImageHeight);
            using var StartCanvas = new SKCanvas(StartImage);
            SKRect StartSourceRect = new SKRect(0, 0, StartImageWidth, StartImageHeight);
            SKRect StartDestRect = new SKRect(0, 0, StartImageWidth, StartImageHeight);
            StartCanvas.DrawBitmap(img, StartSourceRect, StartDestRect);

            using var EndImage = new SKBitmap(EndImageWidth, EndImageHeight);
            using var EndCanvas = new SKCanvas(EndImage);
            SKRect EndSourceRect = new SKRect(EndScanX, EndScanY, EndScanX + EndImageWidth, EndScanY + EndImageHeight);
            SKRect EndDestRect = new SKRect(0, 0, EndImageWidth, EndImageHeight);
            EndCanvas.DrawBitmap(img, EndSourceRect, EndDestRect);
            List<byte[]> result = new List<byte[]>();
            using var StartImg = SKImage.FromBitmap(StartImage);
            using var StartData = StartImg.Encode(SKEncodedImageFormat.Png, 100);
            using var EndImg = SKImage.FromBitmap(EndImage);
            using var EndData = EndImg.Encode(SKEncodedImageFormat.Png, 100);
            result.Add(StartData.ToArray());
            result.Add(EndData.ToArray());
            return result;

        }

SplitImage方法有3个参数:
pos:分割点,指在图上从哪开始切。
direction:切割方向。true为水平方向,false为垂直方向!
img:待切割的图片。

int StartImageWidth,
    StartImageHeight,
    EndImageWidth,
    EndImageHeight;
int EndScanX, EndScanY;
int ImageWidth = img.Width;
int ImageHeight = img.Height;

因为要将图片切成2张图片,所以StartImageWidthStartImageHeight表示第一张图片的宽和高。EndImageWidthEndImageHeight表示第二张图片的宽和高。
EndScanX表示X坐标的切割点,EndScanY表示Y坐标的切割点。
接下来分别定义两个SKCanvasStartCanvasEndCanvas。表示第一张图的画布与第二张图的画布。定义好之后,再定义两个SKRectSKRect表示矩形,他有4个参数。分别是lefttoprightbottom。刚看到这4个参数有点蒙,这是啥意思?随即反应过来了。其中lerttop确定左上角的坐标。rightbottom确定右下角的坐标。再使用DrawBitmap方法,得到两张图片。
需要注意的是,返回的是一个List<byte[]>。里面保存了两张图片的byte[]数据。为什么不直接返回List呢?因为在代码中使用using var。如果返回List的话。出了这个方法,两张图的SKBitmap就被释放了。那么再使用这两张图的SKBitmap,就会异常了。如果是使用var,并不好找一个合适的时机将他们释放掉。所以就返回了一个List<byte[]>。这样都不耽误。

有了这个方法,我们就可以把图片按照要求,切割成2张需要的图片了
将图片切割成上下两部分:

 List<byte[]> bgImageList = CaptchaImageUtils.SplitImage(randomY, true, backgroundImage);
using var bgImage0 = SKBitmap.Decode(bgImageList[0]);
using var bgImage1 = SKBitmap.Decode(bgImageList[1]);

将上半部分图片切割成左右两部分

List<byte[]> bgImageTopList = CaptchaImageUtils.SplitImage(randomX, false, bgImage0);
using var bgImageTop0 = SKBitmap.Decode(bgImageTopList[0]);
using var bgImageTop1 = SKBitmap.Decode(bgImageTopList[1]);

使用SplitImage方法,将刚刚切好的上下2部分的图中的上半部分,切成左右2部分。这时,我们就有4张图片了!

拼接图片

我们计划是,将bgImageTop0 bgImageTop1 拼接一下,但是拼接的顺序是bgImageTop1+bgImageTop0。这样就得到了一张这样的图,我们叫他SliderImage
在这里插入图片描述
然后再将SliderImagebgImage1拼接,就完成了。
先来看一下拼接的方法,给这个拼接方法起个名字,叫ConcatImage吧。具体代码为:

public static byte[] ConcatImage(bool direction,int width,int height, params SKBitmap[] imgArr)
{
    int pos = 0;
    using var NewImage = new SKBitmap(width, height);
    using var NewImageCanvas = new SKCanvas(NewImage);
    foreach(var img in imgArr)
    {
        float x = direction ? pos : 0;
        float y = direction ? 0 : pos;
        NewImageCanvas.DrawBitmap(img, x,y);
        pos += direction ? img.Width : img.Height;
                
    }
    using var NewImg = SKImage.FromBitmap(NewImage);
    using var NewData = NewImg.Encode(SKEncodedImageFormat.Png, 100);
    return NewData.ToArray();
}

ConcatImage方法有4个参数,
directionbool类型,表示拼接方向,true为水平方向拼接,false为垂直方向拼接。
widthint类型,拼接图片的宽度
heightint类型,拼接图片的高度
imgArrparams 类型,要拼接图片的数组

分别定义SKBitmapSKCanvas。然后循环数组。将图片拼接起来,最后返回一个byte[]

拼接完成后,将图片返回给前端。

return new ConcatImageCaptchaInfo
{
    BackgroundImage = bgImage.ToBase64String(SKEncodedImageFormat.Png),
    SliderImage = null,
    BackgroundImageWidth = bgImage.Width,
    BackgroundImageHeight = bgImage.Height,
    SliderImageWidth = 0,
    SliderImageHeight = 0,
    RandomX = randomX,
    RandomY = randomY,
    Tolerant = 0.05F,
    CaptchaType = CaptchaTypeConstant.CONCAT
};

ToBase64String为自己写的扩展方法。可以参考.Net 6实现旋转验证码,这篇文章写了这个方法了。
前端代码跟之前的滑动验证码基本一样。稍微改下就可以了!

总结

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

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

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/151745.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

流量路由技术解析

作者&#xff1a;十眠 流量路由&#xff0c;顾名思义就是将具有某些属性特征的流量&#xff0c;路由到指定的目标。流量路由是流量治理中重要的一环&#xff0c;本节内容将会介绍流量路由常见的场景、流量路由技术的原理以及实现。 流量路由的业务场景 我们可以基于流量路由…

aws sam 本地测试部署 lambda 和 apigateway

使用sam框架可以在部署serverless应用之前&#xff0c;在本地调试application是否符合预期 sam框架安装 serverless应用是lambda函数&#xff0c;事件源和其他资源的组合 使用sam能够基于docker容器在本地测试lambda函数 安装sam wget https://github.com/aws/aws-sam-cli…

ArcGIS基础实验操作100例--实验77按要素分区统计路网

本实验专栏参考自汤国安教授《地理信息系统基础实验操作100例》一书 实验平台&#xff1a;ArcGIS 10.6 实验数据&#xff1a;请访问实验1&#xff08;传送门&#xff09; 高级编辑篇--实验77 按要素分区统计路网 目录 一、实验背景 二、实验数据 三、实验步骤 &#xff08;…

ART-SLAM: Accurate Real-Time 6DoF LiDAR SLAM

IEEE Robotics and Automation Letters 意大利米兰理工学院 Abstract 地面车辆实时六自由度姿态估计&#xff0c;由于自动驾驶和三维建图等诸多应用&#xff0c;是机器人技术中一个相关和被研究广泛的课题。虽然有些系统已经存在&#xff0c;但它们要么不够准确&#xff0c;要…

Qt之标准对话框(QMessageBox、QFileDialog)

文章目录前言如何学习标准对话框QMessageBox消息对话框应用属性实操QFileDialog文件对话框应用属性实操前言 Qt为开发者提供了一些可复用的对话框&#xff0c;他对我们的开发是很重要的。下面我们就来学习 提示&#xff1a;以下是本篇文章正文内容&#xff0c;下面案例可供参考…

无监控,不运维!深入浅出介绍ChengYing监控设计和使用

监控系统俗称「第三只眼」&#xff0c;几乎是我们每天都会打交道的系统&#xff0c;它也一直是IT系统中的核心组成部分&#xff0c;负责问题的发现以及辅助性的定位。 ChengYing作为一站式全自动化全生命周期大数据平台运维管家&#xff0c;自然也提供大数据产品的监控服务。这…

力扣sql基础篇(二)

力扣sql基础篇(二) 1 每月交易I 1.1 题目内容 1.1.1 基本题目信息 1.1.2 示例输入输出 1.2 示例sql语句 # sum函数如果需要筛选,可以考虑在里面嵌套if函数 SELECT DATE_FORMAT(trans_date,"%Y-%m") month,country,count(*) trans_count,COUNT(IF(state"appr…

matlab利用逻辑数组将保密率负数部分转换为零

通信中计算保密率的公式为 r[Rd−Re]r[R_d-R_e]^ r[Rd​−Re​] 其中RdR_dRd​代表合法目的地的数据速率&#xff0c;ReR_eRe​代表窃听节点的数据速率 当窃听节点的速率大于目的节点的速率时候&#xff0c;计算出来的保密率是负值&#xff0c;这在设计的时候可以将这时候的保…

referer、prototype、array、json笔记整理

目录referer、prototype、array、json笔记整理refererReferrer-policy如何设置referer绕过图片防盗链1、利用https网站盗链http资源网站&#xff0c;refer不会发送2、设置meta3、设置referrerpolicy"no-referrer"4、利用iframe伪造请求referer5、客户端在请求时修改h…

【LeetCode每日一题】——233.数字 1 的个数

文章目录一【题目类别】二【题目难度】三【题目编号】四【题目描述】五【题目示例】六【解题思路】七【题目提示】八【时间频度】九【代码实现】十【提交结果】一【题目类别】 数学 二【题目难度】 困难 三【题目编号】 233.数字 1 的个数 四【题目描述】 给定一个整数 …

为你的Typecho使用Redis缓存,优化访问速度-织音博客

前言Typecho虽然轻量&#xff0c;但终究仍是PHP动态脚本&#xff0c;访问时需要频繁调取数据库的信息&#xff0c;导致并发值一高&#xff0c;CPU就100%占用&#xff0c;无法处理新的请求信息。这时&#xff0c;我们可以用Redis来设置缓存&#xff0c;从而不用频繁调动数据库&a…

【Meetup预告】SeaTunnel + OpenMLDB:共筑数据集成生态,加速实时场景落地

2023年1月12日&#xff08;周四&#xff09;20&#xff1a;00-21:30&#xff0c;云原生数据集成平台 SeaTunnel 联合开源机器学习数据库 OpenMLDB 合作带来本期 Meetup。 活动背景 Al 应用的繁荣发展&#xff0c;既得益于数据的爆发式增长&#xff0c;也受困于数据治理的种种…

使用Bokeh进行数据可视化的8个建议(上)

使用 Bokeh 库创建数据可视化的快速提示和示例。 长按关注《Python学研大本营》&#xff0c;加入读者群&#xff0c;分享更多精彩 扫码关注《Python学研大本营》&#xff0c;加入读者群&#xff0c;分享更多精彩 Python 是创建数据可视化的绝佳开源工具。有许多可用的数据可视…

JavaWeb:会话技术之Cookie

1&#xff0c;会话跟踪技术的概述 对于会话跟踪这四个词&#xff0c;我们需要拆开来进行解释&#xff0c;首先要理解什么是会话&#xff0c;然后再去理解什么是会话跟踪&#xff1a; 会话&#xff1a;用户打开浏览器&#xff0c;访问web服务器的资源&#xff0c;会话建立&…

设计模式-到底什么是builder模式

我们来看一个一些常见的开源代码中带builder字样的经典类&#xff1a;&#xff08;jdk&#xff09;Stringbuilder&#xff08;spring&#xff09;springApplicationBuilder&#xff08;es&#xff09;xxxQuerybulider如果只去看这些类的话&#xff0c;应该是一团乱码&#xff0…

【Kotlin】字符串操作 ① ( 截取字符串函数 substring | 拆分字符串函数 split | 解构语法特性 )

文章目录一、截取字符串函数 substring二、拆分字符串函数 split一、截取字符串函数 substring Kotlin 中提供了 截取字符串函数 substring , 可接收 IntRange 类型的参数 , 这是 整数范围 类型 ; 截取字符串函数 substring 函数原型为 : /*** 返回由给定的[range]索引指定的…

【pat】网红点打卡攻略【图】

一个旅游景点&#xff0c;如果被带火了的话&#xff0c;就被称为“网红点”。大家来网红点游玩&#xff0c;俗称“打卡”。在各个网红点打卡的快&#xff08;省&#xff09;乐&#xff08;钱&#xff09;方法称为“攻略”。你的任务就是从一大堆攻略中&#xff0c;找出那个能在…

【MyBatis】第一篇:初体验

还是老规矩看一下百度百科中的解释&#xff1a; MyBatis 是一款优秀的持久层框架&#xff0c;它支持定制化 SQL、存储过程以及高级映射。MyBatis 避免了几乎所有的 JDBC 代码和手动设置参数以及获取结果集。MyBatis 可以使用简单的 XML 或注解来配置和映射原生信息&#xff0c…

Web(三)

JavaScript基础概念&#xff1a;一门客户端脚本语言* 运行在客户端浏览器中的。每一个浏览器都有JavaScript的解析引擎* 脚本语言&#xff1a;不需要编译&#xff0c;直接就可以被浏览器解析执行了功能&#xff1a;* 可以来增强用户和html页面的交互过程&#xff0c;可以来控制…

蓝桥杯C51

#include "reg52.h"sfr AUXR 0x8e; //定义辅助寄存器sbit S5 P3^2; //定义按键S5引脚 sbit S4 P3^3; //定义按键S4引脚unsigned char count 0; //定义中断计数器 unsigned char t_h 0; //定义运行时间的变量 unsigned char t_m 0; …