From e1c047c54bdf46dc112b45e4132d6fed486ca363 Mon Sep 17 00:00:00 2001 From: "dav.olivier@gmail.com" Date: Sat, 20 Nov 2010 21:21:34 +0000 Subject: [PATCH] Support for aztec codes git-svn-id: https://zxing.googlecode.com/svn/trunk@1668 59b500cc-1b3d-0410-9834-0bbf25fbcc57 --- core/src/com/google/zxing/BarcodeFormat.java | 3 + .../com/google/zxing/MultiFormatReader.java | 4 + .../zxing/aztec/AztecDetectorResult.java | 32 + .../com/google/zxing/aztec/AztecReader.java | 93 +++ .../google/zxing/aztec/decoder/Decoder.java | 467 ++++++++++++ .../google/zxing/aztec/detector/Detector.java | 667 ++++++++++++++++++ .../google/zxing/common/DetectorResult.java | 2 +- .../detector/WhiteRectangleDetector.java | 33 +- .../test/data/blackbox/aztec-1/abc-19x19C.png | Bin 0 -> 536 bytes .../test/data/blackbox/aztec-1/abc-19x19C.txt | 1 + core/test/data/blackbox/aztec-1/abc-37x37.png | Bin 0 -> 1357 bytes core/test/data/blackbox/aztec-1/abc-37x37.txt | 1 + .../data/blackbox/aztec-1/lorem-075x075.png | Bin 0 -> 2203 bytes .../data/blackbox/aztec-1/lorem-075x075.txt | 1 + .../data/blackbox/aztec-1/lorem-105x105.png | Bin 0 -> 6013 bytes .../data/blackbox/aztec-1/lorem-105x105.txt | 1 + .../data/blackbox/aztec-1/lorem-151x151.png | Bin 0 -> 11622 bytes .../data/blackbox/aztec-1/lorem-151x151.txt | 1 + .../data/blackbox/aztec-1/tableShifts.png | Bin 0 -> 1246 bytes .../data/blackbox/aztec-1/tableShifts.txt | 1 + .../zxing/aztec/AztecBlackBox1TestCase.java | 35 + .../zxing/aztec/AztecBlackBox2TestCase.java | 35 + 22 files changed, 1369 insertions(+), 8 deletions(-) create mode 100644 core/src/com/google/zxing/aztec/AztecDetectorResult.java create mode 100644 core/src/com/google/zxing/aztec/AztecReader.java create mode 100644 core/src/com/google/zxing/aztec/decoder/Decoder.java create mode 100644 core/src/com/google/zxing/aztec/detector/Detector.java create mode 100644 core/test/data/blackbox/aztec-1/abc-19x19C.png create mode 100644 core/test/data/blackbox/aztec-1/abc-19x19C.txt create mode 100644 core/test/data/blackbox/aztec-1/abc-37x37.png create mode 100644 core/test/data/blackbox/aztec-1/abc-37x37.txt create mode 100644 core/test/data/blackbox/aztec-1/lorem-075x075.png create mode 100644 core/test/data/blackbox/aztec-1/lorem-075x075.txt create mode 100644 core/test/data/blackbox/aztec-1/lorem-105x105.png create mode 100644 core/test/data/blackbox/aztec-1/lorem-105x105.txt create mode 100644 core/test/data/blackbox/aztec-1/lorem-151x151.png create mode 100644 core/test/data/blackbox/aztec-1/lorem-151x151.txt create mode 100644 core/test/data/blackbox/aztec-1/tableShifts.png create mode 100644 core/test/data/blackbox/aztec-1/tableShifts.txt create mode 100644 core/test/src/com/google/zxing/aztec/AztecBlackBox1TestCase.java create mode 100644 core/test/src/com/google/zxing/aztec/AztecBlackBox2TestCase.java diff --git a/core/src/com/google/zxing/BarcodeFormat.java b/core/src/com/google/zxing/BarcodeFormat.java index 1cca09a50..3dc60f7a3 100644 --- a/core/src/com/google/zxing/BarcodeFormat.java +++ b/core/src/com/google/zxing/BarcodeFormat.java @@ -35,6 +35,9 @@ public final class BarcodeFormat { /** Data Matrix 2D barcode format. */ public static final BarcodeFormat DATA_MATRIX = new BarcodeFormat("DATA_MATRIX"); + /** Aztec 2D barcode format. */ + public static final BarcodeFormat AZTEC = new BarcodeFormat("AZTEC"); + /** UPC-E 1D format. */ public static final BarcodeFormat UPC_E = new BarcodeFormat("UPC_E"); diff --git a/core/src/com/google/zxing/MultiFormatReader.java b/core/src/com/google/zxing/MultiFormatReader.java index 42a97fe7c..e2bc48ecd 100644 --- a/core/src/com/google/zxing/MultiFormatReader.java +++ b/core/src/com/google/zxing/MultiFormatReader.java @@ -16,6 +16,7 @@ package com.google.zxing; +import com.google.zxing.aztec.AztecReader; import com.google.zxing.datamatrix.DataMatrixReader; import com.google.zxing.oned.MultiFormatOneDReader; import com.google.zxing.pdf417.PDF417Reader; @@ -116,6 +117,9 @@ public final class MultiFormatReader implements Reader { if (formats.contains(BarcodeFormat.DATA_MATRIX)) { readers.addElement(new DataMatrixReader()); } + if (formats.contains(BarcodeFormat.AZTEC)) { + readers.addElement(new AztecReader()); + } if (formats.contains(BarcodeFormat.PDF417)) { readers.addElement(new PDF417Reader()); } diff --git a/core/src/com/google/zxing/aztec/AztecDetectorResult.java b/core/src/com/google/zxing/aztec/AztecDetectorResult.java new file mode 100644 index 000000000..2c833e3cc --- /dev/null +++ b/core/src/com/google/zxing/aztec/AztecDetectorResult.java @@ -0,0 +1,32 @@ +package com.google.zxing.aztec; + +import com.google.zxing.ResultPoint; +import com.google.zxing.common.BitMatrix; +import com.google.zxing.common.DetectorResult; + +public class AztecDetectorResult extends DetectorResult { + + private final boolean compact; + private final int nbDatablocks; + private final int nbLayers; + + public AztecDetectorResult(BitMatrix bits, ResultPoint[] points, boolean compact, int nbDatablocks, int nbLayers) { + super(bits, points); + this.compact = compact; + this.nbDatablocks = nbDatablocks; + this.nbLayers = nbLayers; + } + + public int getNbLayers() { + return nbLayers; + } + + public int getNbDatablocks() { + return nbDatablocks; + } + + public boolean isCompact() { + return compact; + } + +} diff --git a/core/src/com/google/zxing/aztec/AztecReader.java b/core/src/com/google/zxing/aztec/AztecReader.java new file mode 100644 index 000000000..31828e39a --- /dev/null +++ b/core/src/com/google/zxing/aztec/AztecReader.java @@ -0,0 +1,93 @@ +/* + * Copyright 2007 ZXing authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.google.zxing.aztec; + +import com.google.zxing.BarcodeFormat; +import com.google.zxing.BinaryBitmap; +import com.google.zxing.ChecksumException; +import com.google.zxing.DecodeHintType; +import com.google.zxing.FormatException; +import com.google.zxing.NotFoundException; +import com.google.zxing.Reader; +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.common.DetectorResult; +import com.google.zxing.aztec.decoder.Decoder; +import com.google.zxing.aztec.detector.Detector; + +import java.util.Hashtable; + +/** + * This implementation can detect and decode Aztec codes in an image. + * + * @author David Olivier + */ +public final class AztecReader implements Reader { + + private static final ResultPoint[] NO_POINTS = new ResultPoint[0]; + + /** + * Locates and decodes a Data Matrix code in an image. + * + * @return a String representing the content encoded by the Data Matrix code + * @throws NotFoundException if a Data Matrix code cannot be found + * @throws FormatException if a Data Matrix code cannot be decoded + * @throws ChecksumException if error correction fails + */ + public Result decode(BinaryBitmap image) throws NotFoundException, ChecksumException, FormatException { + return decode(image, null); + } + + public Result decode(BinaryBitmap image, Hashtable hints) + throws NotFoundException, ChecksumException, FormatException { + DecoderResult decoderResult; + ResultPoint[] points; + + AztecDetectorResult detectorResult = new Detector(image.getBlackMatrix()).detect(); + points = detectorResult.getPoints(); + + if (hints != null && detectorResult.getPoints() != null){ + ResultPointCallback rpcb = (ResultPointCallback) hints.get(DecodeHintType.NEED_RESULT_POINT_CALLBACK); + if (rpcb != null){ + for (int i = 0; i < detectorResult.getPoints().length; i++){ + rpcb.foundPossibleResultPoint(detectorResult.getPoints()[i]); + } + } + } + + decoderResult = new Decoder().decode(detectorResult); + + Result result = new Result(decoderResult.getText(), decoderResult.getRawBytes(), points, BarcodeFormat.AZTEC); + + if (decoderResult.getByteSegments() != null) { + result.putMetadata(ResultMetadataType.BYTE_SEGMENTS, decoderResult.getByteSegments()); + } + if (decoderResult.getECLevel() != null) { + result.putMetadata(ResultMetadataType.ERROR_CORRECTION_LEVEL, decoderResult.getECLevel().toString()); + } + + return result; + } + + public void reset() { + // do nothing + } + +} \ No newline at end of file diff --git a/core/src/com/google/zxing/aztec/decoder/Decoder.java b/core/src/com/google/zxing/aztec/decoder/Decoder.java new file mode 100644 index 000000000..fffca2257 --- /dev/null +++ b/core/src/com/google/zxing/aztec/decoder/Decoder.java @@ -0,0 +1,467 @@ +/* + * Copyright 2007 ZXing authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.google.zxing.aztec.decoder; + +import com.google.zxing.ChecksumException; +import com.google.zxing.FormatException; +import com.google.zxing.aztec.AztecDetectorResult; +import com.google.zxing.common.BitMatrix; +import com.google.zxing.common.DecoderResult; +import com.google.zxing.common.reedsolomon.GenericGF; +import com.google.zxing.common.reedsolomon.ReedSolomonDecoder; +import com.google.zxing.common.reedsolomon.ReedSolomonException; + +/** + *

