More javadoc

git-svn-id: https://zxing.googlecode.com/svn/trunk@13 59b500cc-1b3d-0410-9834-0bbf25fbcc57
This commit is contained in:
srowen 2007-11-07 06:40:04 +00:00
parent cc66a80d3e
commit d6cfb083a1
10 changed files with 194 additions and 42 deletions

View file

@ -29,8 +29,8 @@ final class BitMatrixParser {
private FormatInformation parsedFormatInfo; private FormatInformation parsedFormatInfo;
/** /**
* @throws com.google.zxing.ReaderException * @param bitMatrix {@link BitMatrix} to parse
* if dimension is not >= 21 and 1 mod 4 * @throws ReaderException if dimension is not >= 21 and 1 mod 4
*/ */
BitMatrixParser(BitMatrix bitMatrix) throws ReaderException { BitMatrixParser(BitMatrix bitMatrix) throws ReaderException {
int dimension = bitMatrix.getDimension(); int dimension = bitMatrix.getDimension();
@ -40,6 +40,13 @@ final class BitMatrixParser {
this.bitMatrix = bitMatrix; this.bitMatrix = bitMatrix;
} }
/**
* <p>Reads format information from one of its two locations within the QR Code.</p>
*
* @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 { FormatInformation readFormatInformation() throws ReaderException {
if (parsedFormatInfo != null) { if (parsedFormatInfo != null) {
@ -83,6 +90,13 @@ final class BitMatrixParser {
throw new ReaderException("Could not decode format information"); throw new ReaderException("Could not decode format information");
} }
/**
* <p>Reads version information from one of its two locations within the QR Code.</p>
*
* @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 { Version readVersion() throws ReaderException {
if (parsedVersion != null) { if (parsedVersion != null) {
@ -130,11 +144,21 @@ final class BitMatrixParser {
return bitMatrix.get(i, j) ? (versionBits << 1) | 0x1 : versionBits << 1; return bitMatrix.get(i, j) ? (versionBits << 1) | 0x1 : versionBits << 1;
} }
/**
* <p>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.</p>
*
* @return bytes encoded within the QR Code
* @throws ReaderException if the exact number of bytes expected is not read
*/
byte[] readCodewords() throws ReaderException { byte[] readCodewords() throws ReaderException {
FormatInformation formatInfo = readFormatInformation(); FormatInformation formatInfo = readFormatInformation();
Version version = readVersion(); 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()); DataMask dataMask = DataMask.forReference((int) formatInfo.getDataMask());
int dimension = bitMatrix.getDimension(); int dimension = bitMatrix.getDimension();
dataMask.unmaskBitMatrix(bitMatrix.getBits(), dimension); dataMask.unmaskBitMatrix(bitMatrix.getBits(), dimension);
@ -146,21 +170,26 @@ final class BitMatrixParser {
int resultOffset = 0; int resultOffset = 0;
int currentByte = 0; int currentByte = 0;
int bitsRead = 0; int bitsRead = 0;
// Read columns in pairs, from right to left
for (int j = dimension - 1; j > 0; j -= 2) { for (int j = dimension - 1; j > 0; j -= 2) {
if (j == 6) { if (j == 6) {
// Skip whole column with vertical alignment pattern; // Skip whole column with vertical alignment pattern;
// saves time and makes the other code proceed more cleanly // saves time and makes the other code proceed more cleanly
j--; j--;
} }
// Read alternatingly from bottom to top then top to bottom
for (int count = 0; count < dimension; count++) { for (int count = 0; count < dimension; count++) {
int i = readingUp ? dimension - 1 - count : count; int i = readingUp ? dimension - 1 - count : count;
for (int col = 0; col < 2; col++) { for (int col = 0; col < 2; col++) {
// Ignore bits covered by the function pattern
if (!functionPattern.get(i, j - col)) { if (!functionPattern.get(i, j - col)) {
// Read a bit
bitsRead++; bitsRead++;
currentByte <<= 1; currentByte <<= 1;
if (bitMatrix.get(i, j - col)) { if (bitMatrix.get(i, j - col)) {
currentByte |= 1; currentByte |= 1;
} }
// If we've made a whole byte, save it off
if (bitsRead == 8) { if (bitsRead == 8) {
result[resultOffset++] = (byte) currentByte; result[resultOffset++] = (byte) currentByte;
bitsRead = 0; bitsRead = 0;

View file

@ -17,8 +17,10 @@
package com.google.zxing.qrcode.decoder; package com.google.zxing.qrcode.decoder;
/** /**
* This provides an easy abstraction to read bits at a time from a sequence of bytes, where the * <p>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. * number of bits read is not often a multiple of 8.</p>
*
* <p>This class is not thread-safe.</p>
* *
* @author srowen@google.com (Sean Owen) * @author srowen@google.com (Sean Owen)
*/ */
@ -28,11 +30,18 @@ final class BitSource {
private int byteOffset; private int byteOffset;
private int bitOffset; 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) { BitSource(byte[] bytes) {
this.bytes = 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] * @throws IllegalArgumentException if numBits isn't in [1,32]
*/ */
int readBits(int numBits) { int readBits(int numBits) {
@ -77,6 +86,9 @@ final class BitSource {
return result; return result;
} }
/**
* @return number of bits that can be read successfully
*/
int available() { int available() {
return 8 * (bytes.length - byteOffset) - bitOffset; return 8 * (bytes.length - byteOffset) - bitOffset;
} }

View file

@ -17,6 +17,10 @@
package com.google.zxing.qrcode.decoder; package com.google.zxing.qrcode.decoder;
/** /**
* <p>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.</p>
*
* @author srowen@google.com (Sean Owen) * @author srowen@google.com (Sean Owen)
*/ */
final class DataBlock { final class DataBlock {
@ -29,15 +33,32 @@ final class DataBlock {
this.codewords = codewords; this.codewords = codewords;
} }
/**
* <p>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.</p>
*
* @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, static DataBlock[] getDataBlocks(byte[] rawCodewords,
Version version, Version version,
ErrorCorrectionLevel ecLevel) { 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); Version.ECBlocks ecBlocks = version.getECBlocksForLevel(ecLevel);
// First count the total number of data blocks
int totalBlocks = 0; int totalBlocks = 0;
Version.ECB[] ecBlockArray = ecBlocks.getECBlocks(); Version.ECB[] ecBlockArray = ecBlocks.getECBlocks();
for (int i = 0; i < ecBlockArray.length; i++) { for (int i = 0; i < ecBlockArray.length; i++) {
totalBlocks += ecBlockArray[i].getCount(); totalBlocks += ecBlockArray[i].getCount();
} }
// Now establish DataBlocks of the appropriate size and number of data codewords
DataBlock[] result = new DataBlock[totalBlocks]; DataBlock[] result = new DataBlock[totalBlocks];
int numResultBlocks = 0; int numResultBlocks = 0;
for (int j = 0; j < ecBlockArray.length; j++) { for (int j = 0; j < ecBlockArray.length; j++) {
@ -45,8 +66,7 @@ final class DataBlock {
for (int i = 0; i < ecBlock.getCount(); i++) { for (int i = 0; i < ecBlock.getCount(); i++) {
int numDataCodewords = ecBlock.getDataCodewords(); int numDataCodewords = ecBlock.getDataCodewords();
int numBlockCodewords = ecBlocks.getECCodewords() + numDataCodewords; int numBlockCodewords = ecBlocks.getECCodewords() + numDataCodewords;
result[numResultBlocks++] = result[numResultBlocks++] = new DataBlock(numDataCodewords, new byte[numBlockCodewords]);
new DataBlock(numDataCodewords, new byte[numBlockCodewords]);
} }
} }
@ -55,21 +75,18 @@ final class DataBlock {
int shorterBlocksTotalCodewords = result[0].codewords.length; int shorterBlocksTotalCodewords = result[0].codewords.length;
int longerBlocksStartAt = result.length - 1; int longerBlocksStartAt = result.length - 1;
while (longerBlocksStartAt >= 0) { while (longerBlocksStartAt >= 0) {
int numCodewords = int numCodewords = result[longerBlocksStartAt].codewords.length;
result[longerBlocksStartAt].codewords.length;
if (numCodewords == shorterBlocksTotalCodewords) { if (numCodewords == shorterBlocksTotalCodewords) {
break; break;
} }
if (numCodewords != shorterBlocksTotalCodewords + 1) { if (numCodewords != shorterBlocksTotalCodewords + 1) {
throw new IllegalStateException( throw new IllegalStateException("Data block sizes differ by more than 1");
"Data block sizes differ by more than 1");
} }
longerBlocksStartAt--; longerBlocksStartAt--;
} }
longerBlocksStartAt++; longerBlocksStartAt++;
int shorterBlocksNumDataCodewords = int shorterBlocksNumDataCodewords = shorterBlocksTotalCodewords - ecBlocks.getECCodewords();
shorterBlocksTotalCodewords - ecBlocks.getECCodewords();
// The last elements of result may be 1 element longer; // The last elements of result may be 1 element longer;
// first fill out as many elements as all of them have // first fill out as many elements as all of them have
int rawCodewordsOffset = 0; int rawCodewordsOffset = 0;
@ -80,8 +97,7 @@ final class DataBlock {
} }
// Fill out the last data block in the longer ones // Fill out the last data block in the longer ones
for (int j = longerBlocksStartAt; j < numResultBlocks; j++) { for (int j = longerBlocksStartAt; j < numResultBlocks; j++) {
result[j].codewords[shorterBlocksNumDataCodewords] = result[j].codewords[shorterBlocksNumDataCodewords] = rawCodewords[rawCodewordsOffset++];
rawCodewords[rawCodewordsOffset++];
} }
// Now add in error correction blocks // Now add in error correction blocks
int max = result[0].codewords.length; int max = result[0].codewords.length;

View file

@ -17,13 +17,13 @@
package com.google.zxing.qrcode.decoder; package com.google.zxing.qrcode.decoder;
/** /**
* Encapsulates data masks for the data bits in a QR code, per ISO 18004:2006 6.8. Implementations * <p>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, * 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 * 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.</p>
* *
* Note that the diagram in section 6.8.1 is misleading since it indicates that i is column position * <p>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. * and j is row position. In fact, as the text says, i is row position and j is column position.</p>
* *
* @author srowen@google.com (Sean Owen) * @author srowen@google.com (Sean Owen)
*/ */
@ -46,8 +46,20 @@ abstract class DataMask {
private DataMask() { private DataMask() {
} }
/**
* <p>Implementations of this method reverse the data masking process applied to a QR Code and
* make its bits ready to read.</p>
*
* @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); 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) { static DataMask forReference(int reference) {
if (reference < 0 || reference > 7) { if (reference < 0 || reference > 7) {
throw new IllegalArgumentException(); throw new IllegalArgumentException();

View file

@ -21,7 +21,10 @@ import com.google.zxing.ReaderException;
import java.io.UnsupportedEncodingException; import java.io.UnsupportedEncodingException;
/** /**
* See ISO 18004:2006, 6.4.3 - 6.4.7 * <p>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.</p>
*
* <p>See ISO 18004:2006, 6.4.3 - 6.4.7</p>
* *
* @author srowen@google.com (Sean Owen) * @author srowen@google.com (Sean Owen)
*/ */
@ -54,8 +57,9 @@ final class DecodedBitStreamParser {
Mode mode; Mode mode;
do { do {
// While still another segment to read... // 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)) { if (!mode.equals(Mode.TERMINATOR)) {
// How many characters will follow, encoded in this mode?
int count = bits.readBits(mode.getCharacterCountBits(version)); int count = bits.readBits(mode.getCharacterCountBits(version));
if (mode.equals(Mode.NUMERIC)) { if (mode.equals(Mode.NUMERIC)) {
decodeNumericSegment(bits, result, count); decodeNumericSegment(bits, result, count);
@ -66,11 +70,12 @@ final class DecodedBitStreamParser {
} else if (mode.equals(Mode.KANJI)) { } else if (mode.equals(Mode.KANJI)) {
decodeKanjiSegment(bits, result, count); decodeKanjiSegment(bits, result, count);
} else { } else {
throw new ReaderException("Unsupported mode indicator: " + mode); throw new ReaderException("Unsupported mode indicator");
} }
} }
} while (!mode.equals(Mode.TERMINATOR)); } 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(); int bitsLeft = bits.available();
if (bitsLeft > 0) { if (bitsLeft > 0) {
@ -85,9 +90,12 @@ final class DecodedBitStreamParser {
private static void decodeKanjiSegment(BitSource bits, private static void decodeKanjiSegment(BitSource bits,
StringBuffer result, StringBuffer result,
int count) throws ReaderException { 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]; byte[] buffer = new byte[2 * count];
int offset = 0; int offset = 0;
while (count > 0) { while (count > 0) {
// Each 13 bits encodes a 2-byte character
int twoBytes = bits.readBits(13); int twoBytes = bits.readBits(13);
int assembledTwoBytes = ((twoBytes / 0x0C0) << 8) | (twoBytes % 0x0C0); int assembledTwoBytes = ((twoBytes / 0x0C0) << 8) | (twoBytes % 0x0C0);
if (assembledTwoBytes < 0x01F00) { if (assembledTwoBytes < 0x01F00) {
@ -144,7 +152,7 @@ final class DecodedBitStreamParser {
count -= 2; count -= 2;
} }
if (count == 1) { if (count == 1) {
// special case on char left // special case: one character left
result.append(ALPHANUMERIC_CHARS[bits.readBits(6)]); result.append(ALPHANUMERIC_CHARS[bits.readBits(6)]);
} }
} }
@ -152,7 +160,9 @@ final class DecodedBitStreamParser {
private static void decodeNumericSegment(BitSource bits, private static void decodeNumericSegment(BitSource bits,
StringBuffer result, StringBuffer result,
int count) throws ReaderException { int count) throws ReaderException {
// Read three digits at a time
while (count >= 3) { while (count >= 3) {
// Each 10 bits encodes three digits
int threeDigitsBits = bits.readBits(10); int threeDigitsBits = bits.readBits(10);
if (threeDigitsBits >= 1000) { if (threeDigitsBits >= 1000) {
throw new ReaderException("Illegal value for 3-digit unit: " + threeDigitsBits); throw new ReaderException("Illegal value for 3-digit unit: " + threeDigitsBits);
@ -163,6 +173,7 @@ final class DecodedBitStreamParser {
count -= 3; count -= 3;
} }
if (count == 2) { if (count == 2) {
// Two digits left over to read, encoded in 7 bits
int twoDigitsBits = bits.readBits(7); int twoDigitsBits = bits.readBits(7);
if (twoDigitsBits >= 100) { if (twoDigitsBits >= 100) {
throw new ReaderException("Illegal value for 2-digit unit: " + twoDigitsBits); 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]);
result.append(ALPHANUMERIC_CHARS[twoDigitsBits % 10]); result.append(ALPHANUMERIC_CHARS[twoDigitsBits % 10]);
} else if (count == 1) { } else if (count == 1) {
// One digit left over to read
int digitBits = bits.readBits(4); int digitBits = bits.readBits(4);
if (digitBits >= 10) { if (digitBits >= 10) {
throw new ReaderException("Illegal value for digit unit: " + digitBits); throw new ReaderException("Illegal value for digit unit: " + digitBits);

View file

@ -22,6 +22,9 @@ import com.google.zxing.common.reedsolomon.ReedSolomonDecoder;
import com.google.zxing.common.reedsolomon.ReedSolomonException; import com.google.zxing.common.reedsolomon.ReedSolomonException;
/** /**
* <p>The main class which implements QR Code decoding -- as opposed to locating and extracting
* the QR Code from an image.</p>
*
* @author srowen@google.com (Sean Owen) * @author srowen@google.com (Sean Owen)
*/ */
public final class Decoder { public final class Decoder {
@ -29,6 +32,14 @@ public final class Decoder {
private Decoder() { private Decoder() {
} }
/**
* <p>Convenience method that can decode a QR Code represented as a 2D array of booleans.
* "true" is taken to mean a black module.</p>
*
* @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 { public static String decode(boolean[][] image) throws ReaderException {
int dimension = image.length; int dimension = image.length;
BitMatrix bits = new BitMatrix(dimension); BitMatrix bits = new BitMatrix(dimension);
@ -42,18 +53,34 @@ public final class Decoder {
return decode(bits); return decode(bits);
} }
/**
* <p>Decodes a QR Code represented as a {@link BitMatrix}. A 1 or "true" is taken to mean a black module.</p>
*
* @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 { public static String decode(BitMatrix bits) throws ReaderException {
// Construct a parser and read version, error-correction level
BitMatrixParser parser = new BitMatrixParser(bits); BitMatrixParser parser = new BitMatrixParser(bits);
Version version = parser.readVersion(); Version version = parser.readVersion();
ErrorCorrectionLevel ecLevel = parser.readFormatInformation().getErrorCorrectionLevel(); ErrorCorrectionLevel ecLevel = parser.readFormatInformation().getErrorCorrectionLevel();
// Read codewords
byte[] codewords = parser.readCodewords(); byte[] codewords = parser.readCodewords();
// Separate into data blocks
DataBlock[] dataBlocks = DataBlock.getDataBlocks(codewords, version, ecLevel); DataBlock[] dataBlocks = DataBlock.getDataBlocks(codewords, version, ecLevel);
// Count total number of data bytes
int totalBytes = 0; int totalBytes = 0;
for (int i = 0; i < dataBlocks.length; i++) { for (int i = 0; i < dataBlocks.length; i++) {
totalBytes += dataBlocks[i].getNumDataCodewords(); totalBytes += dataBlocks[i].getNumDataCodewords();
} }
byte[] resultBytes = new byte[totalBytes]; byte[] resultBytes = new byte[totalBytes];
int resultOffset = 0; int resultOffset = 0;
// Error-correct and copy data blocks together into a stream of bytes
for (int j = 0; j < dataBlocks.length; j++) { for (int j = 0; j < dataBlocks.length; j++) {
DataBlock dataBlock = dataBlocks[j]; DataBlock dataBlock = dataBlocks[j];
byte[] codewordBytes = dataBlock.getCodewords(); 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); return DecodedBitStreamParser.decode(resultBytes, version);
} }
private static void correctErrors(byte[] codewordBytes, int numDataCodewords) /**
throws ReaderException { * <p>Given data and error-correction codewords received, possibly corrupted by errors, attempts to
* correct the errors in-place using Reed-Solomon error correction.</p>
*
* @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; int numCodewords = codewordBytes.length;
// First read into an array of ints
int[] codewordsInts = new int[numCodewords]; int[] codewordsInts = new int[numCodewords];
for (int i = 0; i < numCodewords; i++) { for (int i = 0; i < numCodewords; i++) {
codewordsInts[i] = codewordBytes[i] & 0xFF; codewordsInts[i] = codewordBytes[i] & 0xFF;
@ -80,6 +116,8 @@ public final class Decoder {
} catch (ReedSolomonException rse) { } catch (ReedSolomonException rse) {
throw new ReaderException(rse.toString()); 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++) { for (int i = 0; i < numDataCodewords; i++) {
codewordBytes[i] = (byte) codewordsInts[i]; codewordBytes[i] = (byte) codewordsInts[i];
} }

View file

@ -39,7 +39,7 @@ final class ErrorCorrectionLevel {
private final int ordinal; private final int ordinal;
private ErrorCorrectionLevel(final int ordinal) { private ErrorCorrectionLevel(int ordinal) {
this.ordinal = ordinal; this.ordinal = ordinal;
} }
@ -47,6 +47,10 @@ final class ErrorCorrectionLevel {
return ordinal; 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) { static ErrorCorrectionLevel forBits(int bits) {
return FOR_BITS[bits]; return FOR_BITS[bits];
} }

View file

@ -17,7 +17,12 @@
package com.google.zxing.qrcode.decoder; package com.google.zxing.qrcode.decoder;
/** /**
* <p>Encapsulates a QR Code's format information, including the data mask used and
* error correction level.</p>
*
* @author srowen@google.com (Sean Owen) * @author srowen@google.com (Sean Owen)
* @see DataMask
* @see ErrorCorrectionLevel
*/ */
final class FormatInformation { final class FormatInformation {
@ -61,6 +66,7 @@ final class FormatInformation {
{0x2BED, 0x1F}, {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 = 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}; 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) { private FormatInformation(int formatInfo) {
// Bits 3,4 // Bits 3,4
errorCorrectionLevel = errorCorrectionLevel = ErrorCorrectionLevel.forBits((formatInfo >> 3) & 0x03);
ErrorCorrectionLevel.forBits((formatInfo >> 3) & 0x03);
// Bottom 3 bits // Bottom 3 bits
dataMask = (byte) (formatInfo & 0x07); dataMask = (byte) (formatInfo & 0x07);
} }
static int numBitsDiffering(int a, int b) { static int numBitsDiffering(int a, int b) {
a ^= b; a ^= b; // a now has a 1 bit exactly where its bit differs with b's
return // Count bits set quickly with a series of lookups:
BITS_SET_IN_HALF_BYTE[a & 0x0F] + return BITS_SET_IN_HALF_BYTE[a & 0x0F] +
BITS_SET_IN_HALF_BYTE[(a >>> 4 & 0x0F)] + BITS_SET_IN_HALF_BYTE[(a >>> 4 & 0x0F)] +
BITS_SET_IN_HALF_BYTE[(a >>> 8 & 0x0F)] + BITS_SET_IN_HALF_BYTE[(a >>> 8 & 0x0F)] +
BITS_SET_IN_HALF_BYTE[(a >>> 12 & 0x0F)] + BITS_SET_IN_HALF_BYTE[(a >>> 12 & 0x0F)] +
@ -88,6 +93,11 @@ final class FormatInformation {
BITS_SET_IN_HALF_BYTE[(a >>> 28 & 0x0F)]; BITS_SET_IN_HALF_BYTE[(a >>> 28 & 0x0F)];
} }
/**
*
* @param rawFormatInfo
* @return
*/
static FormatInformation decodeFormatInformation(int rawFormatInfo) { static FormatInformation decodeFormatInformation(int rawFormatInfo) {
FormatInformation formatInfo = doDecodeFormatInformation(rawFormatInfo); FormatInformation formatInfo = doDecodeFormatInformation(rawFormatInfo);
if (formatInfo != null) { if (formatInfo != null) {
@ -99,16 +109,17 @@ final class FormatInformation {
return doDecodeFormatInformation(rawFormatInfo ^ FORMAT_INFO_MASK_QR); return doDecodeFormatInformation(rawFormatInfo ^ FORMAT_INFO_MASK_QR);
} }
private static FormatInformation doDecodeFormatInformation( private static FormatInformation doDecodeFormatInformation(int rawFormatInfo) {
int rawFormatInfo) {
// Unmask: // Unmask:
int unmaskedFormatInfo = rawFormatInfo ^ FORMAT_INFO_MASK_QR; 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 bestDifference = Integer.MAX_VALUE;
int bestFormatInfo = 0; int bestFormatInfo = 0;
for (int i = 0; i < FORMAT_INFO_DECODE_LOOKUP.length; i++) { for (int i = 0; i < FORMAT_INFO_DECODE_LOOKUP.length; i++) {
int[] decodeInfo = FORMAT_INFO_DECODE_LOOKUP[i]; int[] decodeInfo = FORMAT_INFO_DECODE_LOOKUP[i];
int targetInfo = decodeInfo[0]; int targetInfo = decodeInfo[0];
if (targetInfo == unmaskedFormatInfo) { if (targetInfo == unmaskedFormatInfo) {
// Found an exact match
return new FormatInformation(decodeInfo[1]); return new FormatInformation(decodeInfo[1]);
} }
int bitsDifference = numBitsDiffering(unmaskedFormatInfo, targetInfo); int bitsDifference = numBitsDiffering(unmaskedFormatInfo, targetInfo);

View file

@ -40,6 +40,11 @@ final class Mode {
this.characterCountBitsForVersions = characterCountBitsForVersions; 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 { static Mode forBits(int bits) throws ReaderException {
switch (bits) { switch (bits) {
case 0x0: 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 getCharacterCountBits(Version version) {
int number = version.getVersionNumber(); int number = version.getVersionNumber();
int offset; int offset;

View file

@ -87,41 +87,49 @@ public final class Version {
return ecBlocks[ecLevel.ordinal()]; return ecBlocks[ecLevel.ordinal()];
} }
public static Version getProvisionalVersionForDimension(int dimension) /**
throws ReaderException { * <p>Deduces version information purely from QR Code dimensions.</p>
*
* @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) { if (dimension % 4 != 1) {
throw new ReaderException("Dimension must be 1 mod 4"); throw new ReaderException("Dimension must be 1 mod 4");
} }
return getVersionForNumber((dimension - 17) >> 2); return getVersionForNumber((dimension - 17) >> 2);
} }
public static Version getVersionForNumber(int versionNumber) public static Version getVersionForNumber(int versionNumber) throws ReaderException {
throws ReaderException {
if (versionNumber < 1 || versionNumber > 40) { if (versionNumber < 1 || versionNumber > 40) {
throw new ReaderException( throw new ReaderException("versionNumber must be between 1 and 40");
"versionNumber must be between 1 and 40");
} }
return VERSIONS[versionNumber - 1]; return VERSIONS[versionNumber - 1];
} }
static Version decodeVersionInformation(int versionBits) static Version decodeVersionInformation(int versionBits) throws ReaderException {
throws ReaderException {
int bestDifference = Integer.MAX_VALUE; int bestDifference = Integer.MAX_VALUE;
int bestVersion = 0; int bestVersion = 0;
for (int i = 0; i < VERSION_DECODE_INFO.length; i++) { for (int i = 0; i < VERSION_DECODE_INFO.length; i++) {
int targetVersion = VERSION_DECODE_INFO[i]; int targetVersion = VERSION_DECODE_INFO[i];
// Do the version info bits match exactly? done.
if (targetVersion == versionBits) { if (targetVersion == versionBits) {
return getVersionForNumber(i + 7); return getVersionForNumber(i + 7);
} }
int bitsDifference = // Otherwise see if this is the closest to a real version info bit string
FormatInformation.numBitsDiffering(versionBits, targetVersion); // we have seen so far
int bitsDifference = FormatInformation.numBitsDiffering(versionBits, targetVersion);
if (bitsDifference < bestDifference) { if (bitsDifference < bestDifference) {
bestVersion = i + 7; 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) { if (bestDifference <= 3) {
return getVersionForNumber(bestVersion); return getVersionForNumber(bestVersion);
} }
// If we didn't find a close enough match, fail
return null; return null;
} }