6. 生成二维码

一、前言

本文主要介绍如何使用C++来实现二维码的生成,使用到了开源库:qrencode

代码生成结果如下图,完全是可以扫描的:

image-20230924095901582

二、qrencode库的基本使用

如果你目前在使用vcpkg,那就非常简单了,直接运行以下命令进行安装即可:

.\vcpkg.exe install libqrencode:x64-windows

vcpkg的使用教程可以参考另一篇文章:C++开源库配置

如果没有使用vcpkg,那么就需要先下载我编译好的库:qrencode

解压后有一个头文件与一个静态库文件(为 x64 Release 版本):

在这里插入图片描述

使用方法同样参考文章C++开源库配置的手动配置部分。

如果想要尝试自己编译这个库,可以参考这篇文章:windows 编译qrencode库

然后在代码中使用它也非常简单,基本只需要一个函数:

#include"qrencode.h"
#pragma comment(lib,"qrencode.lib")
int main(){
	QRcode* pQRC = QRcode_encodeString("https://www.kucoding.com", 0, QR_ECLEVEL_H, QR_MODE_8, 1);
}

如果使用的vcpkg下载安装,那么直接包含头文件就可以使用了:

#include<qrencode.h>
int main(){
	QRcode* pQRC = QRcode_encodeString("https://www.kucoding.com", 0, QR_ECLEVEL_H, QR_MODE_8, 1);
}

即,我们大部分情况只用得到这个库中的QRcode_encodeString函数,该函数的作用是将字符串编码为符号串并返回,返回的QRcode对象中就包含了二维码的数据

它有5个参数,但事实上除了第一个参数为我们要进行编码的字符串外,其它参数基本就是固定的:

  1. 要进行编码的字符串
  2. 版本,填0即可自动选择
  3. 纠错级别,基本就填这个值,即高级别即可
  4. 编码模式,现在一般都是采用的utf-8进行编码,所以这个值也是固定的
  5. 是否区分大小写,一般也是要区分的,所以基本固定填1

更多详细介绍可以参考官网说明:QRcode_encodeString

编码成功后,它会返回一个QRcode对象,定义如下:

typedef struct {
	int version;         ///< 版本
	int width;           ///< 宽度
	unsigned char *data; ///< 生成的数据
} QRcode;

由于二维码是长宽相等的正方形,所以data的长度为 width*width

后面我们也主要是通过这两个数据来生成我们的二维码

由于他返回的是一个指针,所以使用完后我们还需要调用一个函数来释放内存:

QRcode_free(pQRC);

这里注意一下上面这个data返回的数据格式,其含义是按照来划分的,一个字节(char)有8位

每一位的含义如下(来源于官网):

MSB 76543210 LSB
    |||||||`- 1=black/0=white
    ||||||`-- 1=ecc/0=data code area
    |||||`--- format information
    ||||`---- version information
    |||`----- timing pattern
    ||`------ alignment pattern
    |`------- finder pattern and separator
    `-------- non-data modules (format, timing, etc.)

但事实上,除了第0位,其它位一般我们都用不上,只要第0位为1,就说明该位置应该为黑色,为0则为白色

一般二维码都为黑白色,但后面自己生成二维码图片时,其实是可以更改生成的二维码颜色的

三、BMP图片生成原理

在众多图片格式中,BMP是其中比较简单的一种,所以我们这里采用BMP文件来保存二维码信息

BMP文件的具体格式可以参考我的另一篇文章:BMP文件格式分析

后面的内容是基于上面这篇文章写的,所以如果不懂BMP格式,请务必先看一下上面这篇文章

这里主要介绍如何用代码来生成一张BMP图片

比如我想生成一个简单的黑白相间的图片,代码应该怎么写呢?

为了简单起见,这里采用的是24位色,但由于二维码一般都是黑白的,所以其实采用1位色就行了,即:只要能够表示黑白两种颜色就行了

首先,我们需要包含头文件:

#include<Windows.h>

因为BMP几个结构都在其中

然后,我这里就想要画一个20*20像素的图像,且黑白相间

