From 9204484581a06a418b6f582ddf569f9e381305fb Mon Sep 17 00:00:00 2001 From: srowen Date: Wed, 7 Nov 2007 06:40:04 +0000 Subject: [PATCH] More javadoc git-svn-id: http://zxing.googlecode.com/svn/trunk@13 59b500cc-1b3d-0410-9834-0bbf25fbcc57 --- .../zxing/qrcode/decoder/BitMatrixParser.java | 33 ++++++++++++++- .../zxing/qrcode/decoder/BitSource.java | 16 ++++++- .../zxing/qrcode/decoder/DataBlock.java | 36 +++++++++++----- .../google/zxing/qrcode/decoder/DataMask.java | 20 +++++++-- .../decoder/DecodedBitStreamParser.java | 20 +++++++-- .../google/zxing/qrcode/decoder/Decoder.java | 42 ++++++++++++++++++- .../qrcode/decoder/ErrorCorrectionLevel.java | 6 ++- .../qrcode/decoder/FormatInformation.java | 25 +++++++---- .../com/google/zxing/qrcode/decoder/Mode.java | 10 +++++ .../google/zxing/qrcode/decoder/Version.java | 28 ++++++++----- 10 files changed, 194 insertions(+), 42 deletions(-) diff --git a/core/src/com/google/zxing/qrcode/decoder/BitMatrixParser.java b/core/src/com/google/zxing/qrcode/decoder/BitMatrixParser.java index 7e20d75e..dffa6c6e 100644 --- a/core/src/com/google/zxing/qrcode/decoder/BitMatrixParser.java +++ b/core/src/com/google/zxing/qrcode/decoder/BitMatrixParser.java @@ -29,8 +29,8 @@ final class BitMatrixParser { private FormatInformation parsedFormatInfo; /** - * @throws com.google.zxing.ReaderException - * if dimension is not >= 21 and 1 mod 4 + * @param bitMatrix {@link BitMatrix} to parse + * @throws ReaderException if dimension is not >= 21 and 1 mod 4 */ BitMatrixParser(BitMatrix bitMatrix) throws ReaderException { int dimension = bitMatrix.getDimension(); @@ -40,6 +40,13 @@ final class BitMatrixParser { this.bitMatrix = bitMatrix; } + /** + *

Reads format information from one of its two locations within the QR Code.

+ * + * @return {@link FormatInformation} encapsulating the QR Code's format info + * @throws ReaderException if both format information locations cannot be parsed as + * the valid encoding of format information + */ FormatInformation readFormatInformation() throws ReaderException { if (parsedFormatInfo != null) { @@ -83,6 +90,13 @@ final class BitMatrixParser { throw new ReaderException("Could not decode format information"); } + /** + *

Reads version information from one of its two locations within the QR Code.

+ * + * @return {@link Version} encapsulating the QR Code's version + * @throws ReaderException if both version information locations cannot be parsed as + * the valid encoding of version information + */ Version readVersion() throws ReaderException { if (parsedVersion != null) { @@ -130,11 +144,21 @@ final class BitMatrixParser { return bitMatrix.get(i, j) ? (versionBits << 1) | 0x1 : versionBits << 1; } + /** + *

Reads the bits in the {@link BitMatrix} representing the finder pattern in the + * correct order in order to reconstitute the codewords bytes contained within the + * QR Code.

