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 @Override
public BitMatrix encode(String contents, BarcodeFormat format, int width, int height) { 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 @Override
@ -41,16 +41,51 @@ public final class AztecWriter implements Writer {
Number eccPercent = hints == null ? null : (Number) hints.get(EncodeHintType.ERROR_CORRECTION); Number eccPercent = hints == null ? null : (Number) hints.get(EncodeHintType.ERROR_CORRECTION);
return encode(contents, return encode(contents,
format, format,
width,
height,
charset == null ? DEFAULT_CHARSET : Charset.forName(charset), charset == null ? DEFAULT_CHARSET : Charset.forName(charset),
eccPercent == null ? Encoder.DEFAULT_EC_PERCENT : eccPercent.intValue()); 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) { if (format != BarcodeFormat.AZTEC) {
throw new IllegalArgumentException("Can only encode AZTEC, but got " + format); throw new IllegalArgumentException("Can only encode AZTEC, but got " + format);
} }
AztecCode aztec = Encoder.encode(contents.getBytes(charset), eccPercent); 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.ReedSolomonDecoder;
import com.google.zxing.common.reedsolomon.ReedSolomonException; 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 * <p>The main class which implements Aztec Code decoding -- as opposed to locating and extracting
* the Aztec Code from an image.</p> * the Aztec Code from an image.</p>
@ -41,24 +43,6 @@ public final class Decoder {
BINARY 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 = { private static final String[] UPPER_TABLE = {
"CTRL_PS", " ", "A", "B", "C", "D", "E", "F", "G", "H", "I", "J", "K", "L", "M", "N", "O", "P", "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" "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" "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 AztecDetectorResult ddata;
private int invertedBitCount;
public DecoderResult decode(AztecDetectorResult detectorResult) throws FormatException { public DecoderResult decode(AztecDetectorResult detectorResult) throws FormatException {
ddata = detectorResult; ddata = detectorResult;
BitMatrix matrix = detectorResult.getBits(); BitMatrix matrix = detectorResult.getBits();
if (!ddata.isCompact()) {
matrix = removeDashedLines(ddata.getBits());
}
boolean[] rawbits = extractBits(matrix); boolean[] rawbits = extractBits(matrix);
boolean[] correctedBits = correctBits(rawbits); boolean[] correctedBits = correctBits(rawbits);
String result = getEncodedData(correctedBits); String result = getEncodedData(correctedBits);
return new DecoderResult(null, result, null, null); 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 * Gets the string encoded in the aztec code bits
* *
* @return the decoded string * @return the decoded string
* @throws FormatException if the input is not valid
*/ */
private String getEncodedData(boolean[] correctedBits) throws FormatException { private static String getEncodedData(boolean[] correctedBits) {
int endIndex = codewordSize * ddata.getNbDatablocks() - invertedBitCount; int endIndex = correctedBits.length;
if (endIndex > correctedBits.length) { Table latchTable = Table.UPPER; // table most recently latched to
throw FormatException.getFormatInstance(); Table shiftTable = Table.UPPER; // table to use for the next read
}
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;
StringBuilder result = new StringBuilder(20); StringBuilder result = new StringBuilder(20);
boolean end = false; int index = 0;
boolean shift = false; while (index < endIndex) {
boolean switchShift = false; if (shiftTable == Table.BINARY) {
boolean binaryShift = false; if (endIndex - index < 5) {
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) {
break; break;
} }
int length = readCode(correctedBits, index, 5);
int length = readCode(correctedBits, startIndex, 5); index += 5;
startIndex += 5;
if (length == 0) { if (length == 0) {
if (endIndex - startIndex < 11) { if (endIndex - index < 11) {
break; break;
} }
length = readCode(correctedBits, index, 11) + 31;
length = readCode(correctedBits, startIndex, 11) + 31; index += 11;
startIndex += 11;
} }
for (int charCount = 0; charCount < length; charCount++) { for (int charCount = 0; charCount < length; charCount++) {
if (endIndex - startIndex < 8) { if (endIndex - index < 8) {
end = true; index = endIndex; // Force outer loop to exit
break; break;
} }
int code = readCode(correctedBits, index, 8);
code = readCode(correctedBits, startIndex, 8);
result.append((char) code); result.append((char) code);
startIndex += 8; index += 8;
} }
binaryShift = false; // Go back to whatever mode we had been in
shiftTable = latchTable;
} else { } else {
if (table == Table.BINARY) { int size = shiftTable == Table.DIGIT ? 4 : 5;
if (endIndex - startIndex < 8) { if (endIndex - index < size) {
break; 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 { } else {
int size = 5; result.append(str);
// Go back to whatever mode we had been in
if (table == Table.DIGIT) { shiftTable = latchTable;
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);
}
} }
} }
if (switchShift) {
table = lastTable;
shift = false;
switchShift = false;
}
} }
return result.toString(); return result.toString();
} }
/** /**
* gets the table corresponding to the char passed * gets the table corresponding to the char passed
*/ */
@ -266,7 +184,8 @@ public final class Decoder {
case DIGIT: case DIGIT:
return DIGIT_TABLE[code]; return DIGIT_TABLE[code];
default: 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 { private boolean[] correctBits(boolean[] rawbits) throws FormatException {
GenericGF gf; GenericGF gf;
int codewordSize;
if (ddata.getNbLayers() <= 2) { if (ddata.getNbLayers() <= 2) {
codewordSize = 6; codewordSize = 6;
@ -294,30 +214,13 @@ public final class Decoder {
} }
int numDataCodewords = ddata.getNbDatablocks(); int numDataCodewords = ddata.getNbDatablocks();
int numECCodewords; int numCodewords = rawbits.length / codewordSize;
int offset; int offset = rawbits.length % codewordSize;
int numECCodewords = numCodewords - numDataCodewords;
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[] dataWords = new int[numCodewords]; int[] dataWords = new int[numCodewords];
for (int i = 0; i < numCodewords; i++) { for (int i = 0; i < numCodewords; i++, offset += codewordSize) {
int flag = 1; dataWords[i] = readCode(rawbits, offset, codewordSize);
for (int j = 1; j <= codewordSize; j++) {
if (rawbits[codewordSize * i + codewordSize - j + offset]) {
dataWords[i] += flag;
}
flag <<= 1;
}
//if (dataWords[i] >= flag) {
// flag++;
//}
} }
try { try {
@ -327,48 +230,34 @@ public final class Decoder {
throw FormatException.getFormatInstance(); throw FormatException.getFormatInstance();
} }
offset = 0; // Now perform the unstuffing operation.
invertedBitCount = 0; // First, count how many bits are going to be thrown out as stuffing
int mask = (1 << codewordSize) - 1;
boolean[] correctedBits = new boolean[numDataCodewords * codewordSize]; int stuffedBits = 0;
for (int i = 0; i < numDataCodewords; i++) { for (int i = 0; i < numDataCodewords; i++) {
int dataWord = dataWords[i];
boolean seriesColor = false; if (dataWord == 0 || dataWord == mask) {
int seriesCount = 0; throw FormatException.getFormatInstance();
int flag = 1 << (codewordSize - 1); } else if (dataWord == 1 || dataWord == mask - 1) {
stuffedBits++;
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;
} }
} }
// 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; return correctedBits;
} }
@ -376,104 +265,72 @@ public final class Decoder {
* Gets the array of bits from an Aztec Code matrix * Gets the array of bits from an Aztec Code matrix
* *
* @return the array of bits * @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 (compact) {
if (ddata.isCompact()) { for (int i = 0; i < alignmentMap.length; i++) {
if (ddata.getNbLayers() > NB_BITS_COMPACT.length) { alignmentMap[i] = i;
throw FormatException.getFormatInstance();
} }
rawbits = new boolean[NB_BITS_COMPACT[ddata.getNbLayers()]];
numCodewords = NB_DATABLOCK_COMPACT[ddata.getNbLayers()];
} else { } else {
if (ddata.getNbLayers() > NB_BITS.length) { int matrixSize = baseMatrixSize + 1 + 2 * ((baseMatrixSize / 2 - 1) / 15);
throw FormatException.getFormatInstance(); 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()];
} }
for (int i = 0, rowOffset = 0; i < layers; i++) {
int layer = ddata.getNbLayers(); int rowSize = compact ? (layers - i) * 4 + 9 : (layers - i) * 4 + 12;
int size = matrix.getHeight(); // The top-left most point of this layer is <low, low> (not including alignment lines)
int rawbitsOffset = 0; int low = i * 2;
int matrixOffset = 0; // The bottom-right most point of this layer is <high, high> (not including alignment lines)
int high = baseMatrixSize - 1 - low;
while (layer != 0) { // We pull bits from the two 2 x rowSize columns and two rowSize x 2 rows
for (int j = 0; j < rowSize; j++) {
int flip = 0; int columnOffset = j * 2;
for (int i = 0; i < 2 * size - 4; i++) { for (int k = 0; k < 2; k++) {
rawbits[rawbitsOffset + i] = matrix.get(matrixOffset + flip, matrixOffset + i / 2); // left column
rawbits[rawbitsOffset + 2 * size - 4 + i] = matrix.get(matrixOffset + i / 2, matrixOffset + size - 1 - flip); rawbits[rowOffset + columnOffset + k] =
flip = (flip + 1) % 2; 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]);
}
} }
rowOffset += rowSize * 8;
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;
} }
return rawbits; 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 * 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) { private static int readCode(boolean[] rawbits, int startIndex, int length) {
int res = 0; int res = 0;
for (int i = startIndex; i < startIndex + length; i++) { for (int i = startIndex; i < startIndex + length; i++) {
res <<= 1; res <<= 1;
if (rawbits[i]) { if (rawbits[i]) {
res++; res++;
} }
} }
return 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); extractParameters(bullsEyeCorners);
// 4. Sample the grid // 4. Sample the grid
BitMatrix bits = sampleGrid(image, BitMatrix bits = sampleGrid(image,
bullsEyeCorners[shift%4], bullsEyeCorners[(shift+1)%4], bullsEyeCorners[shift % 4],
bullsEyeCorners[(shift+2)%4], bullsEyeCorners[(shift+3)%4]); bullsEyeCorners[(shift + 1) % 4],
bullsEyeCorners[(shift + 2) % 4],
bullsEyeCorners[(shift + 3) % 4]);
// 5. Get the corners of the matrix. // 5. Get the corners of the matrix.
ResultPoint[] corners = getMatrixCornerPoints(bullsEyeCorners); ResultPoint[] corners = getMatrixCornerPoints(bullsEyeCorners);
@ -84,89 +86,91 @@ public final class Detector {
* @throws NotFoundException in case of too many errors or invalid parameters * @throws NotFoundException in case of too many errors or invalid parameters
*/ */
private void extractParameters(ResultPoint[] bullsEyeCorners) throws NotFoundException { 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])) { !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(); throw NotFoundException.getNotFoundInstance();
} }
int length = 2 * nbCenterLayers;
//d a // Get the bits around the bull's eye
// int[] sides = {
//c b sampleLine(bullsEyeCorners[0], bullsEyeCorners[1], length), // Right side
sampleLine(bullsEyeCorners[1], bullsEyeCorners[2], length), // Bottom
// Flatten the bits in a single array sampleLine(bullsEyeCorners[2], bullsEyeCorners[3], length), // Left side
boolean[] parameterData; sampleLine(bullsEyeCorners[3], bullsEyeCorners[0], length) // Top
boolean[] shiftedParameterData; };
if (compact) {
shiftedParameterData = new boolean[28]; // bullsEyeCorners[shift] is the corner of the bulls'eye that has three
for (int i = 0; i < 7; i++) { // orientation marks.
shiftedParameterData[i] = resab[2+i]; // sides[shift] is the row/column that goes from the corner with three
shiftedParameterData[i+7] = resbc[2+i]; // orientation marks to the corner with two.
shiftedParameterData[i+14] = rescd[2+i]; shift = getRotation(sides, length);
shiftedParameterData[i+21] = resda[2+i];
} // Flatten the parameter bits into a single 28- or 40-bit long
long parameterData = 0;
parameterData = new boolean[28]; for (int i = 0; i < 4; i++) {
for (int i = 0; i < 28; i++) { int side = sides[(shift + i) % 4];
parameterData[i] = shiftedParameterData[(i+shift*7)%28]; if (compact) {
} // Each side of the form ..XXXXXXX. where Xs are parameter data
} else { parameterData <<= 7;
shiftedParameterData = new boolean[40]; parameterData += (side >> 1) & 0x7F;
for (int i = 0; i < 11; i++) { } else {
if (i < 5) { // Each side of the form ..XXXXX.XXXXX. where Xs are parameter data
shiftedParameterData[i] = resab[2+i]; parameterData <<= 10;
shiftedParameterData[i+10] = resbc[2+i]; parameterData += ((side >> 2) & (0x1f << 5)) + ((side >> 1) & 0x1F);
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];
} }
} }
// corrects the error using RS algorithm // Corrects parameter data using RS. Returns just the data portion
correctParameterData(parameterData, compact); // without the error correction.
int correctedData = getCorrectedParameterData(parameterData, compact);
// gets the parameters from the bit array if (compact) {
getParameters(parameterData); // 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;
}
} }
/** private int[] expectedCornerBits = {
* Gets the Aztec code corners from the bull's eye corners and the parameters. 0xee0, // 07340 XXX .XX X.. ...
* 0x1dc, // 00734 ... XXX .XX X..
* @param bullsEyeCorners the array of bull's eye corners 0x83b, // 04073 X.. ... XXX .XX
* @return the array of aztec code corners 0x707, // 03407 .XX X.. ... XXX
* @throws NotFoundException if the corner points do not fit in the image };
*/
private ResultPoint[] getMatrixCornerPoints(ResultPoint[] bullsEyeCorners) throws NotFoundException { private int getRotation(int[] sides, int length) throws NotFoundException {
return expandSquare(bullsEyeCorners, 2 * nbCenterLayers, getDimension()); // 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 * @param compact true if this is a compact Aztec code
* @throws NotFoundException if the array contains too many errors * @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 numCodewords;
int numDataCodewords; int numDataCodewords;
@ -191,32 +194,22 @@ public final class Detector {
int numECCodewords = numCodewords - numDataCodewords; int numECCodewords = numCodewords - numDataCodewords;
int[] parameterWords = new int[numCodewords]; int[] parameterWords = new int[numCodewords];
for (int i = numCodewords - 1; i >= 0; --i) {
int codewordSize = 4; parameterWords[i] = (int) parameterData & 0xF;
for (int i = 0; i < numCodewords; i++) { parameterData >>= 4;
int flag = 1;
for (int j = 1; j <= codewordSize; j++) {
if (parameterData[codewordSize*i + codewordSize - j]) {
parameterWords[i] += flag;
}
flag <<= 1;
}
} }
try { try {
ReedSolomonDecoder rsDecoder = new ReedSolomonDecoder(GenericGF.AZTEC_PARAM); ReedSolomonDecoder rsDecoder = new ReedSolomonDecoder(GenericGF.AZTEC_PARAM);
rsDecoder.decode(parameterWords, numECCodewords); rsDecoder.decode(parameterWords, numECCodewords);
} catch (ReedSolomonException ignored) { } catch (ReedSolomonException ignored) {
throw NotFoundException.getNotFoundInstance(); throw NotFoundException.getNotFoundInstance();
} }
// Toss the error correction. Just return the data as an integer
for (int i = 0; i < numDataCodewords; i ++) { int result = 0;
int flag = 1; for (int i = 0; i < numDataCodewords; i++) {
for (int j = 1; j <= codewordSize; j++) { result = (result << 4) + parameterWords[i];
parameterData[i*codewordSize+codewordSize-j] = (parameterWords[i] & flag) == flag;
flag <<= 1;
}
} }
return result;
} }
/** /**
@ -247,9 +240,9 @@ public final class Detector {
// //
//c b //c b
if (nbCenterLayers>2) { if (nbCenterLayers > 2) {
float q = distance(poutd, pouta)*nbCenterLayers/(distance(pind, pina)*(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 (q < 0.75 || q > 1.25 || !isWhiteOrBlackRectangle(pouta, poutb, poutc, poutd)) {
break; break;
} }
} }
@ -266,7 +259,7 @@ public final class Detector {
throw NotFoundException.getNotFoundInstance(); 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 // Expand the square by .5 pixel in each direction so that we're on the border
// between the white square and the black square // 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 // 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. // In that case, surely in the bull's eye, we try to expand the rectangle.
int cx = image.getWidth()/2; int cx = image.getWidth() / 2;
int cy = image.getHeight()/2; int cy = image.getHeight() / 2;
pointA = 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(); pointB = getFirstDifferent(new Point(cx + 7, cy + 7), false, 1, 1).toResultPoint();
pointC = 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(); pointD = getFirstDifferent(new Point(cx - 7, cy - 7), false, -1, -1).toResultPoint();
} }
@ -332,10 +325,10 @@ public final class Detector {
} catch (NotFoundException e) { } catch (NotFoundException e) {
// This exception can be in case the initial rectangle is white // This exception can be in case the initial rectangle is white
// In that case we try to expand the rectangle. // In that case we try to expand the rectangle.
pointA = 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(); pointB = getFirstDifferent(new Point(cx + 7, cy + 7), false, 1, 1).toResultPoint();
pointC = 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(); pointD = getFirstDifferent(new Point(cx - 7, cy - 7), false, -1, -1).toResultPoint();
} }
// Recompute the center of the rectangle // Recompute the center of the rectangle
@ -345,6 +338,17 @@ public final class Detector {
return new Point(cx, cy); 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. * Creates a BitMatrix by sampling the provided image.
* topLeft, topRight, bottomRight, and bottomLeft are the centers of the squares on the * 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(); GridSampler sampler = GridSampler.getInstance();
int dimension = getDimension(); int dimension = getDimension();
float low = dimension/2.0f - nbCenterLayers; float low = dimension / 2.0f - nbCenterLayers;
float high = dimension/2.0f + nbCenterLayers; float high = dimension / 2.0f + nbCenterLayers;
return sampler.sampleGrid(image, return sampler.sampleGrid(image,
dimension, dimension,
dimension, dimension,
low, low, // topleft low, low, // topleft
high, low, // topright high, low, // topright
high, high, // bottomright high, high, // bottomright
low, high, // bottomleft low, high, // bottomleft
topLeft.getX(), topLeft.getY(), topLeft.getX(), topLeft.getY(),
topRight.getX(), topRight.getY(), topRight.getX(), topRight.getY(),
bottomRight.getX(), bottomRight.getY(), bottomRight.getX(), bottomRight.getY(),
bottomLeft.getX(), bottomLeft.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++;
} }
/** /**
* Samples a line * Samples a line.
* *
* @param p1 first point * @param p1 start point (inclusive)
* @param p2 second point * @param p2 end point (exclusive)
* @param size number of bits * @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) { private int sampleLine(ResultPoint p1, ResultPoint p2, int size) {
int result = 0;
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;
float d = distance(p1, p2);
float moduleSize = d / size;
float px = p1.getX(); float px = p1.getX();
float py = p1.getY(); 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++) { for (int i = 0; i < size; i++) {
res[i] = image.get(MathUtils.round(px), MathUtils.round(py)); if (image.get(MathUtils.round(px + i * dx), MathUtils.round(py + i * dy))) {
px += dx; result |= 1 << (size - i - 1);
py += dy; }
} }
return result;
return res;
} }
/** /**
* @return true if the border of the rectangle passed in parameter is compound of white points only * @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, private boolean isWhiteOrBlackRectangle(Point p1,
Point p2, Point p2,
@ -448,10 +415,10 @@ public final class Detector {
int corr = 3; int corr = 3;
p1 = new Point(p1.getX() -corr, p1.getY() +corr); p1 = new Point(p1.getX() - corr, p1.getY() + corr);
p2 = new Point(p2.getX() -corr, p2.getY() -corr); p2 = new Point(p2.getX() - corr, p2.getY() - corr);
p3 = new Point(p3.getX() +corr, p3.getY() -corr); p3 = new Point(p3.getX() + corr, p3.getY() - corr);
p4 = new Point(p4.getX() +corr, p4.getY() +corr); p4 = new Point(p4.getX() + corr, p4.getY() + corr);
int cInit = getColor(p4, p1); 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 * @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) { private int getColor(Point p1, Point p2) {
float d = distance(p1,p2); float d = distance(p1, p2);
float dx = (p2.getX() - p1.getX())/d; float dx = (p2.getX() - p1.getX()) / d;
float dy = (p2.getY() - p1.getY())/d; float dy = (p2.getY() - p1.getY()) / d;
int error = 0; int error = 0;
float px = p1.getX(); float px = p1.getX();
@ -494,8 +461,8 @@ public final class Detector {
boolean colorModel = image.get(p1.getX(), p1.getY()); boolean colorModel = image.get(p1.getX(), p1.getY());
for (int i = 0; i < d; i++) { for (int i = 0; i < d; i++) {
px+=dx; px += dx;
py+=dy; py += dy;
if (image.get(MathUtils.round(px), MathUtils.round(py)) != colorModel) { if (image.get(MathUtils.round(px), MathUtils.round(py)) != colorModel) {
error++; error++;
} }
@ -514,40 +481,39 @@ public final class Detector {
* Gets the coordinate of the first point with a different color in the given direction * 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) { private Point getFirstDifferent(Point init, boolean color, int dx, int dy) {
int x = init.getX() +dx; int x = init.getX() + dx;
int y = init.getY() +dy; int y = init.getY() + dy;
while(isValid(x,y) && image.get(x,y) == color) { while (isValid(x, y) && image.get(x, y) == color) {
x+=dx; x += dx;
y+=dy; y += dy;
} }
x-=dx; x -= dx;
y-=dy; y -= dy;
while(isValid(x,y) && image.get(x, y) == color) { while (isValid(x, y) && image.get(x, y) == color) {
x+=dx; x += dx;
} }
x-=dx; x -= dx;
while(isValid(x,y) && image.get(x, y) == color) { while (isValid(x, y) && image.get(x, y) == color) {
y+=dy; 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 * 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 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 * @param newSide the new length of the size of the square in the target bit matrix
* @return the corners of the expanded square * @return the corners of the expanded square
*/ */
private ResultPoint[] expandSquare(ResultPoint[] cornerPoints, float oldSide, float newSide) private static ResultPoint[] expandSquare(ResultPoint[] cornerPoints, float oldSide, float newSide) {
throws NotFoundException {
float ratio = newSide / (2 * oldSide); float ratio = newSide / (2 * oldSide);
float dx = cornerPoints[0].getX() - cornerPoints[2].getX(); float dx = cornerPoints[0].getX() - cornerPoints[2].getX();
float dy = cornerPoints[0].getY() - cornerPoints[2].getY(); 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 result1 = new ResultPoint(centerx + ratio * dx, centery + ratio * dy);
ResultPoint result3 = 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) { private boolean isValid(int x, int y) {
@ -592,7 +558,7 @@ public final class Detector {
if (nbLayers <= 4) { if (nbLayers <= 4) {
return 4 * nbLayers + 15; 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 { static final class Point {

View file

@ -29,20 +29,8 @@ import com.google.zxing.common.reedsolomon.ReedSolomonEncoder;
public final class Encoder { public final class Encoder {
public static final int DEFAULT_EC_PERCENT = 33; // default minimal percentage of error check words 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 MAX_NB_BITS = 32;
private static final int[] NB_BITS_COMPACT; // total bits per full symbol for a given number of layers
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 = { 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, 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 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 * Encodes the given binary content as an Aztec symbol
* *
* @param data input data string * @param data input data string
* @param minECCPercent minimal percentange of error check words (According to ISO/IEC 24778:2008, * @param minECCPercent minimal percentage of error check words (According to ISO/IEC 24778:2008,
* a minimum of 23% + 3 words is recommended) * a minimum of 23% + 3 words is recommended)
* @return Aztec symbol matrix with metadata * @return Aztec symbol matrix with metadata
*/ */
public static AztecCode encode(byte[] data, int minECCPercent) { public static AztecCode encode(byte[] data, int minECCPercent) {
@ -77,56 +65,46 @@ public final class Encoder {
// stuff bits and choose symbol size // stuff bits and choose symbol size
int eccBits = bits.getSize() * minECCPercent / 100 + 11; int eccBits = bits.getSize() * minECCPercent / 100 + 11;
int totalSizeBits = bits.getSize() + eccBits; int totalSizeBits = bits.getSize() + eccBits;
boolean compact;
int layers; int layers;
int totalBitsInLayer;
int wordSize = 0; int wordSize = 0;
int totalSymbolBits = 0;
BitArray stuffedBits = null; BitArray stuffedBits = null;
for (layers = 1; layers < NB_BITS_COMPACT.length; layers++) { // We look at the possible table sizes in the order Compact1, Compact2, Compact3,
if (NB_BITS_COMPACT[layers] >= totalSizeBits) { // Compact4, Normal4,... Normal(i) for i < 4 isn't typically used since Compact(i+1)
if (wordSize != WORD_SIZE[layers]) { // is the same size, but has more data.
wordSize = WORD_SIZE[layers]; for (int i = 0; ; i++) {
stuffedBits = stuffBits(bits, wordSize); if (i > MAX_NB_BITS) {
} throw new IllegalArgumentException("Data too large for an Aztec code");
totalSymbolBits = NB_BITS_COMPACT[layers];
if (stuffedBits.getSize() + eccBits <= NB_BITS_COMPACT[layers]) {
break;
}
} }
} compact = i <= 3;
boolean compact = true; layers = compact ? i + 1 : i;
if (layers == NB_BITS_COMPACT.length) { totalBitsInLayer = totalBitsInLayer(layers, compact);
compact = false; if (totalSizeBits > totalBitsInLayer) {
for (layers = 1; layers < NB_BITS.length; layers++) { continue;
if (NB_BITS[layers] >= totalSizeBits) { }
if (wordSize != WORD_SIZE[layers]) { // [Re]stuff the bits if this is the first opportunity, or if the
wordSize = WORD_SIZE[layers]; // wordSize has changed
stuffedBits = stuffBits(bits, wordSize); if (wordSize != WORD_SIZE[layers]) {
} wordSize = WORD_SIZE[layers];
totalSymbolBits = NB_BITS[layers]; stuffedBits = stuffBits(bits, wordSize);
if (stuffedBits.getSize() + eccBits <= NB_BITS[layers]) { }
break; 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;
int messageSizeInWords = (stuffedBits.getSize() + wordSize - 1) / wordSize;
for (int i = messageSizeInWords * wordSize - stuffedBits.getSize(); i > 0; i--) {
stuffedBits.appendBit(true);
}
// generate check words // generate check words
ReedSolomonEncoder rs = new ReedSolomonEncoder(getGF(wordSize)); ReedSolomonEncoder rs = new ReedSolomonEncoder(getGF(wordSize));
int totalSizeInFullWords = totalSymbolBits / wordSize; int totalWordsInLayer = totalBitsInLayer / wordSize;
int[] messageWords = bitsToWords(stuffedBits, wordSize, totalSizeInFullWords); int[] messageWords = bitsToWords(stuffedBits, wordSize, totalWordsInLayer);
rs.encode(messageWords, totalSizeInFullWords - messageSizeInWords); rs.encode(messageWords, totalWordsInLayer - messageSizeInWords);
// convert to bit array and pad in the beginning // convert to bit array and pad in the beginning
int startPad = totalSymbolBits % wordSize; int startPad = totalBitsInLayer % wordSize;
BitArray messageBits = new BitArray(); BitArray messageBits = new BitArray();
messageBits.appendBits(0, startPad); messageBits.appendBits(0, startPad);
for (int messageWord : messageWords) { for (int messageWord : messageWords) {
@ -158,7 +136,7 @@ public final class Encoder {
} }
BitMatrix matrix = new BitMatrix(matrixSize); BitMatrix matrix = new BitMatrix(matrixSize);
// draw mode and data bits // draw data bits
for (int i = 0, rowOffset = 0; i < layers; i++) { for (int i = 0, rowOffset = 0; i < layers; i++) {
int rowSize = compact ? (layers - i) * 4 + 9 : (layers - i) * 4 + 12; int rowSize = compact ? (layers - i) * 4 + 9 : (layers - i) * 4 + 12;
for (int j = 0; j < rowSize; j++) { for (int j = 0; j < rowSize; j++) {
@ -180,6 +158,8 @@ public final class Encoder {
} }
rowOffset += rowSize * 8; rowOffset += rowSize * 8;
} }
// draw mode message
drawModeMessage(matrix, compact, matrixSize, modeMessage); drawModeMessage(matrix, compact, matrixSize, modeMessage);
// draw alignment marks // draw alignment marks
@ -238,49 +218,50 @@ public final class Encoder {
} }
private static void drawModeMessage(BitMatrix matrix, boolean compact, int matrixSize, BitArray modeMessage) { private static void drawModeMessage(BitMatrix matrix, boolean compact, int matrixSize, BitArray modeMessage) {
int center = matrixSize / 2;
if (compact) { if (compact) {
for (int i = 0; i < 7; i++) { for (int i = 0; i < 7; i++) {
int offset = center - 3 + i;
if (modeMessage.get(i)) { if (modeMessage.get(i)) {
matrix.set(matrixSize / 2 - 3 + i, matrixSize / 2 - 5); matrix.set(offset, center - 5);
} }
if (modeMessage.get(i + 7)) { if (modeMessage.get(i + 7)) {
matrix.set(matrixSize / 2 + 5, matrixSize / 2 - 3 + i); matrix.set(center + 5, offset);
} }
if (modeMessage.get(20 - i)) { if (modeMessage.get(20 - i)) {
matrix.set(matrixSize / 2 - 3 + i, matrixSize / 2 + 5); matrix.set(offset, center + 5);
} }
if (modeMessage.get(27 - i)) { if (modeMessage.get(27 - i)) {
matrix.set(matrixSize / 2 - 5, matrixSize / 2 - 3 + i); matrix.set(center - 5, offset);
} }
} }
} else { } else {
for (int i = 0; i < 10; i++) { for (int i = 0; i < 10; i++) {
int offset = center - 5 + i + i / 5;
if (modeMessage.get(i)) { 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)) { 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)) { 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)) { 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) { private static BitArray generateCheckWords(BitArray stuffedBits, int totalBits, int wordSize) {
int messageSizeInWords = (stuffedBits.getSize() + wordSize - 1) / wordSize; // stuffedBits is guaranteed to be a multiple of the wordSize, so no padding needed
for (int i = messageSizeInWords * wordSize - stuffedBits.getSize(); i > 0; i--) { int messageSizeInWords = stuffedBits.getSize() / wordSize;
stuffedBits.appendBit(true);
}
ReedSolomonEncoder rs = new ReedSolomonEncoder(getGF(wordSize)); ReedSolomonEncoder rs = new ReedSolomonEncoder(getGF(wordSize));
int totalSizeInFullWords = totalSymbolBits / wordSize; int totalWords = totalBits / wordSize;
int[] messageWords = bitsToWords(stuffedBits, wordSize, totalSizeInFullWords); int[] messageWords = bitsToWords(stuffedBits, wordSize, totalWords);
rs.encode(messageWords, totalSizeInFullWords - messageSizeInWords); rs.encode(messageWords, totalWords - messageSizeInWords);
int startPad = totalSymbolBits % wordSize; int startPad = totalBits % wordSize;
BitArray messageBits = new BitArray(); BitArray messageBits = new BitArray();
messageBits.appendBits(0, startPad); messageBits.appendBits(0, startPad);
for (int messageWord : messageWords) { for (int messageWord : messageWords) {
@ -343,22 +324,10 @@ public final class Encoder {
out.appendBits(word, wordSize); 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; 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;
}
}