Aztec updates from fyellin

git-svn-id: https://zxing.googlecode.com/svn/trunk@2834 59b500cc-1b3d-0410-9834-0bbf25fbcc57
This commit is contained in:
srowen@gmail.com 2013-07-01 08:58:45 +00:00
parent 7544a08668
commit e8a23d5085
11 changed files with 300 additions and 145 deletions

View file

@ -76,4 +76,11 @@ public enum EncodeHintType {
*/
PDF417_DIMENSIONS,
/**
* Specifies the required number of layers for an Aztec code:
* a negative number (-1, -2, -3, -4) specifies a compact Aztec code
* 0 indicates to use the minimum number of layers (the default)
* a positive number (1, 2, .. 32) specifies a normaol (non-compact) Aztec code
*/
AZTEC_LAYERS,
}

View file

@ -26,9 +26,9 @@ import com.google.zxing.Result;
import com.google.zxing.ResultMetadataType;
import com.google.zxing.ResultPoint;
import com.google.zxing.ResultPointCallback;
import com.google.zxing.common.DecoderResult;
import com.google.zxing.aztec.decoder.Decoder;
import com.google.zxing.aztec.detector.Detector;
import com.google.zxing.common.DecoderResult;
import java.util.List;
import java.util.Map;
@ -57,8 +57,44 @@ public final class AztecReader implements Reader {
public Result decode(BinaryBitmap image, Map<DecodeHintType,?> hints)
throws NotFoundException, FormatException {
AztecDetectorResult detectorResult = new Detector(image.getBlackMatrix()).detect();
ResultPoint[] points = detectorResult.getPoints();
NotFoundException notFoundException = null;
FormatException formatException = null;
Detector detector = new Detector(image.getBlackMatrix());
ResultPoint[] points = null;
DecoderResult decoderResult = null;
try {
AztecDetectorResult detectorResult = detector.detect(false);
points = detectorResult.getPoints();
decoderResult = new Decoder().decode(detectorResult);
} catch (NotFoundException e) {
notFoundException = e;
} catch (FormatException e) {
formatException = e;
}
if (decoderResult == null) {
try {
AztecDetectorResult detectorResult = detector.detect(true);
points = detectorResult.getPoints();
decoderResult = new Decoder().decode(detectorResult);
} catch (NotFoundException e) {
if (notFoundException != null) {
throw notFoundException;
}
if (formatException != null) {
throw formatException;
}
throw e;
} catch (FormatException e) {
// throw the exception from the non-mirror case, instead
if (notFoundException != null) {
throw notFoundException;
}
if (formatException != null) {
throw formatException;
}
throw e;
}
}
if (hints != null) {
ResultPointCallback rpcb = (ResultPointCallback) hints.get(DecodeHintType.NEED_RESULT_POINT_CALLBACK);
@ -69,8 +105,6 @@ public final class AztecReader implements Reader {
}
}
DecoderResult decoderResult = new Decoder().decode(detectorResult);
Result result = new Result(decoderResult.getText(), decoderResult.getRawBytes(), points, BarcodeFormat.AZTEC);
List<byte[]> byteSegments = decoderResult.getByteSegments();
@ -90,4 +124,4 @@ public final class AztecReader implements Reader {
// do nothing
}
}
}

View file

@ -16,9 +16,6 @@
package com.google.zxing.aztec;
import java.nio.charset.Charset;
import java.util.Map;
import com.google.zxing.BarcodeFormat;
import com.google.zxing.EncodeHintType;
import com.google.zxing.Writer;
@ -26,37 +23,39 @@ import com.google.zxing.aztec.encoder.AztecCode;
import com.google.zxing.aztec.encoder.Encoder;
import com.google.zxing.common.BitMatrix;
import java.nio.charset.Charset;
import java.util.Map;
public final class AztecWriter implements Writer {
private static final Charset DEFAULT_CHARSET = Charset.forName("ISO-8859-1");
@Override
public BitMatrix encode(String contents, BarcodeFormat format, int width, int height) {
return encode(contents, format, width, height, DEFAULT_CHARSET, Encoder.DEFAULT_EC_PERCENT);
return encode(contents, format, width, height, null);
}
@Override
public BitMatrix encode(String contents, BarcodeFormat format, int width, int height, Map<EncodeHintType,?> hints) {
String charset = hints == null ? null : (String) hints.get(EncodeHintType.CHARACTER_SET);
Number eccPercent = hints == null ? null : (Number) hints.get(EncodeHintType.ERROR_CORRECTION);
Integer layers = hints == null ? null : (Integer)hints.get(EncodeHintType.AZTEC_LAYERS);
return encode(contents,
format,
width,
height,
charset == null ? DEFAULT_CHARSET : Charset.forName(charset),
eccPercent == null ? Encoder.DEFAULT_EC_PERCENT : eccPercent.intValue());
eccPercent == null ? Encoder.DEFAULT_EC_PERCENT : eccPercent.intValue(),
layers == null ? Encoder.DEFAULT_AZTEC_LAYERS : layers.intValue());
}
private static BitMatrix encode(String contents,
BarcodeFormat format,
int width,
int height,
Charset charset,
int eccPercent) {
private static BitMatrix encode(String contents, BarcodeFormat format,
int width, int height,
Charset charset, int eccPercent, int layers) {
if (format != BarcodeFormat.AZTEC) {
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, layers);
return renderResult(aztec, width, height);
}
@ -84,8 +83,6 @@ public final class AztecWriter implements Writer {
}
}
}
return output;
}
}

View file

@ -48,13 +48,17 @@ public final class Detector {
this.image = image;
}
public AztecDetectorResult detect() throws NotFoundException {
return detect(false);
}
/**
* Detects an Aztec Code in an image.
*
* @return {@link AztecDetectorResult} encapsulating results of detecting an Aztec Code
* @throws NotFoundException if no Aztec Code can be found
*/
public AztecDetectorResult detect() throws NotFoundException {
public AztecDetectorResult detect(boolean isMirror) throws NotFoundException {
// 1. Get the center of the aztec matrix
Point pCenter = getMatrixCenter();
@ -63,6 +67,12 @@ public final class Detector {
// [topRight, bottomRight, bottomLeft, topLeft]
ResultPoint[] bullsEyeCorners = getBullsEyeCorners(pCenter);
if (isMirror) {
ResultPoint temp = bullsEyeCorners[0];
bullsEyeCorners[0] = bullsEyeCorners[2];
bullsEyeCorners[2] = temp;
}
// 3. Get the size of the matrix and other parameters from the bull's eye
extractParameters(bullsEyeCorners);
@ -135,14 +145,14 @@ public final class Detector {
}
}
private int[] expectedCornerBits = {
private static final int[] EXPECTED_CORNER_BITS = {
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 {
private static int getRotation(int[] sides, int length) throws NotFoundException {
// In a normal pattern, we expect to See
// ** .* D A
// * *
@ -166,7 +176,7 @@ public final class Detector {
// 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) {
if (Integer.bitCount(cornerBits ^ EXPECTED_CORNER_BITS[shift]) <= 2) {
return shift;
}
}
@ -343,7 +353,6 @@ public final class Detector {
*
* @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());

View file

@ -23,11 +23,10 @@ final class BinaryShiftToken extends Token {
private final short binaryShiftStart;
private final short binaryShiftByteCount;
BinaryShiftToken(Token previous,
int totalBitCount,
int binaryShiftStart,
BinaryShiftToken(Token previous,
int binaryShiftStart,
int binaryShiftByteCount) {
super(previous, totalBitCount);
super(previous);
this.binaryShiftStart = (short) binaryShiftStart;
this.binaryShiftByteCount = (short) binaryShiftByteCount;
}
@ -38,7 +37,7 @@ final class BinaryShiftToken extends Token {
if (i == 0 || (i == 31 && binaryShiftByteCount <= 62)) {
// We need a header before the first character, and before
// character 31 when the total byte code is <= 62
bitArray.appendBits(31, 5);
bitArray.appendBits(31, 5); // BINARY_SHIFT
if (binaryShiftByteCount > 62) {
bitArray.appendBits(binaryShiftByteCount - 31, 16);
} else if (i == 0) {
@ -51,7 +50,6 @@ final class BinaryShiftToken extends Token {
}
bitArray.appendBits(text[binaryShiftStart + i], 8);
}
//assert bitArray.getSize() == getTotalBitCount();
}
@Override

View file

@ -29,7 +29,9 @@ 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
public static final int DEFAULT_AZTEC_LAYERS = 0;
private static final int MAX_NB_BITS = 32;
private static final int MAX_NB_BITS_COMPACT = 4;
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,
@ -46,7 +48,7 @@ public final class Encoder {
* @return Aztec symbol matrix with metadata
*/
public static AztecCode encode(byte[] data) {
return encode(data, DEFAULT_EC_PERCENT);
return encode(data, DEFAULT_EC_PERCENT, DEFAULT_AZTEC_LAYERS);
}
/**
@ -55,10 +57,10 @@ public final class Encoder {
* @param data input data string
* @param minECCPercent minimal percentage of error check words (According to ISO/IEC 24778:2008,
* a minimum of 23% + 3 words is recommended)
* @param userSpecifiedLayers if non-zero, a user-specified value for the number of layers
* @return Aztec symbol matrix with metadata
*/
public static AztecCode encode(byte[] data, int minECCPercent) {
public static AztecCode encode(byte[] data, int minECCPercent, int userSpecifiedLayers) {
// High-level encode
BitArray bits = new HighLevelEncoder(data).encode();
@ -68,50 +70,62 @@ public final class Encoder {
boolean compact;
int layers;
int totalBitsInLayer;
int wordSize = 0;
BitArray stuffedBits = null;
// 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");
int wordSize;
BitArray stuffedBits;
if (userSpecifiedLayers != DEFAULT_AZTEC_LAYERS) {
compact = userSpecifiedLayers < 0;
layers = Math.abs(userSpecifiedLayers);
if (layers > (compact ? MAX_NB_BITS_COMPACT : MAX_NB_BITS)) {
throw new IllegalArgumentException(
String.format("Illegal value %s for layers", userSpecifiedLayers));
}
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);
}
wordSize = WORD_SIZE[layers];
int usableBitsInLayers = totalBitsInLayer - (totalBitsInLayer % wordSize);
if (stuffedBits.getSize() + eccBits <= usableBitsInLayers) {
break;
stuffedBits = stuffBits(bits, wordSize);
if (stuffedBits.getSize() + eccBits > usableBitsInLayers) {
throw new IllegalArgumentException("Data to large for user specified layer");
}
if (compact && stuffedBits.getSize() > wordSize * 64) {
// Compact format only allows 64 data words, though C4 can hold more words than that
throw new IllegalArgumentException("Data to large for user specified layer");
}
} else {
wordSize = 0;
stuffedBits = null;
// 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");
}
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 (compact && stuffedBits.getSize() > wordSize * 64) {
// Compact format only allows 64 data words, though C4 can hold more words than that
continue;
}
if (stuffedBits.getSize() + eccBits <= usableBitsInLayers) {
break;
}
}
}
int messageSizeInWords = stuffedBits.getSize() / wordSize;
// generate check words
ReedSolomonEncoder rs = new ReedSolomonEncoder(getGF(wordSize));
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 = totalBitsInLayer % wordSize;
BitArray messageBits = new BitArray();
messageBits.appendBits(0, startPad);
for (int messageWord : messageWords) {
messageBits.appendBits(messageWord, wordSize);
}
BitArray messageBits = generateCheckWords(stuffedBits, totalBitsInLayer, wordSize);
// generate mode message
int messageSizeInWords = stuffedBits.getSize() / wordSize;
BitArray modeMessage = generateModeMessage(compact, layers, messageSizeInWords);
// allocate symbol
@ -254,12 +268,13 @@ public final class Encoder {
}
}
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;
private static BitArray generateCheckWords(BitArray bitArray, int totalBits, int wordSize) {
assert bitArray.getSize() % wordSize == 0;
// bitArray is guaranteed to be a multiple of the wordSize, so no padding needed
int messageSizeInWords = bitArray.getSize() / wordSize;
ReedSolomonEncoder rs = new ReedSolomonEncoder(getGF(wordSize));
int totalWords = totalBits / wordSize;
int[] messageWords = bitsToWords(stuffedBits, wordSize, totalWords);
int[] messageWords = bitsToWords(bitArray, wordSize, totalWords);
rs.encode(messageWords, totalWords - messageSizeInWords);
int startPad = totalBits % wordSize;
BitArray messageBits = new BitArray();
@ -304,7 +319,6 @@ public final class Encoder {
static BitArray stuffBits(BitArray bits, int wordSize) {
BitArray out = new BitArray();
// 1. stuff the bits
int n = bits.getSize();
int mask = (1 << wordSize) - 2;
for (int i = 0; i < n; i += wordSize) {

View file

@ -130,7 +130,7 @@ public final class HighLevelEncoder {
}
}
// A map showing the available shift coodes. (The shifts to BINARY are not
// A map showing the available shift codes. (The shifts to BINARY are not
// shown
static final int[][] SHIFT_TABLE = new int[6][6]; // mode shift codes, per table
static {

View file

@ -24,8 +24,8 @@ final class SimpleToken extends Token {
private final short value;
private final short bitCount;
SimpleToken(Token previous, int totalBitCount, int value, int bitCount) {
super(previous, totalBitCount);
SimpleToken(Token previous, int value, int bitCount) {
super(previous);
this.value = (short) value;
this.bitCount = (short) bitCount;
}

View file

@ -20,31 +20,25 @@ import com.google.zxing.common.BitArray;
abstract class Token {
static final Token EMPTY = new SimpleToken(null, 0, 0, 0);
static final Token EMPTY = new SimpleToken(null, 0, 0);
private final Token previous;
private final int totalBitCount; // For debugging purposes, only
Token(Token previous, int totalBitCount) {
Token(Token previous) {
this.previous = previous;
this.totalBitCount = totalBitCount;
}
final Token getPrevious() {
return previous;
}
final int getTotalBitCount() {
return totalBitCount;
}
final Token add(int value, int bitCount) {
return new SimpleToken(this, this.totalBitCount + bitCount, value, bitCount);
return new SimpleToken(this, value, bitCount);
}
final Token addBinaryShift(int start, int byteCount) {
int bitCount = (byteCount * 8) + (byteCount <= 31 ? 10 : byteCount <= 62 ? 20 : 21);
return new BinaryShiftToken(this, this.totalBitCount + bitCount, start, byteCount);
return new BinaryShiftToken(this, start, byteCount);
}
abstract void appendTo(BitArray bitArray, byte[] text);

View file

@ -18,19 +18,22 @@ package com.google.zxing.aztec.detector;
import com.google.zxing.NotFoundException;
import com.google.zxing.aztec.AztecDetectorResult;
import com.google.zxing.aztec.decoder.Decoder;
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 com.google.zxing.common.DecoderResult;
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;
import java.util.Set;
import java.util.TreeSet;
/**
* Tests for the Detector
@ -61,30 +64,46 @@ public final class DetectorTest extends Assert {
// 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);
AztecCode aztec = Encoder.encode(data.getBytes(LATIN_1), 25, Encoder.DEFAULT_AZTEC_LAYERS);
Random random = new Random(aztec.getMatrix().hashCode()); // pseudo-random, but deterministic
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) {
for (boolean isMirror : new boolean[] { false, true }) {
for (BitMatrix matrix : getRotations(aztec.getMatrix())) {
// Systematically try every possible 1- and 2-bit error.
for (int error1 = 0; error1 < orientationPoints.size(); error1++) {
for (int error2 = error1; error2 < orientationPoints.size(); error2++) {
BitMatrix copy = isMirror ? transpose(matrix) : clone(matrix);
copy.flip(orientationPoints.get(error1).getX(), orientationPoints.get(error1).getY());
if (error2 > error1) {
// if error2 == error1, we only test a single error
copy.flip(orientationPoints.get(error2).getX(), orientationPoints.get(error2).getY());
}
// The detector doesn't seem to work when matrix bits are only 1x1. So magnify.
AztecDetectorResult r = new Detector(makeLarger(copy, 3)).detect(isMirror);
assertNotNull(r);
assertEquals(r.getNbLayers(), layers);
assertEquals(r.isCompact(), compact);
} else {
fail("Should not succeed with more than two errors");
DecoderResult res = new Decoder().decode(r);
assertEquals(data, res.getText());
}
} catch (NotFoundException e) {
assertEquals("Should only fail with three errors", 3, errors);
}
// Try a few random three-bit errors;
for (int i = 0; i < 5; i++) {
BitMatrix copy = clone(matrix);
Set<Integer> errors = new TreeSet<Integer>();
while (errors.size() < 3) {
// Quick and dirty way of getting three distinct integers between 1 and n.
errors.add(random.nextInt(orientationPoints.size()));
}
for (int error : errors) {
copy.flip(orientationPoints.get(error).getX(), orientationPoints.get(error).getY());
}
try {
new Detector(makeLarger(copy, 3)).detect(false);
fail("Should not reach here");
} catch (NotFoundException expected) { }
}
}
}
@ -104,25 +123,55 @@ public final class DetectorTest extends Assert {
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.
// Returns a list of the four rotations of the BitMatrix.
private static List<BitMatrix> getRotations(BitMatrix input) {
BitMatrix matrix0 = input;
BitMatrix matrix90 = rotateRight(input);
BitMatrix matrix180 = rotateRight(matrix90);
BitMatrix matrix270 = rotateRight(matrix180);
return Arrays.asList(matrix0, matrix90, matrix180, matrix270);
}
// Rotates a square BitMatrix to the right by 90 degrees
private static BitMatrix rotateRight(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);
BitMatrix result = 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);
if (input.get(x,y)) {
result.set(y, width - x - 1);
}
}
}
return Arrays.asList(matrix0, matrix90, matrix180, matrix270);
return result;
}
// Returns the transpose of a bit matrix, which is equivalent to rotating the
// matrix to the right, and then flipping it left-to-right
private static BitMatrix transpose(BitMatrix input) {
int width = input.getWidth();
BitMatrix result = new BitMatrix(width);
for (int x = 0; x < width; x++) {
for (int y = 0; y < width; y++) {
if (input.get(x, y)) {
result.set(y, x);
}
}
}
return result;
}
private static BitMatrix clone(BitMatrix input) {
int width = input.getWidth();
BitMatrix result = new BitMatrix(width);
for (int x = 0; x < width; x++) {
for (int y = 0; y < width; y++) {
if (input.get(x,y)) {
result.set(x,y);
}
}
}
return result;
}
private static List<Point> getOrientationPoints(AztecCode code) {

View file

@ -16,6 +16,19 @@
package com.google.zxing.aztec.encoder;
import com.google.zxing.BarcodeFormat;
import com.google.zxing.EncodeHintType;
import com.google.zxing.FormatException;
import com.google.zxing.ResultPoint;
import com.google.zxing.aztec.AztecDetectorResult;
import com.google.zxing.aztec.AztecWriter;
import com.google.zxing.aztec.decoder.Decoder;
import com.google.zxing.common.BitArray;
import com.google.zxing.common.BitMatrix;
import com.google.zxing.common.DecoderResult;
import org.junit.Assert;
import org.junit.Test;
import java.nio.charset.Charset;
import java.security.SecureRandom;
import java.util.EnumMap;
@ -23,20 +36,6 @@ import java.util.Map;
import java.util.Random;
import java.util.regex.Pattern;
import com.google.zxing.FormatException;
import com.google.zxing.aztec.AztecWriter;
import org.junit.Assert;
import org.junit.Test;
import com.google.zxing.BarcodeFormat;
import com.google.zxing.EncodeHintType;
import com.google.zxing.ResultPoint;
import com.google.zxing.aztec.AztecDetectorResult;
import com.google.zxing.aztec.decoder.Decoder;
import com.google.zxing.common.BitArray;
import com.google.zxing.common.BitMatrix;
import com.google.zxing.common.DecoderResult;
/**
* Aztec 2D generator unit tests.
*
@ -139,7 +138,8 @@ public final class EncoderTest extends Assert {
String data = "In ut magna vel mauris malesuada";
AztecWriter writer = new AztecWriter();
BitMatrix matrix = writer.encode(data, BarcodeFormat.AZTEC, 0, 0);
AztecCode aztec = Encoder.encode(data.getBytes(LATIN_1), Encoder.DEFAULT_EC_PERCENT);
AztecCode aztec = Encoder.encode(data.getBytes(LATIN_1),
Encoder.DEFAULT_EC_PERCENT, Encoder.DEFAULT_AZTEC_LAYERS);
BitMatrix expectedMatrix = aztec.getMatrix();
assertEquals(matrix, expectedMatrix);
}
@ -341,16 +341,21 @@ public final class EncoderTest extends Assert {
// Test the output generated by Binary/Switch, particularly near the
// places where the encoding changes: 31, 62, and 2047+31=2078
for (int i : new int[] { 1, 2, 3, 10, 29, 30, 31, 32, 33,
60, 61, 62, 63, 64, 2076, 2077, 2078, 2079, 2080, 3000}) {
60, 61, 62, 63, 64, 2076, 2077, 2078, 2079, 2080, 2100 }) {
// This is the expected length of a binary string of length "i"
int expectedLength = (8 * i) +
( (i <= 31) ? 10 : (i <= 62) ? 20 : (i <= 2078) ? 21 : 31);
// Verify that we are correct about the length.
testHighLevelEncodeString(sb.substring(0, i), expectedLength);
// A lower case letter at the beginning will be merged into binary mode
testHighLevelEncodeString('a' + sb.substring(0, i - 1), expectedLength);
// A lower case letter at the end will also be merged into binary mode
testHighLevelEncodeString(sb.substring(0, i - 1) + 'a', expectedLength);
if (i != 1 && i != 32 && i != 2079) {
// The addition of an 'a' at the beginning or end gets merged into the binary code
// in those cases where adding another binary character only adds 8 or 9 bits to the result.
// So we exclude the border cases i=1,32,2079
// A lower case letter at the beginning will be merged into binary mode
testHighLevelEncodeString('a' + sb.substring(0, i - 1), expectedLength);
// A lower case letter at the end will also be merged into binary mode
testHighLevelEncodeString(sb.substring(0, i - 1) + 'a', expectedLength);
}
// A lower case letter at both ends will enough to latch us into LOWER.
testHighLevelEncodeString('a' + sb.substring(0, i) + 'b', expectedLength + 15);
}
@ -379,10 +384,57 @@ public final class EncoderTest extends Assert {
"...X. XXXXX ..X.. X....... ..X.XXX. ..X..... X.......");
}
@Test
public void testUserSpecifiedLayers() throws Exception {
byte[] alphabet = "ABCDEFGHIJKLMNOPQRSTUVWXYZ".getBytes(LATIN_1);
AztecCode aztec = Encoder.encode(alphabet, 25, -2);
assertEquals(2, aztec.getLayers());
assertTrue(aztec.isCompact());
aztec = Encoder.encode(alphabet, 25, 32);
assertEquals(32, aztec.getLayers());
assertFalse(aztec.isCompact());
try {
Encoder.encode(alphabet, 25, 33);
fail("Encode should have failed. No such thing as 33 layers");
} catch (IllegalArgumentException expected) {}
try {
Encoder.encode(alphabet, 25, -1);
fail("Encode should have failed. Text can't fit in 1-layer compact");
} catch (IllegalArgumentException expected) {}
}
@Test
public void testBorderCompact4Case() throws Exception {
// Compact(4) con hold 608 bits of information, but at most 504 can be data. Rest must
// be error correction
String alphabet = "ABCDEFGHIJKLMNOPQRSTUVWXYZ";
// encodes as 26 * 5 * 4 = 520 bits of data
String alphabet4 = alphabet + alphabet + alphabet + alphabet;
byte[] data = alphabet4.getBytes(LATIN_1);
try {
Encoder.encode(data, 0, -4);
fail("Encode should have failed. Text can't fit in 1-layer compact");
} catch (IllegalArgumentException expected) {}
// If we just try to encode it normally, it will go to a non-compact 4 layer
AztecCode aztecCode = Encoder.encode(data, 0, Encoder.DEFAULT_AZTEC_LAYERS);
assertFalse(aztecCode.isCompact());
assertEquals(4, aztecCode.getLayers());
// But shortening the string to 100 bytes (500 bits of data), compact works fine, even if we
// include more error checking.
aztecCode = Encoder.encode(alphabet4.substring(0, 100).getBytes(LATIN_1), 10, Encoder.DEFAULT_AZTEC_LAYERS);
assertTrue(aztecCode.isCompact());
assertEquals(4, aztecCode.getLayers());
}
// Helper routines
private static void testEncode(String data, boolean compact, int layers, String expected) throws Exception {
AztecCode aztec = Encoder.encode(data.getBytes(LATIN_1), 33);
AztecCode aztec = Encoder.encode(data.getBytes(LATIN_1), 33, Encoder.DEFAULT_AZTEC_LAYERS);
assertEquals("Unexpected symbol format (compact)", compact, aztec.isCompact());
assertEquals("Unexpected nr. of layers", layers, aztec.getLayers());
BitMatrix matrix = aztec.getMatrix();
@ -390,7 +442,7 @@ public final class EncoderTest extends Assert {
}
private static void testEncodeDecode(String data, boolean compact, int layers) throws Exception {
AztecCode aztec = Encoder.encode(data.getBytes(LATIN_1), 25);
AztecCode aztec = Encoder.encode(data.getBytes(LATIN_1), 25, Encoder.DEFAULT_AZTEC_LAYERS);
assertEquals("Unexpected symbol format (compact)", compact, aztec.isCompact());
assertEquals("Unexpected nr. of layers", layers, aztec.getLayers());
BitMatrix matrix = aztec.getMatrix();
@ -422,7 +474,8 @@ public final class EncoderTest extends Assert {
hints.put(EncodeHintType.ERROR_CORRECTION, eccPercent);
AztecWriter writer = new AztecWriter();
BitMatrix matrix = writer.encode(data, BarcodeFormat.AZTEC, 0, 0, hints);
AztecCode aztec = Encoder.encode(data.getBytes(Charset.forName(charset)), eccPercent);
AztecCode aztec = Encoder.encode(data.getBytes(Charset.forName(charset)), eccPercent,
Encoder.DEFAULT_AZTEC_LAYERS);
assertEquals("Unexpected symbol format (compact)", compact, aztec.isCompact());
assertEquals("Unexpected nr. of layers", layers, aztec.getLayers());
BitMatrix matrix2 = aztec.getMatrix();
@ -490,11 +543,11 @@ public final class EncoderTest extends Assert {
assertEquals(s, Decoder.highLevelDecode(toBooleanArray(bits)));
}
private static void testHighLevelEncodeString(String s, int receivedBits) {
private static void testHighLevelEncodeString(String s, int expectedReceivedBits) {
BitArray bits = new HighLevelEncoder(s.getBytes(LATIN_1)).encode();
int receivedBitCount = bits.toString().replace(" ", "").length();
assertEquals("highLevelEncode() failed for input string: " + s, receivedBitCount, receivedBitCount);
assertEquals("highLevelEncode() failed for input string: " + s,
expectedReceivedBits, receivedBitCount);
assertEquals(s, Decoder.highLevelDecode(toBooleanArray(bits)));
}
}