所以第一个结构体可以这样写:

//每行20像素,每个像素用24位,即3个字节表示,所以每行为20*3个字节,然后有20行
int dataSize = 20 * 3 * 20;
BITMAPFILEHEADER kFileHeader{}; //紧跟{}代表初始化同时清零
kFileHeader.bfType = 0x4d42;  // "BM"
kFileHeader.bfSize = sizeof(BITMAPFILEHEADER) + sizeof(BITMAPINFOHEADER) + dataSize; //文件大小(字节数)
kFileHeader.bfOffBits = sizeof(BITMAPFILEHEADER) + sizeof(BITMAPINFOHEADER);

注意:

每行字节数量必须为4的倍数,不够用0补齐,这是BMP格式的硬性规定,但由于我们这里每行为20像素,即60个字节,可以被4整除,所以没有进行补齐

然后是第二结构体:

	BITMAPINFOHEADER kInfoHeader{}; //紧跟{}代表初始化同时清零
	kInfoHeader.biSize = sizeof(BITMAPINFOHEADER);
	kInfoHeader.biWidth = 20; //图片宽度
	kInfoHeader.biHeight = -20; //高度,为负数时,以左上角为图片原点,为负数时,以左下角为图片原点
	kInfoHeader.biPlanes = 1; //平面数,一般都为1
	kInfoHeader.biBitCount = 24; //位数,rgb各8字节,24位

其它基本是固定的,这里要注意宽高是像素宽高,不是字节,同时高度一定要写成负数,如果为正数,你就需要从图片的左下角开始往文件内部写入数据,会有点反直觉

然后就可以申请缓存区开始写数据了:

	char *buf=new char[dataSize]; //申请存放图片数据的缓存区
	for (int y = 0; y < 20; y++) { //第y行
		for (int x = 0; x < 20; x++) { //第x列
			if (y % 2 == 0) { //偶数行,为黑色
				(buf + y * 20 * 3 + x * 3)[0] = 0; //r
				(buf + y * 20 * 3 + x * 3)[1] = 0; //g
				(buf + y * 20 * 3 + x * 3)[2] = 0; //b
			}
			else { //奇数行,为白色
				(buf + y * 20 * 3 + x * 3)[0] = 0xFF; //r
				(buf + y * 20 * 3 + x * 3)[1] = 0xFF; //g
				(buf + y * 20 * 3 + x * 3)[2] = 0xFF; //b
			}

		}
	}

因为图片是二位数据嘛,所以用两个for循环进行遍历赋值

注意这里遍历的是每行、每列的像素宽高,即20,而不是字节,每行的字节数应该为20*3

同时注意内部的赋值方式:

(buf + y * 20 * 3 + x * 3)[0] = 0;

因为buf是一个char类型的数组,buf本身记录的是该数组的第一个元素的地址

因此buf+n就代表第n个元素的地址,这里加的是 y*20*3 (就代表第几行)和 x * 3(第几列)

因为二维数组本质上也是一维数组,只不过编译器帮我们换算了,而这里我们采用的是自己来换算的方式

因为每一列为3个字节宽度,所以x需要乘以3,每一行共有20*3的字节宽度,所以第几行就是首地址加上几个 20*3即可

指针加上一个数字后依旧是指针,只是指针所指向的地址变了,其结果为每个像素(三个字节)的首地址,因此我们就可以用[]来访问这三个字节

为偶数,我们就给这三个字节都赋值为0,即rgb三原色为0,就为黑色,否则rgb都赋值为0XFF,就为白色

最后,我们还需要将上面的所有数据写入文件中:

ofstream f("tmp.bmp");
f.write((const char*)&kFileHeader, sizeof(kFileHeader));
f.write((const char*)&kInfoHeader, sizeof(kInfoHeader));
f.write((const char*)buf, dataSize);

然后就可以在目录中看到生成的图片:

在这里插入图片描述 完整代码:

作者:余识
全部文章:0
会员文章:0
总阅读量:0
c/c++pythonrustJavaScriptwindowslinux