二维码生成器:从零到一,手写一个实用工具

二维码这东西,现在满大街都是。扫码支付、加好友、看菜单……但你真的了解它背后的原理吗?

我个人觉得,写一个二维码生成器,是理解编码原理、图像处理和C++实战能力的绝佳项目。今天我们就来手撸一个。

项目概览:我们要做什么?

说白了,就是把一段文本(比如URL、名片信息),转换成一张黑白相间的二维码图片。输出可以是BMP、PNG等格式。

核心流程分三步:

  1. 数据编码:把输入文本转成二进制位流
  2. 纠错码生成:加入里德-所罗门码,让二维码即使被遮挡一部分也能扫出来
  3. 矩阵绘制:把位流填充到网格中,加上定位图案、掩码等

核心知识点:QR码规范(ISO/IEC 18004)、里德-所罗门纠错算法、位图操作

SVG流程图:二维码生成核心逻辑

二维码生成器核心流程 输入文本 数据编码 纠错码生成 矩阵填充 掩码处理 输出图片 校验与优化 核心:数据编码 → 纠错码 → 矩阵布局 → 掩码 → 输出

第一步:数据编码

QR码支持四种编码模式:数字、字母数字、字节、中文。我们最常用的是字节模式,可以处理UTF-8文本。

编码过程其实不复杂:

  • 先加一个模式指示符(4位)
  • 再加字符计数(8位或更多)
  • 最后是数据本身的二进制流

小技巧:我习惯在编码前先判断输入内容。如果是纯数字,用数字模式能省不少空间。比如"123456"用数字模式只需要10位,字节模式要48位。

// 字节模式编码示例
std::vector<bool> encodeByteMode(const std::string& input) {
    std::vector<bool> bits;
    
    // 模式指示符:0100(4位)
    bits.push_back(0); bits.push_back(1); 
    bits.push_back(0); bits.push_back(0);
    
    // 字符计数(8位)
    int len = input.length();
    for (int i = 7; i >= 0; --i) {
        bits.push_back((len >> i) & 1);
    }
    
    // 数据位
    for (char c : input) {
        for (int i = 7; i >= 0; --i) {
            bits.push_back((c >> i) & 1);
        }
    }
    
    return bits;
}

第二步:纠错码生成

这里要用到里德-所罗门码。听起来高大上,其实原理就是多项式除法。

QR码有四个纠错等级:L(7%)、M(15%)、Q(25%)、H(30%)。等级越高,二维码越耐脏,但数据容量越小。

纠错等级 恢复能力 适用场景
L 7% 干净环境,如屏幕显示
M 15% 一般印刷品
Q 25% 户外海报
H 30% 极端环境,如金属表面

注意:纠错码生成涉及伽罗瓦域运算。我曾经在实现时把多项式系数搞反了,结果生成的二维码怎么都扫不出来。调试了一整天才发现是字节序问题。

// 里德-所罗门纠错码生成(简化版)
std::vector<int> generateRS(const std::vector<int>& data, int eccCount) {
    // 使用生成多项式进行多项式除法
    std::vector<int> remainder(eccCount, 0);
    
    for (int d : data) {
        int factor = d ^ remainder[0];
        for (int i = 0; i < eccCount - 1; ++i) {
            remainder[i] = remainder[i + 1] ^ gfMul(factor, genPoly[i]);
        }
        remainder[eccCount - 1] = gfMul(factor, genPoly[eccCount - 1]);
    }
    
    return remainder;
}

第三步:矩阵布局与掩码

数据位和纠错码准备好后,就要往矩阵里填了。这个过程有点像拼图:

  • 先放定位图案(三个大回形)
  • 再放校正图案(小回形)
  • 最后按蛇形路径填充数据

嗯,这里要注意填充顺序。数据是从右下角开始,按列向上、再向左、再向下……这样来回蛇形走的。我第一次写的时候没注意方向,结果图案全乱了。

掩码的作用是让二维码看起来更「随机」,避免出现大块空白或密集区域。QR规范定义了8种掩码模式,我们需要选一个让惩罚分数最低的。

// 应用掩码
void applyMask(std::vector<std::vector<bool>>& matrix, int maskPattern) {
    int size = matrix.size();
    for (int row = 0; row < size; ++row) {
        for (int col = 0; col < size; ++col) {
            // 跳过功能区域(定位图案等)
            if (isFunctionPattern(row, col)) continue;
            
            bool maskBit = false;
            switch (maskPattern) {
                case 0: maskBit = (row + col) % 2 == 0; break;
                case 1: maskBit = row % 2 == 0; break;
                // ... 其他模式
            }
            if (maskBit) {
                matrix[row][col] = !matrix[row][col];
            }
        }
    }
}

输出图片:生成BMP文件

最后一步是把矩阵输出成图片。BMP格式最简单,不需要依赖任何第三方库。

每个模块(黑白方块)我们放大到几个像素,比如4x4。然后按位图格式写入文件头、信息头、像素数据。

避坑指南:BMP的像素数据是从下往上存储的。我曾经忘了这个,生成的图片上下颠倒。后来加了个行反转就解决了。

// 生成BMP文件
void saveBMP(const std::string& filename, 
             const std::vector<std::vector<bool>>& matrix, 
             int moduleSize) {
    int size = matrix.size() * moduleSize;
    // 写入BMP文件头(54字节)+ 像素数据
    // 每个模块绘制为黑色或白色方块
    for (int y = 0; y < size; ++y) {
        for (int x = 0; x < size; ++x) {
            int row = y / moduleSize;
            int col = x / moduleSize;
            bool black = matrix[row][col];
            // 设置像素颜色
            setPixel(x, y, black ? 0 : 255);
        }
    }
}

完整示例:生成一个二维码

把上面所有步骤串起来,就能生成一个完整的二维码了。下面是一个使用示例:

int main() {
    QRCodeGenerator qr;
    qr.setData("https://example.com");
    qr.setErrorCorrectionLevel(ErrorCorrection::M);
    qr.setModuleSize(4);
    
    // 生成矩阵
    qr.encode();
    
    // 保存为图片
    qr.saveToBMP("qrcode.bmp");
    
    std::cout << "二维码已生成:qrcode.bmp" << std::endl;
    return 0;
}

运行后,你会得到一个标准的二维码图片。拿手机扫一下,就能跳转到指定网址。

总结与经验

写这个项目,我最大的体会是:规范很重要。QR码的ISO标准有100多页,每个细节都有严格定义。稍微偏离一点,生成的二维码就可能无法识别。

另外,调试二维码生成器有个好方法:用手机上的扫码App实时测试。每改一个参数,扫一下看看能不能识别。这样比肉眼检查高效得多。

如果你想让项目更完善,可以加这些功能:

  • 支持中文编码(UTF-8转GB2312)
  • 输出PNG格式(需要zlib库)
  • 添加Logo嵌入功能
  • 支持动态二维码(如定时刷新)

好了,二维码生成器就讲到这里。动手写一个吧,你会发现它比想象中有意思。


公众号:蓝海资料掘金营,微信deep3321