☰
ZXing BitMatrix 位矩阵全解:一个 int 如何扛起 32 个像素的二维码生成底层逻辑
2026/10/27 1:46:13 网站建设 项目流程

ZXing BitMatrix 位矩阵全解:一个 int 如何扛起 32 个像素的二维码生成底层逻辑

【免费下载链接】zxingZXing ("Zebra Crossing") barcode scanning library for Java, Android项目地址: https://gitcode.com/gh_mirrors/zx/zxing

你以为的二维码是一堆黑白方块,但在 ZXing 的内存里它只是一段int数组。本文带你钻进BitMatrix的底层逻辑:位压缩是怎么把 200 万个布尔位压成几千个 int 的、一条链接收端二维码生成流程怎么走、以及新手最容易踩的三个坑。全程以"你"的视角展开,读完就能自己动手生成和读取一张码。

扫码偶尔失败、内存悄悄翻倍?先看看链路中间那个 BitMatrix

想象你写的扫码 App:摄像头给出一帧灰度图,最后吐出一个字符串。中间发生过什么?

  • 图像先被二值化成一张"黑白矩阵"——每个点只剩黑或白
  • 各种 Reader(QRCode、Data Matrix、Aztec……)在这张矩阵上找定位图案、读模块
  • 生成端则是反着来:一串字符最终也要落成同样的黑白矩阵,再渲染成 PNG

这张"黑白矩阵"就是 ZXing 里的 BitMatrix。它是整个项目里所有二维码格式的公共容器——扫码失败排查、生成二维码时内存暴涨,多数时候都和它打交道。🔍

位压缩入门:一个 int 抽屉如何装下 32 个像素格子

为什么不用 boolean[][]

一张 300×300 的码有 9 万个点。用boolean[][]存,每个点至少占 1 字节,外加行数组的对象头,开销不小。而 BitMatrix 的"家当"只有四样:

// BitMatrix 的全部内部状态 private int width; private int height; private int rowSize; // 每行占几个 int:(width + 31) / 32 private int[] bits; // 真正的数据,1D 数组

9 万个点 ÷ 32 ≈ 2813 个 int,约 11KB。位压缩(bit packing)省下的不是常数,而是 32 倍的量级。📦

一个 int 里住着 32 个"格子"

把一个int想成一个抽屉,里面有 32 个格子,从左到右编号 0~31。每个格子只放一个像素:0 表示白,1 表示黑。更妙的是每行的第一个像素都从新抽屉的第一个格子开始——行与行之间永远对齐,所以整行数据可以整块搬走。

读一个像素,就是"算出哪个抽屉、第几个格子",然后移位取一位:

public boolean get(int x, int y) { int offset = y * rowSize + (x / 32); // 定位到第 y 行、第几个 int return ((bits[offset] >>> (x & 0x1f)) & 1) != 0; // 右移到位,取出 0/1 }

写入就是它的镜像:set用|=点亮某个格子,unset用&=熄灭,flip用^=翻转。全部是 CPU 一条指令级别的位运算,没有任何查表。

端到端链路:从 "hello" 到一张 300×300 的码

生成流程:编码 → 纠错 → 填充 → 渲染

QRCodeWriter.encode(...)内部按顺序走完五步(见 QRCodeWriter 源码):

  1. 编码:字符按选定模式(数字、字母、字节)转成比特流
  2. 纠错:Reed-Solomon 算法生成冗余码块,码被划破也能读
  3. 填充:数据按 Z 字路线交错写入模块级矩阵,再叠加掩码图案
  4. 放大:renderResult把模块级ByteMatrix按倍率放大,setRegion批量填出每个黑块,四周垫上 4 模块静区
  5. 渲染:MatrixToImageWriter逐行读 BitMatrix,true 涂黑、false 留白,写进BufferedImage

解码端:BitMatrix 同样是主角

摄像头帧 →LuminanceSource(灰度)→HybridBinarizer(自适应二值化)→ BinaryBitmap 持有的 BitMatrix。注意它是懒加载的:只有二维 Reader 真正需要时才计算整张矩阵,一维码扫到了就一步都省。所有二维解码器都是在同一张 BitMatrix 上做定位与取样。

上手实操:三步生成 / 读取一张码

生成:三步出图

// 1. 指定像素尺寸与纠错级别 QRCodeWriter writer = new QRCodeWriter(); Map<EncodeHintType, Object> hints = new HashMap<>(); hints.put(EncodeHintType.ERROR_CORRECTION, "M"); // 2. encode 直接返回像素级 BitMatrix(不是模块数!) BitMatrix matrix = writer.encode("hello", BarcodeFormat.QR_CODE, 300, 300, hints); // 3. 渲染成 PNG 文件 MatrixToImageWriter.writeToPath(matrix, "png", Paths.get("hello.png"));

读取:解码同样三行

BufferedImage image = ImageIO.read(new File("hello.png")); LuminanceSource source = new BufferedImageLuminanceSource(image); BinaryBitmap bitmap = new BinaryBitmap(new HybridBinarizer(source)); Result result = new MultiFormatReader().decode(bitmap); System.out.println(result.getText()); // hello

性能与内存:又快又省的关键

内存公式:先算账再建矩阵

内存占用 ≈height × ⌈width/32⌉ × 4字节。300×300 的码只要约 9KB,1000×1000 也才 ~125KB。真正会吃掉内存的往往是渲染阶段的BufferedImage(300×300 的 TYPE_INT_RGB 约 360KB,是矩阵的 40 倍)。所以:按需选择输出尺寸,别为"高清"先建超大矩阵再缩放。

行级操作与扫描提速

  • 复用BitArray:getRow(y, row)传入已有实例可避免逐行重新分配,MatrixToImageWriter就是这么干的
  • 先裁后解:getEnclosingRectangle()返回left, top, width, height,用它对BinaryBitmap.crop(...)后再交给 Reader,处理范围立刻缩小
  • 快速探边:getTopLeftOnBit()/getBottomRightOnBit()两个端点一取,码的包围盒就有了

新手最容易踩的 3 个坑

三个高频错误现场

  1. 把像素当模块:encode(…, 300, 300, …)的 300 是像素尺寸,不是 QR 码的模块数。一张 33×33 模块的码放大到 300×300 像素是正常现象,别因为"格子太少"以为码坏了。
  2. 位序记反,渲染镜像错位:每个 int 里最低位(bit 0)对应更小的 x,且每行从新 int 开始。自己手写渲染或跨 int 拼接时一旦顺序写反,图就是镜像/错位的。对照get(x, y)的 offset 计算写最稳。
  3. 复用矩阵忘了 clear:setRegion/setRow都是就地修改,不会自动擦掉旧位。同一实例连续生成两张码,先matrix.clear(),否则上一张的残影会混进新码。

写在最后

BitMatrix 不挑格式:QR Code、Data Matrix、Aztec 的解码器全在它身上干活。想再深入,可以从rotate90()的行交换实现、toString("X ", " ")的调试可视化入手;文档入口在 docs/index.html。

一句话总结:把 9 万个黑白点装进 2813 个 int,就是 BitMatrix 给条形码与二维码带来的全部魔法。

项目源码:https://gitcode.com/gh_mirrors/zx/zxing

【免费下载链接】zxingZXing ("Zebra Crossing") barcode scanning library for Java, Android项目地址: https://gitcode.com/gh_mirrors/zx/zxing

创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询