The main class which implements Aztec Code decoding -- as opposed to locating and extracting + * the Aztec Code from an image.

+ * + * @author David Olivier + */ +public final class Decoder { + + int numCodewords; + int codewordSize; + AztecDetectorResult ddata; + + public Decoder() {} + + public DecoderResult decode(AztecDetectorResult detectorResult) throws FormatException, ChecksumException { + ddata = detectorResult; + BitMatrix matrix = detectorResult.getBits(); + + if (!ddata.isCompact()){ + matrix = removeDashedLines(ddata.getBits()); + } + + boolean[] rawbits = extractBits(matrix); + + boolean[] correctedBits = correctBits(rawbits); + + String result = getEncodedData(correctedBits); + + return new DecoderResult(null, result, null, null); + } + + + /** + * + * Gets the string encoded in the aztec code bits + * + * @param correctedBits + * @return the decoded string + * @throws FormatException if the input is not valid + */ + private String getEncodedData(boolean[] correctedBits) throws FormatException { + + int endIndex = codewordSize * ddata.getNbDatablocks(); + if (endIndex > correctedBits.length){ + throw FormatException.getFormatInstance(); + } + + int lastTable = UPPER; + int table = UPPER; + int startIndex = 0; + int code; + StringBuilder result = new StringBuilder(); + boolean end = false; + boolean shift = false; + boolean switchShift = false; + + while (!end){ + + if (shift){ + // the table is for the next character only + switchShift = true; + } else { + // save the current table in case next one is a shift + lastTable = table; + } + + switch (table) { + case BINARY: + if (endIndex - startIndex < 8){ + end = true; + break; + } + code = readCode(correctedBits, startIndex, 8); + startIndex += 8; + + result.append((char)(code)); + break; + + default: + int size = 5; + + if (table == DIGIT){ + size = 4; + } + + if (endIndex - startIndex < size){ + end = true; + break; + } + + code = readCode(correctedBits, startIndex, size); + startIndex += size; + + String str = getCharacter(table, code); + if (!str.startsWith("CTRL_")){ + result.append(str); + } else { + // Table changes + table = getTable(str.charAt(5)); + + if (str.charAt(6) == 'S'){ + shift = true; + } + } + + break; + } + + if (switchShift){ + table = lastTable; + shift = false; + switchShift = false; + } + + } + return result.toString(); + } + + + /** + * + * gets the table corresponding to the char passed + * + * @param t + * @return + */ + private int getTable(char t) { + int table = UPPER; + + switch (t){ + case 'U': + table = UPPER; break; + case 'L': + table = LOWER; break; + case 'P': + table = PUNCT; break; + case 'M': + table = MIXED; break; + case 'D': + table = DIGIT; break; + case 'B': + table = BINARY; break; + } + + return table; + } + + /** + * + * Gets the character (or string) corresponding to the passed code in the given table + * + * @param table the table used + * @param code the code of the character + * @return + */ + private String getCharacter(int table, int code) { + switch (table){ + case UPPER: + return UPPER_TABLE[code]; + case LOWER: + return LOWER_TABLE[code]; + case MIXED: + return MIXED_TABLE[code]; + case PUNCT: + return PUNCT_TABLE[code]; + case DIGIT: + return DIGIT_TABLE[code]; + default: + return ""; + } + } + + /** + * + *

performs RS error correction on an array of bits

+ * + * @param rawbits + * @return the corrected array + * @throws FormatException if the input contains too many errors + */ + private boolean[] correctBits(boolean[] rawbits) throws FormatException { + GenericGF gf; + + if (ddata.getNbLayers() <= 2){ + codewordSize = 6; + gf = GenericGF.AZTEC_DATA_6; + } else if (ddata.getNbLayers() <= 8) { + codewordSize = 8; + gf = GenericGF.AZTEC_DATA_8; + } else if (ddata.getNbLayers() <= 22) { + codewordSize = 10; + gf = GenericGF.AZTEC_DATA_10; + } else { + codewordSize = 12; + gf = GenericGF.AZTEC_DATA_12; + } + + int numDataCodewords = ddata.getNbDatablocks(); + int numECCodewords = 0; + int offset = 0; + + if (ddata.isCompact()){ + offset = NbBitsCompact[ddata.getNbLayers()] - numCodewords*codewordSize; + numECCodewords = NbDatablockCompact[ddata.getNbLayers()] - numDataCodewords; + } else { + offset = NbBits[ddata.getNbLayers()] - numCodewords*codewordSize; + numECCodewords = NbDatablock[ddata.getNbLayers()] - numDataCodewords; + } + + int[] dataWords = new int[numCodewords]; + for (int i = 0; i < numCodewords; i++){ + int flag = 1; + for (int j = 1; j <= codewordSize; j++){ + if (rawbits[codewordSize*i + codewordSize - j + offset]){ + dataWords[i] += flag; + } + flag <<= 1; + } + + if (dataWords[i] >= flag){ + flag++; + } + } + rawbits = null; + + try { + ReedSolomonDecoder rsDecoder = new ReedSolomonDecoder(gf); + rsDecoder.decode(dataWords, numECCodewords); + } catch (ReedSolomonException rse) { + System.out.println("END: invalid RS"); + throw FormatException.getFormatInstance(); + } + + offset = 0; + + boolean[] correctedBits = new boolean[numDataCodewords*codewordSize]; + for (int i = 0; i < numDataCodewords; i ++){ + + boolean seriesColor = false; + int seriesCount = 0; + int flag = 1 << (codewordSize - 1); + + for (int j = 0; j < codewordSize; j++){ + + boolean color = (dataWords[i] & flag) == flag; + + if (seriesCount != codewordSize - 1){ + + if (seriesColor == color){ + seriesCount++; + } else { + seriesCount = 1; + seriesColor = color; + } + + correctedBits[i*codewordSize+j-offset] = color; + + } else { + + if (color == seriesColor){ + //bit must be inverted + throw FormatException.getFormatInstance(); + } + + seriesColor = false; + seriesCount = 0; + offset++; + } + + flag >>>= 1; + } + } + + return correctedBits; + } + + /** + * + * Gets the array of bits from an Aztec Code matrix + * + * @param matrix + * @return the array of bits + * @throws FormatException if the matrix is not a valid aztec code + */ + private boolean[] extractBits(BitMatrix matrix) throws FormatException { + + boolean[] rawbits; + if (ddata.isCompact()){ + if (ddata.getNbLayers() > NbBitsCompact.length){ + throw FormatException.getFormatInstance(); + } + rawbits = new boolean[NbBitsCompact[ddata.getNbLayers()]]; + numCodewords = NbDatablockCompact[ddata.getNbLayers()]; + } else { + if (ddata.getNbLayers() > NbBits.length){ + throw FormatException.getFormatInstance(); + } + rawbits = new boolean[NbBits[ddata.getNbLayers()]]; + numCodewords = NbDatablock[ddata.getNbLayers()]; + } + + int flip; + int layer = ddata.getNbLayers(); + int size = matrix.height; + int rawbitsOffset = 0; + int matrixOffset = 0; + + while (layer != 0){ + + flip = 0; + for (int i = 0; i < 2*size - 4; i++){ + rawbits[rawbitsOffset+i] = matrix.get(matrixOffset + flip, matrixOffset + i/2); + rawbits[rawbitsOffset+2*size - 4 + i] = matrix.get(matrixOffset + i/2, matrixOffset + size-1-flip); + flip = (flip + 1)%2; + } + + flip = 0; + for (int i = 2*size+1; i > 5; i--){ + rawbits[rawbitsOffset+4*size - 8 + (2*size-i) + 1] = matrix.get(matrixOffset + size-1-flip, matrixOffset + i/2 - 1); + rawbits[rawbitsOffset+6*size - 12 + (2*size-i) + 1] = matrix.get(matrixOffset + i/2 - 1, matrixOffset + flip); + flip = (flip + 1)%2; + } + + matrixOffset += 2; + rawbitsOffset += 8*size-16; + layer--; + size-=4; + } + + return rawbits; + } + + + /** + * + *

Transforms an aztec code matrix by removing the control dashed lines

+ * + * @param matrix + * @return + */ + private BitMatrix removeDashedLines(BitMatrix matrix) { + int nbDashed = 1+ 2* ((matrix.width - 1)/2 / 16); + BitMatrix newMatrix = new BitMatrix(matrix.width - nbDashed, matrix.height - nbDashed); + + int nx = 0; + int ny = 0; + + for (int x = 0; x < matrix.width; x++){ + + ny = 0; + + if ((matrix.width / 2 - x)%16 == 0){ + continue; + } + + for (int y = 0; y < matrix.height; y++){ + + if ((matrix.width / 2 - y)%16 == 0){ + continue; + } + + if (matrix.get(x, y)){ + newMatrix.set(nx, ny); + } + ny++; + } + nx++; + } + + return newMatrix; + } + + /** + * + * Reads a code of given length and at given index in an array of bits + * + * @param rawbits + * @param startIndex + * @param length + * @return + */ + private int readCode(boolean[] rawbits, int startIndex, int length) { + int res = 0; + + for (int i = startIndex; i < startIndex + length; i++){ + res = res << 1; + if (rawbits[i]){ + res++; + } + } + + return res; + } + + + + + static final int UPPER = 0; + static final int LOWER = 1; + static final int MIXED = 2; + static final int DIGIT = 3; + static final int PUNCT = 4; + static final int BINARY = 5; + + static final int NbBitsCompact[] = { + 0,104, 240, 408, 608 + }; + + static final int NbBits[] = { + 0, 128, 288, 480, 704, 960, 1248, 1568, 1920, 2304, 2720, 3168, 3648, 4160, 4704, 5280, 5888, 6528, 7200, 7904, 8640, 9408, 10208, 11040, 11904, 12800, 13728, 14688, 15680, 16704, 17760, 18848, 19968 + }; + + static final int NbDatablockCompact[] = { + 0, 17, 40, 51, 76 + }; + + static final int NbDatablock[] = { + 0, 21, 48, 60, 88, 120, 156, 196, 240, 230, 272, 316, 364, 416, 470, 528, 588, 652, 720, 790, 864, 940, 1020, 920, 992, 1066, 1144, 1224, 1306, 1392, 1480, 1570, 1664 + }; + + static final String UPPER_TABLE[] = { + "CTRL_PS", " ", "A", "B", "C", "D", "E", "F", "G", "H", "I", "J", "K", "L", "M", "N", "O", "P", "Q", "R", "S", "T", "U", "V", "W", "X", "Y", "Z", "CTRL_LL", "CTRL_ML", "CTRL_DL", "CTRL_BS" + }; + + static final String LOWER_TABLE[] = { + "CTRL_PS", " ", "a", "b", "c", "d", "e", "f", "g", "h", "i", "j", "k", "l", "m", "n", "o", "p", "q", "r", "s", "t", "u", "v", "w", "x", "y", "z", "CTRL_US", "CTRL_ML", "CTRL_DL", "CTRL_BS" + }; + + static final String MIXED_TABLE[] = { + "CTRL_PS", " ", ""+(char)1, ""+(char)2, ""+(char)3, ""+(char)4, ""+(char)5, ""+(char)6, ""+(char)7, ""+(char)8, ""+(char)9, ""+(char)10, ""+(char)11, ""+(char)12, ""+(char)13, ""+(char)27, ""+(char)28, ""+(char)29, ""+(char)30, ""+(char)31, "@", "\\", "^", ""+(char)95, "`", "|", "~", ""+(char)127, "CTRL_LL", "CTRL_UL", "CTRL_PL", "CTRL_BS" + }; + + static final String PUNCT_TABLE[] = { + "", ""+(char)13, ""+(char)13+(char)10, ""+(char)46+(char)32, ""+(char)44+(char)32, ""+(char)58+(char)32, "!", "\"", "#", "$", "%", "&", "'", "(", ")", ""+(char)42, "+", ",", "-", ".", "/", ":", ";", "<", "=", ">", "?", "[", "]", "{", "}", "CTRL_UL" + }; + + static final String DIGIT_TABLE[] = { + "CTRL_PS", " ", "0", "1", "2", "3", "4", "5", "6", "7", "8", "9", ",", ".", "CTRL_UL", "CTRL_US" + }; +} diff --git a/core/src/com/google/zxing/aztec/detector/Detector.java b/core/src/com/google/zxing/aztec/detector/Detector.java new file mode 100644 index 000000000..f2c16903f --- /dev/null +++ b/core/src/com/google/zxing/aztec/detector/Detector.java @@ -0,0 +1,667 @@ +/* + * Copyright 2008 ZXing authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.google.zxing.aztec.detector; + +import com.google.zxing.NotFoundException; +import com.google.zxing.ResultPoint; +import com.google.zxing.aztec.AztecDetectorResult; +import com.google.zxing.common.BitMatrix; +import com.google.zxing.common.GridSampler; +import com.google.zxing.common.detector.WhiteRectangleDetector; +import com.google.zxing.common.reedsolomon.GenericGF; +import com.google.zxing.common.reedsolomon.ReedSolomonDecoder; +import com.google.zxing.common.reedsolomon.ReedSolomonException; + +/** + *

Encapsulates logic that can detect an Aztec Code in an image, even if the Aztec Code + * is rotated or skewed, or partially obscured.

+ * + * @author David Olivier + */ +public final class Detector { + + private final BitMatrix image; + + private boolean compact; + private int nbLayers; + private int nbDataBlocks; + private int nbCenterLayers; + private int shift; + + public Detector(BitMatrix image) { + this.image = image; + } + + /** + *

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 { + + // 1. Get the center of the aztec matrix + Point pCenter = getMatrixCenter(); + + // 2. Get the corners of the center bull's eye + Point[] bullEyeCornerPoints = getBullEyeCornerPoints(pCenter); + + // 3. Get the size of the matrix from the bull's eye + extractParameters(bullEyeCornerPoints); + + // 4. Get the corners of the matrix + ResultPoint[] corners = getMatrixCornerPoints(bullEyeCornerPoints); + + // 5. Sample the grid + BitMatrix bits = sampleGrid(image, corners[shift%4], corners[(shift+3)%4], corners[(shift+2)%4], corners[(shift+1)%4]); + + return new AztecDetectorResult(bits, corners, compact, nbDataBlocks, nbLayers); + } + + /** + *

Extracts the number of data layers and data blocks from the layer around the bull's eye

+ * + * @param bullEyeCornerPoints the array of bull's eye corners + * @throws NotFoundException in case of too many errors or invalid parameters + */ + private void extractParameters(Point[] bullEyeCornerPoints) + throws NotFoundException { + + // Get the bits around the bull's eye + boolean[] resab = sampleLine(bullEyeCornerPoints[0], bullEyeCornerPoints[1], 2*nbCenterLayers+1); + boolean[] resbc = sampleLine(bullEyeCornerPoints[1], bullEyeCornerPoints[2], 2*nbCenterLayers+1); + boolean[] rescd = sampleLine(bullEyeCornerPoints[2], bullEyeCornerPoints[3], 2*nbCenterLayers+1); + boolean[] resda = sampleLine(bullEyeCornerPoints[3], bullEyeCornerPoints[0], 2*nbCenterLayers+1); + + boolean[] shiftedParameterData; + boolean[] parameterData; + + // Determine the orientation of the matrix + if (resab[0]==true && resab[2*nbCenterLayers]==true){ + shift = 0; + } else if (resbc[0]==true && resbc[2*nbCenterLayers]==true){ + shift = 1; + } else if (rescd[0]==true && rescd[2*nbCenterLayers]==true){ + shift = 2; + } else if (resda[0]==true && resda[2*nbCenterLayers]==true){ + shift = 3; + } else { + throw NotFoundException.getNotFoundInstance(); + } + + //d a + // + //c b + + // Flatten the bits in a single array + if (compact){ + shiftedParameterData = new boolean[28]; + for (int i = 0; i < 7; i++){ + shiftedParameterData[i] = resab[2+i]; + shiftedParameterData[i+7] = resbc[2+i]; + shiftedParameterData[i+14] = rescd[2+i]; + shiftedParameterData[i+21] = resda[2+i]; + } + + parameterData = new boolean[28]; + for (int i = 0; i < 28; i++){ + parameterData[i] = shiftedParameterData[(i+shift*7)%28]; + } + } else { + shiftedParameterData = new boolean[40]; + for (int i = 0; i < 11; i++){ + if (i < 5){ + shiftedParameterData[i] = resab[2+i]; + shiftedParameterData[i+10] = resbc[2+i]; + shiftedParameterData[i+20] = rescd[2+i]; + shiftedParameterData[i+30] = resda[2+i]; + } + if (i > 5){ + shiftedParameterData[i-1] = resab[2+i]; + shiftedParameterData[i+10-1] = resbc[2+i]; + shiftedParameterData[i+20-1] = rescd[2+i]; + shiftedParameterData[i+30-1] = resda[2+i]; + } + } + + parameterData = new boolean[40]; + for (int i = 0; i < 40; i++){ + parameterData[i] = shiftedParameterData[(i+shift*10)%40]; + } + } + + // corrects the error using RS algorithm + correctParameterData(parameterData, compact); + + // gets the parameters from the bit array + getParameters(parameterData); + } + + /** + * + *

Gets the aztec code corners from the bull's eye corners and the parameters

+ * + * @param bullEyeCornerPoints 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(Point[] bullEyeCornerPoints) throws NotFoundException { + + float ratio = (2*nbLayers+(nbLayers>4?1:0)+(nbLayers-4)/8)/(2.0f*nbCenterLayers); + + int dx = bullEyeCornerPoints[0].x-bullEyeCornerPoints[2].x; + dx+=dx>0?1:-1; + int dy = bullEyeCornerPoints[0].y-bullEyeCornerPoints[2].y; + dy+=dy>0?1:-1; + + int targetcx = round(bullEyeCornerPoints[2].x-ratio*dx); + int targetcy = round(bullEyeCornerPoints[2].y-ratio*dy); + + int targetax = round(bullEyeCornerPoints[0].x+ratio*dx); + int targetay = round(bullEyeCornerPoints[0].y+ratio*dy); + + dx = bullEyeCornerPoints[1].x-bullEyeCornerPoints[3].x; + dx+=dx>0?1:-1; + dy = bullEyeCornerPoints[1].y-bullEyeCornerPoints[3].y; + dy+=dy>0?1:-1; + + int targetdx = round(bullEyeCornerPoints[3].x-ratio*dx); + int targetdy = round(bullEyeCornerPoints[3].y-ratio*dy); + int targetbx = round(bullEyeCornerPoints[1].x+ratio*dx); + int targetby = round(bullEyeCornerPoints[1].y+ratio*dy); + + if (!isValid(targetax, targetay) || !isValid(targetbx, targetby) || !isValid(targetcx, targetcy) || !isValid(targetdx, targetdy)){ + throw NotFoundException.getNotFoundInstance(); + } + + return new ResultPoint[]{new ResultPoint(targetax, targetay), new ResultPoint(targetbx, targetby), new ResultPoint(targetcx, targetcy), new ResultPoint(targetdx, targetdy)}; + } + + /** + * + *

Corrects the parameter bits using Reed-Solomon algorithm

+ * + * @param parameterData paremeter bits + * @param compact true if this is a compact Aztec code + * @throws NotFoundException if the array contains too many errors + */ + private void correctParameterData(boolean[] parameterData, boolean compact) throws NotFoundException { + + int numCodewords; + int numDataCodewords; + int codewordSize = 4; + + if (compact){ + numCodewords = 7; + numDataCodewords = 2; + } else { + numCodewords = 10; + numDataCodewords = 4; + } + + int numECCodewords = numCodewords - numDataCodewords; + int[] parameterWords = new int[numCodewords]; + + for (int i = 0; i < numCodewords; i++){ + int flag = 1; + for (int j = 1; j <= codewordSize; j++){ + if (parameterData[codewordSize*i + codewordSize - j]){ + parameterWords[i] += flag; + } + flag <<= 1; + } + } + + try { + ReedSolomonDecoder rsDecoder = new ReedSolomonDecoder(GenericGF.AZTEC_PARAM); + rsDecoder.decode(parameterWords, numECCodewords); + } catch (ReedSolomonException rse) { + throw NotFoundException.getNotFoundInstance(); + } + + for (int i = 0; i < numDataCodewords; i ++){ + int flag = 1; + for (int j = 1; j <= codewordSize; j++){ + parameterData[i*codewordSize+codewordSize-j] = (parameterWords[i] & flag) == flag; + flag <<= 1; + } + } + } + + /** + * + *

Finds the corners of a bull-eye centered on the passed point

+ * + * @param pCenter Center point + * @return The corners of the bull-eye + * @throws NotFoundException If no valid bull-eye can be found + */ + private Point[] getBullEyeCornerPoints(Point pCenter) throws NotFoundException { + + Point pina = pCenter; + Point pinb = pCenter; + Point pinc = pCenter; + Point pind = pCenter; + Point pouta; + Point poutb; + Point poutc; + Point poutd; + + boolean color = true; + + for (nbCenterLayers = 1; nbCenterLayers < 9; nbCenterLayers++){ + pouta = getFirstDifferent(pina, color, 1, -1); + poutb = getFirstDifferent(pinb, color, 1, 1); + poutc = getFirstDifferent(pinc, color, -1, 1); + poutd = getFirstDifferent(pind, color, -1, -1); + + //d a + // + //c b + + if (nbCenterLayers>2){ + float q = (float)distance(poutd, pouta)*nbCenterLayers/(distance(pind, pina)*(nbCenterLayers+2)); + if ( q < 0.75 || q > 1.25 || !isWhiteOrBlackRectangle(pouta, poutb, poutc, poutd)){ + break; + } + } + + pina = pouta; + pinb = poutb; + pinc = poutc; + pind = poutd; + + color = !color; + } + + if (nbCenterLayers!=5 && nbCenterLayers !=7){ + throw NotFoundException.getNotFoundInstance(); + } + + compact = nbCenterLayers==5; + + float ratio = 0.75f*2/(2*nbCenterLayers-3); + + int dx = pina.x-pinc.x; + int dy = pina.y-pinc.y; + int targetcx = round(pinc.x-ratio*dx); + int targetcy = round(pinc.y-ratio*dy); + int targetax = round(pina.x+ratio*dx); + int targetay = round(pina.y+ratio*dy); + + dx = pinb.x-pind.x; + dy = pinb.y-pind.y; + + int targetdx = round(pind.x-ratio*dx); + int targetdy = round(pind.y-ratio*dy); + int targetbx = round(pinb.x+ratio*dx); + int targetby = round(pinb.y+ratio*dy); + + if (!isValid(targetax, targetay) || !isValid(targetbx, targetby) || !isValid(targetcx, targetcy) || !isValid(targetdx, targetdy)){ + throw NotFoundException.getNotFoundInstance(); + } + + Point pa = new Point(targetax,targetay); + Point pb = new Point(targetbx,targetby); + Point pc = new Point(targetcx,targetcy); + Point pd = new Point(targetdx,targetdy); + + return new Point[]{pa, pb, pc, pd}; + } + + /** + * + * Finds a candidate center point of an Aztec code from an image + * + * @return the center point + */ + private Point getMatrixCenter() { + + ResultPoint pointA; + ResultPoint pointB; + ResultPoint pointC; + ResultPoint pointD; + + //Get a white rectangle that can be the border of the matrix in center bull's eye or + try{ + + ResultPoint[] cornerPoints = new WhiteRectangleDetector(image).detect(); + pointA = cornerPoints[0]; + pointB = cornerPoints[1]; + pointC = cornerPoints[2]; + pointD = cornerPoints[3]; + + } catch (NotFoundException e){ + + // This exception can be in case the initial rectangle is white + // In that case, surely in the bull's eye, we try to expand the rectangle. + int cx = image.width/2; + int cy = image.height/2; + pointA = getFirstDifferent(new Point(cx+15/2, cy-15/2), false, 1, -1).toResultPoint(); + pointB = getFirstDifferent(new Point(cx+15/2, cy+15/2), false, 1, 1).toResultPoint(); + pointC = getFirstDifferent(new Point(cx-15/2, cy+15/2), false, -1, 1).toResultPoint(); + pointD = getFirstDifferent(new Point(cx-15/2, cy-15/2), false, -1, -1).toResultPoint(); + + } + + //Compute the center of the rectangle + int cx = round((pointA.getX() + pointD.getX() + pointB.getX() + pointC.getX())/4); + int cy = round((pointA.getY() + pointD.getY() + pointB.getY() + pointC.getY())/4); + + // Redetermine the white rectangle starting from previously computed center. + // This will ensure that we end up with a white rectangle in center bull's eye + // in order to compute a more accurate center. + try{ + ResultPoint[] cornerPoints = new WhiteRectangleDetector(image, 15, cx, cy).detect(); + pointA = cornerPoints[0]; + pointB = cornerPoints[1]; + pointC = cornerPoints[2]; + pointD = cornerPoints[3]; + } catch (NotFoundException e){ + + // This exception can be in case the initial rectangle is white + // In that case we try to expand the rectangle. + pointA = getFirstDifferent(new Point(cx+15/2, cy-15/2), false, 1, -1).toResultPoint(); + pointB = getFirstDifferent(new Point(cx+15/2, cy+15/2), false, 1, 1).toResultPoint(); + pointC = getFirstDifferent(new Point(cx-15/2, cy+15/2), false, -1, 1).toResultPoint(); + pointD = getFirstDifferent(new Point(cx-15/2, cy-15/2), false, -1, -1).toResultPoint(); + + } + + // Recompute the center of the rectangle + cx = round((pointA.getX() + pointD.getX() + pointB.getX() + pointC.getX())/4); + cy = round((pointA.getY() + pointD.getY() + pointB.getY() + pointC.getY())/4); + + Point pCenter = new Point(cx, cy); + + return pCenter; + } + + /** + * + * Samples an Aztec matrix from an image + * + * @param image + * @param topLeft + * @param bottomLeft + * @param bottomRight + * @param topRight + * @return + * @throws NotFoundException + */ + private BitMatrix sampleGrid(BitMatrix image, + ResultPoint topLeft, + ResultPoint bottomLeft, + ResultPoint bottomRight, + ResultPoint topRight) throws NotFoundException { + + int dimension; + if (compact){ + dimension = 4*nbLayers+11; + } else { + if (nbLayers <= 4){ + dimension = 4*nbLayers + 15; + } else { + dimension = 4*nbLayers + 2*((nbLayers-4)/8 + 1) + 15 ; + } + } + + GridSampler sampler = GridSampler.getInstance(); + + return sampler.sampleGrid(image, + dimension, + dimension, + 0.5f, + 0.5f, + dimension - 0.5f, + 0.5f, + dimension - 0.5f, + dimension - 0.5f, + 0.5f, + dimension - 0.5f, + topLeft.getX(), + topLeft.getY(), + topRight.getX(), + topRight.getY(), + bottomRight.getX(), + bottomRight.getY(), + bottomLeft.getX(), + bottomLeft.getY()); + } + + /** + * + * Sets number of layers and number of datablocks from parameter bits + * + * @param parameterData + */ + private void getParameters(boolean[] parameterData) { + + int nbBitsForNbLayers = 0; + int nbBitsForNbDatablocks = 0; + + if (compact){ + nbBitsForNbLayers = 2; + nbBitsForNbDatablocks = 6; + } else { + nbBitsForNbLayers = 5; + nbBitsForNbDatablocks = 11; + } + + for (int i = 0; i < nbBitsForNbLayers; i++){ + nbLayers = nbLayers << 1; + if (parameterData[i]){ + nbLayers += 1; + } + } + + for (int i = nbBitsForNbLayers; i < nbBitsForNbLayers + nbBitsForNbDatablocks; i++){ + nbDataBlocks = nbDataBlocks << 1; + if (parameterData[i]){ + nbDataBlocks += 1; + } + } + + nbLayers ++; + nbDataBlocks ++; + + } + + /** + * + * Samples a line + * + * @param p1 first point + * @param p2 second point + * @param size number of bits + * @return the array of bits + */ + private boolean[] sampleLine(Point p1, Point p2,int size) { + + boolean[] res = new boolean[size]; + float d = distance(p1,p2); + float moduleSize = d/(size-1); + float dx = moduleSize*(p2.x - p1.x)/(float)d; + float dy = moduleSize*(p2.y - p1.y)/(float)d; + + float px = p1.x; + float py = p1.y; + + for (int i = 0; i < size; i++){ + res[i] = image.get(round(px), round(py)); + px+=dx; + py+=dy; + } + + return res; + } + +/** + * + *

returns true if the border of the rectangle passed in parameter is compound of white points only or black points only

+ * + * @param p1 + * @param p2 + * @param p3 + * @param p4 + * @return + */ +private boolean isWhiteOrBlackRectangle(Point p1, Point p2, Point p3, Point p4) { + + int corr = 3; + + p1 = new Point(p1.x-corr, p1.y+corr); + p2 = new Point(p2.x-corr, p2.y-corr); + p3 = new Point(p3.x+corr, p3.y-corr); + p4 = new Point(p4.x+corr, p4.y+corr); + + int cInit = getColor(p4, p1); + + if (cInit == 0){ + return false; + } + + int c = getColor(p1, p2); + + if (c!=cInit || c == 0){ + return false; + } + + c = getColor(p2, p3); + + if (c!=cInit || c == 0){ + return false; + } + + c = getColor(p3, p4); + + if (c!=cInit || c == 0){ + return false; + } + + return true; +} + +/** + * + * Gets the color of a segment + * + * @param p1 + * @param p2 + * @return 1 if segment more than 90% black, -1 if segment is more than 90% white, 0 else + */ +private int getColor(Point p1, Point p2) { + float d = distance(p1,p2); + float dx = (p2.x - p1.x)/d; + float dy = (p2.y - p1.y)/d; + int error = 0; + + float px = p1.x; + float py = p1.y; + + boolean colorModel = image.get(p1.x, p1.y); + + for (int i = 0; i < d; i++){ + px+=dx; + py+=dy; + if (image.get(round(px), round(py)) != colorModel){ + error++; + } + } + + float errRatio = (float)error/d; + + if (errRatio > 0.1 && errRatio < 0.9){ + return 0; + } + + if (errRatio <= 0.1){ + return colorModel?1:-1; + } else { + return colorModel?-1:1; + } +} + +/** + * + * Gets the coordinate of the first point with a different color in the given direction + * + * @param init Initial point + * @param color Color wanted + * @param dx + * @param dy + * @return + */ +private Point getFirstDifferent(Point init, boolean color, int dx, int dy){ + int x = init.x+dx; + int y = init.y+dy; + + while(isValid(x,y) && image.get(x,y) == color){ + x+=dx; + y+=dy; + } + + x-=dx; + y-=dy; + + while(isValid(x,y) && image.get(x, y) == color){ + x+=dx; + } + x-=dx; + + while(isValid(x,y) && image.get(x, y) == color){ + y+=dy; + } + y-=dy; + + return new Point(x,y); + } + + private class Point { + public int x; + public int y; + + public ResultPoint toResultPoint(){ + return new ResultPoint(x, y); + } + + public Point(int x, int y){ + this.x = x; + this.y = y; + } + } + + private boolean isValid(int x, int y) { + return (x >= 0 && x < image.width && y > 0 && y < image.height); + } + + /** + * Ends up being a bit faster than Math.round(). This merely rounds its + * argument to the nearest int, where x.5 rounds up. + */ + private static int round(float d) { + return (int) (d + 0.5f); + } + +// L2 distance + private static float distance(Point a, Point b) { + return (float) Math.sqrt((a.x - b.x) + * (a.x - b.x) + (a.y - b.y) + * (a.y - b.y)); + } + +} diff --git a/core/src/com/google/zxing/common/DetectorResult.java b/core/src/com/google/zxing/common/DetectorResult.java index 5e3786a35..ea4794d17 100644 --- a/core/src/com/google/zxing/common/DetectorResult.java +++ b/core/src/com/google/zxing/common/DetectorResult.java @@ -25,7 +25,7 @@ import com.google.zxing.ResultPoint; * * @author Sean Owen */ -public final class DetectorResult { +public class DetectorResult { private final BitMatrix bits; private final ResultPoint[] points; diff --git a/core/src/com/google/zxing/common/detector/WhiteRectangleDetector.java b/core/src/com/google/zxing/common/detector/WhiteRectangleDetector.java index 93af0bcc7..ec4232c9d 100644 --- a/core/src/com/google/zxing/common/detector/WhiteRectangleDetector.java +++ b/core/src/com/google/zxing/common/detector/WhiteRectangleDetector.java @@ -32,17 +32,36 @@ import com.google.zxing.common.BitMatrix; */ public final class WhiteRectangleDetector { - private static final int INIT_SIZE = 40; - private static final int CORR = 1; + private final int INIT_SIZE = 30; + private final int CORR = 1; private final BitMatrix image; private final int height; private final int width; - + private final int left_init; + private final int right_init; + private final int down_init; + private final int up_init; + public WhiteRectangleDetector(BitMatrix image) { this.image = image; height = image.getHeight(); width = image.getWidth(); + left_init = (width - INIT_SIZE) >> 1; + right_init = (width + INIT_SIZE) >> 1; + up_init = (height - INIT_SIZE) >> 1; + down_init = (height + INIT_SIZE) >> 1; + } + + public WhiteRectangleDetector(BitMatrix image, int INIT_SIZE, int x, int y) { + this.image = image; + height = image.getHeight(); + width = image.getWidth(); + int halfsize = INIT_SIZE >> 1; + left_init = x - halfsize; + right_init = x + halfsize; + up_init = y - halfsize; + down_init = y + halfsize; } /** @@ -61,10 +80,10 @@ public final class WhiteRectangleDetector { */ public ResultPoint[] detect() throws NotFoundException { - int left = (width - INIT_SIZE) >> 1; - int right = (width + INIT_SIZE) >> 1; - int up = (height - INIT_SIZE) >> 1; - int down = (height + INIT_SIZE) >> 1; + int left = left_init; + int right = right_init; + int up = up_init; + int down = down_init; boolean sizeExceeded = false; boolean aBlackPointFoundOnBorder = true; boolean atLeastOneBlackPointFoundOnBorder = false; diff --git a/core/test/data/blackbox/aztec-1/abc-19x19C.png b/core/test/data/blackbox/aztec-1/abc-19x19C.png new file mode 100644 index 0000000000000000000000000000000000000000..e1332000fa87fac48421d7563f4e8274287c6e41 GIT binary patch literal 536 zcmeAS@N?(olHy`uVBq!ia0vp^bs)^a1SE4EjqWorFy8lcaSW-r_4aOHUbBG!+rwR_ z{;v1oJHY*G>u$4)=KWK>UY>|@n=zp@_PTu82Y<$|SDLF{T|D~y-{P%d1q-S*Q;(nQ zyYv0}>VKDi=SO~JcWLe0OHZx) zRQ)O|Q}9vPtREA00dYmSJ&xP<^?O*zg|h+RPgJbM9fv2+V7!2Wneef zH@VLjTfeGrAJ?mDggF*r7b`KY#3=Vx!N-{pS31PZSwEJu)QF!vVUDJdhXtD z#mS;iqNO3u{2v&0|6Ne%YM-#!_s;Tk3j@om#8Kj=3diREsWSvbhGsI*f{Nh){Lw^`OLA?Kr(i zP!2gFE@@omzB4&->AZ1CG7ZUwuCIH<+lpIjudyZPd!Fa{Ak4p#bxHjBdZ~7S{5J)3}r$^v*>8i}9WZvNf^4D=oRe$OZLVo-hVf-s` zssjZJ+_5~n!%?VqQS!z9kege#VOjM#$iQtPx$zF_wb=)S7lc5;2sFa&{^M=XN`R?ysMvLZX2IShQm&|;=@l()rh0LRRtz*l4QhrqiiRg3^f&GC(K4U;F zPzaaZNsYZCDZ_%PeEcFb0)^zo=Zt3}lt`sl_SK$7^V1m#6F|;~Jhp-Z)_13_P(-85 zA(QjNH!Reb6{EkSeCq-}O^WoC>@W6Yv3Ow06sW~~|76sG?Am||ym3Q;u%ah4gW2Ws zad!wfTpkvcW^pmw16HR9gEg#bD($VAXf}3O+qgr4<9TD?^(t9%f*ecFF zaJlG^;hf$)GUE})RNpmx7^sXI?<}dYGYy4oTl*Gp2ib>nFOOqsH#RJTLX7L~U5NN_ zs4MH7>3@M6M4d_|0|S0uI12HmOJpbYE{oc9n|B_5HB29={5zjAnpq$oqV=^DJ-4#|qMm}4 zr6hPuRAo*&wd&1i-4a|{lhIUtcyv}k%wuQUnxs{N?489cT$LtSi0QQ03Sn6EPkhVE zW)t<)X_GMW0ckrG9Ox3}27{!pBf=OO1|v=8P72wCj5XR|(g|86S^C68ZJ8c!Vk7+S zHh)WU;4Gbot_#}1Cig*W8gQD|6N}D;))NW>ZcRDDKq2S#{$p(izh zKI4`#N4L43p6jnMt11*rX{;~OgC(JkE||CghI#a3XFrYjT}p8LgndpFxGcC(w~95$ za_54DbVpI_03NQ#!!qf|ZH&3G$Ig&_Yp7{fn>g-rjI4pGU!;aCcim_SupVT{^8Q*e zen1ne0Gm;hqJ=#>2D|Y8Vk5;hJSX*Lrn5%6;_$m6TtzGNd%*4yXV_+XwWFC++d(B6D3)Ie+M}pw9$0qyd-OH1WgBqcY{ABjd?!Uct<%&bg|lrsnee vmgslktG*hM3T$!H+O7Y7R0nX}6)4`BQ!lftbtm9&5<&3t^K82G{q27MKGd~? literal 0 HcmV?d00001 diff --git a/core/test/data/blackbox/aztec-1/abc-37x37.txt b/core/test/data/blackbox/aztec-1/abc-37x37.txt new file mode 100644 index 000000000..f5a90a533 --- /dev/null +++ b/core/test/data/blackbox/aztec-1/abc-37x37.txt @@ -0,0 +1 @@ +abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ \ No newline at end of file diff --git a/core/test/data/blackbox/aztec-1/lorem-075x075.png b/core/test/data/blackbox/aztec-1/lorem-075x075.png new file mode 100644 index 0000000000000000000000000000000000000000..0c69b979730b61cd71925de21e9377ea2fac53f8 GIT binary patch literal 2203 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 literal 0 HcmV?d00001 diff --git a/core/test/data/blackbox/aztec-1/lorem-105x105.txt b/core/test/data/blackbox/aztec-1/lorem-105x105.txt new file mode 100644 index 000000000..893acaf65 --- /dev/null +++ b/core/test/data/blackbox/aztec-1/lorem-105x105.txt @@ -0,0 +1 @@ +In ut magna vel mauris malesuada dictum. Nulla ullamcorper metus quis diam cursus facilisis. Sed mollis quam id justo rutrum sagittis. Donec laoreet rutrum est, nec convallis mauris condimentum sit amet. Phasellus gravida, justo et congue auctor, nisi ipsum viverra erat, eget hendrerit felis turpis nec lorem. Nulla ultrices, elit pellentesque aliquet laoreet, justo erat pulvinar nisi, id elementum sapien dolor et diam. Donec ac nunc sodales elit placerat eleifend. Sed ornare luctus ornare. Vestibulum vehicula, massa at pharetra fringilla, risus justo faucibus erat, nec porttitor nibh tellus sed est. Ut justo diam, lobortis eu tristique ac, p.In ut magna vel mauris malesuada dictum. Nulla ullamcorper metus quis diam cursus facilisis. Sed mollis quam id justo rutrum sagittis. Donec laoreet rutrum est, nec convallis mauris condimentum sit amet. Phasellus gravida, justo et congue auctor, nisi ipsum viverra erat, eget hendrerit felis turpis nec lorem. Nulla ultrices, elit pellentesque aliquet laoreet, justo erat pulvinar nisi, id elementum sapien dolor et diam. Donec ac nunc sodales elit placerat eleifend. Sed ornare luctus ornare. Vestibulum vehicula, massa at pharetra fringilla, risus justo faucibus erat, nec porttitor nibh tellus sed est. Ut justo diam, lobortis eu tristique ac, p \ No newline at end of file diff --git a/core/test/data/blackbox/aztec-1/lorem-151x151.png b/core/test/data/blackbox/aztec-1/lorem-151x151.png new file mode 100644 index 0000000000000000000000000000000000000000..d423743fbbb9f83cd3d8e8470872a3eac7c6eceb GIT binary patch literal 11622 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 NdLI*6|IcGz{y#BoRX+d# literal 0 HcmV?d00001 diff --git a/core/test/data/blackbox/aztec-1/lorem-151x151.txt b/core/test/data/blackbox/aztec-1/lorem-151x151.txt new file mode 100644 index 000000000..ab683d7fe --- /dev/null +++ b/core/test/data/blackbox/aztec-1/lorem-151x151.txt @@ -0,0 +1 @@ +In ut magna vel mauris malesuada dictum. Nulla ullamcorper metus quis diam cursus facilisis. Sed mollis quam id justo rutrum sagittis. Donec laoreet rutrum est, nec convallis mauris condimentum sit amet. Phasellus gravida, justo et congue auctor, nisi ipsum viverra erat, eget hendrerit felis turpis nec lorem. Nulla ultrices, elit pellentesque aliquet laoreet, justo erat pulvinar nisi, id elementum sapien dolor et diam. Donec ac nunc sodales elit placerat eleifend. Sed ornare luctus ornare. Vestibulum vehicula, massa at pharetra fringilla, risus justo faucibus erat, nec porttitor nibh tellus sed est. Ut justo diam, lobortis eu tristique ac, p.In ut magna vel mauris malesuada dictum. Nulla ullamcorper metus quis diam cursus facilisis. Sed mollis quam id justo rutrum sagittis. Donec laoreet rutrum est, nec convallis mauris condimentum sit amet. Phasellus gravida, justo et congue auctor, nisi ipsum viverra erat, eget hendrerit felis turpis nec lorem. Nulla ultrices, elit pellentesque aliquet laoreet, justo erat pulvinar nisi, id elementum sapien dolor et diam. Donec ac nunc sodales elit placerat eleifend. Sed ornare luctus ornare. Vestibulum vehicula, massa at pharetra fringilla, risus justo faucibus erat, nec porttitor nibh tellus sed est. Ut justo diam, lobortis eu tristique ac, p. In ut magna vel mauris malesuada dictum. Nulla ullamcorper metus quis diam cursus facilisis. Sed mollis quam id justo rutrum sagittis. Donec laoreet rutrum est, nec convallis mauris condimentum sit amet. Phasellus gravida, justo et congue auctor, nisi ipsum viverra erat, eget hendrerit felis turpis nec lorem. Nulla ultrices, elit pellentesque aliquet laoreet, justo erat pulvinar nisi, id elementum sapien dolor et diam. Donec ac nunc sodales elit placerat eleifend. Sed ornare luctus ornare. Vestibulum vehicula, massa at pharetra fringilla, risus justo faucibus erat, nec porttitor nibh tellus sed est. Ut justo diam, lobortis eu tristique ac, p.In ut magna vel mauris malesuada dictum. Nulla ullamcorper metus quis diam cursus facilisis. Sed mollis quam id justo rutrum sagittis. Donec laoreet rutrum est, nec convallis mauris condimentum sit amet. Phasellus gravida, justo et congue auctor, nisi ipsum viverra erat, eget hendrerit felis turpis nec lorem. Nulla ultrices, elit pellentesque aliquet laoreet, justo erat pulvinar nisi, id elementum sapien dolor et diam. Donec ac nunc sodales elit placerat eleifend. Sed ornare luctus ornare. Vestibulum vehicula, massa at pharetra fringilla, risus justo faucibus erat, nec porttitor nibh tellus sed est. Ut justo diam, lobortis eu tris. In ut magna vel mauris malesuada dictum. Nulla ullamcorper metus quis diam cursus facilisis. Sed mollis quam id justo rutrum sagittis. Donec laoreet rutrum est, nec convallis mauris condimentum sit amet. Phasellus gravida, justo et congue auctor, nisi ipsum viverra erat, eget hendrerit felis turpis nec lorem. \ No newline at end of file diff --git a/core/test/data/blackbox/aztec-1/tableShifts.png b/core/test/data/blackbox/aztec-1/tableShifts.png new file mode 100644 index 0000000000000000000000000000000000000000..99ccd3d3228191c4003503085fbbf9790d9065fe GIT binary patch literal 1246 zcmXX`do-J882=(Ep>yjum(xjW*2&83Fzt>@E^6t-kqMFtXKrPdjp=3*cVhHVJyq** zjN*&VtWLslnNwR!jW%Req#BnUi$tkwDv^;UC6Prwf4rCTJn#Gbe$Vr~zqc|XJjB}S zq!j=F)}drllyU7i9B4D+{0;3{H2|P~3ng8k=gw~}OkG*_ev1^DI%P1dDE@6h+kJYj zqHhYX;h~d5qAku`q)p5B_X}PaR;$kf_9f`bpA2Oe6N;MOhmYo8(x=V{8C}q}EU@^T!>@t^pOF|Q^MDxt^h60d2vt7@>U0AS zRuuwA$j@~pktQlw(ehNfJm^#rrM zq^jW=nU^6Ym|Yi`l4KcVgmf7}&1e^&$duZ}@ItXl%v-zXWG^w|H5P7ZtNDM07IBgn z;wG%=MLN>WFY`I>rm#uJG#HBZex*yx-&n2{c~!=}t)oaR85E;h~fg+Q5|+%TO` z9`5oO_B1MOqSOYl9+^Fus8;b#S@Px|S2n>clQ08;xsMJ?={TRMvaj9B>({B>zQhlW zEJ!8n7H*Q8h{Mn?#Ggli%NsXt!&MHS23fO02KT#N6n8-Lqsh2E%1FzAt|J4AHGM<; zfBL&Y4MjjbinA0asPw%>EJ~;%D){e^SIqf6bpAn!>7ucOl%_Y00nGJI-LQZ#G)cHd zZxS34XS%X=6X1U@#Y|6nvwp0%4axx&MrJ(RFi8n{rklCve;l3CkS6(wzqPFZ4vW|C zb79j(x*?)T9O_#4tXfA!a4ZX@4L@^8th?gD+`g(}?U5DZM7W@kpHokbTp`Iyt}15O zERJz+yvCFS8XHKVM5w<2@pjmyvNp9hc?QwinQH1>$Le4iCQ-Vvw)3(uh#AjqsGws$ zfP-bsI!DjRK)rc@-c~VY(YnN^%BF;Nhs+pjmfUW~uGM$BGIn>LFAsQTg!B zD+GF4CRNy#Gf8gJdi@NL0Jot??w!r{C_+aKv1>3<`KpS7FIdZK?`gmx{zTd#9{5}s ztIL@TQB0q>yN75i{=>Hda!$pr!tb(UBF6%JSUHBY-Ws8fSn~pgH(llDV9yP;ZWC1*`kkNTxdA&W#@E+{SFUWZoE2j!A#P@PR ze0jTkf75TpatKp`_O8X&$8e||sj45;JGgVx^9ivg&9%Q#B(-pfY@0`d!7@)#I(fn{ zSp>^bPU4heccmNJ=?y{|Z(yW0