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;
/**
* @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;
}
/**
* <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 {
if (parsedFormatInfo != null) {
@ -83,6 +90,13 @@ final class BitMatrixParser {
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 {
if (parsedVersion != null) {
@ -130,11 +144,21 @@ final class BitMatrixParser {
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 {
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;

View file

@ -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.
* <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.</p>
*
* <p>This class is not thread-safe.</p>
*
* @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;
}

View file

@ -17,6 +17,10 @@
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)
*/
final class DataBlock {
@ -29,15 +33,32 @@ final class DataBlock {
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,
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;

View file

@ -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
* <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,
* 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
* and j is row position. In fact, as the text says, i is row position and j 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.</p>
*
* @author srowen@google.com (Sean Owen)
*/
@ -46,8 +46,20 @@ abstract class 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);
/**
* @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();

View file

@ -21,7 +21,10 @@ import com.google.zxing.ReaderException;
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)
*/
@ -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);

View file

@ -22,6 +22,9 @@ import com.google.zxing.common.reedsolomon.ReedSolomonDecoder;
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)
*/
public final class Decoder {
@ -29,6 +32,14 @@ public final class 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 {
int dimension = image.length;
BitMatrix bits = new BitMatrix(dimension);
@ -42,18 +53,34 @@ public final class Decoder {
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 {
// 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 {
/**
* <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;
// 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];
}

View file

@ -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];
}

View file

@ -17,7 +17,12 @@
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)
* @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);

View file

@ -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;

View file

@ -87,41 +87,49 @@ public final class Version {
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) {
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;
}