diff --git a/core/src/com/google/zxing/aztec/AztecWriter.java b/core/src/com/google/zxing/aztec/AztecWriter.java
index 4e304c182..9e89e84c0 100644
--- a/core/src/com/google/zxing/aztec/AztecWriter.java
+++ b/core/src/com/google/zxing/aztec/AztecWriter.java
@@ -32,7 +32,7 @@ public final class AztecWriter implements Writer {
@Override
public BitMatrix encode(String contents, BarcodeFormat format, int width, int height) {
- return encode(contents, format, DEFAULT_CHARSET, Encoder.DEFAULT_EC_PERCENT);
+ return encode(contents, format, width, height, DEFAULT_CHARSET, Encoder.DEFAULT_EC_PERCENT);
}
@Override
@@ -41,16 +41,51 @@ public final class AztecWriter implements Writer {
Number eccPercent = hints == null ? null : (Number) hints.get(EncodeHintType.ERROR_CORRECTION);
return encode(contents,
format,
+ width,
+ height,
charset == null ? DEFAULT_CHARSET : Charset.forName(charset),
eccPercent == null ? Encoder.DEFAULT_EC_PERCENT : eccPercent.intValue());
}
- private static BitMatrix encode(String contents, BarcodeFormat format, Charset charset, int eccPercent) {
+ private static BitMatrix encode(String contents,
+ BarcodeFormat format,
+ int width,
+ int height,
+ Charset charset,
+ int eccPercent) {
if (format != BarcodeFormat.AZTEC) {
throw new IllegalArgumentException("Can only encode AZTEC, but got " + format);
}
AztecCode aztec = Encoder.encode(contents.getBytes(charset), eccPercent);
- return aztec.getMatrix();
+ return renderResult(aztec, width, height);
+ }
+
+ private static BitMatrix renderResult(AztecCode code, int width, int height) {
+ BitMatrix input = code.getMatrix();
+ if (input == null) {
+ throw new IllegalStateException();
+ }
+ int inputWidth = input.getWidth();
+ int inputHeight = input.getHeight();
+ int outputWidth = Math.max(width, inputWidth);
+ int outputHeight = Math.max(height, inputHeight);
+
+ int multiple = Math.min(outputWidth / inputWidth, outputHeight / inputHeight);
+ int leftPadding = (outputWidth - (inputWidth * multiple)) / 2;
+ int topPadding = (outputHeight - (inputHeight * multiple)) / 2;
+
+ BitMatrix output = new BitMatrix(outputWidth, outputHeight);
+
+ for (int inputY = 0, outputY = topPadding; inputY < inputHeight; inputY++, outputY += multiple) {
+ // Write the contents of this row of the barcode
+ for (int inputX = 0, outputX = leftPadding; inputX < inputWidth; inputX++, outputX += multiple) {
+ if (input.get(inputX, inputY)) {
+ output.setRegion(outputX, outputY, multiple, multiple);
+ }
+ }
+ }
+
+ return output;
}
}
diff --git a/core/src/com/google/zxing/aztec/decoder/Decoder.java b/core/src/com/google/zxing/aztec/decoder/Decoder.java
index b2293bf7a..2dc1973ed 100644
--- a/core/src/com/google/zxing/aztec/decoder/Decoder.java
+++ b/core/src/com/google/zxing/aztec/decoder/Decoder.java
@@ -24,6 +24,8 @@ import com.google.zxing.common.reedsolomon.GenericGF;
import com.google.zxing.common.reedsolomon.ReedSolomonDecoder;
import com.google.zxing.common.reedsolomon.ReedSolomonException;
+import java.util.Arrays;
+
/**
*
The main class which implements Aztec Code decoding -- as opposed to locating and extracting
* the Aztec Code from an image.
@@ -41,24 +43,6 @@ public final class Decoder {
BINARY
}
- private static final int[] NB_BITS_COMPACT = {
- 0, 104, 240, 408, 608
- };
-
- private static final int[] NB_BITS = {
- 0, 128, 288, 480, 704, 960, 1248, 1568, 1920, 2304, 2720, 3168, 3648, 4160, 4704, 5280, 5888, 6528,
- 7200, 7904, 8640, 9408, 10208, 11040, 11904, 12800, 13728, 14688, 15680, 16704, 17760, 18848, 19968
- };
-
- private static final int[] NB_DATABLOCK_COMPACT = {
- 0, 17, 40, 51, 76
- };
-
- private static final int[] NB_DATABLOCK = {
- 0, 21, 48, 60, 88, 120, 156, 196, 240, 230, 272, 316, 364, 416, 470, 528, 588, 652, 720, 790, 864,
- 940, 1020, 920, 992, 1066, 1144, 1224, 1306, 1392, 1480, 1570, 1664
- };
-
private static final String[] UPPER_TABLE = {
"CTRL_PS", " ", "A", "B", "C", "D", "E", "F", "G", "H", "I", "J", "K", "L", "M", "N", "O", "P",
"Q", "R", "S", "T", "U", "V", "W", "X", "Y", "Z", "CTRL_LL", "CTRL_ML", "CTRL_DL", "CTRL_BS"
@@ -84,148 +68,82 @@ public final class Decoder {
"CTRL_PS", " ", "0", "1", "2", "3", "4", "5", "6", "7", "8", "9", ",", ".", "CTRL_UL", "CTRL_US"
};
- private int numCodewords;
- private int codewordSize;
private AztecDetectorResult ddata;
- private int invertedBitCount;
public DecoderResult decode(AztecDetectorResult detectorResult) throws FormatException {
ddata = detectorResult;
BitMatrix matrix = detectorResult.getBits();
-
- if (!ddata.isCompact()) {
- matrix = removeDashedLines(ddata.getBits());
- }
-
boolean[] rawbits = extractBits(matrix);
-
boolean[] correctedBits = correctBits(rawbits);
-
String result = getEncodedData(correctedBits);
-
return new DecoderResult(null, result, null, null);
}
+ // This method is used for testing the high-level encoder
+ public static String highLevelDecode(boolean[] correctedBits) {
+ return getEncodedData(correctedBits);
+ }
+
/**
* Gets the string encoded in the aztec code bits
*
* @return the decoded string
- * @throws FormatException if the input is not valid
*/
- private String getEncodedData(boolean[] correctedBits) throws FormatException {
- int endIndex = codewordSize * ddata.getNbDatablocks() - invertedBitCount;
- if (endIndex > correctedBits.length) {
- throw FormatException.getFormatInstance();
- }
- return getEncodedData(correctedBits, endIndex);
- }
-
- // This method is used for testing the high-level encoder
- public static String highLevelDecode(boolean[] correctedBits) {
- return getEncodedData(correctedBits, correctedBits.length);
- }
-
- private static String getEncodedData(boolean[] correctedBits, int endIndex) {
- Table lastTable = Table.UPPER;
- Table table = Table.UPPER;
- int startIndex = 0;
+ private static String getEncodedData(boolean[] correctedBits) {
+ int endIndex = correctedBits.length;
+ Table latchTable = Table.UPPER; // table most recently latched to
+ Table shiftTable = Table.UPPER; // table to use for the next read
StringBuilder result = new StringBuilder(20);
- boolean end = false;
- boolean shift = false;
- boolean switchShift = false;
- boolean binaryShift = false;
-
- while (!end) {
-
- if (shift) {
- // the table is for the next character only
- switchShift = true;
- } else {
- // save the current table in case next one is a shift
- lastTable = table;
- }
-
- int code;
- if (binaryShift) {
- if (endIndex - startIndex < 5) {
+ int index = 0;
+ while (index < endIndex) {
+ if (shiftTable == Table.BINARY) {
+ if (endIndex - index < 5) {
break;
}
-
- int length = readCode(correctedBits, startIndex, 5);
- startIndex += 5;
+ int length = readCode(correctedBits, index, 5);
+ index += 5;
if (length == 0) {
- if (endIndex - startIndex < 11) {
+ if (endIndex - index < 11) {
break;
}
-
- length = readCode(correctedBits, startIndex, 11) + 31;
- startIndex += 11;
+ length = readCode(correctedBits, index, 11) + 31;
+ index += 11;
}
for (int charCount = 0; charCount < length; charCount++) {
- if (endIndex - startIndex < 8) {
- end = true;
+ if (endIndex - index < 8) {
+ index = endIndex; // Force outer loop to exit
break;
}
-
- code = readCode(correctedBits, startIndex, 8);
+ int code = readCode(correctedBits, index, 8);
result.append((char) code);
- startIndex += 8;
+ index += 8;
}
- binaryShift = false;
+ // Go back to whatever mode we had been in
+ shiftTable = latchTable;
} else {
- if (table == Table.BINARY) {
- if (endIndex - startIndex < 8) {
- break;
+ int size = shiftTable == Table.DIGIT ? 4 : 5;
+ if (endIndex - index < size) {
+ break;
+ }
+ int code = readCode(correctedBits, index, size);
+ index += size;
+ String str = getCharacter(shiftTable, code);
+ if (str.startsWith("CTRL_")) {
+ // Table changes
+ shiftTable = getTable(str.charAt(5));
+ if (str.charAt(6) == 'L') {
+ latchTable = shiftTable;
}
- code = readCode(correctedBits, startIndex, 8);
- startIndex += 8;
-
- result.append((char) code);
-
} else {
- int size = 5;
-
- if (table == Table.DIGIT) {
- size = 4;
- }
-
- if (endIndex - startIndex < size) {
- break;
- }
-
- code = readCode(correctedBits, startIndex, size);
- startIndex += size;
-
- String str = getCharacter(table, code);
- if (str.startsWith("CTRL_")) {
- // Table changes
- table = getTable(str.charAt(5));
-
- if (str.charAt(6) == 'S') {
- shift = true;
- if (str.charAt(5) == 'B') {
- binaryShift = true;
- }
- }
- } else {
- result.append(str);
- }
-
-
+ result.append(str);
+ // Go back to whatever mode we had been in
+ shiftTable = latchTable;
}
}
-
- if (switchShift) {
- table = lastTable;
- shift = false;
- switchShift = false;
- }
-
}
return result.toString();
}
-
/**
* gets the table corresponding to the char passed
*/
@@ -266,7 +184,8 @@ public final class Decoder {
case DIGIT:
return DIGIT_TABLE[code];
default:
- return "";
+ // Should not reach here.
+ throw new IllegalStateException("Bad table");
}
}
@@ -278,6 +197,7 @@ public final class Decoder {
*/
private boolean[] correctBits(boolean[] rawbits) throws FormatException {
GenericGF gf;
+ int codewordSize;
if (ddata.getNbLayers() <= 2) {
codewordSize = 6;
@@ -294,30 +214,13 @@ public final class Decoder {
}
int numDataCodewords = ddata.getNbDatablocks();
- int numECCodewords;
- int offset;
-
- if (ddata.isCompact()) {
- offset = NB_BITS_COMPACT[ddata.getNbLayers()] - numCodewords * codewordSize;
- numECCodewords = NB_DATABLOCK_COMPACT[ddata.getNbLayers()] - numDataCodewords;
- } else {
- offset = NB_BITS[ddata.getNbLayers()] - numCodewords * codewordSize;
- numECCodewords = NB_DATABLOCK[ddata.getNbLayers()] - numDataCodewords;
- }
+ int numCodewords = rawbits.length / codewordSize;
+ int offset = rawbits.length % codewordSize;
+ int numECCodewords = numCodewords - numDataCodewords;
int[] dataWords = new int[numCodewords];
- for (int i = 0; i < numCodewords; i++) {
- int flag = 1;
- for (int j = 1; j <= codewordSize; j++) {
- if (rawbits[codewordSize * i + codewordSize - j + offset]) {
- dataWords[i] += flag;
- }
- flag <<= 1;
- }
-
- //if (dataWords[i] >= flag) {
- // flag++;
- //}
+ for (int i = 0; i < numCodewords; i++, offset += codewordSize) {
+ dataWords[i] = readCode(rawbits, offset, codewordSize);
}
try {
@@ -327,48 +230,34 @@ public final class Decoder {
throw FormatException.getFormatInstance();
}
- offset = 0;
- invertedBitCount = 0;
-
- boolean[] correctedBits = new boolean[numDataCodewords * codewordSize];
+ // Now perform the unstuffing operation.
+ // First, count how many bits are going to be thrown out as stuffing
+ int mask = (1 << codewordSize) - 1;
+ int stuffedBits = 0;
for (int i = 0; i < numDataCodewords; i++) {
-
- boolean seriesColor = false;
- int seriesCount = 0;
- int flag = 1 << (codewordSize - 1);
-
- for (int j = 0; j < codewordSize; j++) {
-
- boolean color = (dataWords[i] & flag) == flag;
-
- if (seriesCount == codewordSize - 1) {
-
- if (color == seriesColor) {
- //bit must be inverted
- throw FormatException.getFormatInstance();
- }
-
- seriesColor = false;
- seriesCount = 0;
- offset++;
- invertedBitCount++;
- } else {
-
- if (seriesColor == color) {
- seriesCount++;
- } else {
- seriesCount = 1;
- seriesColor = color;
- }
-
- correctedBits[i * codewordSize + j - offset] = color;
-
- }
-
- flag >>>= 1;
+ int dataWord = dataWords[i];
+ if (dataWord == 0 || dataWord == mask) {
+ throw FormatException.getFormatInstance();
+ } else if (dataWord == 1 || dataWord == mask - 1) {
+ stuffedBits++;
}
}
-
+ // Now, actually unpack the bits and remove the stuffing
+ boolean[] correctedBits = new boolean[numDataCodewords * codewordSize - stuffedBits];
+ int index = 0;
+ for (int i = 0; i < numDataCodewords; i++) {
+ int dataWord = dataWords[i];
+ if (dataWord == 1 || dataWord == mask - 1) {
+ // next codewordSize-1 bits are all zeros or all ones
+ Arrays.fill(correctedBits, index, index + codewordSize - 1, dataWord > 1);
+ index += codewordSize - 1;
+ } else {
+ for (int bit = codewordSize - 1; bit >= 0; --bit) {
+ correctedBits[index++] = (dataWord & (1 << bit)) != 0;
+ }
+ }
+ }
+ assert index == correctedBits.length;
return correctedBits;
}
@@ -376,104 +265,72 @@ public final class Decoder {
* Gets the array of bits from an Aztec Code matrix
*
* @return the array of bits
- * @throws FormatException if the matrix is not a valid aztec code
*/
- private boolean[] extractBits(BitMatrix matrix) throws FormatException {
+ boolean[] extractBits(BitMatrix matrix) {
+ boolean compact = ddata.isCompact();
+ int layers = ddata.getNbLayers();
+ int baseMatrixSize = compact ? 11 + layers * 4 : 14 + layers * 4; // not including alignment lines
+ int[] alignmentMap = new int[baseMatrixSize];
+ boolean[] rawbits = new boolean[totalBitsInLayer(layers, compact)];
- boolean[] rawbits;
- if (ddata.isCompact()) {
- if (ddata.getNbLayers() > NB_BITS_COMPACT.length) {
- throw FormatException.getFormatInstance();
+ if (compact) {
+ for (int i = 0; i < alignmentMap.length; i++) {
+ alignmentMap[i] = i;
}
- rawbits = new boolean[NB_BITS_COMPACT[ddata.getNbLayers()]];
- numCodewords = NB_DATABLOCK_COMPACT[ddata.getNbLayers()];
} else {
- if (ddata.getNbLayers() > NB_BITS.length) {
- throw FormatException.getFormatInstance();
+ int matrixSize = baseMatrixSize + 1 + 2 * ((baseMatrixSize / 2 - 1) / 15);
+ int origCenter = baseMatrixSize / 2;
+ int center = matrixSize / 2;
+ for (int i = 0; i < origCenter; i++) {
+ int newOffset = i + i / 15;
+ alignmentMap[origCenter - i - 1] = center - newOffset - 1;
+ alignmentMap[origCenter + i] = center + newOffset + 1;
}
- rawbits = new boolean[NB_BITS[ddata.getNbLayers()]];
- numCodewords = NB_DATABLOCK[ddata.getNbLayers()];
}
-
- int layer = ddata.getNbLayers();
- int size = matrix.getHeight();
- int rawbitsOffset = 0;
- int matrixOffset = 0;
-
- while (layer != 0) {
-
- int flip = 0;
- for (int i = 0; i < 2 * size - 4; i++) {
- rawbits[rawbitsOffset + i] = matrix.get(matrixOffset + flip, matrixOffset + i / 2);
- rawbits[rawbitsOffset + 2 * size - 4 + i] = matrix.get(matrixOffset + i / 2, matrixOffset + size - 1 - flip);
- flip = (flip + 1) % 2;
+ for (int i = 0, rowOffset = 0; i < layers; i++) {
+ int rowSize = compact ? (layers - i) * 4 + 9 : (layers - i) * 4 + 12;
+ // The top-left most point of this layer is (not including alignment lines)
+ int low = i * 2;
+ // The bottom-right most point of this layer is (not including alignment lines)
+ int high = baseMatrixSize - 1 - low;
+ // We pull bits from the two 2 x rowSize columns and two rowSize x 2 rows
+ for (int j = 0; j < rowSize; j++) {
+ int columnOffset = j * 2;
+ for (int k = 0; k < 2; k++) {
+ // left column
+ rawbits[rowOffset + columnOffset + k] =
+ matrix.get(alignmentMap[low + k], alignmentMap[low + j]);
+ // bottom row
+ rawbits[rowOffset + 2 * rowSize + columnOffset + k] =
+ matrix.get(alignmentMap[low + j], alignmentMap[high - k]);
+ // right column
+ rawbits[rowOffset + 4 * rowSize + columnOffset + k] =
+ matrix.get(alignmentMap[high - k], alignmentMap[high - j]);
+ // top row
+ rawbits[rowOffset + 6 * rowSize + columnOffset + k] =
+ matrix.get(alignmentMap[high - j], alignmentMap[low + k]);
+ }
}
-
- flip = 0;
- for (int i = 2 * size + 1; i > 5; i--) {
- rawbits[rawbitsOffset + 4 * size - 8 + (2 * size - i) + 1] =
- matrix.get(matrixOffset + size - 1 - flip, matrixOffset + i / 2 - 1);
- rawbits[rawbitsOffset + 6 * size - 12 + (2 * size - i) + 1] =
- matrix.get(matrixOffset + i / 2 - 1, matrixOffset + flip);
- flip = (flip + 1) % 2;
- }
-
- matrixOffset += 2;
- rawbitsOffset += 8 * size - 16;
- layer--;
- size -= 4;
+ rowOffset += rowSize * 8;
}
-
return rawbits;
}
- /**
- * Transforms an Aztec code matrix by removing the control dashed lines
- */
- private static BitMatrix removeDashedLines(BitMatrix matrix) {
- int nbDashed = 1 + 2 * ((matrix.getWidth() - 1) / 2 / 16);
- BitMatrix newMatrix = new BitMatrix(matrix.getWidth() - nbDashed, matrix.getHeight() - nbDashed);
-
- int nx = 0;
-
- for (int x = 0; x < matrix.getWidth(); x++) {
-
- if ((matrix.getWidth() / 2 - x) % 16 == 0) {
- continue;
- }
-
- int ny = 0;
- for (int y = 0; y < matrix.getHeight(); y++) {
-
- if ((matrix.getWidth() / 2 - y) % 16 == 0) {
- continue;
- }
-
- if (matrix.get(x, y)) {
- newMatrix.set(nx, ny);
- }
- ny++;
- }
- nx++;
- }
-
- return newMatrix;
- }
-
/**
* Reads a code of given length and at given index in an array of bits
*/
private static int readCode(boolean[] rawbits, int startIndex, int length) {
int res = 0;
-
for (int i = startIndex; i < startIndex + length; i++) {
res <<= 1;
if (rawbits[i]) {
res++;
}
}
-
return res;
}
+ private static int totalBitsInLayer(int layers, boolean compact) {
+ return ((compact ? 88 : 112) + 16 * layers) * layers;
+ }
}
diff --git a/core/src/com/google/zxing/aztec/detector/Detector.java b/core/src/com/google/zxing/aztec/detector/Detector.java
index fc4403760..8cbe9bb72 100644
--- a/core/src/com/google/zxing/aztec/detector/Detector.java
+++ b/core/src/com/google/zxing/aztec/detector/Detector.java
@@ -67,9 +67,11 @@ public final class Detector {
extractParameters(bullsEyeCorners);
// 4. Sample the grid
- BitMatrix bits = sampleGrid(image,
- bullsEyeCorners[shift%4], bullsEyeCorners[(shift+1)%4],
- bullsEyeCorners[(shift+2)%4], bullsEyeCorners[(shift+3)%4]);
+ BitMatrix bits = sampleGrid(image,
+ bullsEyeCorners[shift % 4],
+ bullsEyeCorners[(shift + 1) % 4],
+ bullsEyeCorners[(shift + 2) % 4],
+ bullsEyeCorners[(shift + 3) % 4]);
// 5. Get the corners of the matrix.
ResultPoint[] corners = getMatrixCornerPoints(bullsEyeCorners);
@@ -84,89 +86,91 @@ public final class Detector {
* @throws NotFoundException in case of too many errors or invalid parameters
*/
private void extractParameters(ResultPoint[] bullsEyeCorners) throws NotFoundException {
- if (!isValid(bullsEyeCorners[0]) || !isValid(bullsEyeCorners[1]) ||
+ if (!isValid(bullsEyeCorners[0]) || !isValid(bullsEyeCorners[1]) ||
!isValid(bullsEyeCorners[2]) || !isValid(bullsEyeCorners[3])) {
- throw NotFoundException.getNotFoundInstance();
- }
- int twoCenterLayers = 2 * nbCenterLayers;
- // Get the bits around the bull's eye
- boolean[] resab = sampleLine(bullsEyeCorners[0], bullsEyeCorners[1], twoCenterLayers+1);
- boolean[] resbc = sampleLine(bullsEyeCorners[1], bullsEyeCorners[2], twoCenterLayers+1);
- boolean[] rescd = sampleLine(bullsEyeCorners[2], bullsEyeCorners[3], twoCenterLayers+1);
- boolean[] resda = sampleLine(bullsEyeCorners[3], bullsEyeCorners[0], twoCenterLayers+1);
-
- // Determine the orientation of the matrix
- if (resab[0] && resab[twoCenterLayers]) {
- shift = 0;
- } else if (resbc[0] && resbc[twoCenterLayers]) {
- shift = 1;
- } else if (rescd[0] && rescd[twoCenterLayers]) {
- shift = 2;
- } else if (resda[0] && resda[twoCenterLayers]) {
- shift = 3;
- } else {
throw NotFoundException.getNotFoundInstance();
}
-
- //d a
- //
- //c b
-
- // Flatten the bits in a single array
- boolean[] parameterData;
- boolean[] shiftedParameterData;
- if (compact) {
- shiftedParameterData = new boolean[28];
- for (int i = 0; i < 7; i++) {
- shiftedParameterData[i] = resab[2+i];
- shiftedParameterData[i+7] = resbc[2+i];
- shiftedParameterData[i+14] = rescd[2+i];
- shiftedParameterData[i+21] = resda[2+i];
- }
-
- parameterData = new boolean[28];
- for (int i = 0; i < 28; i++) {
- parameterData[i] = shiftedParameterData[(i+shift*7)%28];
- }
- } else {
- shiftedParameterData = new boolean[40];
- for (int i = 0; i < 11; i++) {
- if (i < 5) {
- shiftedParameterData[i] = resab[2+i];
- shiftedParameterData[i+10] = resbc[2+i];
- shiftedParameterData[i+20] = rescd[2+i];
- shiftedParameterData[i+30] = resda[2+i];
- }
- if (i > 5) {
- shiftedParameterData[i-1] = resab[2+i];
- shiftedParameterData[i+9] = resbc[2+i];
- shiftedParameterData[i+19] = rescd[2+i];
- shiftedParameterData[i+29] = resda[2+i];
- }
- }
-
- parameterData = new boolean[40];
- for (int i = 0; i < 40; i++) {
- parameterData[i] = shiftedParameterData[(i+shift*10)%40];
+ int length = 2 * nbCenterLayers;
+ // Get the bits around the bull's eye
+ int[] sides = {
+ sampleLine(bullsEyeCorners[0], bullsEyeCorners[1], length), // Right side
+ sampleLine(bullsEyeCorners[1], bullsEyeCorners[2], length), // Bottom
+ sampleLine(bullsEyeCorners[2], bullsEyeCorners[3], length), // Left side
+ sampleLine(bullsEyeCorners[3], bullsEyeCorners[0], length) // Top
+ };
+
+ // bullsEyeCorners[shift] is the corner of the bulls'eye that has three
+ // orientation marks.
+ // sides[shift] is the row/column that goes from the corner with three
+ // orientation marks to the corner with two.
+ shift = getRotation(sides, length);
+
+ // Flatten the parameter bits into a single 28- or 40-bit long
+ long parameterData = 0;
+ for (int i = 0; i < 4; i++) {
+ int side = sides[(shift + i) % 4];
+ if (compact) {
+ // Each side of the form ..XXXXXXX. where Xs are parameter data
+ parameterData <<= 7;
+ parameterData += (side >> 1) & 0x7F;
+ } else {
+ // Each side of the form ..XXXXX.XXXXX. where Xs are parameter data
+ parameterData <<= 10;
+ parameterData += ((side >> 2) & (0x1f << 5)) + ((side >> 1) & 0x1F);
}
}
- // corrects the error using RS algorithm
- correctParameterData(parameterData, compact);
+ // Corrects parameter data using RS. Returns just the data portion
+ // without the error correction.
+ int correctedData = getCorrectedParameterData(parameterData, compact);
- // gets the parameters from the bit array
- getParameters(parameterData);
+ if (compact) {
+ // 8 bits: 2 bits layers and 6 bits data blocks
+ nbLayers = (correctedData >> 6) + 1;
+ nbDataBlocks = (correctedData & 0x3F) + 1;
+ } else {
+ // 16 bits: 5 bits layers and 11 bits data blocks
+ nbLayers = (correctedData >> 11) + 1;
+ nbDataBlocks = (correctedData & 0x7FF) + 1;
+ }
}
- /**
- * Gets the Aztec code corners from the bull's eye corners and the parameters.
- *
- * @param bullsEyeCorners the array of bull's eye corners
- * @return the array of aztec code corners
- * @throws NotFoundException if the corner points do not fit in the image
- */
- private ResultPoint[] getMatrixCornerPoints(ResultPoint[] bullsEyeCorners) throws NotFoundException {
- return expandSquare(bullsEyeCorners, 2 * nbCenterLayers, getDimension());
+ private int[] expectedCornerBits = {
+ 0xee0, // 07340 XXX .XX X.. ...
+ 0x1dc, // 00734 ... XXX .XX X..
+ 0x83b, // 04073 X.. ... XXX .XX
+ 0x707, // 03407 .XX X.. ... XXX
+ };
+
+ private int getRotation(int[] sides, int length) throws NotFoundException {
+ // In a normal pattern, we expect to See
+ // ** .* D A
+ // * *
+ //
+ // . *
+ // .. .. C B
+ //
+ // Grab the 3 bits from each of the sides the form the locator pattern and concatenate
+ // into a 12-bit integer. Start with the bit at A
+ int cornerBits = 0;
+ for (int side : sides) {
+ // XX......X where X's are orientation marks
+ int t = ((side >> (length - 2)) << 1) + (side & 1);
+ cornerBits = (cornerBits << 3) + t;
+ }
+ // Mov the bottom bit to the top, so that the three bits of the locator pattern at A are
+ // together. cornerBits is now:
+ // 3 orientation bits at A || 3 orientation bits at B || ... || 3 orientation bits at D
+ cornerBits = ((cornerBits & 1) << 11) + (cornerBits >> 1);
+ // The result shift indicates which element of BullsEyeCorners[] goes into the top-left
+ // corner. Since the four rotation values have a Hamming distance of 8, we
+ // can easily tolerate two errors.
+ for (int shift = 0; shift < 4; shift++) {
+ if (Integer.bitCount(cornerBits ^ expectedCornerBits[shift]) <= 2) {
+ return shift;
+ }
+ }
+ throw NotFoundException.getNotFoundInstance();
}
/**
@@ -176,8 +180,7 @@ public final class Detector {
* @param compact true if this is a compact Aztec code
* @throws NotFoundException if the array contains too many errors
*/
- private static void correctParameterData(boolean[] parameterData, boolean compact) throws NotFoundException {
-
+ private static int getCorrectedParameterData(long parameterData, boolean compact) throws NotFoundException {
int numCodewords;
int numDataCodewords;
@@ -191,32 +194,22 @@ public final class Detector {
int numECCodewords = numCodewords - numDataCodewords;
int[] parameterWords = new int[numCodewords];
-
- int codewordSize = 4;
- for (int i = 0; i < numCodewords; i++) {
- int flag = 1;
- for (int j = 1; j <= codewordSize; j++) {
- if (parameterData[codewordSize*i + codewordSize - j]) {
- parameterWords[i] += flag;
- }
- flag <<= 1;
- }
+ for (int i = numCodewords - 1; i >= 0; --i) {
+ parameterWords[i] = (int) parameterData & 0xF;
+ parameterData >>= 4;
}
-
try {
ReedSolomonDecoder rsDecoder = new ReedSolomonDecoder(GenericGF.AZTEC_PARAM);
rsDecoder.decode(parameterWords, numECCodewords);
} catch (ReedSolomonException ignored) {
throw NotFoundException.getNotFoundInstance();
}
-
- for (int i = 0; i < numDataCodewords; i ++) {
- int flag = 1;
- for (int j = 1; j <= codewordSize; j++) {
- parameterData[i*codewordSize+codewordSize-j] = (parameterWords[i] & flag) == flag;
- flag <<= 1;
- }
+ // Toss the error correction. Just return the data as an integer
+ int result = 0;
+ for (int i = 0; i < numDataCodewords; i++) {
+ result = (result << 4) + parameterWords[i];
}
+ return result;
}
/**
@@ -247,9 +240,9 @@ public final class Detector {
//
//c b
- if (nbCenterLayers>2) {
- float q = distance(poutd, pouta)*nbCenterLayers/(distance(pind, pina)*(nbCenterLayers+2));
- if ( q < 0.75 || q > 1.25 || !isWhiteOrBlackRectangle(pouta, poutb, poutc, poutd)) {
+ if (nbCenterLayers > 2) {
+ float q = distance(poutd, pouta) * nbCenterLayers / (distance(pind, pina) * (nbCenterLayers + 2));
+ if (q < 0.75 || q > 1.25 || !isWhiteOrBlackRectangle(pouta, poutb, poutc, poutd)) {
break;
}
}
@@ -266,7 +259,7 @@ public final class Detector {
throw NotFoundException.getNotFoundInstance();
}
- compact = nbCenterLayers==5;
+ compact = nbCenterLayers == 5;
// Expand the square by .5 pixel in each direction so that we're on the border
// between the white square and the black square
@@ -307,12 +300,12 @@ public final class Detector {
// This exception can be in case the initial rectangle is white
// In that case, surely in the bull's eye, we try to expand the rectangle.
- int cx = image.getWidth()/2;
- int cy = image.getHeight()/2;
- pointA = getFirstDifferent(new Point(cx+7, cy-7), false, 1, -1).toResultPoint();
- pointB = getFirstDifferent(new Point(cx+7, cy+7), false, 1, 1).toResultPoint();
- pointC = getFirstDifferent(new Point(cx-7, cy+7), false, -1, 1).toResultPoint();
- pointD = getFirstDifferent(new Point(cx-7, cy-7), false, -1, -1).toResultPoint();
+ int cx = image.getWidth() / 2;
+ int cy = image.getHeight() / 2;
+ pointA = getFirstDifferent(new Point(cx + 7, cy - 7), false, 1, -1).toResultPoint();
+ pointB = getFirstDifferent(new Point(cx + 7, cy + 7), false, 1, 1).toResultPoint();
+ pointC = getFirstDifferent(new Point(cx - 7, cy + 7), false, -1, 1).toResultPoint();
+ pointD = getFirstDifferent(new Point(cx - 7, cy - 7), false, -1, -1).toResultPoint();
}
@@ -332,10 +325,10 @@ public final class Detector {
} catch (NotFoundException e) {
// This exception can be in case the initial rectangle is white
// In that case we try to expand the rectangle.
- pointA = getFirstDifferent(new Point(cx+7, cy-7), false, 1, -1).toResultPoint();
- pointB = getFirstDifferent(new Point(cx+7, cy+7), false, 1, 1).toResultPoint();
- pointC = getFirstDifferent(new Point(cx-7, cy+7), false, -1, 1).toResultPoint();
- pointD = getFirstDifferent(new Point(cx-7, cy-7), false, -1, -1).toResultPoint();
+ pointA = getFirstDifferent(new Point(cx + 7, cy - 7), false, 1, -1).toResultPoint();
+ pointB = getFirstDifferent(new Point(cx + 7, cy + 7), false, 1, 1).toResultPoint();
+ pointC = getFirstDifferent(new Point(cx - 7, cy + 7), false, -1, 1).toResultPoint();
+ pointD = getFirstDifferent(new Point(cx - 7, cy - 7), false, -1, -1).toResultPoint();
}
// Recompute the center of the rectangle
@@ -345,6 +338,17 @@ public final class Detector {
return new Point(cx, cy);
}
+ /**
+ * Gets the Aztec code corners from the bull's eye corners and the parameters.
+ *
+ * @param bullsEyeCorners the array of bull's eye corners
+ * @return the array of aztec code corners
+ * @throws NotFoundException if the corner points do not fit in the image
+ */
+ private ResultPoint[] getMatrixCornerPoints(ResultPoint[] bullsEyeCorners) {
+ return expandSquare(bullsEyeCorners, 2 * nbCenterLayers, getDimension());
+ }
+
/**
* Creates a BitMatrix by sampling the provided image.
* topLeft, topRight, bottomRight, and bottomLeft are the centers of the squares on the
@@ -359,87 +363,50 @@ public final class Detector {
GridSampler sampler = GridSampler.getInstance();
int dimension = getDimension();
- float low = dimension/2.0f - nbCenterLayers;
- float high = dimension/2.0f + nbCenterLayers;
+ float low = dimension / 2.0f - nbCenterLayers;
+ float high = dimension / 2.0f + nbCenterLayers;
return sampler.sampleGrid(image,
- dimension,
- dimension,
- low, low, // topleft
- high, low, // topright
- high, high, // bottomright
- low, high, // bottomleft
- topLeft.getX(), topLeft.getY(),
- topRight.getX(), topRight.getY(),
- bottomRight.getX(), bottomRight.getY(),
- bottomLeft.getX(), bottomLeft.getY());
- }
-
- /**
- * Sets number of layers and number of data blocks from parameter bits
- */
- private void getParameters(boolean[] parameterData) {
-
- int nbBitsForNbLayers;
- int nbBitsForNbDatablocks;
-
- if (compact) {
- nbBitsForNbLayers = 2;
- nbBitsForNbDatablocks = 6;
- } else {
- nbBitsForNbLayers = 5;
- nbBitsForNbDatablocks = 11;
- }
-
- for (int i = 0; i < nbBitsForNbLayers; i++) {
- nbLayers <<= 1;
- if (parameterData[i]) {
- nbLayers++;
- }
- }
-
- for (int i = nbBitsForNbLayers; i < nbBitsForNbLayers + nbBitsForNbDatablocks; i++) {
- nbDataBlocks <<= 1;
- if (parameterData[i]) {
- nbDataBlocks++;
- }
- }
-
- nbLayers++;
- nbDataBlocks++;
+ dimension,
+ dimension,
+ low, low, // topleft
+ high, low, // topright
+ high, high, // bottomright
+ low, high, // bottomleft
+ topLeft.getX(), topLeft.getY(),
+ topRight.getX(), topRight.getY(),
+ bottomRight.getX(), bottomRight.getY(),
+ bottomLeft.getX(), bottomLeft.getY());
}
/**
- * Samples a line
+ * Samples a line.
*
- * @param p1 first point
- * @param p2 second point
+ * @param p1 start point (inclusive)
+ * @param p2 end point (exclusive)
* @param size number of bits
- * @return the array of bits
+ * @return the array of bits as an int (first bit is high-order bit of result)
*/
- private boolean[] sampleLine(ResultPoint p1, ResultPoint p2, int size) {
-
- boolean[] res = new boolean[size];
- float d = distance(p1,p2);
- float moduleSize = d/(size-1);
- float dx = moduleSize*(p2.getX() - p1.getX())/d;
- float dy = moduleSize*(p2.getY() - p1.getY())/d;
+ private int sampleLine(ResultPoint p1, ResultPoint p2, int size) {
+ int result = 0;
+ float d = distance(p1, p2);
+ float moduleSize = d / size;
float px = p1.getX();
float py = p1.getY();
-
+ float dx = moduleSize * (p2.getX() - p1.getX()) / d;
+ float dy = moduleSize * (p2.getY() - p1.getY()) / d;
for (int i = 0; i < size; i++) {
- res[i] = image.get(MathUtils.round(px), MathUtils.round(py));
- px += dx;
- py += dy;
+ if (image.get(MathUtils.round(px + i * dx), MathUtils.round(py + i * dy))) {
+ result |= 1 << (size - i - 1);
+ }
}
-
- return res;
+ return result;
}
/**
* @return true if the border of the rectangle passed in parameter is compound of white points only
- * or black points only
+ * or black points only
*/
private boolean isWhiteOrBlackRectangle(Point p1,
Point p2,
@@ -448,10 +415,10 @@ public final class Detector {
int corr = 3;
- p1 = new Point(p1.getX() -corr, p1.getY() +corr);
- p2 = new Point(p2.getX() -corr, p2.getY() -corr);
- p3 = new Point(p3.getX() +corr, p3.getY() -corr);
- p4 = new Point(p4.getX() +corr, p4.getY() +corr);
+ p1 = new Point(p1.getX() - corr, p1.getY() + corr);
+ p2 = new Point(p2.getX() - corr, p2.getY() - corr);
+ p3 = new Point(p3.getX() + corr, p3.getY() - corr);
+ p4 = new Point(p4.getX() + corr, p4.getY() + corr);
int cInit = getColor(p4, p1);
@@ -483,9 +450,9 @@ public final class Detector {
* @return 1 if segment more than 90% black, -1 if segment is more than 90% white, 0 else
*/
private int getColor(Point p1, Point p2) {
- float d = distance(p1,p2);
- float dx = (p2.getX() - p1.getX())/d;
- float dy = (p2.getY() - p1.getY())/d;
+ float d = distance(p1, p2);
+ float dx = (p2.getX() - p1.getX()) / d;
+ float dy = (p2.getY() - p1.getY()) / d;
int error = 0;
float px = p1.getX();
@@ -494,8 +461,8 @@ public final class Detector {
boolean colorModel = image.get(p1.getX(), p1.getY());
for (int i = 0; i < d; i++) {
- px+=dx;
- py+=dy;
+ px += dx;
+ py += dy;
if (image.get(MathUtils.round(px), MathUtils.round(py)) != colorModel) {
error++;
}
@@ -514,40 +481,39 @@ public final class Detector {
* Gets the coordinate of the first point with a different color in the given direction
*/
private Point getFirstDifferent(Point init, boolean color, int dx, int dy) {
- int x = init.getX() +dx;
- int y = init.getY() +dy;
+ int x = init.getX() + dx;
+ int y = init.getY() + dy;
- while(isValid(x,y) && image.get(x,y) == color) {
- x+=dx;
- y+=dy;
+ while (isValid(x, y) && image.get(x, y) == color) {
+ x += dx;
+ y += dy;
}
- x-=dx;
- y-=dy;
+ x -= dx;
+ y -= dy;
- while(isValid(x,y) && image.get(x, y) == color) {
- x+=dx;
+ while (isValid(x, y) && image.get(x, y) == color) {
+ x += dx;
}
- x-=dx;
+ x -= dx;
- while(isValid(x,y) && image.get(x, y) == color) {
- y+=dy;
+ while (isValid(x, y) && image.get(x, y) == color) {
+ y += dy;
}
- y-=dy;
+ y -= dy;
- return new Point(x,y);
+ return new Point(x, y);
}
/**
* Expand the square represented by the corner points by pushing out equally in all directions
*
- * @param cornerPoints the corners of the square, which has the bull's eye at its center
+ * @param cornerPoints the corners of the square, which has the bull's eye at its center
* @param oldSide the original length of the side of the square in the target bit matrix
* @param newSide the new length of the size of the square in the target bit matrix
* @return the corners of the expanded square
*/
- private ResultPoint[] expandSquare(ResultPoint[] cornerPoints, float oldSide, float newSide)
- throws NotFoundException {
+ private static ResultPoint[] expandSquare(ResultPoint[] cornerPoints, float oldSide, float newSide) {
float ratio = newSide / (2 * oldSide);
float dx = cornerPoints[0].getX() - cornerPoints[2].getX();
float dy = cornerPoints[0].getY() - cornerPoints[2].getY();
@@ -564,7 +530,7 @@ public final class Detector {
ResultPoint result1 = new ResultPoint(centerx + ratio * dx, centery + ratio * dy);
ResultPoint result3 = new ResultPoint(centerx - ratio * dx, centery - ratio * dy);
- return new ResultPoint[] { result0, result1, result2, result3 };
+ return new ResultPoint[]{result0, result1, result2, result3};
}
private boolean isValid(int x, int y) {
@@ -592,7 +558,7 @@ public final class Detector {
if (nbLayers <= 4) {
return 4 * nbLayers + 15;
}
- return 4 * nbLayers + 2 * ((nbLayers-4)/8 + 1) + 15;
+ return 4 * nbLayers + 2 * ((nbLayers - 4) / 8 + 1) + 15;
}
static final class Point {
diff --git a/core/src/com/google/zxing/aztec/encoder/Encoder.java b/core/src/com/google/zxing/aztec/encoder/Encoder.java
index 9d18618bc..acbe7c736 100644
--- a/core/src/com/google/zxing/aztec/encoder/Encoder.java
+++ b/core/src/com/google/zxing/aztec/encoder/Encoder.java
@@ -29,20 +29,8 @@ import com.google.zxing.common.reedsolomon.ReedSolomonEncoder;
public final class Encoder {
public static final int DEFAULT_EC_PERCENT = 33; // default minimal percentage of error check words
- private static final int[] NB_BITS; // total bits per compact symbol for a given number of layers
- private static final int[] NB_BITS_COMPACT; // total bits per full symbol for a given number of layers
+ private static final int MAX_NB_BITS = 32;
- static {
- NB_BITS_COMPACT = new int[5];
- for (int i = 1; i < NB_BITS_COMPACT.length; i++) {
- NB_BITS_COMPACT[i] = (88 + 16 * i) * i;
- }
- NB_BITS = new int[33];
- for (int i = 1; i < NB_BITS.length; i++) {
- NB_BITS[i] = (112 + 16 * i) * i;
- }
- }
-
private static final int[] WORD_SIZE = {
4, 6, 6, 8, 8, 8, 8, 8, 8, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10,
12, 12, 12, 12, 12, 12, 12, 12, 12, 12
@@ -65,8 +53,8 @@ public final class Encoder {
* Encodes the given binary content as an Aztec symbol
*
* @param data input data string
- * @param minECCPercent minimal percentange of error check words (According to ISO/IEC 24778:2008,
- * a minimum of 23% + 3 words is recommended)
+ * @param minECCPercent minimal percentage of error check words (According to ISO/IEC 24778:2008,
+ * a minimum of 23% + 3 words is recommended)
* @return Aztec symbol matrix with metadata
*/
public static AztecCode encode(byte[] data, int minECCPercent) {
@@ -77,56 +65,46 @@ public final class Encoder {
// stuff bits and choose symbol size
int eccBits = bits.getSize() * minECCPercent / 100 + 11;
int totalSizeBits = bits.getSize() + eccBits;
+ boolean compact;
int layers;
+ int totalBitsInLayer;
int wordSize = 0;
- int totalSymbolBits = 0;
BitArray stuffedBits = null;
- for (layers = 1; layers < NB_BITS_COMPACT.length; layers++) {
- if (NB_BITS_COMPACT[layers] >= totalSizeBits) {
- if (wordSize != WORD_SIZE[layers]) {
- wordSize = WORD_SIZE[layers];
- stuffedBits = stuffBits(bits, wordSize);
- }
- totalSymbolBits = NB_BITS_COMPACT[layers];
- if (stuffedBits.getSize() + eccBits <= NB_BITS_COMPACT[layers]) {
- break;
- }
+ // We look at the possible table sizes in the order Compact1, Compact2, Compact3,
+ // Compact4, Normal4,... Normal(i) for i < 4 isn't typically used since Compact(i+1)
+ // is the same size, but has more data.
+ for (int i = 0; ; i++) {
+ if (i > MAX_NB_BITS) {
+ throw new IllegalArgumentException("Data too large for an Aztec code");
}
- }
- boolean compact = true;
- if (layers == NB_BITS_COMPACT.length) {
- compact = false;
- for (layers = 1; layers < NB_BITS.length; layers++) {
- if (NB_BITS[layers] >= totalSizeBits) {
- if (wordSize != WORD_SIZE[layers]) {
- wordSize = WORD_SIZE[layers];
- stuffedBits = stuffBits(bits, wordSize);
- }
- totalSymbolBits = NB_BITS[layers];
- if (stuffedBits.getSize() + eccBits <= NB_BITS[layers]) {
- break;
- }
- }
+ compact = i <= 3;
+ layers = compact ? i + 1 : i;
+ totalBitsInLayer = totalBitsInLayer(layers, compact);
+ if (totalSizeBits > totalBitsInLayer) {
+ continue;
+ }
+ // [Re]stuff the bits if this is the first opportunity, or if the
+ // wordSize has changed
+ if (wordSize != WORD_SIZE[layers]) {
+ wordSize = WORD_SIZE[layers];
+ stuffedBits = stuffBits(bits, wordSize);
+ }
+ int usableBitsInLayers = totalBitsInLayer - (totalBitsInLayer % wordSize);
+ if (stuffedBits.getSize() + eccBits <= usableBitsInLayers) {
+ break;
}
- }
- if (layers == NB_BITS.length) {
- throw new IllegalArgumentException("Data too large for an Aztec code");
}
- // pad the end
- int messageSizeInWords = (stuffedBits.getSize() + wordSize - 1) / wordSize;
- for (int i = messageSizeInWords * wordSize - stuffedBits.getSize(); i > 0; i--) {
- stuffedBits.appendBit(true);
- }
+ int messageSizeInWords = stuffedBits.getSize() / wordSize;
// generate check words
ReedSolomonEncoder rs = new ReedSolomonEncoder(getGF(wordSize));
- int totalSizeInFullWords = totalSymbolBits / wordSize;
- int[] messageWords = bitsToWords(stuffedBits, wordSize, totalSizeInFullWords);
- rs.encode(messageWords, totalSizeInFullWords - messageSizeInWords);
+ int totalWordsInLayer = totalBitsInLayer / wordSize;
+ int[] messageWords = bitsToWords(stuffedBits, wordSize, totalWordsInLayer);
+ rs.encode(messageWords, totalWordsInLayer - messageSizeInWords);
// convert to bit array and pad in the beginning
- int startPad = totalSymbolBits % wordSize;
+ int startPad = totalBitsInLayer % wordSize;
BitArray messageBits = new BitArray();
messageBits.appendBits(0, startPad);
for (int messageWord : messageWords) {
@@ -158,7 +136,7 @@ public final class Encoder {
}
BitMatrix matrix = new BitMatrix(matrixSize);
- // draw mode and data bits
+ // draw data bits
for (int i = 0, rowOffset = 0; i < layers; i++) {
int rowSize = compact ? (layers - i) * 4 + 9 : (layers - i) * 4 + 12;
for (int j = 0; j < rowSize; j++) {
@@ -180,6 +158,8 @@ public final class Encoder {
}
rowOffset += rowSize * 8;
}
+
+ // draw mode message
drawModeMessage(matrix, compact, matrixSize, modeMessage);
// draw alignment marks
@@ -238,49 +218,50 @@ public final class Encoder {
}
private static void drawModeMessage(BitMatrix matrix, boolean compact, int matrixSize, BitArray modeMessage) {
+ int center = matrixSize / 2;
if (compact) {
for (int i = 0; i < 7; i++) {
+ int offset = center - 3 + i;
if (modeMessage.get(i)) {
- matrix.set(matrixSize / 2 - 3 + i, matrixSize / 2 - 5);
+ matrix.set(offset, center - 5);
}
if (modeMessage.get(i + 7)) {
- matrix.set(matrixSize / 2 + 5, matrixSize / 2 - 3 + i);
+ matrix.set(center + 5, offset);
}
if (modeMessage.get(20 - i)) {
- matrix.set(matrixSize / 2 - 3 + i, matrixSize / 2 + 5);
+ matrix.set(offset, center + 5);
}
if (modeMessage.get(27 - i)) {
- matrix.set(matrixSize / 2 - 5, matrixSize / 2 - 3 + i);
+ matrix.set(center - 5, offset);
}
}
} else {
for (int i = 0; i < 10; i++) {
+ int offset = center - 5 + i + i / 5;
if (modeMessage.get(i)) {
- matrix.set(matrixSize / 2 - 5 + i + i / 5, matrixSize / 2 - 7);
+ matrix.set(offset, center - 7);
}
if (modeMessage.get(i + 10)) {
- matrix.set(matrixSize / 2 + 7, matrixSize / 2 - 5 + i + i / 5);
+ matrix.set(center + 7, offset);
}
if (modeMessage.get(29 - i)) {
- matrix.set(matrixSize / 2 - 5 + i + i / 5, matrixSize / 2 + 7);
+ matrix.set(offset, center + 7);
}
if (modeMessage.get(39 - i)) {
- matrix.set(matrixSize / 2 - 7, matrixSize / 2 - 5 + i + i / 5);
+ matrix.set(center - 7, offset);
}
}
}
}
- private static BitArray generateCheckWords(BitArray stuffedBits, int totalSymbolBits, int wordSize) {
- int messageSizeInWords = (stuffedBits.getSize() + wordSize - 1) / wordSize;
- for (int i = messageSizeInWords * wordSize - stuffedBits.getSize(); i > 0; i--) {
- stuffedBits.appendBit(true);
- }
+ private static BitArray generateCheckWords(BitArray stuffedBits, int totalBits, int wordSize) {
+ // stuffedBits is guaranteed to be a multiple of the wordSize, so no padding needed
+ int messageSizeInWords = stuffedBits.getSize() / wordSize;
ReedSolomonEncoder rs = new ReedSolomonEncoder(getGF(wordSize));
- int totalSizeInFullWords = totalSymbolBits / wordSize;
- int[] messageWords = bitsToWords(stuffedBits, wordSize, totalSizeInFullWords);
- rs.encode(messageWords, totalSizeInFullWords - messageSizeInWords);
- int startPad = totalSymbolBits % wordSize;
+ int totalWords = totalBits / wordSize;
+ int[] messageWords = bitsToWords(stuffedBits, wordSize, totalWords);
+ rs.encode(messageWords, totalWords - messageSizeInWords);
+ int startPad = totalBits % wordSize;
BitArray messageBits = new BitArray();
messageBits.appendBits(0, startPad);
for (int messageWord : messageWords) {
@@ -343,22 +324,10 @@ public final class Encoder {
out.appendBits(word, wordSize);
}
}
-
- // 2. pad last word to wordSize
- n = out.getSize();
- int remainder = n % wordSize;
- if (remainder != 0) {
- int j = 1;
- for (int i = 0; i < remainder; i++) {
- if (!out.get(n - 1 - i)) {
- j = 0;
- }
- }
- for (int i = remainder; i < wordSize - 1; i++) {
- out.appendBit(true);
- }
- out.appendBit(j == 0);
- }
return out;
}
+
+ private static int totalBitsInLayer(int layers, boolean compact) {
+ return ((compact ? 88 : 112) + 16 * layers) * layers;
+ }
}
diff --git a/core/test/src/com/google/zxing/aztec/detector/DetectorTest.java b/core/test/src/com/google/zxing/aztec/detector/DetectorTest.java
new file mode 100644
index 000000000..d4982c672
--- /dev/null
+++ b/core/test/src/com/google/zxing/aztec/detector/DetectorTest.java
@@ -0,0 +1,142 @@
+/*
+ * Copyright 2013 ZXing authors
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.google.zxing.aztec.detector;
+
+import com.google.zxing.NotFoundException;
+import com.google.zxing.aztec.AztecDetectorResult;
+import com.google.zxing.aztec.detector.Detector.Point;
+import com.google.zxing.aztec.encoder.AztecCode;
+import com.google.zxing.aztec.encoder.Encoder;
+import com.google.zxing.common.BitMatrix;
+import org.junit.Assert;
+import org.junit.Test;
+
+import java.nio.charset.Charset;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.List;
+import java.util.Random;
+
+/**
+ * Tests for the Detector
+ *
+ * @author Frank Yellin
+ */
+public final class DetectorTest extends Assert {
+
+ private static final Charset LATIN_1 = Charset.forName("ISO-8859-1");
+
+ @Test
+ public void testErrorInParameterLocatorZeroZero() throws Exception {
+ // Layers=1, CodeWords=1. So the parameter info and its Reed-Solomon info
+ // will be completely zero!
+ testErrorInParameterLocator("X");
+ }
+
+ @Test
+ public void testErrorInParameterLocatorCompact() throws Exception {
+ testErrorInParameterLocator("This is an example Aztec symbol for Wikipedia.");
+ }
+
+ @Test
+ public void testErrorInParameterLocatorNotCompact() throws Exception {
+ String alphabet = "ABCDEFGHIJKLMNOPQRSTUVWXYabcdefghijklmnopqrstuvwxyz";
+ testErrorInParameterLocator(alphabet + alphabet + alphabet);
+ }
+
+ // Test that we can tolerate errors in the parameter locator bits
+ private static void testErrorInParameterLocator(String data) throws Exception {
+ AztecCode aztec = Encoder.encode(data.getBytes(LATIN_1), 25);
+ int layers = aztec.getLayers();
+ boolean compact = aztec.isCompact();
+ List orientationPoints = getOrientationPoints(aztec);
+ Random random = new Random(aztec.getMatrix().hashCode()); // random, but repeatable
+ for (BitMatrix matrix : getRotations(aztec.getMatrix())) {
+ // Each time through this loop, we reshuffle the corners, to get a different set of errors
+ Collections.shuffle(orientationPoints, random);
+ for (int errors = 1; errors <= 3; errors++) {
+ // Add another error to one of the parameter locator bits
+ matrix.flip(orientationPoints.get(errors).getX(), orientationPoints.get(errors).getY());
+ try {
+ // The detector can't yet deal with bitmaps in which each square is only 1x1 pixel.
+ // We zoom it larger.
+ AztecDetectorResult r = new Detector(makeLarger(matrix, 3)).detect();
+ if (errors < 3) {
+ assertNotNull(r);
+ assertEquals(r.getNbLayers(), layers);
+ assertEquals(r.isCompact(), compact);
+ } else {
+ fail("Should not succeed with more than two errors");
+ }
+ } catch (NotFoundException e) {
+ assertEquals("Should only fail with three errors", 3, errors);
+ }
+ }
+ }
+ }
+
+ // Zooms a bit matrix so that each bit is factor x factor
+ private static BitMatrix makeLarger(BitMatrix input, int factor) {
+ int width = input.getWidth();
+ BitMatrix output = new BitMatrix(width * factor);
+ for (int inputY = 0; inputY < width; inputY++) {
+ for (int inputX = 0; inputX < width; inputX++) {
+ if (input.get(inputX, inputY)) {
+ output.setRegion(inputX * factor, inputY * factor, factor, factor);
+ }
+ }
+ }
+ return output;
+ }
+
+ // Returns a list of the four rotations of the BitMatrix. The identity rotation is
+ // explicitly a copy, so that it can be modified without affecting the original matrix.
+ private static List getRotations(BitMatrix input) {
+ int width = input.getWidth();
+ BitMatrix matrix0 = new BitMatrix(width);
+ BitMatrix matrix90 = new BitMatrix(width);
+ BitMatrix matrix180 = new BitMatrix(width);
+ BitMatrix matrix270 = new BitMatrix(width);
+ for (int x = 0; x < width; x++) {
+ for (int y = 0; y < width; y++) {
+ if (input.get(x, y)) {
+ matrix0.set(x, y);
+ matrix90.set(y, width - x - 1);
+ matrix180.set(width - x - 1, width - y - 1);
+ matrix270.set(width - y - 1, x);
+ }
+ }
+ }
+ return Arrays.asList(matrix0, matrix90, matrix180, matrix270);
+ }
+
+ private static List getOrientationPoints(AztecCode code) {
+ int center = code.getMatrix().getWidth() / 2;
+ int offset = code.isCompact() ? 5 : 7;
+ List result = new ArrayList();
+ for (int xSign = -1; xSign <= 1; xSign += 2) {
+ for (int ySign = -1; ySign <= 1; ySign += 2) {
+ result.add(new Point(center + xSign * offset, center + ySign * offset));
+ result.add(new Point(center + xSign * (offset - 1), center + ySign * offset));
+ result.add(new Point(center + xSign * offset, center + ySign * (offset - 1)));
+ }
+ }
+ return result;
+ }
+
+}