AztecWriter improvements from fyellin

git-svn-id: https://zxing.googlecode.com/svn/trunk@2822 59b500cc-1b3d-0410-9834-0bbf25fbcc57
This commit is contained in:
srowen@gmail.com 2013-06-18 20:40:01 +00:00
parent ea1c78af59
commit 228f3d20ca
5 changed files with 522 additions and 553 deletions

View file

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

View file

@ -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;
/**
* <p>The main class which implements Aztec Code decoding -- as opposed to locating and extracting
* the Aztec Code from an image.</p>
@ -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 <low, low> (not including alignment lines)
int low = i * 2;
// The bottom-right most point of this layer is <high, high> (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;
}
}

View file

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

View file

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

View file

@ -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<Point> 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<BitMatrix> 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<Point> getOrientationPoints(AztecCode code) {
int center = code.getMatrix().getWidth() / 2;
int offset = code.isCompact() ? 5 : 7;
List<Point> result = new ArrayList<Point>();
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;
}
}