From e8a23d508504efad2379333fc45ecad1856a9ada Mon Sep 17 00:00:00 2001 From: "srowen@gmail.com" Date: Mon, 1 Jul 2013 08:58:45 +0000 Subject: [PATCH] Aztec updates from fyellin git-svn-id: https://zxing.googlecode.com/svn/trunk@2834 59b500cc-1b3d-0410-9834-0bbf25fbcc57 --- core/src/com/google/zxing/EncodeHintType.java | 7 ++ .../com/google/zxing/aztec/AztecReader.java | 46 +++++++- .../com/google/zxing/aztec/AztecWriter.java | 25 ++-- .../google/zxing/aztec/detector/Detector.java | 19 ++- .../zxing/aztec/encoder/BinaryShiftToken.java | 10 +- .../google/zxing/aztec/encoder/Encoder.java | 104 +++++++++-------- .../zxing/aztec/encoder/HighLevelEncoder.java | 2 +- .../zxing/aztec/encoder/SimpleToken.java | 4 +- .../com/google/zxing/aztec/encoder/Token.java | 14 +-- .../zxing/aztec/detector/DetectorTest.java | 109 +++++++++++++----- .../zxing/aztec/encoder/EncoderTest.java | 105 ++++++++++++----- 11 files changed, 300 insertions(+), 145 deletions(-) diff --git a/core/src/com/google/zxing/EncodeHintType.java b/core/src/com/google/zxing/EncodeHintType.java index 340b59485..5b1961937 100644 --- a/core/src/com/google/zxing/EncodeHintType.java +++ b/core/src/com/google/zxing/EncodeHintType.java @@ -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, } diff --git a/core/src/com/google/zxing/aztec/AztecReader.java b/core/src/com/google/zxing/aztec/AztecReader.java index fd7b44538..2cb210108 100644 --- a/core/src/com/google/zxing/aztec/AztecReader.java +++ b/core/src/com/google/zxing/aztec/AztecReader.java @@ -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 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 byteSegments = decoderResult.getByteSegments(); @@ -90,4 +124,4 @@ public final class AztecReader implements Reader { // do nothing } -} \ No newline at end of file +} diff --git a/core/src/com/google/zxing/aztec/AztecWriter.java b/core/src/com/google/zxing/aztec/AztecWriter.java index 9e89e84c0..a2f2db8c0 100644 --- a/core/src/com/google/zxing/aztec/AztecWriter.java +++ b/core/src/com/google/zxing/aztec/AztecWriter.java @@ -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 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; } - } diff --git a/core/src/com/google/zxing/aztec/detector/Detector.java b/core/src/com/google/zxing/aztec/detector/Detector.java index 8cbe9bb72..fa4321a41 100644 --- a/core/src/com/google/zxing/aztec/detector/Detector.java +++ b/core/src/com/google/zxing/aztec/detector/Detector.java @@ -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()); diff --git a/core/src/com/google/zxing/aztec/encoder/BinaryShiftToken.java b/core/src/com/google/zxing/aztec/encoder/BinaryShiftToken.java index 7d8940cea..637ba63a5 100644 --- a/core/src/com/google/zxing/aztec/encoder/BinaryShiftToken.java +++ b/core/src/com/google/zxing/aztec/encoder/BinaryShiftToken.java @@ -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 diff --git a/core/src/com/google/zxing/aztec/encoder/Encoder.java b/core/src/com/google/zxing/aztec/encoder/Encoder.java index acbe7c736..d014261c4 100644 --- a/core/src/com/google/zxing/aztec/encoder/Encoder.java +++ b/core/src/com/google/zxing/aztec/encoder/Encoder.java @@ -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) { diff --git a/core/src/com/google/zxing/aztec/encoder/HighLevelEncoder.java b/core/src/com/google/zxing/aztec/encoder/HighLevelEncoder.java index cf022769a..d6ef604c1 100644 --- a/core/src/com/google/zxing/aztec/encoder/HighLevelEncoder.java +++ b/core/src/com/google/zxing/aztec/encoder/HighLevelEncoder.java @@ -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 { diff --git a/core/src/com/google/zxing/aztec/encoder/SimpleToken.java b/core/src/com/google/zxing/aztec/encoder/SimpleToken.java index da3276486..047d962ad 100644 --- a/core/src/com/google/zxing/aztec/encoder/SimpleToken.java +++ b/core/src/com/google/zxing/aztec/encoder/SimpleToken.java @@ -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; } diff --git a/core/src/com/google/zxing/aztec/encoder/Token.java b/core/src/com/google/zxing/aztec/encoder/Token.java index a41da2c3f..9d407d890 100644 --- a/core/src/com/google/zxing/aztec/encoder/Token.java +++ b/core/src/com/google/zxing/aztec/encoder/Token.java @@ -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); diff --git a/core/test/src/com/google/zxing/aztec/detector/DetectorTest.java b/core/test/src/com/google/zxing/aztec/detector/DetectorTest.java index d4982c672..37b84963a 100644 --- a/core/test/src/com/google/zxing/aztec/detector/DetectorTest.java +++ b/core/test/src/com/google/zxing/aztec/detector/DetectorTest.java @@ -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 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 errors = new TreeSet(); + 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 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 getOrientationPoints(AztecCode code) { diff --git a/core/test/src/com/google/zxing/aztec/encoder/EncoderTest.java b/core/test/src/com/google/zxing/aztec/encoder/EncoderTest.java index 3418cbf48..341922361 100644 --- a/core/test/src/com/google/zxing/aztec/encoder/EncoderTest.java +++ b/core/test/src/com/google/zxing/aztec/encoder/EncoderTest.java @@ -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))); } - }