+ * + * @return bytes encoded within the QR Code + * @throws ReaderException if the exact number of bytes expected is not read + */ byte[] readCodewords() throws ReaderException { FormatInformation formatInfo = readFormatInformation(); Version version = readVersion(); + // Get the data mask for the format used in this QR Code. This will exclude + // some bits from reading as we wind through the bit matrix. DataMask dataMask = DataMask.forReference((int) formatInfo.getDataMask()); int dimension = bitMatrix.getDimension(); dataMask.unmaskBitMatrix(bitMatrix.getBits(), dimension); @@ -146,21 +170,26 @@ final class BitMatrixParser { int resultOffset = 0; int currentByte = 0; int bitsRead = 0; + // Read columns in pairs, from right to left for (int j = dimension - 1; j > 0; j -= 2) { if (j == 6) { // Skip whole column with vertical alignment pattern; // saves time and makes the other code proceed more cleanly j--; } + // Read alternatingly from bottom to top then top to bottom for (int count = 0; count < dimension; count++) { int i = readingUp ? dimension - 1 - count : count; for (int col = 0; col < 2; col++) { + // Ignore bits covered by the function pattern if (!functionPattern.get(i, j - col)) { + // Read a bit bitsRead++; currentByte <<= 1; if (bitMatrix.get(i, j - col)) { currentByte |= 1; } + // If we've made a whole byte, save it off if (bitsRead == 8) { result[resultOffset++] = (byte) currentByte; bitsRead = 0; diff --git a/core/src/com/google/zxing/qrcode/decoder/BitSource.java b/core/src/com/google/zxing/qrcode/decoder/BitSource.java index b58ec925..7fc077d2 100755 --- a/core/src/com/google/zxing/qrcode/decoder/BitSource.java +++ b/core/src/com/google/zxing/qrcode/decoder/BitSource.java @@ -17,8 +17,10 @@ package com.google.zxing.qrcode.decoder; /** - * This provides an easy abstraction to read bits at a time from a sequence of bytes, where the - * number of bits read is not often a multiple of 8. + *

This provides an easy abstraction to read bits at a time from a sequence of bytes, where the + * number of bits read is not often a multiple of 8.

+ * + *

This class is not thread-safe.

* * @author srowen@google.com (Sean Owen) */ @@ -28,11 +30,18 @@ final class BitSource { private int byteOffset; private int bitOffset; + /** + * @param bytes bytes from which this will read bits. Bits will be read from the first byte first. + * Bits are read within a byte from most-significant to least-significant bit. + */ BitSource(byte[] bytes) { this.bytes = bytes; } /** + * @param numBits number of bits to read + * @return int representing the bits read. The bits will appear as the least-significant + * bits of the int * @throws IllegalArgumentException if numBits isn't in [1,32] */ int readBits(int numBits) { @@ -77,6 +86,9 @@ final class BitSource { return result; } + /** + * @return number of bits that can be read successfully + */ int available() { return 8 * (bytes.length - byteOffset) - bitOffset; } diff --git a/core/src/com/google/zxing/qrcode/decoder/DataBlock.java b/core/src/com/google/zxing/qrcode/decoder/DataBlock.java index 2737d807..ca146cc1 100755 --- a/core/src/com/google/zxing/qrcode/decoder/DataBlock.java +++ b/core/src/com/google/zxing/qrcode/decoder/DataBlock.java @@ -17,6 +17,10 @@ package com.google.zxing.qrcode.decoder; /** + *

Encapsulates a block of data within a QR Code. QR Codes may split their data into + * multiple blocks, each of which is a unit of data and error-correction codewords. Each + * is represented by an instance of this class.

+ * * @author srowen@google.com (Sean Owen) */ final class DataBlock { @@ -29,15 +33,32 @@ final class DataBlock { this.codewords = codewords; } + /** + *

When QR Codes use multiple data blocks, they are actually interleave the bytes of each of them. + * That is, the first byte of data block 1 to n is written, then the second bytes, and so on. This + * method will separate the data into original blocks.

+ * + * @param rawCodewords bytes as read directly from the QR Code + * @param version version of the QR Code + * @param ecLevel error-correction level of the QR Code + * @return {@link DataBlock}s containing original bytes, "de-interleaved" from representation in the + * QR Code + */ static DataBlock[] getDataBlocks(byte[] rawCodewords, Version version, ErrorCorrectionLevel ecLevel) { + // Figure out the number and size of data blocks used by this version and + // error correction level Version.ECBlocks ecBlocks = version.getECBlocksForLevel(ecLevel); + + // First count the total number of data blocks int totalBlocks = 0; Version.ECB[] ecBlockArray = ecBlocks.getECBlocks(); for (int i = 0; i < ecBlockArray.length; i++) { totalBlocks += ecBlockArray[i].getCount(); } + + // Now establish DataBlocks of the appropriate size and number of data codewords DataBlock[] result = new DataBlock[totalBlocks]; int numResultBlocks = 0; for (int j = 0; j < ecBlockArray.length; j++) { @@ -45,8 +66,7 @@ final class DataBlock { for (int i = 0; i < ecBlock.getCount(); i++) { int numDataCodewords = ecBlock.getDataCodewords(); int numBlockCodewords = ecBlocks.getECCodewords() + numDataCodewords; - result[numResultBlocks++] = - new DataBlock(numDataCodewords, new byte[numBlockCodewords]); + result[numResultBlocks++] = new DataBlock(numDataCodewords, new byte[numBlockCodewords]); } } @@ -55,21 +75,18 @@ final class DataBlock { int shorterBlocksTotalCodewords = result[0].codewords.length; int longerBlocksStartAt = result.length - 1; while (longerBlocksStartAt >= 0) { - int numCodewords = - result[longerBlocksStartAt].codewords.length; + int numCodewords = result[longerBlocksStartAt].codewords.length; if (numCodewords == shorterBlocksTotalCodewords) { break; } if (numCodewords != shorterBlocksTotalCodewords + 1) { - throw new IllegalStateException( - "Data block sizes differ by more than 1"); + throw new IllegalStateException("Data block sizes differ by more than 1"); } longerBlocksStartAt--; } longerBlocksStartAt++; - int shorterBlocksNumDataCodewords = - shorterBlocksTotalCodewords - ecBlocks.getECCodewords(); + int shorterBlocksNumDataCodewords = shorterBlocksTotalCodewords - ecBlocks.getECCodewords(); // The last elements of result may be 1 element longer; // first fill out as many elements as all of them have int rawCodewordsOffset = 0; @@ -80,8 +97,7 @@ final class DataBlock { } // Fill out the last data block in the longer ones for (int j = longerBlocksStartAt; j < numResultBlocks; j++) { - result[j].codewords[shorterBlocksNumDataCodewords] = - rawCodewords[rawCodewordsOffset++]; + result[j].codewords[shorterBlocksNumDataCodewords] = rawCodewords[rawCodewordsOffset++]; } // Now add in error correction blocks int max = result[0].codewords.length; diff --git a/core/src/com/google/zxing/qrcode/decoder/DataMask.java b/core/src/com/google/zxing/qrcode/decoder/DataMask.java index 68f1b67e..225d4633 100755 --- a/core/src/com/google/zxing/qrcode/decoder/DataMask.java +++ b/core/src/com/google/zxing/qrcode/decoder/DataMask.java @@ -17,13 +17,13 @@ package com.google.zxing.qrcode.decoder; /** - * Encapsulates data masks for the data bits in a QR code, per ISO 18004:2006 6.8. Implementations + *

Encapsulates data masks for the data bits in a QR code, per ISO 18004:2006 6.8. Implementations * of this class can un-mask a raw BitMatrix. For simplicity, they will unmask the entire BitMatrix, * including areas used for finder patterns, timing patterns, etc. These areas should be unused - * after the point they are unmasked anyway. + * after the point they are unmasked anyway.

* - * Note that the diagram in section 6.8.1 is misleading since it indicates that i is column position - * and j is row position. In fact, as the text says, i is row position and j is column position. + *

Note that the diagram in section 6.8.1 is misleading since it indicates that i is column position + * and j is row position. In fact, as the text says, i is row position and j is column position.

* * @author srowen@google.com (Sean Owen) */ @@ -46,8 +46,20 @@ abstract class DataMask { private DataMask() { } + /** + *

Implementations of this method reverse the data masking process applied to a QR Code and + * make its bits ready to read.

+ * + * @param bits representation of QR Code bits from {@link com.google.zxing.common.BitMatrix#getBits()} + * @param dimension dimension of QR Code, represented by bits, being unmasked + */ abstract void unmaskBitMatrix(int[] bits, int dimension); + /** + * @param reference a value between 0 and 7 indicating one of the eight possible + * data mask patterns a QR Code may use + * @return {@link DataMask} encapsulating the data mask pattern + */ static DataMask forReference(int reference) { if (reference < 0 || reference > 7) { throw new IllegalArgumentException(); diff --git a/core/src/com/google/zxing/qrcode/decoder/DecodedBitStreamParser.java b/core/src/com/google/zxing/qrcode/decoder/DecodedBitStreamParser.java index 8fa91f44..08cc5bd5 100644 --- a/core/src/com/google/zxing/qrcode/decoder/DecodedBitStreamParser.java +++ b/core/src/com/google/zxing/qrcode/decoder/DecodedBitStreamParser.java @@ -21,7 +21,10 @@ import com.google.zxing.ReaderException; import java.io.UnsupportedEncodingException; /** - * See ISO 18004:2006, 6.4.3 - 6.4.7 + *

QR Codes can encode text as bits in one of several modes, and can use multiple modes + * in one QR Code. This class decodes the bits back into text.

+ * + *

See ISO 18004:2006, 6.4.3 - 6.4.7

* * @author srowen@google.com (Sean Owen) */ @@ -54,8 +57,9 @@ final class DecodedBitStreamParser { Mode mode; do { // While still another segment to read... - mode = Mode.forBits(bits.readBits(4)); + mode = Mode.forBits(bits.readBits(4)); // mode is encoded by 4 bits if (!mode.equals(Mode.TERMINATOR)) { + // How many characters will follow, encoded in this mode? int count = bits.readBits(mode.getCharacterCountBits(version)); if (mode.equals(Mode.NUMERIC)) { decodeNumericSegment(bits, result, count); @@ -66,11 +70,12 @@ final class DecodedBitStreamParser { } else if (mode.equals(Mode.KANJI)) { decodeKanjiSegment(bits, result, count); } else { - throw new ReaderException("Unsupported mode indicator: " + mode); + throw new ReaderException("Unsupported mode indicator"); } } } while (!mode.equals(Mode.TERMINATOR)); + // I thought it wasn't allowed to leave extra bytes after the terminator but it happens /* int bitsLeft = bits.available(); if (bitsLeft > 0) { @@ -85,9 +90,12 @@ final class DecodedBitStreamParser { private static void decodeKanjiSegment(BitSource bits, StringBuffer result, int count) throws ReaderException { + // Each character will require 2 bytes. Read the characters as 2-byte pairs + // and decode as Shift_JIS afterwards byte[] buffer = new byte[2 * count]; int offset = 0; while (count > 0) { + // Each 13 bits encodes a 2-byte character int twoBytes = bits.readBits(13); int assembledTwoBytes = ((twoBytes / 0x0C0) << 8) | (twoBytes % 0x0C0); if (assembledTwoBytes < 0x01F00) { @@ -144,7 +152,7 @@ final class DecodedBitStreamParser { count -= 2; } if (count == 1) { - // special case on char left + // special case: one character left result.append(ALPHANUMERIC_CHARS[bits.readBits(6)]); } } @@ -152,7 +160,9 @@ final class DecodedBitStreamParser { private static void decodeNumericSegment(BitSource bits, StringBuffer result, int count) throws ReaderException { + // Read three digits at a time while (count >= 3) { + // Each 10 bits encodes three digits int threeDigitsBits = bits.readBits(10); if (threeDigitsBits >= 1000) { throw new ReaderException("Illegal value for 3-digit unit: " + threeDigitsBits); @@ -163,6 +173,7 @@ final class DecodedBitStreamParser { count -= 3; } if (count == 2) { + // Two digits left over to read, encoded in 7 bits int twoDigitsBits = bits.readBits(7); if (twoDigitsBits >= 100) { throw new ReaderException("Illegal value for 2-digit unit: " + twoDigitsBits); @@ -170,6 +181,7 @@ final class DecodedBitStreamParser { result.append(ALPHANUMERIC_CHARS[twoDigitsBits / 10]); result.append(ALPHANUMERIC_CHARS[twoDigitsBits % 10]); } else if (count == 1) { + // One digit left over to read int digitBits = bits.readBits(4); if (digitBits >= 10) { throw new ReaderException("Illegal value for digit unit: " + digitBits); diff --git a/core/src/com/google/zxing/qrcode/decoder/Decoder.java b/core/src/com/google/zxing/qrcode/decoder/Decoder.java index a37128f5..8279eeae 100644 --- a/core/src/com/google/zxing/qrcode/decoder/Decoder.java +++ b/core/src/com/google/zxing/qrcode/decoder/Decoder.java @@ -22,6 +22,9 @@ import com.google.zxing.common.reedsolomon.ReedSolomonDecoder; import com.google.zxing.common.reedsolomon.ReedSolomonException; /** + *

The main class which implements QR Code decoding -- as opposed to locating and extracting + * the QR Code from an image.

+ * * @author srowen@google.com (Sean Owen) */ public final class Decoder { @@ -29,6 +32,14 @@ public final class Decoder { private Decoder() { } + /** + *

Convenience method that can decode a QR Code represented as a 2D array of booleans. + * "true" is taken to mean a black module.

+ * + * @param image booleans representing white/black QR Code modules + * @return text encoded within the QR Code + * @throws ReaderException if the QR Code cannot be decoded + */ public static String decode(boolean[][] image) throws ReaderException { int dimension = image.length; BitMatrix bits = new BitMatrix(dimension); @@ -42,18 +53,34 @@ public final class Decoder { return decode(bits); } + /** + *

Decodes a QR Code represented as a {@link BitMatrix}. A 1 or "true" is taken to mean a black module.

+ * + * @param bits booleans representing white/black QR Code modules + * @return text encoded within the QR Code + * @throws ReaderException if the QR Code cannot be decoded + */ public static String decode(BitMatrix bits) throws ReaderException { + + // Construct a parser and read version, error-correction level BitMatrixParser parser = new BitMatrixParser(bits); Version version = parser.readVersion(); ErrorCorrectionLevel ecLevel = parser.readFormatInformation().getErrorCorrectionLevel(); + + // Read codewords byte[] codewords = parser.readCodewords(); + // Separate into data blocks DataBlock[] dataBlocks = DataBlock.getDataBlocks(codewords, version, ecLevel); + + // Count total number of data bytes int totalBytes = 0; for (int i = 0; i < dataBlocks.length; i++) { totalBytes += dataBlocks[i].getNumDataCodewords(); } byte[] resultBytes = new byte[totalBytes]; int resultOffset = 0; + + // Error-correct and copy data blocks together into a stream of bytes for (int j = 0; j < dataBlocks.length; j++) { DataBlock dataBlock = dataBlocks[j]; byte[] codewordBytes = dataBlock.getCodewords(); @@ -64,12 +91,21 @@ public final class Decoder { } } + // Decode the contents of that stream of bytes return DecodedBitStreamParser.decode(resultBytes, version); } - private static void correctErrors(byte[] codewordBytes, int numDataCodewords) - throws ReaderException { + /** + *

Given data and error-correction codewords received, possibly corrupted by errors, attempts to + * correct the errors in-place using Reed-Solomon error correction.

+ * + * @param codewordBytes data and error correction codewords + * @param numDataCodewords number of codewords that are data bytes + * @throws ReaderException if error correction fails + */ + private static void correctErrors(byte[] codewordBytes, int numDataCodewords) throws ReaderException { int numCodewords = codewordBytes.length; + // First read into an array of ints int[] codewordsInts = new int[numCodewords]; for (int i = 0; i < numCodewords; i++) { codewordsInts[i] = codewordBytes[i] & 0xFF; @@ -80,6 +116,8 @@ public final class Decoder { } catch (ReedSolomonException rse) { throw new ReaderException(rse.toString()); } + // Copy back into array of bytes -- only need to worry about the bytes that were data + // We don't care about errors in the error-correction codewords for (int i = 0; i < numDataCodewords; i++) { codewordBytes[i] = (byte) codewordsInts[i]; } diff --git a/core/src/com/google/zxing/qrcode/decoder/ErrorCorrectionLevel.java b/core/src/com/google/zxing/qrcode/decoder/ErrorCorrectionLevel.java index 9b9adb56..93b0a68a 100644 --- a/core/src/com/google/zxing/qrcode/decoder/ErrorCorrectionLevel.java +++ b/core/src/com/google/zxing/qrcode/decoder/ErrorCorrectionLevel.java @@ -39,7 +39,7 @@ final class ErrorCorrectionLevel { private final int ordinal; - private ErrorCorrectionLevel(final int ordinal) { + private ErrorCorrectionLevel(int ordinal) { this.ordinal = ordinal; } @@ -47,6 +47,10 @@ final class ErrorCorrectionLevel { return ordinal; } + /** + * @param bits int containing the two bits encoding a QR Code's error correction level + * @return {@link ErrorCorrectionLevel} representing the encoded error correction level + */ static ErrorCorrectionLevel forBits(int bits) { return FOR_BITS[bits]; } diff --git a/core/src/com/google/zxing/qrcode/decoder/FormatInformation.java b/core/src/com/google/zxing/qrcode/decoder/FormatInformation.java index e483ebd6..c72154a3 100644 --- a/core/src/com/google/zxing/qrcode/decoder/FormatInformation.java +++ b/core/src/com/google/zxing/qrcode/decoder/FormatInformation.java @@ -17,7 +17,12 @@ package com.google.zxing.qrcode.decoder; /** + *

Encapsulates a QR Code's format information, including the data mask used and + * error correction level.

+ * * @author srowen@google.com (Sean Owen) + * @see DataMask + * @see ErrorCorrectionLevel */ final class FormatInformation { @@ -61,6 +66,7 @@ final class FormatInformation { {0x2BED, 0x1F}, }; + /** Offset i holds the number of 1 bits in the binary representation of i */ private static final int[] BITS_SET_IN_HALF_BYTE = new int[]{0, 1, 1, 2, 1, 2, 2, 3, 1, 2, 2, 3, 2, 3, 3, 4}; @@ -69,16 +75,15 @@ final class FormatInformation { private FormatInformation(int formatInfo) { // Bits 3,4 - errorCorrectionLevel = - ErrorCorrectionLevel.forBits((formatInfo >> 3) & 0x03); + errorCorrectionLevel = ErrorCorrectionLevel.forBits((formatInfo >> 3) & 0x03); // Bottom 3 bits dataMask = (byte) (formatInfo & 0x07); } static int numBitsDiffering(int a, int b) { - a ^= b; - return - BITS_SET_IN_HALF_BYTE[a & 0x0F] + + a ^= b; // a now has a 1 bit exactly where its bit differs with b's + // Count bits set quickly with a series of lookups: + return BITS_SET_IN_HALF_BYTE[a & 0x0F] + BITS_SET_IN_HALF_BYTE[(a >>> 4 & 0x0F)] + BITS_SET_IN_HALF_BYTE[(a >>> 8 & 0x0F)] + BITS_SET_IN_HALF_BYTE[(a >>> 12 & 0x0F)] + @@ -88,6 +93,11 @@ final class FormatInformation { BITS_SET_IN_HALF_BYTE[(a >>> 28 & 0x0F)]; } + /** + * + * @param rawFormatInfo + * @return + */ static FormatInformation decodeFormatInformation(int rawFormatInfo) { FormatInformation formatInfo = doDecodeFormatInformation(rawFormatInfo); if (formatInfo != null) { @@ -99,16 +109,17 @@ final class FormatInformation { return doDecodeFormatInformation(rawFormatInfo ^ FORMAT_INFO_MASK_QR); } - private static FormatInformation doDecodeFormatInformation( - int rawFormatInfo) { + private static FormatInformation doDecodeFormatInformation(int rawFormatInfo) { // Unmask: int unmaskedFormatInfo = rawFormatInfo ^ FORMAT_INFO_MASK_QR; + // Find the int in FORMAT_INFO_DECODE_LOOKUP with fewest bits differing int bestDifference = Integer.MAX_VALUE; int bestFormatInfo = 0; for (int i = 0; i < FORMAT_INFO_DECODE_LOOKUP.length; i++) { int[] decodeInfo = FORMAT_INFO_DECODE_LOOKUP[i]; int targetInfo = decodeInfo[0]; if (targetInfo == unmaskedFormatInfo) { + // Found an exact match return new FormatInformation(decodeInfo[1]); } int bitsDifference = numBitsDiffering(unmaskedFormatInfo, targetInfo); diff --git a/core/src/com/google/zxing/qrcode/decoder/Mode.java b/core/src/com/google/zxing/qrcode/decoder/Mode.java index 125c5cd3..455785ab 100644 --- a/core/src/com/google/zxing/qrcode/decoder/Mode.java +++ b/core/src/com/google/zxing/qrcode/decoder/Mode.java @@ -40,6 +40,11 @@ final class Mode { this.characterCountBitsForVersions = characterCountBitsForVersions; } + /** + * @param bits four bits encoding a QR Code data mode + * @return {@link Mode} encoded by these bits + * @throws ReaderException if bits do not correspond to a known mode + */ static Mode forBits(int bits) throws ReaderException { switch (bits) { case 0x0: @@ -57,6 +62,11 @@ final class Mode { } } + /** + * @param version version in question + * @return number of bits used, in this QR Code symbol {@link Version}, to encode the + * count of characters that will follow encoded in this {@link Mode} + */ int getCharacterCountBits(Version version) { int number = version.getVersionNumber(); int offset; diff --git a/core/src/com/google/zxing/qrcode/decoder/Version.java b/core/src/com/google/zxing/qrcode/decoder/Version.java index 80188ced..2bd6648f 100755 --- a/core/src/com/google/zxing/qrcode/decoder/Version.java +++ b/core/src/com/google/zxing/qrcode/decoder/Version.java @@ -87,41 +87,49 @@ public final class Version { return ecBlocks[ecLevel.ordinal()]; } - public static Version getProvisionalVersionForDimension(int dimension) - throws ReaderException { + /** + *

Deduces version information purely from QR Code dimensions.

+ * + * @param dimension dimension in modules + * @return {@link Version} for a QR Code of that dimension + * @throws ReaderException if dimension is not 1 mod 4 + */ + public static Version getProvisionalVersionForDimension(int dimension) throws ReaderException { if (dimension % 4 != 1) { throw new ReaderException("Dimension must be 1 mod 4"); } return getVersionForNumber((dimension - 17) >> 2); } - public static Version getVersionForNumber(int versionNumber) - throws ReaderException { + public static Version getVersionForNumber(int versionNumber) throws ReaderException { if (versionNumber < 1 || versionNumber > 40) { - throw new ReaderException( - "versionNumber must be between 1 and 40"); + throw new ReaderException("versionNumber must be between 1 and 40"); } return VERSIONS[versionNumber - 1]; } - static Version decodeVersionInformation(int versionBits) - throws ReaderException { + static Version decodeVersionInformation(int versionBits) throws ReaderException { int bestDifference = Integer.MAX_VALUE; int bestVersion = 0; for (int i = 0; i < VERSION_DECODE_INFO.length; i++) { int targetVersion = VERSION_DECODE_INFO[i]; + // Do the version info bits match exactly? done. if (targetVersion == versionBits) { return getVersionForNumber(i + 7); } - int bitsDifference = - FormatInformation.numBitsDiffering(versionBits, targetVersion); + // Otherwise see if this is the closest to a real version info bit string + // we have seen so far + int bitsDifference = FormatInformation.numBitsDiffering(versionBits, targetVersion); if (bitsDifference < bestDifference) { bestVersion = i + 7; } } + // We can tolerate up to 3 bits of error since no two version info codewords will + // differ in less than 4 bits. if (bestDifference <= 3) { return getVersionForNumber(bestVersion); } + // If we didn't find a close enough match, fail return null; } -- 2.20.1