87284155 2019-06-25
本文主要研究下zxing的qrcode的一些代码。
<dependency> <groupId>com.google.zxing</groupId> <artifactId>core</artifactId> <version>3.3.1</version> </dependency> <dependency> <groupId>com.google.zxing</groupId> <artifactId>javase</artifactId> <version>3.3.1</version> </dependency>
core-3.3.1-sources.jar!/com/google/zxing/qrcode/QRCodeWriter.java
QRCodeWriter的encode方法进行编码,转换为BitMatrix
@Override public BitMatrix encode(String contents, BarcodeFormat format, int width, int height, Map<EncodeHintType,?> hints) throws WriterException { if (contents.isEmpty()) { throw new IllegalArgumentException("Found empty contents"); } if (format != BarcodeFormat.QR_CODE) { throw new IllegalArgumentException("Can only encode QR_CODE, but got " + format); } if (width < 0 || height < 0) { throw new IllegalArgumentException("Requested dimensions are too small: " + width + 'x' + height); } ErrorCorrectionLevel errorCorrectionLevel = ErrorCorrectionLevel.L; int quietZone = QUIET_ZONE_SIZE; if (hints != null) { if (hints.containsKey(EncodeHintType.ERROR_CORRECTION)) { errorCorrectionLevel = ErrorCorrectionLevel.valueOf(hints.get(EncodeHintType.ERROR_CORRECTION).toString()); } if (hints.containsKey(EncodeHintType.MARGIN)) { quietZone = Integer.parseInt(hints.get(EncodeHintType.MARGIN).toString()); } } QRCode code = Encoder.encode(contents, errorCorrectionLevel, hints); return renderResult(code, width, height, quietZone); }
core-3.3.1-sources.jar!/com/google/zxing/qrcode/encoder/QRCode.java
public final class QRCode { public static final int NUM_MASK_PATTERNS = 8; private Mode mode; private ErrorCorrectionLevel ecLevel; private Version version; private int maskPattern; private ByteMatrix matrix; public QRCode() { maskPattern = -1; } public Mode getMode() { return mode; } public ErrorCorrectionLevel getECLevel() { return ecLevel; } public Version getVersion() { return version; } public int getMaskPattern() { return maskPattern; } public ByteMatrix getMatrix() { return matrix; } @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 void setMode(Mode value) { mode = value; } public void setECLevel(ErrorCorrectionLevel value) { ecLevel = value; } public void setVersion(Version version) { this.version = version; } public void setMaskPattern(int value) { maskPattern = value; } public void setMatrix(ByteMatrix value) { matrix = value; } // Check if "mask_pattern" is valid. public static boolean isValidMaskPattern(int maskPattern) { return maskPattern >= 0 && maskPattern < NUM_MASK_PATTERNS; } }
是由Encoder类encode而来
core/3.3.1/core-3.3.1-sources.jar!/com/google/zxing/qrcode/encoder/Encoder.java
public static QRCode encode(String content, ErrorCorrectionLevel ecLevel, Map<EncodeHintType,?> hints) throws WriterException { // Determine what character encoding has been specified by the caller, if any String encoding = DEFAULT_BYTE_MODE_ENCODING; boolean hasEncodingHint = hints != null && hints.containsKey(EncodeHintType.CHARACTER_SET); if (hasEncodingHint) { encoding = hints.get(EncodeHintType.CHARACTER_SET).toString(); } // Pick an encoding mode appropriate for the content. Note that this will not attempt to use // multiple modes / segments even if that were more efficient. Twould be nice. Mode mode = chooseMode(content, encoding); // This will store the header information, like mode and // length, as well as "header" segments like an ECI segment. BitArray headerBits = new BitArray(); // Append ECI segment if applicable if (mode == Mode.BYTE && (hasEncodingHint || !DEFAULT_BYTE_MODE_ENCODING.equals(encoding))) { CharacterSetECI eci = CharacterSetECI.getCharacterSetECIByName(encoding); if (eci != null) { appendECI(eci, headerBits); } } // (With ECI in place,) Write the mode marker appendModeInfo(mode, headerBits); // Collect data within the main segment, separately, to count its size if needed. Don't add it to // main payload yet. BitArray dataBits = new BitArray(); appendBytes(content, mode, dataBits, encoding); Version version; if (hints != null && hints.containsKey(EncodeHintType.QR_VERSION)) { int versionNumber = Integer.parseInt(hints.get(EncodeHintType.QR_VERSION).toString()); version = Version.getVersionForNumber(versionNumber); int bitsNeeded = calculateBitsNeeded(mode, headerBits, dataBits, version); if (!willFit(bitsNeeded, version, ecLevel)) { throw new WriterException("Data too big for requested version"); } } else { version = recommendVersion(ecLevel, mode, headerBits, dataBits); } BitArray headerAndDataBits = new BitArray(); headerAndDataBits.appendBitArray(headerBits); // Find "length" of main segment and write it int numLetters = mode == Mode.BYTE ? dataBits.getSizeInBytes() : content.length(); appendLengthInfo(numLetters, version, mode, headerAndDataBits); // Put data together into the overall payload headerAndDataBits.appendBitArray(dataBits); Version.ECBlocks ecBlocks = version.getECBlocksForLevel(ecLevel); int numDataBytes = version.getTotalCodewords() - ecBlocks.getTotalECCodewords(); // Terminate the bits properly. terminateBits(numDataBytes, headerAndDataBits); // Interleave data bits with error correction code. BitArray finalBits = interleaveWithECBytes(headerAndDataBits, version.getTotalCodewords(), numDataBytes, ecBlocks.getNumBlocks()); QRCode qrCode = new QRCode(); qrCode.setECLevel(ecLevel); qrCode.setMode(mode); qrCode.setVersion(version); // Choose the mask pattern and set to "qrCode". int dimension = version.getDimensionForVersion(); ByteMatrix matrix = new ByteMatrix(dimension, dimension); int maskPattern = chooseMaskPattern(finalBits, ecLevel, version, matrix); qrCode.setMaskPattern(maskPattern); // Build the matrix and set it to "qrCode". MatrixUtil.buildMatrix(finalBits, ecLevel, version, maskPattern, matrix); qrCode.setMatrix(matrix); return qrCode; }
这里重点看Version的这段
Version version; if (hints != null && hints.containsKey(EncodeHintType.QR_VERSION)) { int versionNumber = Integer.parseInt(hints.get(EncodeHintType.QR_VERSION).toString()); version = Version.getVersionForNumber(versionNumber); int bitsNeeded = calculateBitsNeeded(mode, headerBits, dataBits, version); if (!willFit(bitsNeeded, version, ecLevel)) { throw new WriterException("Data too big for requested version"); } } else { version = recommendVersion(ecLevel, mode, headerBits, dataBits); }
这里计算version,同时判断content的大小,是否超出qrcode的容量,超出的话,抛出WriterException("Data too big for requested version")
/** * @return true if the number of input bits will fit in a code with the specified version and * error correction level. */ private static boolean willFit(int numInputBits, Version version, ErrorCorrectionLevel ecLevel) { // In the following comments, we use numbers of Version 7-H. // numBytes = 196 int numBytes = version.getTotalCodewords(); // getNumECBytes = 130 Version.ECBlocks ecBlocks = version.getECBlocksForLevel(ecLevel); int numEcBytes = ecBlocks.getTotalECCodewords(); // getNumDataBytes = 196 - 130 = 66 int numDataBytes = numBytes - numEcBytes; int totalInputBytes = (numInputBits + 7) / 8; return numDataBytes >= totalInputBytes; }
// Note that the input matrix uses 0 == white, 1 == black, while the output matrix uses // 0 == black, 255 == white (i.e. an 8 bit greyscale bitmap). private static BitMatrix renderResult(QRCode code, int width, int height, int quietZone) { ByteMatrix input = code.getMatrix(); if (input == null) { throw new IllegalStateException(); } int inputWidth = input.getWidth(); int inputHeight = input.getHeight(); int qrWidth = inputWidth + (quietZone * 2); int qrHeight = inputHeight + (quietZone * 2); int outputWidth = Math.max(width, qrWidth); int outputHeight = Math.max(height, qrHeight); int multiple = Math.min(outputWidth / qrWidth, outputHeight / qrHeight); // Padding includes both the quiet zone and the extra white pixels to accommodate the requested // dimensions. For example, if input is 25x25 the QR will be 33x33 including the quiet zone. // If the requested size is 200x160, the multiple will be 4, for a QR of 132x132. These will // handle all the padding from 100x100 (the actual QR) up to 200x160. int leftPadding = (outputWidth - (inputWidth * multiple)) / 2; int topPadding = (outputHeight - (inputHeight * multiple)) / 2; BitMatrix output = new BitMatrix(outputWidth, outputHeight); for (int inputY = 0, outputY = topPadding; inputY < inputHeight; inputY++, outputY += multiple) { // Write the contents of this row of the barcode for (int inputX = 0, outputX = leftPadding; inputX < inputWidth; inputX++, outputX += multiple) { if (input.get(inputX, inputY) == 1) { output.setRegion(outputX, outputY, multiple, multiple); } } } return output; }
这个renderResult根据QRCode信息来构造BitMatrix。可以看到这里重新计算了输出的宽度和高度,是取了qrcode的宽高+两边的quietZone与输入参数的目标宽高取最大值。也就是说如果qrcode的宽高大于目标的宽高,则以qrcode的宽高为准,这种情况下的quietZone基本跟输出的一致。
一般而言qrcode的宽高小于目标宽高的话,这种情况下quietZone就跟输出的不一致,需要经过重新放大,得到的才是最后输出的padding。