mirror of
https://github.com/zxing/zxing.git
synced 2025-01-13 04:07:27 -08:00
Another attack on integrating encoder and decoder: Version is done. Attempted to rationalize encoding API to deal with strings, not bytes. More code style changes like removal of 'final' on locals and arguments.
git-svn-id: https://zxing.googlecode.com/svn/trunk@781 59b500cc-1b3d-0410-9834-0bbf25fbcc57
This commit is contained in:
parent
1f2699c196
commit
6c5b6849b7
|
@ -38,11 +38,6 @@ public final class ByteArray {
|
|||
this.size = size;
|
||||
}
|
||||
|
||||
public ByteArray(String string) {
|
||||
bytes = string.getBytes();
|
||||
size = bytes.length;
|
||||
}
|
||||
|
||||
public ByteArray(byte[] byteArray) {
|
||||
bytes = byteArray;
|
||||
size = bytes.length;
|
||||
|
|
|
@ -68,7 +68,7 @@ public final class QRCodeWriter implements Writer {
|
|||
}
|
||||
|
||||
QRCode code = new QRCode();
|
||||
Encoder.encode(new ByteArray(contents), errorCorrectionLevel, code);
|
||||
Encoder.encode(contents, errorCorrectionLevel, code);
|
||||
return renderResult(code, width, height);
|
||||
}
|
||||
|
||||
|
|
|
@ -65,7 +65,7 @@ final class DataBlock {
|
|||
Version.ECB ecBlock = ecBlockArray[j];
|
||||
for (int i = 0; i < ecBlock.getCount(); i++) {
|
||||
int numDataCodewords = ecBlock.getDataCodewords();
|
||||
int numBlockCodewords = ecBlocks.getECCodewords() + numDataCodewords;
|
||||
int numBlockCodewords = ecBlocks.getECCodewordsPerBlock() + numDataCodewords;
|
||||
result[numResultBlocks++] = new DataBlock(numDataCodewords, new byte[numBlockCodewords]);
|
||||
}
|
||||
}
|
||||
|
@ -86,7 +86,7 @@ final class DataBlock {
|
|||
}
|
||||
longerBlocksStartAt++;
|
||||
|
||||
int shorterBlocksNumDataCodewords = shorterBlocksTotalCodewords - ecBlocks.getECCodewords();
|
||||
int shorterBlocksNumDataCodewords = shorterBlocksTotalCodewords - ecBlocks.getECCodewordsPerBlock();
|
||||
// The last elements of result may be 1 element longer;
|
||||
// first fill out as many elements as all of them have
|
||||
int rawCodewordsOffset = 0;
|
||||
|
|
|
@ -57,7 +57,7 @@ public final class Version {
|
|||
this.alignmentPatternCenters = alignmentPatternCenters;
|
||||
this.ecBlocks = new ECBlocks[]{ecBlocks1, ecBlocks2, ecBlocks3, ecBlocks4};
|
||||
int total = 0;
|
||||
int ecCodewords = ecBlocks1.getECCodewords();
|
||||
int ecCodewords = ecBlocks1.getECCodewordsPerBlock();
|
||||
ECB[] ecbArray = ecBlocks1.getECBlocks();
|
||||
for (int i = 0; i < ecbArray.length; i++) {
|
||||
ECB ecBlock = ecbArray[i];
|
||||
|
@ -82,7 +82,7 @@ public final class Version {
|
|||
return 17 + 4 * versionNumber;
|
||||
}
|
||||
|
||||
ECBlocks getECBlocksForLevel(ErrorCorrectionLevel ecLevel) {
|
||||
public ECBlocks getECBlocksForLevel(ErrorCorrectionLevel ecLevel) {
|
||||
return ecBlocks[ecLevel.ordinal()];
|
||||
}
|
||||
|
||||
|
@ -107,7 +107,7 @@ public final class Version {
|
|||
return VERSIONS[versionNumber - 1];
|
||||
}
|
||||
|
||||
static Version decodeVersionInformation(int versionBits) throws ReaderException {
|
||||
static Version decodeVersionInformation(int versionBits) {
|
||||
int bestDifference = Integer.MAX_VALUE;
|
||||
int bestVersion = 0;
|
||||
for (int i = 0; i < VERSION_DECODE_INFO.length; i++) {
|
||||
|
@ -180,25 +180,37 @@ public final class Version {
|
|||
* each set of blocks. It also holds the number of error-correction codewords per block since it
|
||||
* will be the same across all blocks within one version.</p>
|
||||
*/
|
||||
static final class ECBlocks {
|
||||
private final int ecCodewords;
|
||||
public static final class ECBlocks {
|
||||
private final int ecCodewordsPerBlock;
|
||||
private final ECB[] ecBlocks;
|
||||
|
||||
private ECBlocks(int ecCodewords, ECB ecBlocks) {
|
||||
this.ecCodewords = ecCodewords;
|
||||
private ECBlocks(int ecCodewordsPerBlock, ECB ecBlocks) {
|
||||
this.ecCodewordsPerBlock = ecCodewordsPerBlock;
|
||||
this.ecBlocks = new ECB[]{ecBlocks};
|
||||
}
|
||||
|
||||
private ECBlocks(int ecCodewords, ECB ecBlocks1, ECB ecBlocks2) {
|
||||
this.ecCodewords = ecCodewords;
|
||||
private ECBlocks(int ecCodewordsPerBlock, ECB ecBlocks1, ECB ecBlocks2) {
|
||||
this.ecCodewordsPerBlock = ecCodewordsPerBlock;
|
||||
this.ecBlocks = new ECB[]{ecBlocks1, ecBlocks2};
|
||||
}
|
||||
|
||||
int getECCodewords() {
|
||||
return ecCodewords;
|
||||
public int getECCodewordsPerBlock() {
|
||||
return ecCodewordsPerBlock;
|
||||
}
|
||||
|
||||
ECB[] getECBlocks() {
|
||||
public int getNumBlocks() {
|
||||
int total = 0;
|
||||
for (int i = 0; i < ecBlocks.length; i++) {
|
||||
total += ecBlocks[i].getCount();
|
||||
}
|
||||
return total;
|
||||
}
|
||||
|
||||
public int getTotalECCodewords() {
|
||||
return ecCodewordsPerBlock * getNumBlocks();
|
||||
}
|
||||
|
||||
public ECB[] getECBlocks() {
|
||||
return ecBlocks;
|
||||
}
|
||||
}
|
||||
|
@ -208,20 +220,20 @@ public final class Version {
|
|||
* This includes the number of data codewords, and the number of times a block with these
|
||||
* parameters is used consecutively in the QR code version's format.</p>
|
||||
*/
|
||||
static final class ECB {
|
||||
final int count;
|
||||
final int dataCodewords;
|
||||
public static final class ECB {
|
||||
private final int count;
|
||||
private final int dataCodewords;
|
||||
|
||||
ECB(int count, int dataCodewords) {
|
||||
this.count = count;
|
||||
this.dataCodewords = dataCodewords;
|
||||
}
|
||||
|
||||
int getCount() {
|
||||
public int getCount() {
|
||||
return count;
|
||||
}
|
||||
|
||||
int getDataCodewords() {
|
||||
public int getDataCodewords() {
|
||||
return dataCodewords;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -38,11 +38,11 @@ public final class BitVector {
|
|||
}
|
||||
|
||||
// Return the bit value at "index".
|
||||
public int at(final int index) {
|
||||
public int at(int index) {
|
||||
if (index < 0 || index >= sizeInBits) {
|
||||
throw new IllegalArgumentException("Bad index: " + index);
|
||||
}
|
||||
final int value = array[index >> 3] & 0xff;
|
||||
int value = array[index >> 3] & 0xff;
|
||||
return (value >> (7 - (index & 0x7))) & 1;
|
||||
}
|
||||
|
||||
|
@ -57,11 +57,11 @@ public final class BitVector {
|
|||
}
|
||||
|
||||
// Append one bit to the bit vector.
|
||||
public void appendBit(final int bit) {
|
||||
public void appendBit(int bit) {
|
||||
if (!(bit == 0 || bit == 1)) {
|
||||
throw new IllegalArgumentException("Bad bit");
|
||||
}
|
||||
final int numBitsInLastByte = sizeInBits & 0x7;
|
||||
int numBitsInLastByte = sizeInBits & 0x7;
|
||||
// We'll expand array if we don't have bits in the last byte.
|
||||
if (numBitsInLastByte == 0) {
|
||||
appendByte(0);
|
||||
|
@ -79,7 +79,7 @@ public final class BitVector {
|
|||
// - appendBits(0x00, 1) adds 0.
|
||||
// - appendBits(0x00, 4) adds 0000.
|
||||
// - appendBits(0xff, 8) adds 11111111.
|
||||
public void appendBits(final int value, final int numBits) {
|
||||
public void appendBits(int value, int numBits) {
|
||||
if (numBits < 0 || numBits > 32) {
|
||||
throw new IllegalArgumentException("Num bits must be between 0 and 32");
|
||||
}
|
||||
|
@ -87,11 +87,11 @@ public final class BitVector {
|
|||
while (numBitsLeft > 0) {
|
||||
// Optimization for byte-oriented appending.
|
||||
if ((sizeInBits & 0x7) == 0 && numBitsLeft >= 8) {
|
||||
final int newByte = (value >> (numBitsLeft - 8)) & 0xff;
|
||||
int newByte = (value >> (numBitsLeft - 8)) & 0xff;
|
||||
appendByte(newByte);
|
||||
numBitsLeft -= 8;
|
||||
} else {
|
||||
final int bit = (value >> (numBitsLeft - 1)) & 1;
|
||||
int bit = (value >> (numBitsLeft - 1)) & 1;
|
||||
appendBit(bit);
|
||||
--numBitsLeft;
|
||||
}
|
||||
|
@ -99,7 +99,7 @@ public final class BitVector {
|
|||
}
|
||||
|
||||
// Append "bits".
|
||||
public void appendBitVector(final BitVector bits) {
|
||||
public void appendBitVector(BitVector bits) {
|
||||
int size = bits.size();
|
||||
for (int i = 0; i < size; ++i) {
|
||||
appendBit(bits.at(i));
|
||||
|
@ -107,7 +107,7 @@ public final class BitVector {
|
|||
}
|
||||
|
||||
// Modify the bit vector by XOR'ing with "other"
|
||||
public void xor(final BitVector other) {
|
||||
public void xor(BitVector other) {
|
||||
if (sizeInBits != other.size()) {
|
||||
throw new IllegalArgumentException("BitVector sizes don't match");
|
||||
}
|
||||
|
@ -124,9 +124,9 @@ public final class BitVector {
|
|||
StringBuffer result = new StringBuffer(sizeInBits);
|
||||
for (int i = 0; i < sizeInBits; ++i) {
|
||||
if (at(i) == 0) {
|
||||
result.append("0");
|
||||
result.append('0');
|
||||
} else if (at(i) == 1) {
|
||||
result.append("1");
|
||||
result.append('1');
|
||||
} else {
|
||||
throw new IllegalArgumentException("Byte isn't 0 or 1");
|
||||
}
|
||||
|
@ -144,7 +144,7 @@ public final class BitVector {
|
|||
// run out of room.
|
||||
private void appendByte(int value) {
|
||||
if ((sizeInBits >> 3) == array.length) {
|
||||
byte[] newArray = new byte[array.length * 2];
|
||||
byte[] newArray = new byte[(array.length << 1)];
|
||||
System.arraycopy(array, 0, newArray, 0, array.length);
|
||||
array = newArray;
|
||||
}
|
||||
|
|
|
@ -26,6 +26,7 @@ import com.google.zxing.qrcode.decoder.Mode;
|
|||
import com.google.zxing.qrcode.decoder.Version;
|
||||
|
||||
import java.util.Vector;
|
||||
import java.io.UnsupportedEncodingException;
|
||||
|
||||
/**
|
||||
* @author satorux@google.com (Satoru Takabayashi) - creator
|
||||
|
@ -43,70 +44,26 @@ public final class Encoder {
|
|||
25, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, -1, -1, -1, -1, -1, // 0x50-0x5f
|
||||
};
|
||||
|
||||
private static final class RSBlockInfo {
|
||||
|
||||
final int numBytes;
|
||||
final int[][] blockInfo;
|
||||
|
||||
public RSBlockInfo(int numBytes, int[][] blockInfo) {
|
||||
this.numBytes = numBytes;
|
||||
this.blockInfo = blockInfo;
|
||||
}
|
||||
|
||||
private Encoder() {
|
||||
}
|
||||
|
||||
// The table is from table 12 of JISX0510:2004 (p. 30). The "blockInfo" parts are ordered by
|
||||
// L, M, Q, H. Within each blockInfo, the 0th element is getNumECBytes, and the 1st element is
|
||||
// getNumRSBlocks. The table was doublechecked by komatsu.
|
||||
private static final RSBlockInfo[] RS_BLOCK_TABLE = {
|
||||
new RSBlockInfo( 26, new int[][]{ { 7, 1}, { 10, 1}, { 13, 1}, { 17, 1}}), // Version 1
|
||||
new RSBlockInfo( 44, new int[][]{ { 10, 1}, { 16, 1}, { 22, 1}, { 28, 1}}), // Version 2
|
||||
new RSBlockInfo( 70, new int[][]{ { 15, 1}, { 26, 1}, { 36, 2}, { 44, 2}}), // Version 3
|
||||
new RSBlockInfo( 100, new int[][]{ { 20, 1}, { 36, 2}, { 52, 2}, { 64, 4}}), // Version 4
|
||||
new RSBlockInfo( 134, new int[][]{ { 26, 1}, { 48, 2}, { 72, 4}, { 88, 4}}), // Version 5
|
||||
new RSBlockInfo( 172, new int[][]{ { 36, 2}, { 64, 4}, { 96, 4}, { 112, 4}}), // Version 6
|
||||
new RSBlockInfo( 196, new int[][]{ { 40, 2}, { 72, 4}, { 108, 6}, { 130, 5}}), // Version 7
|
||||
new RSBlockInfo( 242, new int[][]{ { 48, 2}, { 88, 4}, { 132, 6}, { 156, 6}}), // Version 8
|
||||
new RSBlockInfo( 292, new int[][]{ { 60, 2}, { 110, 5}, { 160, 8}, { 192, 8}}), // Version 9
|
||||
new RSBlockInfo( 346, new int[][]{ { 72, 4}, { 130, 5}, { 192, 8}, { 224, 8}}), // Version 10
|
||||
new RSBlockInfo( 404, new int[][]{ { 80, 4}, { 150, 5}, { 224, 8}, { 264, 11}}), // Version 11
|
||||
new RSBlockInfo( 466, new int[][]{ { 96, 4}, { 176, 8}, { 260, 10}, { 308, 11}}), // Version 12
|
||||
new RSBlockInfo( 532, new int[][]{ {104, 4}, { 198, 9}, { 288, 12}, { 352, 16}}), // Version 13
|
||||
new RSBlockInfo( 581, new int[][]{ {120, 4}, { 216, 9}, { 320, 16}, { 384, 16}}), // Version 14
|
||||
new RSBlockInfo( 655, new int[][]{ {132, 6}, { 240, 10}, { 360, 12}, { 432, 18}}), // Version 15
|
||||
new RSBlockInfo( 733, new int[][]{ {144, 6}, { 280, 10}, { 408, 17}, { 480, 16}}), // Version 16
|
||||
new RSBlockInfo( 815, new int[][]{ {168, 6}, { 308, 11}, { 448, 16}, { 532, 19}}), // Version 17
|
||||
new RSBlockInfo( 901, new int[][]{ {180, 6}, { 338, 13}, { 504, 18}, { 588, 21}}), // Version 18
|
||||
new RSBlockInfo( 991, new int[][]{ {196, 7}, { 364, 14}, { 546, 21}, { 650, 25}}), // Version 19
|
||||
new RSBlockInfo(1085, new int[][]{ {224, 8}, { 416, 16}, { 600, 20}, { 700, 25}}), // Version 20
|
||||
new RSBlockInfo(1156, new int[][]{ {224, 8}, { 442, 17}, { 644, 23}, { 750, 25}}), // Version 21
|
||||
new RSBlockInfo(1258, new int[][]{ {252, 9}, { 476, 17}, { 690, 23}, { 816, 34}}), // Version 22
|
||||
new RSBlockInfo(1364, new int[][]{ {270, 9}, { 504, 18}, { 750, 25}, { 900, 30}}), // Version 23
|
||||
new RSBlockInfo(1474, new int[][]{ {300, 10}, { 560, 20}, { 810, 27}, { 960, 32}}), // Version 24
|
||||
new RSBlockInfo(1588, new int[][]{ {312, 12}, { 588, 21}, { 870, 29}, {1050, 35}}), // Version 25
|
||||
new RSBlockInfo(1706, new int[][]{ {336, 12}, { 644, 23}, { 952, 34}, {1110, 37}}), // Version 26
|
||||
new RSBlockInfo(1828, new int[][]{ {360, 12}, { 700, 25}, {1020, 34}, {1200, 40}}), // Version 27
|
||||
new RSBlockInfo(1921, new int[][]{ {390, 13}, { 728, 26}, {1050, 35}, {1260, 42}}), // Version 28
|
||||
new RSBlockInfo(2051, new int[][]{ {420, 14}, { 784, 28}, {1140, 38}, {1350, 45}}), // Version 29
|
||||
new RSBlockInfo(2185, new int[][]{ {450, 15}, { 812, 29}, {1200, 40}, {1440, 48}}), // Version 30
|
||||
new RSBlockInfo(2323, new int[][]{ {480, 16}, { 868, 31}, {1290, 43}, {1530, 51}}), // Version 31
|
||||
new RSBlockInfo(2465, new int[][]{ {510, 17}, { 924, 33}, {1350, 45}, {1620, 54}}), // Version 32
|
||||
new RSBlockInfo(2611, new int[][]{ {540, 18}, { 980, 35}, {1440, 48}, {1710, 57}}), // Version 33
|
||||
new RSBlockInfo(2761, new int[][]{ {570, 19}, {1036, 37}, {1530, 51}, {1800, 60}}), // Version 34
|
||||
new RSBlockInfo(2876, new int[][]{ {570, 19}, {1064, 38}, {1590, 53}, {1890, 63}}), // Version 35
|
||||
new RSBlockInfo(3034, new int[][]{ {600, 20}, {1120, 40}, {1680, 56}, {1980, 66}}), // Version 36
|
||||
new RSBlockInfo(3196, new int[][]{ {630, 21}, {1204, 43}, {1770, 59}, {2100, 70}}), // Version 37
|
||||
new RSBlockInfo(3362, new int[][]{ {660, 22}, {1260, 45}, {1860, 62}, {2220, 74}}), // Version 38
|
||||
new RSBlockInfo(3532, new int[][]{ {720, 24}, {1316, 47}, {1950, 65}, {2310, 77}}), // Version 39
|
||||
new RSBlockInfo(3706, new int[][]{ {750, 25}, {1372, 49}, {2040, 68}, {2430, 81}}), // Version 40
|
||||
};
|
||||
// The mask penalty calculation is complicated. See Table 21 of JISX0510:2004 (p.45) for details.
|
||||
// Basically it applies four rules and summate all penalties.
|
||||
private static int calculateMaskPenalty(ByteMatrix matrix) {
|
||||
int penalty = 0;
|
||||
penalty += MaskUtil.applyMaskPenaltyRule1(matrix);
|
||||
penalty += MaskUtil.applyMaskPenaltyRule2(matrix);
|
||||
penalty += MaskUtil.applyMaskPenaltyRule3(matrix);
|
||||
penalty += MaskUtil.applyMaskPenaltyRule4(matrix);
|
||||
return penalty;
|
||||
}
|
||||
|
||||
private static final class BlockPair {
|
||||
|
||||
private final ByteArray dataBytes;
|
||||
private final ByteArray errorCorrectionBytes;
|
||||
|
||||
public BlockPair(ByteArray data, ByteArray errorCorrection) {
|
||||
BlockPair(ByteArray data, ByteArray errorCorrection) {
|
||||
dataBytes = data;
|
||||
errorCorrectionBytes = errorCorrection;
|
||||
}
|
||||
|
@ -129,22 +86,22 @@ public final class Encoder {
|
|||
//
|
||||
// Note that there is no way to encode bytes in MODE_KANJI. We might want to add EncodeWithMode()
|
||||
// with which clients can specify the encoding mode. For now, we don't need the functionality.
|
||||
public static void encode(final ByteArray bytes, ErrorCorrectionLevel ecLevel, QRCode qrCode)
|
||||
public static void encode(String content, ErrorCorrectionLevel ecLevel, QRCode qrCode)
|
||||
throws WriterException {
|
||||
// Step 1: Choose the mode (encoding).
|
||||
final Mode mode = chooseMode(bytes);
|
||||
Mode mode = chooseMode(content);
|
||||
|
||||
// Step 2: Append "bytes" into "dataBits" in appropriate encoding.
|
||||
BitVector dataBits = new BitVector();
|
||||
appendBytes(bytes, mode, dataBits);
|
||||
appendBytes(content, mode, dataBits);
|
||||
// Step 3: Initialize QR code that can contain "dataBits".
|
||||
final int numInputBytes = dataBits.sizeInBytes();
|
||||
int numInputBytes = dataBits.sizeInBytes();
|
||||
initQRCode(numInputBytes, ecLevel, mode, qrCode);
|
||||
|
||||
// Step 4: Build another bit vector that contains header and data.
|
||||
BitVector headerAndDataBits = new BitVector();
|
||||
appendModeInfo(qrCode.getMode(), headerAndDataBits);
|
||||
appendLengthInfo(bytes.size(), qrCode.getVersion(), qrCode.getMode(), headerAndDataBits);
|
||||
appendLengthInfo(content.length(), qrCode.getVersion(), qrCode.getMode(), headerAndDataBits);
|
||||
headerAndDataBits.appendBitVector(dataBits);
|
||||
|
||||
// Step 5: Terminate the bits properly.
|
||||
|
@ -179,54 +136,43 @@ public final class Encoder {
|
|||
return -1;
|
||||
}
|
||||
|
||||
// Choose the best mode by examining the content of "bytes". The function is guaranteed to return
|
||||
// a valid mode.
|
||||
// Choose the best mode by examining the content.
|
||||
//
|
||||
// Note that this function does not return MODE_KANJI, as we cannot distinguish Shift_JIS from
|
||||
// other encodings such as ISO-8859-1, from data bytes alone. For example "\xE0\xE0" can be
|
||||
// interpreted as one character in Shift_JIS, but also two characters in ISO-8859-1.
|
||||
//
|
||||
// JAVAPORT: This MODE_KANJI limitation sounds like a problem for us.
|
||||
public static Mode chooseMode(final ByteArray bytes) throws WriterException {
|
||||
public static Mode chooseMode(String content) {
|
||||
boolean hasNumeric = false;
|
||||
boolean hasAlphanumeric = false;
|
||||
boolean hasOther = false;
|
||||
for (int i = 0; i < bytes.size(); ++i) {
|
||||
final int oneByte = bytes.at(i);
|
||||
if (oneByte >= '0' && oneByte <= '9') {
|
||||
for (int i = 0; i < content.length(); ++i) {
|
||||
char c = content.charAt(i);
|
||||
if (c >= '0' && c <= '9') {
|
||||
hasNumeric = true;
|
||||
} else if (getAlphanumericCode(oneByte) != -1) {
|
||||
} else if (getAlphanumericCode(c) != -1) {
|
||||
hasAlphanumeric = true;
|
||||
} else {
|
||||
hasOther = true;
|
||||
return Mode.BYTE;
|
||||
}
|
||||
}
|
||||
if (hasOther) {
|
||||
return Mode.BYTE;
|
||||
} else if (hasAlphanumeric) {
|
||||
if (hasAlphanumeric) {
|
||||
return Mode.ALPHANUMERIC;
|
||||
} else if (hasNumeric) {
|
||||
return Mode.NUMERIC;
|
||||
}
|
||||
// "bytes" must be empty to reach here.
|
||||
if (!bytes.empty()) {
|
||||
throw new WriterException("Bytes left over");
|
||||
}
|
||||
return Mode.BYTE;
|
||||
}
|
||||
|
||||
private static int chooseMaskPattern(final BitVector bits, ErrorCorrectionLevel ecLevel, int version,
|
||||
private static int chooseMaskPattern(BitVector bits, ErrorCorrectionLevel ecLevel, int version,
|
||||
ByteMatrix matrix) throws WriterException {
|
||||
if (!QRCode.isValidMatrixWidth(matrix.width())) {
|
||||
throw new WriterException("Invalid matrix width: " + matrix.width());
|
||||
}
|
||||
|
||||
int minPenalty = Integer.MAX_VALUE; // Lower penalty is better.
|
||||
int bestMaskPattern = -1;
|
||||
// We try all mask patterns to choose the best one.
|
||||
for (int maskPattern = 0; maskPattern < QRCode.NUM_MASK_PATTERNS; maskPattern++) {
|
||||
MatrixUtil.buildMatrix(bits, ecLevel, version, maskPattern, matrix);
|
||||
final int penalty = MaskUtil.calculateMaskPenalty(matrix);
|
||||
int penalty = calculateMaskPenalty(matrix);
|
||||
if (penalty < minPenalty) {
|
||||
minPenalty = penalty;
|
||||
bestMaskPattern = maskPattern;
|
||||
|
@ -236,36 +182,37 @@ public final class Encoder {
|
|||
}
|
||||
|
||||
// Initialize "qrCode" according to "numInputBytes", "ecLevel", and "mode". On success, modify
|
||||
// "qrCode" and return true.
|
||||
// "qrCode".
|
||||
private static void initQRCode(int numInputBytes, ErrorCorrectionLevel ecLevel, Mode mode, QRCode qrCode)
|
||||
throws WriterException {
|
||||
qrCode.setECLevel(ecLevel);
|
||||
qrCode.setMode(mode);
|
||||
|
||||
// In the following comments, we use numbers of Version 7-H.
|
||||
for (int i = 0; i < RS_BLOCK_TABLE.length; ++i) {
|
||||
final RSBlockInfo row = RS_BLOCK_TABLE[i];
|
||||
for (int versionNum = 1; versionNum <= 40; versionNum++) {
|
||||
Version version = Version.getVersionForNumber(versionNum);
|
||||
// numBytes = 196
|
||||
final int numBytes = row.numBytes;
|
||||
int numBytes = version.getTotalCodewords();
|
||||
// getNumECBytes = 130
|
||||
final int numEcBytes = row.blockInfo[ecLevel.ordinal()][0];
|
||||
Version.ECBlocks ecBlocks = version.getECBlocksForLevel(ecLevel);
|
||||
int numEcBytes = ecBlocks.getTotalECCodewords();
|
||||
// getNumRSBlocks = 5
|
||||
final int numRSBlocks = row.blockInfo[ecLevel.ordinal()][1];
|
||||
int numRSBlocks = ecBlocks.getNumBlocks();
|
||||
// getNumDataBytes = 196 - 130 = 66
|
||||
final int numDataBytes = numBytes - numEcBytes;
|
||||
int numDataBytes = numBytes - numEcBytes;
|
||||
// We want to choose the smallest version which can contain data of "numInputBytes" + some
|
||||
// extra bits for the header (mode info and length info). The header can be three bytes
|
||||
// (precisely 4 + 16 bits) at most. Hence we do +3 here.
|
||||
if (numDataBytes >= numInputBytes + 3) {
|
||||
// Yay, we found the proper rs block info!
|
||||
qrCode.setVersion(i + 1);
|
||||
qrCode.setVersion(versionNum);
|
||||
qrCode.setNumTotalBytes(numBytes);
|
||||
qrCode.setNumDataBytes(numDataBytes);
|
||||
qrCode.setNumRSBlocks(numRSBlocks);
|
||||
// getNumECBytes = 196 - 66 = 130
|
||||
qrCode.setNumECBytes(numBytes - numDataBytes);
|
||||
qrCode.setNumECBytes(numEcBytes);
|
||||
// matrix width = 21 + 6 * 4 = 45
|
||||
qrCode.setMatrixWidth(21 + i * 4);
|
||||
qrCode.setMatrixWidth(version.getDimensionForVersion());
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
@ -274,7 +221,7 @@ public final class Encoder {
|
|||
|
||||
// Terminate bits as described in 8.4.8 and 8.4.9 of JISX0510:2004 (p.24).
|
||||
static void terminateBits(int numDataBytes, BitVector bits) throws WriterException {
|
||||
final int capacity = numDataBytes * 8;
|
||||
int capacity = numDataBytes << 3;
|
||||
if (bits.size() > capacity) {
|
||||
throw new WriterException("data bits cannot fit in the QR Code" + bits.size() + " > " + capacity);
|
||||
}
|
||||
|
@ -282,10 +229,10 @@ public final class Encoder {
|
|||
for (int i = 0; i < 4 && bits.size() < capacity; ++i) {
|
||||
bits.appendBit(0);
|
||||
}
|
||||
final int numBitsInLastByte = bits.size() % 8;
|
||||
int numBitsInLastByte = bits.size() % 8;
|
||||
// If the last byte isn't 8-bit aligned, we'll add padding bits.
|
||||
if (numBitsInLastByte > 0) {
|
||||
final int numPaddingBits = 8 - numBitsInLastByte;
|
||||
int numPaddingBits = 8 - numBitsInLastByte;
|
||||
for (int i = 0; i < numPaddingBits; ++i) {
|
||||
bits.appendBit(0);
|
||||
}
|
||||
|
@ -295,7 +242,7 @@ public final class Encoder {
|
|||
throw new WriterException("Number of bits is not a multiple of 8");
|
||||
}
|
||||
// If we have more space, we'll fill the space with padding patterns defined in 8.4.9 (p.24).
|
||||
final int numPaddingBytes = numDataBytes - bits.sizeInBytes();
|
||||
int numPaddingBytes = numDataBytes - bits.sizeInBytes();
|
||||
for (int i = 0; i < numPaddingBytes; ++i) {
|
||||
if (i % 2 == 0) {
|
||||
bits.appendBits(0xec, 8);
|
||||
|
@ -318,21 +265,21 @@ public final class Encoder {
|
|||
throw new WriterException("Block ID too large");
|
||||
}
|
||||
// numRsBlocksInGroup2 = 196 % 5 = 1
|
||||
final int numRsBlocksInGroup2 = numTotalBytes % numRSBlocks;
|
||||
int numRsBlocksInGroup2 = numTotalBytes % numRSBlocks;
|
||||
// numRsBlocksInGroup1 = 5 - 1 = 4
|
||||
final int numRsBlocksInGroup1 = numRSBlocks - numRsBlocksInGroup2;
|
||||
int numRsBlocksInGroup1 = numRSBlocks - numRsBlocksInGroup2;
|
||||
// numTotalBytesInGroup1 = 196 / 5 = 39
|
||||
final int numTotalBytesInGroup1 = numTotalBytes / numRSBlocks;
|
||||
int numTotalBytesInGroup1 = numTotalBytes / numRSBlocks;
|
||||
// numTotalBytesInGroup2 = 39 + 1 = 40
|
||||
final int numTotalBytesInGroup2 = numTotalBytesInGroup1 + 1;
|
||||
int numTotalBytesInGroup2 = numTotalBytesInGroup1 + 1;
|
||||
// numDataBytesInGroup1 = 66 / 5 = 13
|
||||
final int numDataBytesInGroup1 = numDataBytes / numRSBlocks;
|
||||
int numDataBytesInGroup1 = numDataBytes / numRSBlocks;
|
||||
// numDataBytesInGroup2 = 13 + 1 = 14
|
||||
final int numDataBytesInGroup2 = numDataBytesInGroup1 + 1;
|
||||
int numDataBytesInGroup2 = numDataBytesInGroup1 + 1;
|
||||
// numEcBytesInGroup1 = 39 - 13 = 26
|
||||
final int numEcBytesInGroup1 = numTotalBytesInGroup1 - numDataBytesInGroup1;
|
||||
int numEcBytesInGroup1 = numTotalBytesInGroup1 - numDataBytesInGroup1;
|
||||
// numEcBytesInGroup2 = 40 - 14 = 26
|
||||
final int numEcBytesInGroup2 = numTotalBytesInGroup2 - numDataBytesInGroup2;
|
||||
int numEcBytesInGroup2 = numTotalBytesInGroup2 - numDataBytesInGroup2;
|
||||
// Sanity checks.
|
||||
// 26 = 26
|
||||
if (numEcBytesInGroup1 != numEcBytesInGroup2) {
|
||||
|
@ -363,7 +310,7 @@ public final class Encoder {
|
|||
// Interleave "bits" with corresponding error correction bytes. On success, store the result in
|
||||
// "result" and return true. The interleave rule is complicated. See 8.6
|
||||
// of JISX0510:2004 (p.37) for details.
|
||||
static void interleaveWithECBytes(final BitVector bits, int numTotalBytes,
|
||||
static void interleaveWithECBytes(BitVector bits, int numTotalBytes,
|
||||
int numDataBytes, int numRSBlocks, BitVector result) throws WriterException {
|
||||
|
||||
// "bits" must have "getNumDataBytes" bytes of data.
|
||||
|
@ -403,7 +350,7 @@ public final class Encoder {
|
|||
// First, place data blocks.
|
||||
for (int i = 0; i < maxNumDataBytes; ++i) {
|
||||
for (int j = 0; j < blocks.size(); ++j) {
|
||||
final ByteArray dataBytes = ((BlockPair) blocks.elementAt(j)).getDataBytes();
|
||||
ByteArray dataBytes = ((BlockPair) blocks.elementAt(j)).getDataBytes();
|
||||
if (i < dataBytes.size()) {
|
||||
result.appendBits(dataBytes.at(i), 8);
|
||||
}
|
||||
|
@ -412,7 +359,7 @@ public final class Encoder {
|
|||
// Then, place error correction blocks.
|
||||
for (int i = 0; i < maxNumEcBytes; ++i) {
|
||||
for (int j = 0; j < blocks.size(); ++j) {
|
||||
final ByteArray ecBytes = ((BlockPair) blocks.elementAt(j)).getErrorCorrectionBytes();
|
||||
ByteArray ecBytes = ((BlockPair) blocks.elementAt(j)).getErrorCorrectionBytes();
|
||||
if (i < ecBytes.size()) {
|
||||
result.appendBits(ecBytes.at(i), 8);
|
||||
}
|
||||
|
@ -448,18 +395,8 @@ public final class Encoder {
|
|||
|
||||
// Append length info. On success, store the result in "bits" and return true. On error, return
|
||||
// false.
|
||||
static void appendLengthInfo(int numBytes, int version, Mode mode, BitVector bits) throws WriterException {
|
||||
int numLetters = numBytes;
|
||||
// In Kanji mode, a letter is represented in two bytes.
|
||||
if (mode.equals(Mode.KANJI)) {
|
||||
if (numLetters % 2 != 0) {
|
||||
throw new WriterException("Number of letters must be even");
|
||||
}
|
||||
numLetters /= 2;
|
||||
}
|
||||
|
||||
final int numBits = mode.getCharacterCountBits(Version.getVersionForNumber(version));
|
||||
|
||||
static void appendLengthInfo(int numLetters, int version, Mode mode, BitVector bits) throws WriterException {
|
||||
int numBits = mode.getCharacterCountBits(Version.getVersionForNumber(version));
|
||||
if (numLetters > ((1 << numBits) - 1)) {
|
||||
throw new WriterException(numLetters + "is bigger than" + ((1 << numBits) - 1));
|
||||
}
|
||||
|
@ -468,61 +405,54 @@ public final class Encoder {
|
|||
|
||||
// Append "bytes" in "mode" mode (encoding) into "bits". On success, store the result in "bits"
|
||||
// and return true.
|
||||
static void appendBytes(final ByteArray bytes, Mode mode, BitVector bits) throws WriterException {
|
||||
static void appendBytes(String content, Mode mode, BitVector bits) throws WriterException {
|
||||
if (mode.equals(Mode.NUMERIC)) {
|
||||
appendNumericBytes(bytes, bits);
|
||||
appendNumericBytes(content, bits);
|
||||
} else if (mode.equals(Mode.ALPHANUMERIC)) {
|
||||
appendAlphanumericBytes(bytes, bits);
|
||||
appendAlphanumericBytes(content, bits);
|
||||
} else if (mode.equals(Mode.BYTE)) {
|
||||
append8BitBytes(bytes, bits);
|
||||
append8BitBytes(content, bits);
|
||||
} else if (mode.equals(Mode.KANJI)) {
|
||||
appendKanjiBytes(bytes, bits);
|
||||
appendKanjiBytes(content, bits);
|
||||
} else {
|
||||
throw new WriterException("Invalid mode: " + mode);
|
||||
}
|
||||
}
|
||||
|
||||
// Append "bytes" to "bits" using QRCode.MODE_NUMERIC mode. On success, store the result in "bits"
|
||||
// and return true.
|
||||
static void appendNumericBytes(final ByteArray bytes, BitVector bits) throws WriterException {
|
||||
// Validate all the bytes first.
|
||||
for (int i = 0; i < bytes.size(); ++i) {
|
||||
int oneByte = bytes.at(i);
|
||||
if (oneByte < '0' || oneByte > '9') {
|
||||
throw new WriterException("Non-digit found");
|
||||
}
|
||||
}
|
||||
for (int i = 0; i < bytes.size();) {
|
||||
final int num1 = bytes.at(i) - '0';
|
||||
if (i + 2 < bytes.size()) {
|
||||
static void appendNumericBytes(String content, BitVector bits) {
|
||||
int length = content.length();
|
||||
int i = 0;
|
||||
while (i < length) {
|
||||
int num1 = content.charAt(i) - '0';
|
||||
if (i + 2 < length) {
|
||||
// Encode three numeric letters in ten bits.
|
||||
final int num2 = bytes.at(i + 1) - '0';
|
||||
final int num3 = bytes.at(i + 2) - '0';
|
||||
int num2 = content.charAt(i + 1) - '0';
|
||||
int num3 = content.charAt(i + 2) - '0';
|
||||
bits.appendBits(num1 * 100 + num2 * 10 + num3, 10);
|
||||
i += 3;
|
||||
} else if (i + 1 < bytes.size()) {
|
||||
} else if (i + 1 < length) {
|
||||
// Encode two numeric letters in seven bits.
|
||||
final int num2 = bytes.at(i + 1) - '0';
|
||||
int num2 = content.charAt(i + 1) - '0';
|
||||
bits.appendBits(num1 * 10 + num2, 7);
|
||||
i += 2;
|
||||
} else {
|
||||
// Encode one numeric letter in four bits.
|
||||
bits.appendBits(num1, 4);
|
||||
++i;
|
||||
i++;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Append "bytes" to "bits" using QRCode.MODE_ALPHANUMERIC mode. On success, store the result in
|
||||
// "bits" and return true.
|
||||
static void appendAlphanumericBytes(final ByteArray bytes, BitVector bits) throws WriterException {
|
||||
for (int i = 0; i < bytes.size();) {
|
||||
final int code1 = getAlphanumericCode(bytes.at(i));
|
||||
static void appendAlphanumericBytes(String content, BitVector bits) throws WriterException {
|
||||
int length = content.length();
|
||||
int i = 0;
|
||||
while (i < length) {
|
||||
int code1 = getAlphanumericCode(content.charAt(i));
|
||||
if (code1 == -1) {
|
||||
throw new WriterException();
|
||||
}
|
||||
if (i + 1 < bytes.size()) {
|
||||
final int code2 = getAlphanumericCode(bytes.at(i + 1));
|
||||
if (i + 1 < length) {
|
||||
int code2 = getAlphanumericCode(content.charAt(i + 1));
|
||||
if (code2 == -1) {
|
||||
throw new WriterException();
|
||||
}
|
||||
|
@ -532,31 +462,35 @@ public final class Encoder {
|
|||
} else {
|
||||
// Encode one alphanumeric letter in six bits.
|
||||
bits.appendBits(code1, 6);
|
||||
++i;
|
||||
i++;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Append "bytes" to "bits" using QRCode.MODE_8BIT_BYTE mode. On success, store the result in
|
||||
// "bits" and return true.
|
||||
static void append8BitBytes(final ByteArray bytes, BitVector bits) {
|
||||
for (int i = 0; i < bytes.size(); ++i) {
|
||||
bits.appendBits(bytes.at(i), 8);
|
||||
static void append8BitBytes(String content, BitVector bits) throws WriterException {
|
||||
byte[] bytes;
|
||||
try {
|
||||
bytes = content.getBytes("ISO-8859-1"); // TODO support specifying encoding?
|
||||
} catch (UnsupportedEncodingException uee) {
|
||||
throw new WriterException(uee.toString());
|
||||
}
|
||||
for (int i = 0; i < bytes.length; ++i) {
|
||||
bits.appendBits(bytes[i], 8);
|
||||
}
|
||||
}
|
||||
|
||||
// Append "bytes" to "bits" using QRCode.MODE_KANJI mode. On success, store the result in "bits"
|
||||
// and return true. See 8.4.5 of JISX0510:2004 (p.21) for how to encode
|
||||
// Kanji bytes.
|
||||
static void appendKanjiBytes(final ByteArray bytes, BitVector bits) throws WriterException {
|
||||
if (bytes.size() % 2 != 0) {
|
||||
throw new WriterException("Number of bytes must be even");
|
||||
static void appendKanjiBytes(String content, BitVector bits) throws WriterException {
|
||||
byte[] bytes;
|
||||
try {
|
||||
bytes = content.getBytes("Shift_JIS");
|
||||
} catch (UnsupportedEncodingException uee) {
|
||||
throw new WriterException(uee.toString());
|
||||
}
|
||||
for (int i = 0; i < bytes.size(); i += 2) {
|
||||
if (!isValidKanji(bytes.at(i), bytes.at(i + 1))) {
|
||||
throw new WriterException("Invalid Kanji at " + i);
|
||||
}
|
||||
final int code = (bytes.at(i) << 8) | bytes.at(i + 1);
|
||||
int length = bytes.length;
|
||||
for (int i = 0; i < length; i += 2) {
|
||||
int byte1 = bytes[i] & 0xFF;
|
||||
int byte2 = bytes[i + 1] & 0xFF;
|
||||
int code = (byte1 << 8) | byte2;
|
||||
int subtracted = -1;
|
||||
if (code >= 0x8140 && code <= 0x9ffc) {
|
||||
subtracted = code - 0x8140;
|
||||
|
@ -564,35 +498,11 @@ public final class Encoder {
|
|||
subtracted = code - 0xc140;
|
||||
}
|
||||
if (subtracted == -1) {
|
||||
throw new WriterException("Invalid byte sequence: " + bytes);
|
||||
throw new WriterException("Invalid byte sequence");
|
||||
}
|
||||
final int encoded = ((subtracted >> 8) * 0xc0) + (subtracted & 0xff);
|
||||
int encoded = ((subtracted >> 8) * 0xc0) + (subtracted & 0xff);
|
||||
bits.appendBits(encoded, 13);
|
||||
}
|
||||
}
|
||||
|
||||
// Check if "byte1" and "byte2" can compose a valid Kanji letter (2-byte Shift_JIS letter). The
|
||||
// numbers are from http://ja.wikipedia.org/wiki/Shift_JIS.
|
||||
static boolean isValidKanji(final int byte1, final int byte2) {
|
||||
return (byte2 != 0x7f &&
|
||||
((byte1 >= 0x81 && byte1 <= 0x9f &&
|
||||
byte2 >= 0x40 && byte2 <= 0xfc) ||
|
||||
((byte1 >= 0xe0 && byte1 <= 0xfc &&
|
||||
byte2 >= 0x40 && byte2 <= 0xfc))));
|
||||
}
|
||||
|
||||
// Check if "bytes" is a valid Kanji sequence. Used by the unit tests.
|
||||
static boolean isValidKanjiSequence(final ByteArray bytes) {
|
||||
if (bytes.size() % 2 != 0) {
|
||||
return false;
|
||||
}
|
||||
int i = 0;
|
||||
for (; i < bytes.size(); i += 2) {
|
||||
if (!isValidKanji(bytes.at(i), bytes.at(i + 1))) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
return i == bytes.size(); // Consumed all bytes?
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -28,26 +28,15 @@ public final class MaskUtil {
|
|||
// do nothing
|
||||
}
|
||||
|
||||
// The mask penalty calculation is complicated. See Table 21 of JISX0510:2004 (p.45) for details.
|
||||
// Basically it applies four rules and summate all penalties.
|
||||
public static int calculateMaskPenalty(final ByteMatrix matrix) {
|
||||
int penalty = 0;
|
||||
penalty += applyMaskPenaltyRule1(matrix);
|
||||
penalty += applyMaskPenaltyRule2(matrix);
|
||||
penalty += applyMaskPenaltyRule3(matrix);
|
||||
penalty += applyMaskPenaltyRule4(matrix);
|
||||
return penalty;
|
||||
}
|
||||
|
||||
// Apply mask penalty rule 1 and return the penalty. Find repetitive cells with the same color and
|
||||
// give penalty to them. Example: 00000 or 11111.
|
||||
public static int applyMaskPenaltyRule1(final ByteMatrix matrix) {
|
||||
public static int applyMaskPenaltyRule1(ByteMatrix matrix) {
|
||||
return applyMaskPenaltyRule1Internal(matrix, true) + applyMaskPenaltyRule1Internal(matrix, false);
|
||||
}
|
||||
|
||||
// Apply mask penalty rule 2 and return the penalty. Find 2x2 blocks with the same color and give
|
||||
// penalty to them.
|
||||
public static int applyMaskPenaltyRule2(final ByteMatrix matrix) {
|
||||
public static int applyMaskPenaltyRule2(ByteMatrix matrix) {
|
||||
int penalty = 0;
|
||||
byte[][] array = matrix.getArray();
|
||||
int width = matrix.width();
|
||||
|
@ -66,7 +55,7 @@ public final class MaskUtil {
|
|||
// Apply mask penalty rule 3 and return the penalty. Find consecutive cells of 00001011101 or
|
||||
// 10111010000, and give penalty to them. If we find patterns like 000010111010000, we give
|
||||
// penalties twice (i.e. 40 * 2).
|
||||
public static int applyMaskPenaltyRule3(final ByteMatrix matrix) {
|
||||
public static int applyMaskPenaltyRule3(ByteMatrix matrix) {
|
||||
int penalty = 0;
|
||||
byte[][] array = matrix.getArray();
|
||||
int width = matrix.width();
|
||||
|
@ -128,7 +117,7 @@ public final class MaskUtil {
|
|||
// - 55% => 10
|
||||
// - 55% => 20
|
||||
// - 100% => 100
|
||||
public static int applyMaskPenaltyRule4(final ByteMatrix matrix) {
|
||||
public static int applyMaskPenaltyRule4(ByteMatrix matrix) {
|
||||
int numDarkCells = 0;
|
||||
byte[][] array = matrix.getArray();
|
||||
int width = matrix.width();
|
||||
|
@ -140,14 +129,14 @@ public final class MaskUtil {
|
|||
}
|
||||
}
|
||||
}
|
||||
final int numTotalCells = matrix.height() * matrix.width();
|
||||
int numTotalCells = matrix.height() * matrix.width();
|
||||
double darkRatio = (double) numDarkCells / numTotalCells;
|
||||
return Math.abs((int) (darkRatio * 100 - 50)) / 5 * 10;
|
||||
}
|
||||
|
||||
// Return the mask bit for "getMaskPattern" at "x" and "y". See 8.8 of JISX0510:2004 for mask
|
||||
// pattern conditions.
|
||||
public static int getDataMaskBit(final int maskPattern, final int x, final int y) {
|
||||
public static int getDataMaskBit(int maskPattern, int x, int y) {
|
||||
if (!QRCode.isValidMaskPattern(maskPattern)) {
|
||||
throw new IllegalArgumentException("Invalid mask pattern");
|
||||
}
|
||||
|
@ -174,7 +163,7 @@ public final class MaskUtil {
|
|||
|
||||
// Helper function for applyMaskPenaltyRule1. We need this for doing this calculation in both
|
||||
// vertical and horizontal orders respectively.
|
||||
private static int applyMaskPenaltyRule1Internal(final ByteMatrix matrix, boolean isHorizontal) {
|
||||
private static int applyMaskPenaltyRule1Internal(ByteMatrix matrix, boolean isHorizontal) {
|
||||
int penalty = 0;
|
||||
int numSameBitCells = 0;
|
||||
int prevBit = -1;
|
||||
|
@ -186,12 +175,12 @@ public final class MaskUtil {
|
|||
// for (int i = 0; i < matrix.width(); ++i) {
|
||||
// for (int j = 0; j < matrix.height(); ++j) {
|
||||
// int bit = matrix.get(j, i);
|
||||
final int iLimit = isHorizontal ? matrix.height() : matrix.width();
|
||||
final int jLimit = isHorizontal ? matrix.width() : matrix.height();
|
||||
int iLimit = isHorizontal ? matrix.height() : matrix.width();
|
||||
int jLimit = isHorizontal ? matrix.width() : matrix.height();
|
||||
byte[][] array = matrix.getArray();
|
||||
for (int i = 0; i < iLimit; ++i) {
|
||||
for (int j = 0; j < jLimit; ++j) {
|
||||
final int bit = isHorizontal ? array[i][j] : array[j][i];
|
||||
int bit = isHorizontal ? array[i][j] : array[j][i];
|
||||
if (bit == prevBit) {
|
||||
numSameBitCells += 1;
|
||||
// Found five repetitive cells with the same color (bit).
|
||||
|
|
|
@ -136,9 +136,9 @@ public final class MatrixUtil {
|
|||
|
||||
// Build 2D matrix of QR Code from "dataBits" with "ecLevel", "version" and "getMaskPattern". On
|
||||
// success, store the result in "matrix" and return true.
|
||||
public static void buildMatrix(final BitVector dataBits, ErrorCorrectionLevel ecLevel, int version,
|
||||
public static void buildMatrix(BitVector dataBits, ErrorCorrectionLevel ecLevel, int version,
|
||||
int maskPattern, ByteMatrix matrix) throws WriterException {
|
||||
MatrixUtil.clearMatrix(matrix);
|
||||
clearMatrix(matrix);
|
||||
embedBasicPatterns(version, matrix);
|
||||
// Type information appear with any version.
|
||||
embedTypeInfo(ecLevel, maskPattern, matrix);
|
||||
|
@ -175,22 +175,22 @@ public final class MatrixUtil {
|
|||
for (int i = 0; i < typeInfoBits.size(); ++i) {
|
||||
// Place bits in LSB to MSB order. LSB (least significant bit) is the last value in
|
||||
// "typeInfoBits".
|
||||
final int bit = typeInfoBits.at(typeInfoBits.size() - 1 - i);
|
||||
int bit = typeInfoBits.at(typeInfoBits.size() - 1 - i);
|
||||
|
||||
// Type info bits at the left top corner. See 8.9 of JISX0510:2004 (p.46).
|
||||
final int x1 = TYPE_INFO_COORDINATES[i][0];
|
||||
final int y1 = TYPE_INFO_COORDINATES[i][1];
|
||||
int x1 = TYPE_INFO_COORDINATES[i][0];
|
||||
int y1 = TYPE_INFO_COORDINATES[i][1];
|
||||
matrix.set(y1, x1, bit);
|
||||
|
||||
if (i < 8) {
|
||||
// Right top corner.
|
||||
final int x2 = matrix.width() - i - 1;
|
||||
final int y2 = 8;
|
||||
int x2 = matrix.width() - i - 1;
|
||||
int y2 = 8;
|
||||
matrix.set(y2, x2, bit);
|
||||
} else {
|
||||
// Left bottom corner.
|
||||
final int x2 = 8;
|
||||
final int y2 = matrix.height() - 7 + (i - 8);
|
||||
int x2 = 8;
|
||||
int y2 = matrix.height() - 7 + (i - 8);
|
||||
matrix.set(y2, x2, bit);
|
||||
}
|
||||
}
|
||||
|
@ -209,7 +209,7 @@ public final class MatrixUtil {
|
|||
for (int i = 0; i < 6; ++i) {
|
||||
for (int j = 0; j < 3; ++j) {
|
||||
// Place bits in LSB (least significant bit) to MSB order.
|
||||
final int bit = versionInfoBits.at(bitIndex);
|
||||
int bit = versionInfoBits.at(bitIndex);
|
||||
bitIndex--;
|
||||
// Left bottom corner.
|
||||
matrix.set(matrix.height() - 11 + j, i, bit);
|
||||
|
@ -222,7 +222,7 @@ public final class MatrixUtil {
|
|||
// Embed "dataBits" using "getMaskPattern". On success, modify the matrix and return true.
|
||||
// For debugging purposes, it skips masking process if "getMaskPattern" is -1.
|
||||
// See 8.7 of JISX0510:2004 (p.38) for how to embed data bits.
|
||||
public static void embedDataBits(final BitVector dataBits, int maskPattern, ByteMatrix matrix)
|
||||
public static void embedDataBits(BitVector dataBits, int maskPattern, ByteMatrix matrix)
|
||||
throws WriterException {
|
||||
int bitIndex = 0;
|
||||
int direction = -1;
|
||||
|
@ -236,7 +236,7 @@ public final class MatrixUtil {
|
|||
}
|
||||
while (y >= 0 && y < matrix.height()) {
|
||||
for (int i = 0; i < 2; ++i) {
|
||||
final int xx = x - i;
|
||||
int xx = x - i;
|
||||
// Skip the cell if it's not empty.
|
||||
if (!isEmpty(matrix.get(y, xx))) {
|
||||
continue;
|
||||
|
@ -253,7 +253,7 @@ public final class MatrixUtil {
|
|||
|
||||
// Skip masking if mask_pattern is -1.
|
||||
if (maskPattern != -1) {
|
||||
final int mask = MaskUtil.getDataMaskBit(maskPattern, xx, y);
|
||||
int mask = MaskUtil.getDataMaskBit(maskPattern, xx, y);
|
||||
bit ^= mask;
|
||||
}
|
||||
matrix.set(y, xx, bit);
|
||||
|
@ -266,7 +266,7 @@ public final class MatrixUtil {
|
|||
}
|
||||
// All bits should be consumed.
|
||||
if (bitIndex != dataBits.size()) {
|
||||
throw new WriterException("Not all bits consumed: " + bitIndex + "/" + dataBits.size());
|
||||
throw new WriterException("Not all bits consumed: " + bitIndex + '/' + dataBits.size());
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -312,7 +312,7 @@ public final class MatrixUtil {
|
|||
public static int calculateBCHCode(int value, int poly) {
|
||||
// If poly is "1 1111 0010 0101" (version info poly), msbSetInPoly is 13. We'll subtract 1
|
||||
// from 13 to make it 12.
|
||||
final int msbSetInPoly = findMSBSet(poly);
|
||||
int msbSetInPoly = findMSBSet(poly);
|
||||
value <<= msbSetInPoly - 1;
|
||||
// Do the division business using exclusive-or operations.
|
||||
while (findMSBSet(value) >= msbSetInPoly) {
|
||||
|
@ -325,15 +325,15 @@ public final class MatrixUtil {
|
|||
// Make bit vector of type information. On success, store the result in "bits" and return true.
|
||||
// Encode error correction level and mask pattern. See 8.9 of
|
||||
// JISX0510:2004 (p.45) for details.
|
||||
public static void makeTypeInfoBits(ErrorCorrectionLevel ecLevel, final int maskPattern, BitVector bits)
|
||||
public static void makeTypeInfoBits(ErrorCorrectionLevel ecLevel, int maskPattern, BitVector bits)
|
||||
throws WriterException {
|
||||
if (!QRCode.isValidMaskPattern(maskPattern)) {
|
||||
throw new WriterException("Invalid mask pattern");
|
||||
}
|
||||
final int typeInfo = (ecLevel.getBits() << 3) | maskPattern;
|
||||
int typeInfo = (ecLevel.getBits() << 3) | maskPattern;
|
||||
bits.appendBits(typeInfo, 5);
|
||||
|
||||
final int bchCode = MatrixUtil.calculateBCHCode(typeInfo, TYPE_INFO_POLY);
|
||||
int bchCode = calculateBCHCode(typeInfo, TYPE_INFO_POLY);
|
||||
bits.appendBits(bchCode, 10);
|
||||
|
||||
BitVector maskBits = new BitVector();
|
||||
|
@ -349,7 +349,7 @@ public final class MatrixUtil {
|
|||
// See 8.10 of JISX0510:2004 (p.45) for details.
|
||||
public static void makeVersionInfoBits(int version, BitVector bits) throws WriterException {
|
||||
bits.appendBits(version, 6);
|
||||
final int bchCode = MatrixUtil.calculateBCHCode(version, VERSION_INFO_POLY);
|
||||
int bchCode = calculateBCHCode(version, VERSION_INFO_POLY);
|
||||
bits.appendBits(bchCode, 12);
|
||||
|
||||
if (bits.size() != 18) { // Just in case.
|
||||
|
@ -358,12 +358,12 @@ public final class MatrixUtil {
|
|||
}
|
||||
|
||||
// Check if "value" is empty.
|
||||
private static boolean isEmpty(final int value) {
|
||||
private static boolean isEmpty(int value) {
|
||||
return value == -1;
|
||||
}
|
||||
|
||||
// Check if "value" is valid.
|
||||
private static boolean isValidValue(final int value) {
|
||||
private static boolean isValidValue(int value) {
|
||||
return (value == -1 || // Empty.
|
||||
value == 0 || // Light (white).
|
||||
value == 1); // Dark (black).
|
||||
|
@ -373,7 +373,7 @@ public final class MatrixUtil {
|
|||
// -8 is for skipping position detection patterns (size 7), and two horizontal/vertical
|
||||
// separation patterns (size 1). Thus, 8 = 7 + 1.
|
||||
for (int i = 8; i < matrix.width() - 8; ++i) {
|
||||
final int bit = (i + 1) % 2;
|
||||
int bit = (i + 1) % 2;
|
||||
// Horizontal line.
|
||||
if (!isValidValue(matrix.get(6, i))) {
|
||||
throw new WriterException();
|
||||
|
@ -399,7 +399,7 @@ public final class MatrixUtil {
|
|||
matrix.set(matrix.height() - 8, 8, 1);
|
||||
}
|
||||
|
||||
private static void embedHorizontalSeparationPattern(final int xStart, final int yStart,
|
||||
private static void embedHorizontalSeparationPattern(int xStart, int yStart,
|
||||
ByteMatrix matrix) throws WriterException {
|
||||
// We know the width and height.
|
||||
if (HORIZONTAL_SEPARATION_PATTERN[0].length != 8 || HORIZONTAL_SEPARATION_PATTERN.length != 1) {
|
||||
|
@ -413,7 +413,7 @@ public final class MatrixUtil {
|
|||
}
|
||||
}
|
||||
|
||||
private static void embedVerticalSeparationPattern(final int xStart, final int yStart,
|
||||
private static void embedVerticalSeparationPattern(int xStart, int yStart,
|
||||
ByteMatrix matrix) throws WriterException {
|
||||
// We know the width and height.
|
||||
if (VERTICAL_SEPARATION_PATTERN[0].length != 1 || VERTICAL_SEPARATION_PATTERN.length != 7) {
|
||||
|
@ -430,7 +430,7 @@ public final class MatrixUtil {
|
|||
// Note that we cannot unify the function with embedPositionDetectionPattern() despite they are
|
||||
// almost identical, since we cannot write a function that takes 2D arrays in different sizes in
|
||||
// C/C++. We should live with the fact.
|
||||
private static void embedPositionAdjustmentPattern(final int xStart, final int yStart,
|
||||
private static void embedPositionAdjustmentPattern(int xStart, int yStart,
|
||||
ByteMatrix matrix) throws WriterException {
|
||||
// We know the width and height.
|
||||
if (POSITION_ADJUSTMENT_PATTERN[0].length != 5 || POSITION_ADJUSTMENT_PATTERN.length != 5) {
|
||||
|
@ -446,7 +446,7 @@ public final class MatrixUtil {
|
|||
}
|
||||
}
|
||||
|
||||
private static void embedPositionDetectionPattern(final int xStart, final int yStart,
|
||||
private static void embedPositionDetectionPattern(int xStart, int yStart,
|
||||
ByteMatrix matrix) throws WriterException {
|
||||
// We know the width and height.
|
||||
if (POSITION_DETECTION_PATTERN[0].length != 7 || POSITION_DETECTION_PATTERN.length != 7) {
|
||||
|
@ -465,7 +465,7 @@ public final class MatrixUtil {
|
|||
// Embed position detection patterns and surrounding vertical/horizontal separators.
|
||||
private static void embedPositionDetectionPatternsAndSeparators(ByteMatrix matrix) throws WriterException {
|
||||
// Embed three big squares at corners.
|
||||
final int pdpWidth = POSITION_DETECTION_PATTERN[0].length;
|
||||
int pdpWidth = POSITION_DETECTION_PATTERN[0].length;
|
||||
// Left top corner.
|
||||
embedPositionDetectionPattern(0, 0, matrix);
|
||||
// Right top corner.
|
||||
|
@ -474,7 +474,7 @@ public final class MatrixUtil {
|
|||
embedPositionDetectionPattern(0, matrix.width() - pdpWidth, matrix);
|
||||
|
||||
// Embed horizontal separation patterns around the squares.
|
||||
final int hspWidth = HORIZONTAL_SEPARATION_PATTERN[0].length;
|
||||
int hspWidth = HORIZONTAL_SEPARATION_PATTERN[0].length;
|
||||
// Left top corner.
|
||||
embedHorizontalSeparationPattern(0, hspWidth - 1, matrix);
|
||||
// Right top corner.
|
||||
|
@ -484,7 +484,7 @@ public final class MatrixUtil {
|
|||
embedHorizontalSeparationPattern(0, matrix.width() - hspWidth, matrix);
|
||||
|
||||
// Embed vertical separation patterns around the squares.
|
||||
final int vspSize = VERTICAL_SEPARATION_PATTERN.length;
|
||||
int vspSize = VERTICAL_SEPARATION_PATTERN.length;
|
||||
// Left top corner.
|
||||
embedVerticalSeparationPattern(vspSize, 0, matrix);
|
||||
// Right top corner.
|
||||
|
@ -495,18 +495,18 @@ public final class MatrixUtil {
|
|||
}
|
||||
|
||||
// Embed position adjustment patterns if need be.
|
||||
private static void maybeEmbedPositionAdjustmentPatterns(final int version, ByteMatrix matrix)
|
||||
private static void maybeEmbedPositionAdjustmentPatterns(int version, ByteMatrix matrix)
|
||||
throws WriterException {
|
||||
if (version < 2) { // The patterns appear if version >= 2
|
||||
return;
|
||||
}
|
||||
final int index = version - 1;
|
||||
final int[] coordinates = POSITION_ADJUSTMENT_PATTERN_COORDINATE_TABLE[index];
|
||||
final int numCoordinates = POSITION_ADJUSTMENT_PATTERN_COORDINATE_TABLE[index].length;
|
||||
int index = version - 1;
|
||||
int[] coordinates = POSITION_ADJUSTMENT_PATTERN_COORDINATE_TABLE[index];
|
||||
int numCoordinates = POSITION_ADJUSTMENT_PATTERN_COORDINATE_TABLE[index].length;
|
||||
for (int i = 0; i < numCoordinates; ++i) {
|
||||
for (int j = 0; j < numCoordinates; ++j) {
|
||||
final int y = coordinates[i];
|
||||
final int x = coordinates[j];
|
||||
int y = coordinates[i];
|
||||
int x = coordinates[j];
|
||||
if (x == -1 || y == -1) {
|
||||
continue;
|
||||
}
|
||||
|
|
|
@ -26,12 +26,6 @@ import com.google.zxing.qrcode.decoder.Mode;
|
|||
*/
|
||||
public final class QRCode {
|
||||
|
||||
// Magic numbers.
|
||||
private static final int MIN_VERSION = 1;
|
||||
private static final int MAX_VERSION = 40;
|
||||
// For matrix width, see 7.3.1 of JISX0510:2004 (p.5).
|
||||
private static final int MIN_MATRIX_WIDTH = 21; // Version 1
|
||||
private static final int MAX_MATRIX_WIDTH = 177; // Version 40 (21 + 4 * (40 -1)).
|
||||
public static final int NUM_MASK_PATTERNS = 8;
|
||||
|
||||
private Mode mode;
|
||||
|
@ -104,7 +98,7 @@ public final class QRCode {
|
|||
}
|
||||
|
||||
// ByteMatrix data of the QR Code.
|
||||
public final ByteMatrix getMatrix() {
|
||||
public ByteMatrix getMatrix() {
|
||||
return matrix;
|
||||
}
|
||||
|
||||
|
@ -136,21 +130,18 @@ public final class QRCode {
|
|||
numECBytes != -1 &&
|
||||
numRSBlocks != -1 &&
|
||||
// Then check them in other ways..
|
||||
isValidVersion(version) &&
|
||||
isValidMatrixWidth(matrixWidth) &&
|
||||
isValidMaskPattern(maskPattern) &&
|
||||
numTotalBytes == numDataBytes + numECBytes &&
|
||||
// ByteMatrix stuff.
|
||||
matrix != null &&
|
||||
matrixWidth == matrix.width() &&
|
||||
// See 7.3.1 of JISX0510:2004 (p.5).
|
||||
matrixWidth == MIN_MATRIX_WIDTH + (version - 1) * 4 &&
|
||||
matrix.width() == matrix.height(); // Must be square.
|
||||
}
|
||||
|
||||
// Return debug String.
|
||||
public String toString() {
|
||||
StringBuffer result = new StringBuffer();
|
||||
StringBuffer result = new StringBuffer(200);
|
||||
result.append("<<\n");
|
||||
result.append(" mode: ");
|
||||
result.append(mode);
|
||||
|
@ -221,16 +212,6 @@ public final class QRCode {
|
|||
matrix = value;
|
||||
}
|
||||
|
||||
// Check if "version" is valid.
|
||||
public static boolean isValidVersion(final int version) {
|
||||
return version >= MIN_VERSION && version <= MAX_VERSION;
|
||||
}
|
||||
|
||||
// Check if "width" is valid.
|
||||
public static boolean isValidMatrixWidth(int width) {
|
||||
return width >= MIN_MATRIX_WIDTH && width <= MAX_MATRIX_WIDTH;
|
||||
}
|
||||
|
||||
// Check if "mask_pattern" is valid.
|
||||
public static boolean isValidMaskPattern(int maskPattern) {
|
||||
return maskPattern >= 0 && maskPattern < NUM_MASK_PATTERNS;
|
||||
|
|
|
@ -70,34 +70,34 @@ public final class QRCodeWriterTestCase extends TestCase {
|
|||
|
||||
public void testQRCodeWriter() throws WriterException {
|
||||
// The QR should be multiplied up to fit, with extra padding if necessary
|
||||
final int bigEnough = 256;
|
||||
int bigEnough = 256;
|
||||
QRCodeWriter writer = new QRCodeWriter();
|
||||
ByteMatrix matrix = writer.encode("http://www.google.com/", BarcodeFormat.QR_CODE, bigEnough,
|
||||
bigEnough, null);
|
||||
assertTrue(matrix != null);
|
||||
assertNotNull(matrix);
|
||||
assertEquals(bigEnough, matrix.width());
|
||||
assertEquals(bigEnough, matrix.height());
|
||||
|
||||
// The QR will not fit in this size, so the matrix should come back bigger
|
||||
final int tooSmall = 20;
|
||||
int tooSmall = 20;
|
||||
matrix = writer.encode("http://www.google.com/", BarcodeFormat.QR_CODE, tooSmall,
|
||||
tooSmall, null);
|
||||
assertTrue(matrix != null);
|
||||
assertNotNull(matrix);
|
||||
assertTrue(tooSmall < matrix.width());
|
||||
assertTrue(tooSmall < matrix.height());
|
||||
|
||||
// We should also be able to handle non-square requests by padding them
|
||||
final int strangeWidth = 500;
|
||||
final int strangeHeight = 100;
|
||||
int strangeWidth = 500;
|
||||
int strangeHeight = 100;
|
||||
matrix = writer.encode("http://www.google.com/", BarcodeFormat.QR_CODE, strangeWidth,
|
||||
strangeHeight, null);
|
||||
assertTrue(matrix != null);
|
||||
assertNotNull(matrix);
|
||||
assertEquals(strangeWidth, matrix.width());
|
||||
assertEquals(strangeHeight, matrix.height());
|
||||
}
|
||||
|
||||
private static boolean compareToGoldenFile(final String contents, final ErrorCorrectionLevel ecLevel,
|
||||
final int resolution, final String fileName) throws WriterException {
|
||||
private static void compareToGoldenFile(String contents, ErrorCorrectionLevel ecLevel,
|
||||
int resolution, String fileName) throws WriterException {
|
||||
|
||||
BufferedImage image = loadImage(fileName);
|
||||
assertNotNull(image);
|
||||
|
@ -116,24 +116,26 @@ public final class QRCodeWriterTestCase extends TestCase {
|
|||
resolution, generatedResult.height());
|
||||
assertTrue("Expected " + goldenResult.toString() + " but got " + generatedResult.toString(),
|
||||
Arrays.deepEquals(goldenResult.getArray(), generatedResult.getArray()));
|
||||
return true;
|
||||
}
|
||||
|
||||
// Golden images are generated with "qrcode_sample.cc". The images are checked with both eye balls
|
||||
// and cell phones. We expect pixel-perfect results, because the error correction level is known,
|
||||
// and the pixel dimensions matches exactly.
|
||||
public void testRegressionTest() throws WriterException {
|
||||
assertTrue(compareToGoldenFile("http://www.google.com/", ErrorCorrectionLevel.M, 99,
|
||||
"renderer-test-01.png"));
|
||||
public void testRegressionTest() throws WriterException, IOException {
|
||||
compareToGoldenFile("http://www.google.com/", ErrorCorrectionLevel.M, 99,
|
||||
"renderer-test-01.png");
|
||||
|
||||
assertTrue(compareToGoldenFile("12345", ErrorCorrectionLevel.L, 58, "renderer-test-02.png"));
|
||||
compareToGoldenFile("12345", ErrorCorrectionLevel.L, 58, "renderer-test-02.png");
|
||||
|
||||
// Test in Katakana in Shift_JIS.
|
||||
final byte[] KATAKANA_INPUT = {
|
||||
(byte)0x83, 0x65, (byte)0x83, 0x58, (byte)0x83, 0x67
|
||||
};
|
||||
assertTrue(compareToGoldenFile(new String(KATAKANA_INPUT), ErrorCorrectionLevel.H, 145,
|
||||
"renderer-test-03.png"));
|
||||
// TODO: this test is bogus now that byte mode has been basically fixed to assuming ISO-8859-1 encoding
|
||||
// The real solution is to implement Kanji mode, in which case the golden file will be wrong again
|
||||
/*
|
||||
compareToGoldenFile(
|
||||
new String(new byte[] {(byte)0x83, 0x65, (byte)0x83, 0x58, (byte)0x83, 0x67}, "Shift_JIS"),
|
||||
ErrorCorrectionLevel.H, 145,
|
||||
"renderer-test-03.png");
|
||||
*/
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -22,13 +22,15 @@ import com.google.zxing.qrcode.decoder.ErrorCorrectionLevel;
|
|||
import com.google.zxing.qrcode.decoder.Mode;
|
||||
import junit.framework.TestCase;
|
||||
|
||||
import java.io.UnsupportedEncodingException;
|
||||
|
||||
/**
|
||||
* @author satorux@google.com (Satoru Takabayashi) - creator
|
||||
* @author mysen@google.com (Chris Mysen) - ported from C++
|
||||
*/
|
||||
public final class EncoderTestCase extends TestCase {
|
||||
|
||||
public void testGetAlphanumericCode() throws WriterException {
|
||||
public void testGetAlphanumericCode() {
|
||||
// The first ten code points are numbers.
|
||||
for (int i = 0; i < 10; ++i) {
|
||||
assertEquals(i, Encoder.getAlphanumericCode('0' + i));
|
||||
|
@ -58,36 +60,33 @@ public final class EncoderTestCase extends TestCase {
|
|||
|
||||
public void testChooseMode() throws WriterException {
|
||||
// Numeric mode.
|
||||
assertEquals(Mode.NUMERIC, Encoder.chooseMode(new ByteArray("0")));
|
||||
assertEquals(Mode.NUMERIC, Encoder.chooseMode(new ByteArray("0123456789")));
|
||||
assertEquals(Mode.NUMERIC, Encoder.chooseMode("0"));
|
||||
assertEquals(Mode.NUMERIC, Encoder.chooseMode("0123456789"));
|
||||
// Alphanumeric mode.
|
||||
assertEquals(Mode.ALPHANUMERIC, Encoder.chooseMode(new ByteArray("A")));
|
||||
assertEquals(Mode.ALPHANUMERIC, Encoder.chooseMode("A"));
|
||||
assertEquals(Mode.ALPHANUMERIC,
|
||||
Encoder.chooseMode(new ByteArray("0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ $%*+-./:")));
|
||||
Encoder.chooseMode("0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ $%*+-./:"));
|
||||
// 8-bit byte mode.
|
||||
assertEquals(Mode.BYTE, Encoder.chooseMode(new ByteArray("a")));
|
||||
assertEquals(Mode.BYTE, Encoder.chooseMode(new ByteArray("#")));
|
||||
assertEquals(Mode.BYTE, Encoder.chooseMode(new ByteArray("")));
|
||||
assertEquals(Mode.BYTE, Encoder.chooseMode("a"));
|
||||
assertEquals(Mode.BYTE, Encoder.chooseMode("#"));
|
||||
assertEquals(Mode.BYTE, Encoder.chooseMode(""));
|
||||
// Kanji mode. We used to use MODE_KANJI for these, but we stopped
|
||||
// doing that as we cannot distinguish Shift_JIS from other encodings
|
||||
// from data bytes alone. See also comments in qrcode_encoder.h.
|
||||
|
||||
// AIUE in Hiragana in Shift_JIS
|
||||
byte[] dat1 = {0x8,0xa,0x8,0xa,0x8,0xa,0x8,(byte)0xa6};
|
||||
assertEquals(Mode.BYTE, Encoder.chooseMode(new ByteArray(dat1)));
|
||||
assertEquals(Mode.BYTE, Encoder.chooseMode(shiftJISString(new byte[] {0x8,0xa,0x8,0xa,0x8,0xa,0x8,(byte)0xa6})));
|
||||
|
||||
// Nihon in Kanji in Shift_JIS.
|
||||
byte[] dat2 = {0x9,0xf,0x9,0x7b};
|
||||
assertEquals(Mode.BYTE, Encoder.chooseMode(new ByteArray(dat2)));
|
||||
assertEquals(Mode.BYTE, Encoder.chooseMode(shiftJISString(new byte[] {0x9,0xf,0x9,0x7b})));
|
||||
|
||||
// Sou-Utsu-Byou in Kanji in Shift_JIS.
|
||||
byte[] dat3 = {0xe,0x4,0x9,0x5,0x9,0x61};
|
||||
assertEquals(Mode.BYTE, Encoder.chooseMode(new ByteArray(dat3)));
|
||||
assertEquals(Mode.BYTE, Encoder.chooseMode(shiftJISString(new byte[] {0xe,0x4,0x9,0x5,0x9,0x61})));
|
||||
}
|
||||
|
||||
public void testEncode() throws WriterException {
|
||||
QRCode qrCode = new QRCode();
|
||||
Encoder.encode(new ByteArray("ABCDEF"), ErrorCorrectionLevel.H, qrCode);
|
||||
Encoder.encode("ABCDEF", ErrorCorrectionLevel.H, qrCode);
|
||||
// The following is a valid QR Code that can be read by cell phones.
|
||||
String expected =
|
||||
"<<\n" +
|
||||
|
@ -159,7 +158,7 @@ public final class EncoderTestCase extends TestCase {
|
|||
}
|
||||
{
|
||||
BitVector bits = new BitVector();
|
||||
Encoder.appendLengthInfo(1024, // 512 letters (1024/2).
|
||||
Encoder.appendLengthInfo(512, // 512 letters (1024/2).
|
||||
40, // version 40.
|
||||
Mode.KANJI,
|
||||
bits);
|
||||
|
@ -172,25 +171,18 @@ public final class EncoderTestCase extends TestCase {
|
|||
// Should use appendNumericBytes.
|
||||
// 1 = 01 = 0001 in 4 bits.
|
||||
BitVector bits = new BitVector();
|
||||
Encoder.appendBytes(new ByteArray("1"), Mode.NUMERIC, bits);
|
||||
Encoder.appendBytes("1", Mode.NUMERIC, bits);
|
||||
assertEquals("0001" , bits.toString());
|
||||
// 'A' cannot be encoded in MODE_NUMERIC.
|
||||
try {
|
||||
Encoder.appendBytes(new ByteArray("A"), Mode.NUMERIC, bits);
|
||||
fail("Should have thrown exception");
|
||||
} catch (WriterException we) {
|
||||
// good
|
||||
}
|
||||
}
|
||||
{
|
||||
// Should use appendAlphanumericBytes.
|
||||
// A = 10 = 0xa = 001010 in 6 bits
|
||||
BitVector bits = new BitVector();
|
||||
Encoder.appendBytes(new ByteArray("A"), Mode.ALPHANUMERIC, bits);
|
||||
Encoder.appendBytes("A", Mode.ALPHANUMERIC, bits);
|
||||
assertEquals("001010" , bits.toString());
|
||||
// Lower letters such as 'a' cannot be encoded in MODE_ALPHANUMERIC.
|
||||
try {
|
||||
Encoder.appendBytes(new ByteArray("a"), Mode.ALPHANUMERIC, bits);
|
||||
Encoder.appendBytes("a", Mode.ALPHANUMERIC, bits);
|
||||
} catch (WriterException we) {
|
||||
// good
|
||||
}
|
||||
|
@ -199,33 +191,20 @@ public final class EncoderTestCase extends TestCase {
|
|||
// Should use append8BitBytes.
|
||||
// 0x61, 0x62, 0x63
|
||||
BitVector bits = new BitVector();
|
||||
Encoder.appendBytes(new ByteArray("abc"), Mode.BYTE, bits);
|
||||
assertEquals("01100001" + "01100010" + "01100011", bits.toString());
|
||||
Encoder.appendBytes("abc", Mode.BYTE, bits);
|
||||
assertEquals("011000010110001001100011", bits.toString());
|
||||
// Anything can be encoded in QRCode.MODE_8BIT_BYTE.
|
||||
byte[] bytes = {0x00};
|
||||
Encoder.appendBytes(new ByteArray(bytes), Mode.BYTE, bits);
|
||||
Encoder.appendBytes("\0", Mode.BYTE, bits);
|
||||
}
|
||||
{
|
||||
// Should use appendKanjiBytes.
|
||||
// 0x93, 0x5f
|
||||
BitVector bits = new BitVector();
|
||||
byte[] bytes = {(byte)0x93,0x5f};
|
||||
Encoder.appendBytes(new ByteArray(bytes), Mode.KANJI, bits);
|
||||
Encoder.appendBytes(shiftJISString(new byte[] {(byte)0x93,0x5f}), Mode.KANJI, bits);
|
||||
assertEquals("0110110011111", bits.toString());
|
||||
// ASCII characters can not be encoded in QRCode.MODE_KANJI.
|
||||
|
||||
try {
|
||||
Encoder.appendBytes(new ByteArray("a"), Mode.KANJI, bits);
|
||||
} catch (WriterException we) {
|
||||
// good
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public void testInit() {
|
||||
// TODO: should be implemented.
|
||||
}
|
||||
|
||||
public void testTerminateBits() throws WriterException {
|
||||
{
|
||||
BitVector v = new BitVector();
|
||||
|
@ -306,14 +285,14 @@ public final class EncoderTestCase extends TestCase {
|
|||
|
||||
public void testInterleaveWithECBytes() throws WriterException {
|
||||
{
|
||||
final byte[] dataBytes = {32, 65, (byte)205, 69, 41, (byte)220, 46, (byte)128, (byte)236};
|
||||
byte[] dataBytes = {32, 65, (byte)205, 69, 41, (byte)220, 46, (byte)128, (byte)236};
|
||||
BitVector in = new BitVector();
|
||||
for (byte dataByte: dataBytes) {
|
||||
in.appendBits(dataByte, 8);
|
||||
}
|
||||
BitVector out = new BitVector();
|
||||
Encoder.interleaveWithECBytes(in, 26, 9, 1, out);
|
||||
final byte[] expected = {
|
||||
byte[] expected = {
|
||||
// Data bytes.
|
||||
32, 65, (byte)205, 69, 41, (byte)220, 46, (byte)128, (byte)236,
|
||||
// Error correction bytes.
|
||||
|
@ -321,7 +300,7 @@ public final class EncoderTestCase extends TestCase {
|
|||
(byte)237, 85, (byte)224, 96, 74, (byte)219, 61,
|
||||
};
|
||||
assertEquals(expected.length, out.sizeInBytes());
|
||||
final byte[] outArray = out.getArray();
|
||||
byte[] outArray = out.getArray();
|
||||
// Can't use Arrays.equals(), because outArray may be longer than out.sizeInBytes()
|
||||
for (int x = 0; x < expected.length; x++) {
|
||||
assertEquals(expected[x], outArray[x]);
|
||||
|
@ -329,7 +308,7 @@ public final class EncoderTestCase extends TestCase {
|
|||
}
|
||||
// Numbers are from http://www.swetake.com/qr/qr8.html
|
||||
{
|
||||
final byte[] dataBytes = {
|
||||
byte[] dataBytes = {
|
||||
67, 70, 22, 38, 54, 70, 86, 102, 118, (byte)134, (byte)150, (byte)166, (byte)182,
|
||||
(byte)198, (byte)214, (byte)230, (byte)247, 7, 23, 39, 55, 71, 87, 103, 119, (byte)135,
|
||||
(byte)151, (byte)166, 22, 38, 54, 70, 86, 102, 118, (byte)134, (byte)150, (byte)166,
|
||||
|
@ -343,7 +322,7 @@ public final class EncoderTestCase extends TestCase {
|
|||
}
|
||||
BitVector out = new BitVector();
|
||||
Encoder.interleaveWithECBytes(in, 134, 62, 4, out);
|
||||
final byte[] expected = {
|
||||
byte[] expected = {
|
||||
// Data bytes.
|
||||
67, (byte)230, 54, 55, 70, (byte)247, 70, 71, 22, 7, 86, 87, 38, 23, 102, 103, 54, 39,
|
||||
118, 119, 70, 55, (byte)134, (byte)135, 86, 71, (byte)150, (byte)151, 102, 87, (byte)166,
|
||||
|
@ -361,85 +340,76 @@ public final class EncoderTestCase extends TestCase {
|
|||
(byte)187, 49, (byte)156, (byte)214,
|
||||
};
|
||||
assertEquals(expected.length, out.sizeInBytes());
|
||||
final byte[] outArray = out.getArray();
|
||||
byte[] outArray = out.getArray();
|
||||
for (int x = 0; x < expected.length; x++) {
|
||||
assertEquals(expected[x], outArray[x]);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public void testAppendNumericBytes() throws WriterException {
|
||||
public void testAppendNumericBytes() {
|
||||
{
|
||||
// 1 = 01 = 0001 in 4 bits.
|
||||
BitVector bits = new BitVector();
|
||||
Encoder.appendNumericBytes(new ByteArray("1"), bits);
|
||||
Encoder.appendNumericBytes("1", bits);
|
||||
assertEquals("0001" , bits.toString());
|
||||
}
|
||||
{
|
||||
// 12 = 0xc = 0001100 in 7 bits.
|
||||
BitVector bits = new BitVector();
|
||||
Encoder.appendNumericBytes(new ByteArray("12"), bits);
|
||||
Encoder.appendNumericBytes("12", bits);
|
||||
assertEquals("0001100" , bits.toString());
|
||||
}
|
||||
{
|
||||
// 123 = 0x7b = 0001111011 in 10 bits.
|
||||
BitVector bits = new BitVector();
|
||||
Encoder.appendNumericBytes(new ByteArray("123"), bits);
|
||||
Encoder.appendNumericBytes("123", bits);
|
||||
assertEquals("0001111011" , bits.toString());
|
||||
}
|
||||
{
|
||||
// 1234 = "123" + "4" = 0001111011 + 0100
|
||||
BitVector bits = new BitVector();
|
||||
Encoder.appendNumericBytes(new ByteArray("1234"), bits);
|
||||
Encoder.appendNumericBytes("1234", bits);
|
||||
assertEquals("0001111011" + "0100" , bits.toString());
|
||||
}
|
||||
{
|
||||
// Empty.
|
||||
BitVector bits = new BitVector();
|
||||
Encoder.appendNumericBytes(new ByteArray(""), bits);
|
||||
Encoder.appendNumericBytes("", bits);
|
||||
assertEquals("" , bits.toString());
|
||||
}
|
||||
{
|
||||
// Invalid data.
|
||||
BitVector bits = new BitVector();
|
||||
try {
|
||||
Encoder.appendNumericBytes(new ByteArray("abc"), bits);
|
||||
} catch (WriterException we) {
|
||||
// good
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public void testAppendAlphanumericBytes() throws WriterException {
|
||||
{
|
||||
// A = 10 = 0xa = 001010 in 6 bits
|
||||
BitVector bits = new BitVector();
|
||||
Encoder.appendAlphanumericBytes(new ByteArray("A"), bits);
|
||||
Encoder.appendAlphanumericBytes("A", bits);
|
||||
assertEquals("001010" , bits.toString());
|
||||
}
|
||||
{
|
||||
// AB = 10 * 45 + 11 = 461 = 0x1cd = 00111001101 in 11 bits
|
||||
BitVector bits = new BitVector();
|
||||
Encoder.appendAlphanumericBytes(new ByteArray("AB"), bits);
|
||||
Encoder.appendAlphanumericBytes("AB", bits);
|
||||
assertEquals("00111001101", bits.toString());
|
||||
}
|
||||
{
|
||||
// ABC = "AB" + "C" = 00111001101 + 001100
|
||||
BitVector bits = new BitVector();
|
||||
Encoder.appendAlphanumericBytes(new ByteArray("ABC"), bits);
|
||||
Encoder.appendAlphanumericBytes("ABC", bits);
|
||||
assertEquals("00111001101" + "001100" , bits.toString());
|
||||
}
|
||||
{
|
||||
// Empty.
|
||||
BitVector bits = new BitVector();
|
||||
Encoder.appendAlphanumericBytes(new ByteArray(""), bits);
|
||||
Encoder.appendAlphanumericBytes("", bits);
|
||||
assertEquals("" , bits.toString());
|
||||
}
|
||||
{
|
||||
// Invalid data.
|
||||
BitVector bits = new BitVector();
|
||||
try {
|
||||
Encoder.appendAlphanumericBytes(new ByteArray("abc"), bits);
|
||||
Encoder.appendAlphanumericBytes("abc", bits);
|
||||
} catch (WriterException we) {
|
||||
// good
|
||||
}
|
||||
|
@ -450,93 +420,26 @@ public final class EncoderTestCase extends TestCase {
|
|||
{
|
||||
// 0x61, 0x62, 0x63
|
||||
BitVector bits = new BitVector();
|
||||
Encoder.append8BitBytes(new ByteArray("abc"), bits);
|
||||
Encoder.append8BitBytes("abc", bits);
|
||||
assertEquals("01100001" + "01100010" + "01100011", bits.toString());
|
||||
}
|
||||
{
|
||||
// Empty.
|
||||
BitVector bits = new BitVector();
|
||||
Encoder.append8BitBytes(new ByteArray(""), bits);
|
||||
Encoder.append8BitBytes("", bits);
|
||||
assertEquals("", bits.toString());
|
||||
}
|
||||
}
|
||||
|
||||
// Numbers are from page 21 of JISX0510:2004
|
||||
public void testAppendKanjiBytes() throws WriterException {
|
||||
{
|
||||
BitVector bits = new BitVector();
|
||||
byte[] dat1 = {(byte)0x93,0x5f};
|
||||
Encoder.appendKanjiBytes(new ByteArray(dat1), bits);
|
||||
Encoder.appendKanjiBytes(shiftJISString(new byte[] {(byte)0x93,0x5f}), bits);
|
||||
assertEquals("0110110011111", bits.toString());
|
||||
byte[] dat2 = {(byte)0xe4,(byte)0xaa};
|
||||
Encoder.appendKanjiBytes(new ByteArray(dat2), bits);
|
||||
Encoder.appendKanjiBytes(shiftJISString(new byte[] {(byte)0xe4,(byte)0xaa}), bits);
|
||||
assertEquals("0110110011111" + "1101010101010", bits.toString());
|
||||
}
|
||||
}
|
||||
|
||||
// JAVAPORT: Uncomment and fix up with new Reed Solomon objects
|
||||
// static boolean ComparePoly(final int[] expected, final int size, final GF_Poly poly) {
|
||||
// if (size != poly.degree() + 1) {
|
||||
// return false;
|
||||
// }
|
||||
// for (int i = 0; i < size; ++i) {
|
||||
// // "expected" is ordered in a reverse order. We reverse the coeff
|
||||
// // index for comparison.
|
||||
// final int coeff = GaloisField.GetField(8).Log(
|
||||
// poly.coeff(size - i - 1));
|
||||
// if (expected[i] != coeff) {
|
||||
// Debug.LOG_ERROR("values don't match at " + i + ": " +
|
||||
// expected[i] + " vs. " + coeff);
|
||||
// return false;
|
||||
// }
|
||||
// }
|
||||
// return true;
|
||||
// }
|
||||
//
|
||||
// // Numbers are from Appendix A of JISX0510 2004 (p.59).
|
||||
// public void testGetECPoly() {
|
||||
// {
|
||||
// final GF_Poly poly = Encoder.GetECPoly(7);
|
||||
// final int[] expected = {0, 87, 229, 146, 149, 238, 102, 21};
|
||||
// assertTrue(ComparePoly(expected, expected.length, poly));
|
||||
// }
|
||||
// {
|
||||
// final GF_Poly poly = Encoder.GetECPoly(17);
|
||||
// final int[] expected = {
|
||||
// 0, 43, 139, 206, 78, 43, 239, 123, 206, 214, 147, 24, 99, 150,
|
||||
// 39, 243, 163, 136
|
||||
// };
|
||||
// assertTrue(ComparePoly(expected, expected.length, poly));
|
||||
// }
|
||||
// {
|
||||
// final GF_Poly poly = Encoder.GetECPoly(34);
|
||||
// final int[] expected = {
|
||||
// 0, 111, 77, 146, 94, 26, 21, 108, 19,
|
||||
// 105, 94, 113, 193, 86, 140, 163, 125,
|
||||
// 58,
|
||||
// 158, 229, 239, 218, 103, 56, 70, 114,
|
||||
// 61, 183, 129, 167, 13, 98, 62, 129, 51
|
||||
// };
|
||||
// assertTrue(ComparePoly(expected, expected.length, poly));
|
||||
// }
|
||||
// {
|
||||
// final GF_Poly poly = Encoder.GetECPoly(68);
|
||||
// final int[] expected = {
|
||||
// 0, 247, 159, 223, 33, 224, 93, 77, 70,
|
||||
// 90, 160, 32, 254, 43, 150, 84, 101,
|
||||
// 190,
|
||||
// 205, 133, 52, 60, 202, 165, 220, 203,
|
||||
// 151, 93, 84, 15, 84, 253, 173, 160,
|
||||
// 89, 227, 52, 199, 97, 95, 231, 52,
|
||||
// 177, 41, 125, 137, 241, 166, 225, 118,
|
||||
// 2, 54,
|
||||
// 32, 82, 215, 175, 198, 43, 238, 235,
|
||||
// 27, 101, 184, 127, 3, 5, 8, 163, 238
|
||||
// };
|
||||
// assertTrue(ComparePoly(expected, expected.length, poly));
|
||||
// }
|
||||
// }
|
||||
|
||||
// Numbers are from http://www.swetake.com/qr/qr3.html and
|
||||
// http://www.swetake.com/qr/qr9.html
|
||||
public void testGenerateECBytes() {
|
||||
|
@ -577,39 +480,6 @@ public final class EncoderTestCase extends TestCase {
|
|||
}
|
||||
}
|
||||
|
||||
public void testIsValidKanji() {
|
||||
assertTrue(Encoder.isValidKanji(0x82, 0xa0)); // Hiragana "A".
|
||||
assertTrue(Encoder.isValidKanji(0x93, 0xfa)); // Nichi in Kanji.
|
||||
assertTrue(Encoder.isValidKanji(0x8a, 0xbf)); // Kan in Kanji.
|
||||
assertTrue(Encoder.isValidKanji(0xe7, 0x4e)); // Sou in Kanji.
|
||||
assertTrue(Encoder.isValidKanji(0xea, 0xa2)); // Haruka in Kanji.
|
||||
|
||||
assertFalse(Encoder.isValidKanji('0', '1'));
|
||||
assertFalse(Encoder.isValidKanji(0x82, 0x7f));
|
||||
assertFalse(Encoder.isValidKanji(0xa0, 0xa0));
|
||||
}
|
||||
|
||||
public void testIsValidKanjiSequence() {
|
||||
// AIUEO in Katakana
|
||||
byte[] dat1 = {
|
||||
(byte)0x83, 0x41, (byte)0x83, 0x43, (byte)0x83, 0x45, (byte)0x83, 0x47, (byte)0x83, 0x49
|
||||
};
|
||||
assertTrue(Encoder.isValidKanjiSequence(new ByteArray(dat1)));
|
||||
// 012345 in multi-byte letters.
|
||||
byte[] dat2 = {
|
||||
(byte)0x82, 0x4f, (byte)0x82, 0x50, (byte)0x82, 0x51, (byte)0x82, 0x52, (byte)0x82, 0x53,
|
||||
(byte)0x82, 0x54
|
||||
};
|
||||
assertTrue(Encoder.isValidKanjiSequence(new ByteArray(dat2)));
|
||||
// Yoroshiku in Kanji.
|
||||
byte[] dat3 = {
|
||||
(byte)0x96, (byte)0xe9, (byte)0x98, 0x49, (byte)0x8e, (byte)0x80, (byte)0x8b, (byte)0xea
|
||||
};
|
||||
assertTrue(Encoder.isValidKanjiSequence(new ByteArray(dat3)));
|
||||
assertFalse(Encoder.isValidKanjiSequence(new ByteArray("0123")));
|
||||
assertFalse(Encoder.isValidKanjiSequence(new ByteArray("ABC")));
|
||||
}
|
||||
|
||||
public void testBugInBitVectorNumBytes() throws WriterException {
|
||||
// There was a bug in BitVector.sizeInBytes() that caused it to return a
|
||||
// smaller-by-one value (ex. 1465 instead of 1466) if the number of bits
|
||||
|
@ -639,12 +509,20 @@ public final class EncoderTestCase extends TestCase {
|
|||
// - To be precise, it needs 11727 + 4 (getMode info) + 14 (length info) =
|
||||
// 11745 bits = 1468.125 bytes are needed (i.e. cannot fit in 1468
|
||||
// bytes).
|
||||
final int arraySize = 3518;
|
||||
byte[] dataBytes = new byte[arraySize];
|
||||
for (int x = 0; x < arraySize; x++) {
|
||||
dataBytes[x] = '0';
|
||||
StringBuilder builder = new StringBuilder(3518);
|
||||
for (int x = 0; x < 3518; x++) {
|
||||
builder.append('0');
|
||||
}
|
||||
QRCode qrCode = new QRCode();
|
||||
Encoder.encode(new ByteArray(dataBytes), ErrorCorrectionLevel.L, qrCode);
|
||||
Encoder.encode(builder.toString(), ErrorCorrectionLevel.L, qrCode);
|
||||
}
|
||||
|
||||
private static String shiftJISString(byte[] bytes) throws WriterException {
|
||||
try {
|
||||
return new String(bytes, "Shift_JIS");
|
||||
} catch (UnsupportedEncodingException uee) {
|
||||
throw new WriterException(uee.toString());
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -234,7 +234,7 @@ public final class MatrixUtilTestCase extends TestCase {
|
|||
" 1 0 1 1 1 0 1 0 1 1 1 1 0 0 0 0 1 1 1 0 0\n" +
|
||||
" 1 0 0 0 0 0 1 0 0 0 0 0 0 0 0 0 1 0 1 0 0\n" +
|
||||
" 1 1 1 1 1 1 1 0 0 0 1 1 1 1 1 0 1 0 0 1 0\n";
|
||||
char bytes[] = {32, 65, 205, 69, 41, 220, 46, 128, 236,
|
||||
char[] bytes = {32, 65, 205, 69, 41, 220, 46, 128, 236,
|
||||
42, 159, 74, 221, 244, 169, 239, 150, 138,
|
||||
70, 237, 85, 224, 96, 74, 219 , 61};
|
||||
BitVector bits = new BitVector();
|
||||
|
|
|
@ -155,20 +155,6 @@ public final class QRCodeTestCase extends TestCase {
|
|||
}
|
||||
}
|
||||
|
||||
public void testIsValidVersion() {
|
||||
assertFalse(QRCode.isValidVersion(0));
|
||||
assertTrue(QRCode.isValidVersion(1));
|
||||
assertTrue(QRCode.isValidVersion(40));
|
||||
assertFalse(QRCode.isValidVersion(0));
|
||||
}
|
||||
|
||||
public void testIsValidMatrixWidth() {
|
||||
assertFalse(QRCode.isValidMatrixWidth(20));
|
||||
assertTrue(QRCode.isValidMatrixWidth(21));
|
||||
assertTrue(QRCode.isValidMatrixWidth(177));
|
||||
assertFalse(QRCode.isValidMatrixWidth(178));
|
||||
}
|
||||
|
||||
public void testIsValidMaskPattern() {
|
||||
assertFalse(QRCode.isValidMaskPattern(-1));
|
||||
assertTrue(QRCode.isValidMaskPattern(0));
|
||||
|
|
Loading…
Reference in a new issue