一、需求
为.Net Core Web Api 项目的前端登录页面绘制一张验证码图片,验证码图片包含4位由字母或数字组成的验证码,且需要具有纹底和干扰线增强验证码的识别难度。
二、绘图所需的类
绘制图片需要使用 GDI+ ,GDI+(Graphics Device Interface Plus)是C#编程语言中的一个图形绘制类库,该类库库提供了一系列与图形绘制相关的类。
现实中为了得到一幅画,我们需要准备画纸、绘画工具、颜料等,最后由画家使用这些材料在画纸上描绘出图形,文字等元素。
GDI+ 中的类是对现实绘画的抽象,可以分为以下几种:
1、画纸类
BitMap(int width, int height)
BitMap是Image类的子类,图像由许多像素点组成的,BitMap对象便是以像素点来表示图像。
创建一个长度为300px,宽度为100px的图像对象(默认像素点为黑色)
BitMap image = new BitMap(300,100);
//创建了一张长300px,宽100px的纯黑色基础图像。
2、画面元素类
(1)点 Point (int x, int y)
创建一个点对象,该点的坐标为(x, y)
Point point = new Point(0,0);
补充:坐标原点 (0,0) 在屏幕左上角。
(2)矩形 Rectangle (int x, int y, int width, int height)
创建一个矩形对象,该矩形左上顶点与坐标原点 (0, 0)重合,宽度为300px,高度为100px。
Rectangle rectangle = new Rectangle(0, 0, 300, 100);
(3)字体 Font(string familyName, float emSize, System.Drawing.FontStyle style)
创建字体对象并设置字体家族、字号大小、风格等属性
Font font = new Font("Georgia", 30, (FontStyle.Bold | FontStyle.Italic));
//字体属性:微软雅黑,字号30,加粗,斜体
3、绘画工具类
(1)纹理刷 TextureBrush (Image image)
创建一个带有纹理的画刷对象,纹理从指定的图片中提取。
Image TextTureImage = Image.FromFile("./StaticDataSource/TextTure.jpg");
TextureBrush textureBrush = new TextureBrush(TextTureImage);
TextTure.jpg如下:
(2)线性变化刷 LinearGradientBrush(Point point1, Point point2,Color color1, Color color2)
创建一个颜色渐变的画刷对象,用此画刷书写可以写出颜色渐变的文字。
LinearGradientBrush brush = new LinearGradientBrush(new Point(0, 0), new Point(300, 100), Color.Blue, Color.DarkRed);
补充:渐变方向为左上角(point1)到右下角(point2),渐变颜色由蓝色(Blue)到暗红(DarkRed)。
4、画家类Graphics
Graphics的方法如下:
(1)static Graphics FromImage(Image image)
该方法返回graphics对象,该graphics对象与参数image对象绑定,调用graphics对象的方法可以往image对象输入画面元素。
BitMap image = new BitMap(300,100);
Graphics graphics = Graphics.FromImage(image);//绑定基础图像image
备注:可以理解为画家与画纸绑定后,画家才能使用绘画工具在画纸上作画。
(2)void FillRectangle (TextureBrush textureBrush, Rectangle rectangle)
画家使用纹理刷(textureBrush),在画纸上刷出一个矩形(rectangle)。
(3)DrawString(string s,Font font, Brush brush, float x, float y)
画家使用画刷(brush),在纸上的指定坐标点(x, y),写下字符串s,且字体样式为font。
三、代码实现
1、后端接口
[HttpGet]
public ActionResult GetCaptchaImage() {
//创建基础图像,宽300px,高100px
Bitmap image = new Bitmap(300, 100);
//将基础图像和画家对象绑定
Graphics graphics = Graphics.FromImage(image);
//创建纹理刷
Image TextTureImage = Image.FromFile("./StaticDataSource/TextTure.jpg");
TextureBrush textureBrush = new TextureBrush(TextTureImage);
//创建一个矩形,矩形的左上顶点坐标为(0,0),右下顶点坐标为(300,100)
Rectangle rectangle = new Rectangle(0, 0, 300, 100);
//画家使用纹理刷,在基础图像上刷出一个矩形
graphics.FillRectangle(textureBrush, rectangle);
//创建渐变画刷
LinearGradientBrush brush = new LinearGradientBrush(new Point(0, 0),new Point(300,100), Color.Blue, Color.DarkRed);
//自定义的GenerateCode方法生成验证码数据,此处为["J","j","F","5"]
ArrayList codeList = GenerateCode();
int x = 40;
foreach (string code in codeList)
{
//画家使用渐变画刷书写字符code,字符字体为font,字符坐标为(x,30)
graphics.DrawString(code, font, brush, x, 30);
//字符的横坐标需要增加,否则字符会叠在一起
x += 60;
}
//在验证码上画2根不同颜色的干扰线,需要4个点,2支笔
Point p1 = new Point(0, 70);
Point p2 = new Point(300, 60);
Pen pen1 = new Pen(Color.Green, 2);//设置笔的颜色和宽度
Point p3 = new Point(0, 50);
Point p4 = new Point(300, 60);
Pen pen2 = new Pen(Color.Gray, 2);
//画家使用pen1,画第1条干扰线
graphics.DrawLine(pen1, p1, p2);
//画家使用pen2,画第2条干扰线
graphics.DrawLine(pen2, p3, p4);
MemoryStream stream = new MemoryStream();
//完成绘制,将图像image以.png格式保存到内存流stream中
image.Save(stream, ImageFormat.Png);
//销毁graphics对象和笔对象
graphics.Dispose();
pen1.Dispose();
pen2.Dispose();
//往redis中存验证码字符串并设置过期时间,用于登录时校验验证码,此处省略。
//将stream转为字节流数组返给前端
return Ok(stream.ToArray());
}
private ArrayList GenerateCode()
{
ArrayList code = new ArrayList();
string[] chars =
{
"A","B","C","D","E","F","G","H","I","J","K","L",
"M","N","P","Q","R","S","T","U","V","W","X","Y","Z",
"a","b","c","d","e","f","g","h","i","j","k","l",
"m","n","p","q","r","s","t","u","v","w","x","y","z",
"1","2","3","4","5","6","7","8","9"
};
Random random = new Random();
for (int i = 0; i <4; i++) {
int j = random.Next(0,chars.Length);
code.Add(chars[j]);
}
return code;
}
2、前端Vue3
<img :src="codeUrl" @click="getCode"/>
<script lang="ts" setup>
const getCode = async () => {
await axios.get('https://localhost:7106/Captcha/GetCaptchaImage')
.then(res => { codeUrl.value = "data:image/gif;base64," + res.data })
}
</script>
注意:前端需要添加 "data:image/gif;base64," 前缀,将后端返回的字节数组转为base64字符串,才能被<image>标签识别为图片。
四、补充
绘制图片需要用到多种GDI+中的类型,初学时比较难以理解。本文为求通俗易懂使用了较多的个人理解,表述上没有官方文档严谨,建议读者以微软官方文档为准。
微软官方文档地址: