mirror of
https://github.com/zxing/zxing.git
synced 2025-02-02 05:41:08 -08:00
Aztec updates from fyellin
git-svn-id: https://zxing.googlecode.com/svn/trunk@2834 59b500cc-1b3d-0410-9834-0bbf25fbcc57
This commit is contained in:
parent
7544a08668
commit
e8a23d5085
|
@ -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,
|
||||
}
|
||||
|
|
|
@ -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
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -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());
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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) {
|
||||
|
|
|
@ -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 {
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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) {
|
||||
|
|
|
@ -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)));
|
||||
}
|
||||
|
||||
}
|
||||
|
|
Loading…
Reference in a new issue