mirror of
https://github.com/zxing/zxing.git
synced 2025-03-05 20:48:51 -08:00
More javadoc
git-svn-id: https://zxing.googlecode.com/svn/trunk@13 59b500cc-1b3d-0410-9834-0bbf25fbcc57
This commit is contained in:
parent
cc66a80d3e
commit
d6cfb083a1
|
@ -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;
|
||||||
|
|
|
@ -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;
|
||||||
}
|
}
|
||||||
|
|
|
@ -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;
|
||||||
|
|
|
@ -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();
|
||||||
|
|
|
@ -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);
|
||||||
|
|
|
@ -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];
|
||||||
}
|
}
|
||||||
|
|
|
@ -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];
|
||||||
}
|
}
|
||||||
|
|
|
@ -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);
|
||||||
|
|
|
@ -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;
|
||||||
|
|
|
@ -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;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
Loading…
Reference in a new issue