mirror of
https://github.com/zxing/zxing.git
synced 2025-02-02 05:41:08 -08:00
AztecWriter improvements from fyellin
git-svn-id: https://zxing.googlecode.com/svn/trunk@2822 59b500cc-1b3d-0410-9834-0bbf25fbcc57
This commit is contained in:
parent
ea1c78af59
commit
228f3d20ca
|
@ -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;
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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 {
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
|
142
core/test/src/com/google/zxing/aztec/detector/DetectorTest.java
Normal file
142
core/test/src/com/google/zxing/aztec/detector/DetectorTest.java
Normal 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;
|
||||
}
|
||||
|
||||
}
|
Loading…
Reference in a new issue