2021SC@SDUSC
前言:在上一篇博客中,介绍了QR码的相关知识,并对生成QR码的入口类QRCodeWriter做了代码分析。在本篇博客中,将对QR编码类,即core.src.main.java.com.google.zxing.qrcode.encoder包中代码进行详细分析。
QRCode
QRCode是定义QR码的类,有QR码的模式、纠错等级、版本、掩膜版本、字节矩阵等属性。具体代码如下:
public final class QRCode {
public static final int NUM_MASK_PATTERNS = 8; // 掩膜最大版本为8
private Mode mode; // 模式
private ErrorCorrectionLevel ecLevel;// 纠错等级
private Version version;// 版本
private int maskPattern;// 掩膜版本
private ByteMatrix matrix;// 字节矩阵
public QRCode() {
maskPattern = -1;
}
//get 、set方法已省略
@Override
public String toString() {
StringBuilder result = new StringBuilder(200);
result.append("<<\n");
result.append(" mode: ");
result.append(mode);
result.append("\n ecLevel: ");
result.append(ecLevel);
result.append("\n version: ");
result.append(version);
result.append("\n maskPattern: ");
result.append(maskPattern);
if (matrix == null) {
result.append("\n matrix: null\n");
} else {
result.append("\n matrix:\n");
result.append(matrix);
}
result.append(">>\n");
return result.toString();
}
// 检查遮罩版本是否有效。
public static boolean isValidMaskPattern(int maskPattern) {
return maskPattern >= 0 && maskPattern < NUM_MASK_PATTERNS;
}
}
BlockPair
BlockPair类定义了生成二维码所需的数据块,具体定义如下:
final class BlockPair {
private final byte[] dataBytes;
private final byte[] errorCorrectionBytes;
BlockPair(byte[] data, byte[] errorCorrection) {
dataBytes = data;
errorCorrectionBytes = errorCorrection;
}
public byte[] getDataBytes() {
return dataBytes;
}
public byte[] getErrorCorrectionBytes() {
return errorCorrectionBytes;
}
}
ByteMatrix
ByteMatrix类是一个int的2D数组(矩阵),但是因为它只被赋值-1、0和1,所以使用的内存和字节更少。类定义如下(省略set、get、toString方法):
public final class ByteMatrix {
private final byte[][] bytes;
private final int width;
private final int height;
public ByteMatrix(int width, int height) {
bytes = new byte[height][width];
this.width = width;
this.height = height;
}
public byte get(int x, int y) {
return bytes[y][x];
}
// 以字节形式返回内部表示形式,按行主顺序。数组[y][x]表示点(x,y)
public byte[][] getArray() {
return bytes;
}
public void clear(byte value) {
for (byte[] aByte : bytes) {
Arrays.fill(aByte, value);
}
}
}
Mask(掩膜)
在上述代码中的mask,是掩膜的意思。从物理的角度来看,在半导体制造中,许多芯片工艺步骤采用光刻技术,用于这些步骤的图形“底片”称为掩膜(也称作“掩模”),其作用是:在硅片上选定的区域中对一个不透明的图形模板遮盖,继而下面的腐蚀或扩散将只影响选定的区域以外的区域。图像掩膜与其类似,用选定的图像、图形或物体,对处理的图像(全部或局部)进行遮挡,来控制图像处理的区域或处理过程。
为了增加QR码识别读取时的可靠性,应该防止编码时黑色块和白色块过于密集地集中在一片区域上,那就需要编码时均匀地在编码区上合理地摆放每一个黑白信息位,而且还要避免出现类似其它功能图形的黑白块,特别是行或者列的黑白块以1011101这种形式出现,那就与位置探测图形的行和列一致,这肯定会造成在解码识别时对位流的误判。
QR码的掩膜版一共8种。他是在数据字和纠错字都在矩阵中填充完后,选择掩膜版类型与消息字(数据字和纠错字)XOR后的出的矩阵即为最终的QR码所展现的图案。下图展示了掩膜图案生成的条件。
掩膜过程如下图所示:
掩膜的评价标准:
下面图形是对应的Mask Pattern
MaskUtil
QR码对于掩膜的处理,有一个专门的类MaskUtil。代码及分析注释如下:
这部分的代码分析得比较简略,原因是对于掩码了解不深,待我深入理解后再对其补充。
final class MaskUtil {
// Penalty weights
private static final int N1 = 3;
private static final int N2 = 3;
private static final int N3 = 40;
private static final int N4 = 10;
private MaskUtil() {}
// 应用掩码Penalty weights N1并返回惩罚。找到具有相同颜色的重复单元格并对其进行惩罚。示例:00000或11111。
static int applyMaskPenaltyRule1(ByteMatrix matrix) {
return applyMaskPenaltyRule1Internal(matrix, true) + applyMaskPenaltyRule1Internal(matrix, false);
}
// 应用掩码Penalty weights N2并返回惩罚。找到相同颜色的2x2块,并对其进行处罚。这实际上相当于spec的规则,即查找MxN块并给出与(M-1)x(N-1)成比例的惩罚,因为这是此类块中2x2块的数量。
static int applyMaskPenaltyRule2(ByteMatrix matrix) {
int penalty = 0;
byte[][] array = matrix.getArray();
int width = matrix.getWidth();
int height = matrix.getHeight();
for (int y = 0; y < height - 1; y++) {
byte[] arrayY = array[y];
for (int x = 0; x < width - 1; x++) {
int value = arrayY[x];
if (value == arrayY[x + 1] && value == array[y + 1][x] && value == array[y + 1][x + 1]) {
penalty++;
}
}
}
return N2 * penalty;
}
// 应用掩码Penalty weights N3并返回惩罚。找出连续运行的从黑色开始的1:1:3:1:1:4或从白色开始的4:1:1:3:1:1,并对他们进行处罚。如果我们发现像00001110110000这样的模式,我们会给出一次惩罚。
static int applyMaskPenaltyRule3(ByteMatrix matrix) {
int numPenalties = 0;
byte[][] array = matrix.getArray();
int width = matrix.getWidth();
int height = matrix.getHeight();
for (int y = 0; y < height; y++) {
for (int x = 0; x < width; x++) {
byte[] arrayY = array[y];
if (x + 6 < width && arrayY[x] == 1 && arrayY[x + 1] == 0 && arrayY[x + 2] == 1 && arrayY[x + 3] == 1 && arrayY[x + 4] == 1 && arrayY[x + 5] == 0 && arrayY[x + 6] == 1 && (isWhiteHorizontal(arrayY, x - 4, x) || isWhiteHorizontal(arrayY, x + 7, x + 11))) {
numPenalties++;
}
if (y + 6 < height && array[y][x] == 1 && array[y + 1][x] == 0 && array[y + 2][x] == 1 && array[y + 3][x] == 1 && array[y + 4][x] == 1 && array[y + 5][x] == 0 && array[y + 6][x] == 1 && (isWhiteVertical(array, x, y - 4, y) || isWhiteVertical(array, x, y + 7, y + 11))) {
numPenalties++;
}
}
}
return numPenalties * N3;
}
private static boolean isWhiteHorizontal(byte[] rowArray, int from, int to) {
from = Math.max(from, 0);
to = Math.min(to, rowArray.length);
for (int i = from; i < to; i++) {
if (rowArray[i] == 1) {
return false;
}
}
return true;
}
private static boolean isWhiteVertical(byte[][] array, int col, int from, int to) {
from = Math.max(from, 0);
to = Math.min(to, array.length);
for (int i = from; i < to; i++) {
if (array[i][col] == 1) {
return false;
}
}
return true;
}
// 应用掩码Penalty weights N4并返回惩罚。计算暗块的比率,如果比率远离50%,则给予惩罚。5%的距离将被罚10分。
static int applyMaskPenaltyRule4(ByteMatrix matrix) {
int numDarkCells = 0;
byte[][] array = matrix.getArray();
int width = matrix.getWidth();
int height = matrix.getHeight();
for (int y = 0; y < height; y++) {
byte[] arrayY = array[y];
for (int x = 0; x < width; x++) {
if (arrayY[x] == 1) {
numDarkCells++;
}
}
}
int numTotalCells = matrix.getHeight() * matrix.getWidth();
int fivePercentVariances = Math.abs(numDarkCells * 2 - numTotalCells) * 10 / numTotalCells;
return fivePercentVariances * N4;
}
// 在“x”和“y”处返回“getMaskPattern”的掩码位。
static boolean getDataMaskBit(int maskPattern, int x, int y) {
int intermediate;
int temp;
switch (maskPattern) {
case 0:intermediate = (y + x) & 0x1;
break;
case 1:intermediate = y & 0x1;
break;
case 2:intermediate = x % 3;
break;
case 3:intermediate = (y + x) % 3;
break;
case 4:intermediate = ((y / 2) + (x / 3)) & 0x1;
break;
case 5:temp = y * x;intermediate = (temp & 0x1) + (temp % 3);
break;
case 6:temp = y * x;intermediate = ((temp & 0x1) + (temp % 3)) & 0x1;
break;
case 7:temp = y * x;intermediate = ((temp % 3) + ((y + x) & 0x1)) & 0x1;
break;
default:
throw new IllegalArgumentException("Invalid mask pattern: " + maskPattern);
}
return intermediate == 0;
}
// applyMaskPenaltyRule1的助手函数。我们需要它来分别以垂直和水平顺序进行计算。
private static int applyMaskPenaltyRule1Internal(ByteMatrix matrix, boolean isHorizontal) {
int penalty = 0;
int iLimit = isHorizontal ? matrix.getHeight() : matrix.getWidth();
int jLimit = isHorizontal ? matrix.getWidth() : matrix.getHeight();
byte[][] array = matrix.getArray();
for (int i = 0; i < iLimit; i++) {
int numSameBitCells = 0;
int prevBit = -1;
for (int j = 0; j < jLimit; j++) {
int bit = isHorizontal ? array[i][j] : array[j][i];
if (bit == prevBit) {
numSameBitCells++;
} else {
if (numSameBitCells >= 5) {
penalty += N1 + (numSameBitCells - 5);
}
numSameBitCells = 1; // 包括单元格本身。
prevBit = bit;
}
}
if (numSameBitCells >= 5) {
penalty += N1 + (numSameBitCells - 5);
}
}
return penalty;
}
}