diff --git a/core/src/com/google/zxing/pdf417/PDF417Common.java b/core/src/com/google/zxing/pdf417/PDF417Common.java index 193b03c99..1e7080b41 100644 --- a/core/src/com/google/zxing/pdf417/PDF417Common.java +++ b/core/src/com/google/zxing/pdf417/PDF417Common.java @@ -15,6 +15,8 @@ */ package com.google.zxing.pdf417; +import java.util.Collection; + /** * @author SITA Lab (kevin.osullivan@sita.aero) * @author Guenther Grau @@ -22,8 +24,9 @@ package com.google.zxing.pdf417; public final class PDF417Common { public static final int NUMBER_OF_CODEWORDS = 929; - // Maximum Codewords (Data + Error). Was designed to be the same as the number of codewords - public static final int MAX_CODEWORDS_IN_BARCODE = NUMBER_OF_CODEWORDS; + // Maximum Codewords (Data + Error). + public static final int MAX_CODEWORDS_IN_BARCODE = NUMBER_OF_CODEWORDS - 1; + public static final int MIN_ROWS_IN_BARCODE = 3; public static final int MAX_ROWS_IN_BARCODE = 90; // One left row indication column + max 30 data columns + one right row indicator column public static final int MAX_CODEWORDS_IN_ROW = 32; @@ -31,6 +34,8 @@ public final class PDF417Common { public static final int MODULES_IN_STOP_PATTERN = 18; public static final int BARS_IN_MODULE = 8; + private static final int[] EMPTY_INT_ARRAY = {}; + private PDF417Common() { } @@ -41,7 +46,19 @@ public final class PDF417Common { } return bitCountSum; } - + + public static int[] toIntArray(Collection list) { + if (list == null || list.isEmpty()) { + return EMPTY_INT_ARRAY; + } + int[] result = new int[list.size()]; + int i = 0; + for (Integer integer : list) { + result[i++] = integer; + } + return result; + } + /** * Translate the symbol into a codeword. * @@ -317,7 +334,7 @@ public final class PDF417Common { 0x1f9d4, 0x1fa1a, 0x1fa2e, 0x1fa32, 0x1fa34, 0x1fa4e, 0x1fa5c, 0x1fa62, 0x1fa64, 0x1fa68, 0x1fa76, 0x1fa8e, 0x1fa9c, 0x1fab8, 0x1fac2, 0x1fac4, 0x1fac8, 0x1fad0, 0x1fade, 0x1fae6, 0x1faec, 0x1fb16, 0x1fb26, 0x1fb2c, 0x1fb3a, 0x1fb46, 0x1fb4c, 0x1fb58, 0x1fb6e, 0x1fb72, 0x1fb74, 0x1fb8a, 0x1fb92, 0x1fb94, 0x1fba2, 0x1fba4, - 0x1fba8, 0x1fbb6, 0x1fbda }; + 0x1fba8, 0x1fbb6, 0x1fbda}; /** * This table contains to codewords for all symbols. @@ -462,5 +479,5 @@ public final class PDF417Common { 440, 437, 1497, 1494, 1490, 1503, 761, 709, 707, 1706, 913, 912, 2198, 1386, 2164, 2161, 1621, 1766, 2103, 1208, 2058, 2054, 1145, 1142, 2005, 2002, 1999, 2009, 1488, 1429, 1426, 2200, 1698, 1659, 1656, 1975, 1053, 1957, 1954, 1001, 998, 1924, 1921, 1918, 1928, 937, 934, 931, 1879, 1876, 1873, 1870, 945, 1885, 1882, 1323, 1273, 1270, - 2105, 1202, 1199, 1196, 1211, 2061, 2057, 1576, 1543, 1540, 1484, 1481, 1478, 1491, 1700 }; + 2105, 1202, 1199, 1196, 1211, 2061, 2057, 1576, 1543, 1540, 1484, 1481, 1478, 1491, 1700}; } diff --git a/core/src/com/google/zxing/pdf417/PDF417Reader.java b/core/src/com/google/zxing/pdf417/PDF417Reader.java index f9d04b720..bfe1aaf9d 100644 --- a/core/src/com/google/zxing/pdf417/PDF417Reader.java +++ b/core/src/com/google/zxing/pdf417/PDF417Reader.java @@ -84,7 +84,7 @@ public final class PDF417Reader implements Reader, MultipleBarcodeReader { private static Result[] decode(BinaryBitmap image, Map hints, boolean multiple) throws NotFoundException, FormatException, ChecksumException { List results = new ArrayList(); - PDF417DetectorResult detectorResult = new Detector(image).detect(multiple); + PDF417DetectorResult detectorResult = Detector.detect(image, hints, multiple); for (ResultPoint[] points : detectorResult.getPoints()) { DecoderResult decoderResult = PDF417ScanningDecoder.decode(detectorResult.getBits(), points[4], points[5], points[6], points[7], getMinCodewordWidth(points), getMaxCodewordWidth(points)); diff --git a/core/src/com/google/zxing/pdf417/decoder/BarcodeMatrix.java b/core/src/com/google/zxing/pdf417/decoder/BarcodeMatrix.java deleted file mode 100644 index 5cd711589..000000000 --- a/core/src/com/google/zxing/pdf417/decoder/BarcodeMatrix.java +++ /dev/null @@ -1,55 +0,0 @@ -/* - * Copyright 2013 ZXing authors - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.google.zxing.pdf417.decoder; - -import java.util.HashMap; -import java.util.Map; - -/** - * @author Guenther Grau - */ -final class BarcodeMatrix { - - private final Map values = new HashMap(); - private int maxRow = -1; - private int maxColumn = -1; - - private static String getKey(int barcodeRow, int barcodeColumn) { - return barcodeRow + "," + barcodeColumn; - } - - void setValue(int row, int column, int value) { - maxRow = Math.max(maxRow, row); - maxColumn = Math.max(maxColumn, column); - String key = getKey(row, column); - BarcodeValue barcodeValue = values.get(key); - if (barcodeValue == null) { - barcodeValue = new BarcodeValue(); - values.put(key, barcodeValue); - } - barcodeValue.setValue(value); - } - - public Integer getValue(int row, int column) { - BarcodeValue barcodeValue = values.get(getKey(row, column)); - if (barcodeValue == null) { - return null; - } - return barcodeValue.getValue(); - } - -} diff --git a/core/src/com/google/zxing/pdf417/decoder/BarcodeValue.java b/core/src/com/google/zxing/pdf417/decoder/BarcodeValue.java index aca04c0d8..3343c17ab 100644 --- a/core/src/com/google/zxing/pdf417/decoder/BarcodeValue.java +++ b/core/src/com/google/zxing/pdf417/decoder/BarcodeValue.java @@ -16,6 +16,10 @@ package com.google.zxing.pdf417.decoder; +import com.google.zxing.pdf417.PDF417Common; + +import java.util.ArrayList; +import java.util.Collection; import java.util.HashMap; import java.util.Map; import java.util.Map.Entry; @@ -24,10 +28,13 @@ import java.util.Map.Entry; * @author Guenther Grau */ final class BarcodeValue { - private final Map values = new HashMap(); - public void setValue(int value) { + /** + * Add an occurrence of a value + * @param value + */ + void setValue(int value) { Integer confidence = values.get(value); if (confidence == null) { confidence = 0; @@ -36,24 +43,23 @@ final class BarcodeValue { values.put(value, confidence); } - public Integer getValue() { + /** + * Determines the maximum occurrence of a set value and returns all values which were set with this occurrence. + * @return an array of int, containing the values with the highest occurrence, or null, if no value was set + */ + int[] getValue() { int maxConfidence = -1; - Integer result = null; - boolean ambiguous = false; + Collection result = new ArrayList(); for (Entry entry : values.entrySet()) { if (entry.getValue() > maxConfidence) { maxConfidence = entry.getValue(); - result = entry.getKey(); - ambiguous = false; - // TODO fix this clause? - //} else if (entry.getValue() > maxConfidence) { - // ambigous = true; + result.clear(); + result.add(entry.getKey()); + } else if (entry.getValue() == maxConfidence) { + result.add(entry.getKey()); } } - if (ambiguous) { - return null; - } - return result; + return PDF417Common.toIntArray(result); } public Integer getConfidence(int value) { diff --git a/core/src/com/google/zxing/pdf417/decoder/BoundingBox.java b/core/src/com/google/zxing/pdf417/decoder/BoundingBox.java index 1183be059..5760a6e84 100644 --- a/core/src/com/google/zxing/pdf417/decoder/BoundingBox.java +++ b/core/src/com/google/zxing/pdf417/decoder/BoundingBox.java @@ -24,7 +24,7 @@ import com.google.zxing.common.BitMatrix; * @author Guenther Grau */ final class BoundingBox { - + private BitMatrix image; private ResultPoint topLeft; private ResultPoint bottomLeft; @@ -40,8 +40,10 @@ final class BoundingBox { ResultPoint bottomLeft, ResultPoint topRight, ResultPoint bottomRight) throws NotFoundException { - if ((topLeft == null && topRight == null) || (bottomLeft == null && bottomRight == null) || - (topLeft != null && bottomLeft == null) || (topRight != null && bottomRight == null)) { + if ((topLeft == null && topRight == null) || + (bottomLeft == null && bottomRight == null) || + (topLeft != null && bottomLeft == null) || + (topRight != null && bottomRight == null)) { throw NotFoundException.getNotFoundInstance(); } init(image, topLeft, bottomLeft, topRight, bottomRight); @@ -51,11 +53,10 @@ final class BoundingBox { init(boundingBox.image, boundingBox.topLeft, boundingBox.bottomLeft, boundingBox.topRight, boundingBox.bottomRight); } - private void init(BitMatrix image, - ResultPoint topLeft, + private void init(BitMatrix image, + ResultPoint topLeft, ResultPoint bottomLeft, - - ResultPoint topRight, + ResultPoint topRight, ResultPoint bottomRight) { this.image = image; this.topLeft = topLeft; @@ -75,7 +76,12 @@ final class BoundingBox { return new BoundingBox(leftBox.image, leftBox.topLeft, leftBox.bottomLeft, rightBox.topRight, rightBox.bottomRight); } - void addMissingRows(int missingStartRows, int missingEndRows, boolean isLeft) { + BoundingBox addMissingRows(int missingStartRows, int missingEndRows, boolean isLeft) throws NotFoundException { + ResultPoint newTopLeft = topLeft; + ResultPoint newBottomLeft = bottomLeft; + ResultPoint newTopRight = topRight; + ResultPoint newBottomRight = bottomRight; + if (missingStartRows > 0) { ResultPoint top = isLeft ? topLeft : topRight; int newMinY = (int) top.getY() - missingStartRows; @@ -85,27 +91,29 @@ final class BoundingBox { // TODO use existing points to better interpolate the new x positions ResultPoint newTop = new ResultPoint(top.getX(), newMinY); if (isLeft) { - topLeft = newTop; + newTopLeft = newTop; } else { - topRight = newTop; + newTopRight = newTop; } } if (missingEndRows > 0) { ResultPoint bottom = isLeft ? bottomLeft : bottomRight; - int newMaxY = (int) bottom.getY() - missingStartRows; + int newMaxY = (int) bottom.getY() + missingEndRows; if (newMaxY >= image.getHeight()) { newMaxY = image.getHeight() - 1; } // TODO use existing points to better interpolate the new x positions ResultPoint newBottom = new ResultPoint(bottom.getX(), newMaxY); if (isLeft) { - bottomLeft = newBottom; + newBottomLeft = newBottom; } else { - bottomRight = newBottom; + newBottomRight = newBottom; } } + calculateMinMaxValues(); + return new BoundingBox(image, newTopLeft, newBottomLeft, newTopRight, newBottomRight); } private void calculateMinMaxValues() { diff --git a/core/src/com/google/zxing/pdf417/decoder/Codeword.java b/core/src/com/google/zxing/pdf417/decoder/Codeword.java index a9a3fe731..bb5477da3 100644 --- a/core/src/com/google/zxing/pdf417/decoder/Codeword.java +++ b/core/src/com/google/zxing/pdf417/decoder/Codeword.java @@ -76,4 +76,9 @@ final class Codeword { this.rowNumber = rowNumber; } + @Override + public String toString() { + return rowNumber + "|" + value; + } + } diff --git a/core/src/com/google/zxing/pdf417/decoder/DetectionResult.java b/core/src/com/google/zxing/pdf417/decoder/DetectionResult.java index 50cf2d010..8c52308f8 100644 --- a/core/src/com/google/zxing/pdf417/decoder/DetectionResult.java +++ b/core/src/com/google/zxing/pdf417/decoder/DetectionResult.java @@ -16,16 +16,20 @@ package com.google.zxing.pdf417.decoder; +import com.google.zxing.pdf417.PDF417Common; + +import java.util.Formatter; + /** * @author Guenther Grau */ final class DetectionResult { - + private static final int ADJUST_ROW_NUMBER_SKIP = 2; - + private final BarcodeMetadata barcodeMetadata; private final DetectionResultColumn[] detectionResultColumns; - private final BoundingBox boundingBox; + private BoundingBox boundingBox; private final int barcodeColumnCount; DetectionResult(BarcodeMetadata barcodeMetadata, BoundingBox boundingBox) { @@ -35,44 +39,10 @@ final class DetectionResult { detectionResultColumns = new DetectionResultColumn[barcodeColumnCount + 2]; } - int getImageStartRow(int barcodeColumn) { - while (barcodeColumn > 0) { - DetectionResultColumn detectionResultColumn = detectionResultColumns[--barcodeColumn]; - // TODO compare start row with previous result columns - // Could try detecting codewords from right to left - // if all else fails, could calculate estimate - Codeword[] codewords = detectionResultColumn.getCodewords(); - for (int rowNumber = 0; rowNumber < codewords.length; rowNumber++) { - if (codewords[rowNumber] != null) { - // next column might start earlier if barcode is not aligned with image - if (rowNumber > 0) { - rowNumber--; - } - return detectionResultColumn.getImageRow(rowNumber); - } - } - } - return -1; - } - - void setDetectionResultColumn(int barcodeColumn, DetectionResultColumn detectionResultColumn) { - detectionResultColumns[barcodeColumn] = detectionResultColumn; - } - - DetectionResultColumn getDetectionResultColumn(int barcodeColumn) { - return detectionResultColumns[barcodeColumn]; - } - - private void adjustIndicatorColumnRowNumbers(DetectionResultColumn detectionResultColumn) { - if (detectionResultColumn != null) { - ((DetectionResultRowIndicatorColumn) detectionResultColumn).adjustIndicatorColumnRowNumbers(barcodeMetadata); - } - } - DetectionResultColumn[] getDetectionResultColumns() { adjustIndicatorColumnRowNumbers(detectionResultColumns[0]); adjustIndicatorColumnRowNumbers(detectionResultColumns[barcodeColumnCount + 1]); - int unadjustedCodewordCount = 900; + int unadjustedCodewordCount = PDF417Common.MAX_CODEWORDS_IN_BARCODE; int previousUnadjustedCount; do { previousUnadjustedCount = unadjustedCodewordCount; @@ -81,6 +51,13 @@ final class DetectionResult { return detectionResultColumns; } + private void adjustIndicatorColumnRowNumbers(DetectionResultColumn detectionResultColumn) { + if (detectionResultColumn != null) { + ((DetectionResultRowIndicatorColumn) detectionResultColumn) + .adjustCompleteIndicatorColumnRowNumbers(barcodeMetadata); + } + } + // TODO ensure that no detected codewords with unknown row number are left // we should be able to estimate the row height and use it as a hint for the row number // we should also fill the rows top to bottom and bottom to top @@ -108,6 +85,7 @@ final class DetectionResult { } private int adjustRowNumbersByRow() { + adjustRowNumbersFromBothRI(); // TODO we should only do full row adjustments if row numbers of left and right row indicator column match. // Maybe it's even better to calculated the height (in codeword rows) and divide it by the number of barcode // rows. This, together with the LRI and RRI row numbers should allow us to get a good estimate where a row @@ -116,6 +94,31 @@ final class DetectionResult { return unadjustedCount + adjustRowNumbersFromRRI(); } + private int adjustRowNumbersFromBothRI() { + if (detectionResultColumns[0] == null || detectionResultColumns[barcodeColumnCount + 1] == null) { + return 0; + } + Codeword[] LRIcodewords = detectionResultColumns[0].getCodewords(); + Codeword[] RRIcodewords = detectionResultColumns[barcodeColumnCount + 1].getCodewords(); + for (int codewordsRow = 0; codewordsRow < LRIcodewords.length; codewordsRow++) { + if (LRIcodewords[codewordsRow] != null && + RRIcodewords[codewordsRow] != null && + LRIcodewords[codewordsRow].getRowNumber() == RRIcodewords[codewordsRow].getRowNumber()) { + for (int barcodeColumn = 1; barcodeColumn <= barcodeColumnCount; barcodeColumn++) { + Codeword codeword = detectionResultColumns[barcodeColumn].getCodewords()[codewordsRow]; + if (codeword == null) { + continue; + } + codeword.setRowNumber(LRIcodewords[codewordsRow].getRowNumber()); + if (!codeword.hasValidRowNumber()) { + detectionResultColumns[barcodeColumn].getCodewords()[codewordsRow] = null; + } + } + } + } + return 0; + } + private int adjustRowNumbersFromRRI() { if (detectionResultColumns[barcodeColumnCount + 1] == null) { return 0; @@ -128,9 +131,7 @@ final class DetectionResult { } int rowIndicatorRowNumber = codewords[codewordsRow].getRowNumber(); int invalidRowCounts = 0; - for (int barcodeColumn = barcodeColumnCount + 1; - barcodeColumn > 0 && invalidRowCounts < ADJUST_ROW_NUMBER_SKIP; - barcodeColumn--) { + for (int barcodeColumn = barcodeColumnCount + 1; barcodeColumn > 0 && invalidRowCounts < ADJUST_ROW_NUMBER_SKIP; barcodeColumn--) { Codeword codeword = detectionResultColumns[barcodeColumn].getCodewords()[codewordsRow]; if (codeword != null) { invalidRowCounts = adjustRowNumberIfValid(rowIndicatorRowNumber, invalidRowCounts, codeword); @@ -155,9 +156,7 @@ final class DetectionResult { } int rowIndicatorRowNumber = codewords[codewordsRow].getRowNumber(); int invalidRowCounts = 0; - for (int barcodeColumn = 1; - barcodeColumn < barcodeColumnCount + 1 && invalidRowCounts < ADJUST_ROW_NUMBER_SKIP; - barcodeColumn++) { + for (int barcodeColumn = 1; barcodeColumn < barcodeColumnCount + 1 && invalidRowCounts < ADJUST_ROW_NUMBER_SKIP; barcodeColumn++) { Codeword codeword = detectionResultColumns[barcodeColumn].getCodewords()[codewordsRow]; if (codeword != null) { invalidRowCounts = adjustRowNumberIfValid(rowIndicatorRowNumber, invalidRowCounts, codeword); @@ -171,7 +170,6 @@ final class DetectionResult { } private static int adjustRowNumberIfValid(int rowIndicatorRowNumber, int invalidRowCounts, Codeword codeword) { - if (codeword == null) { return invalidRowCounts; } @@ -252,8 +250,48 @@ final class DetectionResult { return barcodeMetadata.getErrorCorrectionLevel(); } + public void setBoundingBox(BoundingBox boundingBox) { + this.boundingBox = boundingBox; + } + BoundingBox getBoundingBox() { return boundingBox; } + void setDetectionResultColumn(int barcodeColumn, DetectionResultColumn detectionResultColumn) { + detectionResultColumns[barcodeColumn] = detectionResultColumn; + } + + DetectionResultColumn getDetectionResultColumn(int barcodeColumn) { + return detectionResultColumns[barcodeColumn]; + } + + @Override + public String toString() { + DetectionResultColumn rowIndicatorColumn = detectionResultColumns[0]; + if (rowIndicatorColumn == null) { + rowIndicatorColumn = detectionResultColumns[barcodeColumnCount + 1]; + } + Formatter formatter = new Formatter(); + for (int codewordsRow = 0; codewordsRow < rowIndicatorColumn.getCodewords().length; codewordsRow++) { + formatter.format("CW %3d:", codewordsRow); + for (int barcodeColumn = 0; barcodeColumn < barcodeColumnCount + 2; barcodeColumn++) { + if (detectionResultColumns[barcodeColumn] == null) { + formatter.format(" | "); + continue; + } + Codeword codeword = detectionResultColumns[barcodeColumn].getCodewords()[codewordsRow]; + if (codeword == null) { + formatter.format(" | "); + continue; + } + formatter.format(" %3d|%3d", codeword.getRowNumber(), codeword.getValue()); + } + formatter.format("\n"); + } + String result = formatter.toString(); + formatter.close(); + return result; + } + } diff --git a/core/src/com/google/zxing/pdf417/decoder/DetectionResultColumn.java b/core/src/com/google/zxing/pdf417/decoder/DetectionResultColumn.java index 57c2f0409..1d9a471d5 100644 --- a/core/src/com/google/zxing/pdf417/decoder/DetectionResultColumn.java +++ b/core/src/com/google/zxing/pdf417/decoder/DetectionResultColumn.java @@ -16,6 +16,8 @@ package com.google.zxing.pdf417.decoder; +import java.util.Formatter; + /** * @author Guenther Grau */ @@ -37,14 +39,14 @@ class DetectionResultColumn { return codeword; } for (int i = 1; i < MAX_NEARBY_DISTANCE; i++) { - int nearImageRow = getCodewordsIndex(imageRow) - i; + int nearImageRow = imageRowToCodewordIndex(imageRow) - i; if (nearImageRow >= 0) { codeword = codewords[nearImageRow]; if (codeword != null) { return codeword; } } - nearImageRow = getCodewordsIndex(imageRow) + i; + nearImageRow = imageRowToCodewordIndex(imageRow) + i; if (nearImageRow < codewords.length) { codeword = codewords[nearImageRow]; if (codeword != null) { @@ -55,20 +57,20 @@ class DetectionResultColumn { return null; } - final int getCodewordsIndex(int imageRow) { + final int imageRowToCodewordIndex(int imageRow) { return imageRow - boundingBox.getMinY(); } - final int getImageRow(int codewordIndex) { + final int codewordIndexToImageRow(int codewordIndex) { return boundingBox.getMinY() + codewordIndex; } final void setCodeword(int imageRow, Codeword codeword) { - codewords[getCodewordsIndex(imageRow)] = codeword; + codewords[imageRowToCodewordIndex(imageRow)] = codeword; } final Codeword getCodeword(int imageRow) { - return codewords[getCodewordsIndex(imageRow)]; + return codewords[imageRowToCodewordIndex(imageRow)]; } final BoundingBox getBoundingBox() { @@ -79,4 +81,20 @@ class DetectionResultColumn { return codewords; } + @Override + public String toString() { + Formatter formatter = new Formatter(); + int row = 0; + for (Codeword codeword : codewords) { + if (codeword == null) { + formatter.format("%3d: | \n", row++); + continue; + } + formatter.format("%3d: %3d|%3d\n", row++, codeword.getRowNumber(), codeword.getValue()); + } + String result = formatter.toString(); + formatter.close(); + return result; + } + } diff --git a/core/src/com/google/zxing/pdf417/decoder/DetectionResultRowIndicatorColumn.java b/core/src/com/google/zxing/pdf417/decoder/DetectionResultRowIndicatorColumn.java index c71cb655a..00e050cf0 100644 --- a/core/src/com/google/zxing/pdf417/decoder/DetectionResultRowIndicatorColumn.java +++ b/core/src/com/google/zxing/pdf417/decoder/DetectionResultRowIndicatorColumn.java @@ -17,15 +17,13 @@ package com.google.zxing.pdf417.decoder; import com.google.zxing.ResultPoint; +import com.google.zxing.pdf417.PDF417Common; /** * @author Guenther Grau */ final class DetectionResultRowIndicatorColumn extends DetectionResultColumn { - private static final int MIN_BARCODE_ROWS = 3; - private static final int MAX_BARCODE_ROWS = 90; - private final boolean isLeft; DetectionResultRowIndicatorColumn(BoundingBox boundingBox, boolean isLeft) { @@ -41,32 +39,22 @@ final class DetectionResultRowIndicatorColumn extends DetectionResultColumn { } } - int[] getRowHeights() { - BarcodeMetadata barcodeMetadata = getBarcodeMetadata(); - if (barcodeMetadata == null) { - return null; - } - adjustIndicatorColumnRowNumbers(barcodeMetadata); - int[] result = new int[barcodeMetadata.getRowCount()]; - for (Codeword codeword : getCodewords()) { - if (codeword != null) { - result[codeword.getRowNumber()]++; - } - } - return result; - } - + // TODO implement properly // TODO maybe we should add missing codewords to store the correct row number to make // finding row numbers for other columns easier // use row height count to make detection of invalid row numbers more reliable - int adjustIndicatorColumnRowNumbers(BarcodeMetadata barcodeMetadata) { + int adjustCompleteIndicatorColumnRowNumbers(BarcodeMetadata barcodeMetadata) { + Codeword[] codewords = getCodewords(); + setRowNumbers(); + removeIncorrectCodewords(codewords, barcodeMetadata); BoundingBox boundingBox = getBoundingBox(); ResultPoint top = isLeft ? boundingBox.getTopLeft() : boundingBox.getTopRight(); ResultPoint bottom = isLeft ? boundingBox.getBottomLeft() : boundingBox.getBottomRight(); - int firstRow = getCodewordsIndex((int) top.getY()); - int lastRow = getCodewordsIndex((int) bottom.getY()); + int firstRow = imageRowToCodewordIndex((int) top.getY()); + int lastRow = imageRowToCodewordIndex((int) bottom.getY()); + // We need to be careful using the average row height. Barcode could be skewed so that we have smaller and + // taller rows float averageRowHeight = (lastRow - firstRow) / (float) barcodeMetadata.getRowCount(); - Codeword[] codewords = getCodewords(); int barcodeRow = -1; int maxRowHeight = 1; int currentRowHeight = 0; @@ -76,11 +64,6 @@ final class DetectionResultRowIndicatorColumn extends DetectionResultColumn { } Codeword codeword = codewords[codewordsRow]; - codeword.setRowNumberAsRowIndicatorColumn(); - - // This only works if we have a complete RI column. If the RI column is cut off at the top or bottom, it - // will calculate the wrong numbers and delete correct codewords. Could be used once the barcode height has - // been calculated properly. // float expectedRowNumber = (codewordsRow - firstRow) / averageRowHeight; // if (Math.abs(codeword.getRowNumber() - expectedRowNumber) > 2) { // SimpleLog.log(LEVEL.WARNING, @@ -129,6 +112,63 @@ final class DetectionResultRowIndicatorColumn extends DetectionResultColumn { return (int) (averageRowHeight + 0.5); } + int[] getRowHeights() { + BarcodeMetadata barcodeMetadata = getBarcodeMetadata(); + if (barcodeMetadata == null) { + return null; + } + adjustIncompleteIndicatorColumnRowNumbers(barcodeMetadata); + int[] result = new int[barcodeMetadata.getRowCount()]; + for (Codeword codeword : getCodewords()) { + if (codeword != null) { + result[codeword.getRowNumber()]++; + } + } + return result; + } + + // TODO maybe we should add missing codewords to store the correct row number to make + // finding row numbers for other columns easier + // use row height count to make detection of invalid row numbers more reliable + int adjustIncompleteIndicatorColumnRowNumbers(BarcodeMetadata barcodeMetadata) { + BoundingBox boundingBox = getBoundingBox(); + ResultPoint top = isLeft ? boundingBox.getTopLeft() : boundingBox.getTopRight(); + ResultPoint bottom = isLeft ? boundingBox.getBottomLeft() : boundingBox.getBottomRight(); + int firstRow = imageRowToCodewordIndex((int) top.getY()); + int lastRow = imageRowToCodewordIndex((int) bottom.getY()); + float averageRowHeight = (lastRow - firstRow) / (float) barcodeMetadata.getRowCount(); + Codeword[] codewords = getCodewords(); + int barcodeRow = -1; + int maxRowHeight = 1; + int currentRowHeight = 0; + for (int codewordsRow = firstRow; codewordsRow < lastRow; codewordsRow++) { + if (codewords[codewordsRow] == null) { + continue; + } + Codeword codeword = codewords[codewordsRow]; + + codeword.setRowNumberAsRowIndicatorColumn(); + + int rowDifference = codeword.getRowNumber() - barcodeRow; + + // TODO improve handling with case where first row indicator doesn't start with 0 + + if (rowDifference == 0) { + currentRowHeight++; + } else if (rowDifference == 1) { + maxRowHeight = Math.max(maxRowHeight, currentRowHeight); + currentRowHeight = 1; + barcodeRow = codeword.getRowNumber(); + } else if (codeword.getRowNumber() >= barcodeMetadata.getRowCount()) { + codewords[codewordsRow] = null; + } else { + barcodeRow = codeword.getRowNumber(); + currentRowHeight = 1; + } + } + return (int) (averageRowHeight + 0.5); + } + BarcodeMetadata getBarcodeMetadata() { Codeword[] codewords = getCodewords(); BarcodeValue barcodeColumnCount = new BarcodeValue(); @@ -158,15 +198,18 @@ final class DetectionResultRowIndicatorColumn extends DetectionResultColumn { break; } } - if ((barcodeColumnCount.getValue() == null) || (barcodeRowCountUpperPart.getValue() == null) || - (barcodeRowCountLowerPart.getValue() == null) || (barcodeECLevel.getValue() == null) || - barcodeColumnCount.getValue() < 1 || - barcodeRowCountUpperPart.getValue() + barcodeRowCountLowerPart.getValue() < MIN_BARCODE_ROWS || - barcodeRowCountUpperPart.getValue() + barcodeRowCountLowerPart.getValue() > MAX_BARCODE_ROWS) { + // Maybe we should check if we have ambiguous values? + if ((barcodeColumnCount.getValue().length == 0) || + (barcodeRowCountUpperPart.getValue().length == 0) || + (barcodeRowCountLowerPart.getValue().length == 0) || + (barcodeECLevel.getValue().length == 0) || + barcodeColumnCount.getValue()[0] < 1 || + barcodeRowCountUpperPart.getValue()[0] + barcodeRowCountLowerPart.getValue()[0] < PDF417Common.MIN_ROWS_IN_BARCODE || + barcodeRowCountUpperPart.getValue()[0] + barcodeRowCountLowerPart.getValue()[0] > PDF417Common.MAX_ROWS_IN_BARCODE) { return null; } - BarcodeMetadata barcodeMetadata = new BarcodeMetadata(barcodeColumnCount.getValue(), - barcodeRowCountUpperPart.getValue(), barcodeRowCountLowerPart.getValue(), barcodeECLevel.getValue()); + BarcodeMetadata barcodeMetadata = new BarcodeMetadata(barcodeColumnCount.getValue()[0], + barcodeRowCountUpperPart.getValue()[0], barcodeRowCountLowerPart.getValue()[0], barcodeECLevel.getValue()[0]); removeIncorrectCodewords(codewords, barcodeMetadata); return barcodeMetadata; } @@ -213,4 +256,9 @@ final class DetectionResultRowIndicatorColumn extends DetectionResultColumn { return isLeft; } + @Override + public String toString() { + return "IsLeft: " + isLeft + '\n' + super.toString(); + } + } diff --git a/core/src/com/google/zxing/pdf417/decoder/PDF417ScanningDecoder.java b/core/src/com/google/zxing/pdf417/decoder/PDF417ScanningDecoder.java index 862708304..66efa42a3 100644 --- a/core/src/com/google/zxing/pdf417/decoder/PDF417ScanningDecoder.java +++ b/core/src/com/google/zxing/pdf417/decoder/PDF417ScanningDecoder.java @@ -27,14 +27,14 @@ import com.google.zxing.pdf417.decoder.ec.ErrorCorrection; import java.util.ArrayList; import java.util.Collection; +import java.util.Formatter; +import java.util.List; /** * @author Guenther Grau */ public final class PDF417ScanningDecoder { - private static final int ROW_HEIGHT_SKEW = 2; - // private static final int STOP_PATTERN_VALUE = 130324; private static final int CODEWORD_SKEW_SIZE = 2; private static final int MAX_ERRORS = 3; @@ -44,44 +44,41 @@ public final class PDF417ScanningDecoder { private PDF417ScanningDecoder() { } - // TODO don't pass in minCodewordWidth and maxCodewordWidth, pass in barcode columns for start and stop pattern // columns. That way width can be deducted from the pattern column. // This approach also allows to detect more details about the barcode, e.g. if a bar type (white or black) is wider // than it should be. This can happen if the scanner used a bad blackpoint. - public static DecoderResult decode(BitMatrix image, + public static DecoderResult decode(BitMatrix image, ResultPoint imageTopLeft, - ResultPoint imageBottomLeft, + ResultPoint imageBottomLeft, ResultPoint imageTopRight, ResultPoint imageBottomRight, int minCodewordWidth, - int maxCodewordWidth) - throws NotFoundException, FormatException, ChecksumException { + int maxCodewordWidth) throws NotFoundException, FormatException, ChecksumException { BoundingBox boundingBox = new BoundingBox(image, imageTopLeft, imageBottomLeft, imageTopRight, imageBottomRight); DetectionResultRowIndicatorColumn leftRowIndicatorColumn = null; DetectionResultRowIndicatorColumn rightRowIndicatorColumn = null; DetectionResult detectionResult = null; - boolean again = true; - while (again) { - again = false; + for (int i = 0; i < 2; i++) { if (imageTopLeft != null) { leftRowIndicatorColumn = getRowIndicatorColumn(image, boundingBox, imageTopLeft, true, minCodewordWidth, maxCodewordWidth); - leftRowIndicatorColumn.setRowNumbers(); } if (imageTopRight != null) { rightRowIndicatorColumn = getRowIndicatorColumn(image, boundingBox, imageTopRight, false, minCodewordWidth, maxCodewordWidth); - rightRowIndicatorColumn.setRowNumbers(); } detectionResult = merge(leftRowIndicatorColumn, rightRowIndicatorColumn); if (detectionResult == null) { throw NotFoundException.getNotFoundInstance(); } - if (detectionResult.getBoundingBox().getMinY() < boundingBox.getMinY() || - detectionResult.getBoundingBox().getMaxY() > boundingBox.getMaxY()) { + if (i == 0 && + (detectionResult.getBoundingBox().getMinY() < boundingBox.getMinY() || detectionResult.getBoundingBox() + .getMaxY() > boundingBox.getMaxY())) { boundingBox = detectionResult.getBoundingBox(); - again = true; + } else { + detectionResult.setBoundingBox(boundingBox); + break; } } int maxBarcodeColumn = detectionResult.getBarcodeColumnCount() + 1; @@ -105,7 +102,7 @@ public final class PDF417ScanningDecoder { int startColumn = -1; int previousStartColumn = startColumn; // TODO start at a row for which we know the start position, then detect upwards and downwards from there. - for (int imageRow = boundingBox.getMinY(); imageRow < boundingBox.getMaxY(); imageRow++) { + for (int imageRow = boundingBox.getMinY(); imageRow <= boundingBox.getMaxY(); imageRow++) { startColumn = getStartColumn(detectionResult, barcodeColumn, imageRow, leftToRight); if (startColumn < 0 || startColumn > boundingBox.getMaxX()) { if (previousStartColumn == -1) { @@ -136,20 +133,13 @@ public final class PDF417ScanningDecoder { if (barcodeMetadata == null) { return null; } - BoundingBox boundingBox = getBoundingBox(leftRowIndicatorColumn, rightRowIndicatorColumn); + BoundingBox boundingBox = BoundingBox.merge(adjustBoundingBox(leftRowIndicatorColumn), + adjustBoundingBox(rightRowIndicatorColumn)); return new DetectionResult(barcodeMetadata, boundingBox); } - private static BoundingBox getBoundingBox(DetectionResultRowIndicatorColumn leftRowIndicatorColumn, - DetectionResultRowIndicatorColumn rightRowIndicatorColumn) + private static BoundingBox adjustBoundingBox(DetectionResultRowIndicatorColumn rowIndicatorColumn) throws NotFoundException { - BoundingBox box1 = adjustBoundingBox(leftRowIndicatorColumn); - BoundingBox box2 = adjustBoundingBox(rightRowIndicatorColumn); - - return BoundingBox.merge(box1, box2); - } - - private static BoundingBox adjustBoundingBox(DetectionResultRowIndicatorColumn rowIndicatorColumn) { if (rowIndicatorColumn == null) { return null; } @@ -157,23 +147,27 @@ public final class PDF417ScanningDecoder { int maxRowHeight = getMax(rowHeights); int missingStartRows = 0; for (int rowHeight : rowHeights) { - if (rowHeight < maxRowHeight - ROW_HEIGHT_SKEW) { - missingStartRows++; - } else { + missingStartRows += maxRowHeight - rowHeight; + if (rowHeight > 0) { break; } } + Codeword[] codewords = rowIndicatorColumn.getCodewords(); + for (int row = 0; missingStartRows > 0 && codewords[row] == null; row++) { + missingStartRows--; + } int missingEndRows = 0; for (int row = rowHeights.length - 1; row >= 0; row--) { - if (rowHeights[row] < maxRowHeight - ROW_HEIGHT_SKEW) { - missingEndRows++; - } else { + missingEndRows += maxRowHeight - rowHeights[row]; + if (rowHeights[row] > 0) { break; } } - rowIndicatorColumn.getBoundingBox().addMissingRows(missingStartRows * maxRowHeight, missingEndRows * maxRowHeight, + for (int row = codewords.length - 1; missingEndRows > 0 && codewords[row] == null; row--) { + missingEndRows--; + } + return rowIndicatorColumn.getBoundingBox().addMissingRows(missingStartRows, missingEndRows, rowIndicatorColumn.isLeft()); - return rowIndicatorColumn.getBoundingBox(); } private static int getMax(int[] values) { @@ -203,9 +197,12 @@ public final class PDF417ScanningDecoder { return leftBarcodeMetadata; } - private static DetectionResultRowIndicatorColumn getRowIndicatorColumn(BitMatrix image, BoundingBox boundingBox, - ResultPoint startPoint, boolean leftToRight, - int minCodewordWidth, int maxCodewordWidth) { + private static DetectionResultRowIndicatorColumn getRowIndicatorColumn(BitMatrix image, + BoundingBox boundingBox, + ResultPoint startPoint, + boolean leftToRight, + int minCodewordWidth, + int maxCodewordWidth) { DetectionResultRowIndicatorColumn rowIndicatorColumn = new DetectionResultRowIndicatorColumn(boundingBox, leftToRight); for (int i = 0; i < 2; i++) { @@ -228,38 +225,114 @@ public final class PDF417ScanningDecoder { return rowIndicatorColumn; } - private static DecoderResult createDecoderResult(DetectionResult detectionResult) throws NotFoundException, - FormatException, ChecksumException { - BarcodeMatrix barcodeMatrix = createBarcodeMatrix(detectionResult); - Integer numberOfCodewords = barcodeMatrix.getValue(0, 1); - int calculatedNumberOfCodewords = detectionResult.getBarcodeColumnCount() * detectionResult.getBarcodeRowCount() - + private static void adjustCodewordCount(DetectionResult detectionResult, BarcodeValue[][] barcodeMatrix) + throws NotFoundException { + int[] numberOfCodewords = barcodeMatrix[0][1].getValue(); + int calculatedNumberOfCodewords = detectionResult.getBarcodeColumnCount() * + detectionResult.getBarcodeRowCount() - getNumberOfECCodeWords(detectionResult.getBarcodeECLevel()); - if (numberOfCodewords == null) { + if (numberOfCodewords.length == 0) { if (calculatedNumberOfCodewords < 1 || calculatedNumberOfCodewords > PDF417Common.MAX_CODEWORDS_IN_BARCODE) { throw NotFoundException.getNotFoundInstance(); } - barcodeMatrix.setValue(0, 1, calculatedNumberOfCodewords); - } else if (numberOfCodewords != calculatedNumberOfCodewords) { - // The calculated one is more reliable as it is derived from the row indicator column - numberOfCodewords = calculatedNumberOfCodewords; + barcodeMatrix[0][1].setValue(calculatedNumberOfCodewords); + } else if (numberOfCodewords[0] != calculatedNumberOfCodewords) { + // The calculated one is more reliable as it is derived from the row indicator columns + barcodeMatrix[0][1].setValue(calculatedNumberOfCodewords); } + } + + private static DecoderResult createDecoderResult(DetectionResult detectionResult) throws FormatException, + ChecksumException, NotFoundException { + BarcodeValue[][] barcodeMatrix = createBarcodeMatrix(detectionResult); + adjustCodewordCount(detectionResult, barcodeMatrix); Collection erasures = new ArrayList(); int[] codewords = new int[detectionResult.getBarcodeRowCount() * detectionResult.getBarcodeColumnCount()]; + List ambiguousIndexValuesList = new ArrayList(); + List ambiguousIndexesList = new ArrayList(); for (int row = 0; row < detectionResult.getBarcodeRowCount(); row++) { for (int column = 0; column < detectionResult.getBarcodeColumnCount(); column++) { - Integer codeword = barcodeMatrix.getValue(row, column + 1); - if (codeword == null) { - erasures.add(row * detectionResult.getBarcodeColumnCount() + column); + int[] values = barcodeMatrix[row][column + 1].getValue(); + int codewordIndex = row * detectionResult.getBarcodeColumnCount() + column; + if (values.length == 0) { + erasures.add(codewordIndex); + } else if (values.length == 1) { + codewords[codewordIndex] = values[0]; } else { - codewords[row * detectionResult.getBarcodeColumnCount() + column] = codeword; + ambiguousIndexesList.add(codewordIndex); + ambiguousIndexValuesList.add(values); } } } - return decodeCodewords(codewords, detectionResult.getBarcodeECLevel(), getErasureArray(erasures)); + int[][] ambiguousIndexValues = new int[ambiguousIndexValuesList.size()][]; + for (int i = 0; i < ambiguousIndexValues.length; i++) { + ambiguousIndexValues[i] = ambiguousIndexValuesList.get(i); + } + return createDecoderResultFromAmbiguousValues(detectionResult.getBarcodeECLevel(), codewords, + PDF417Common.toIntArray(erasures), PDF417Common.toIntArray(ambiguousIndexesList), ambiguousIndexValues); } - private static BarcodeMatrix createBarcodeMatrix(DetectionResult detectionResult) { - BarcodeMatrix barcodeMatrix = new BarcodeMatrix(); + /** + * This method deals with the fact, that the decoding process doesn't always yield a single most likely value. The + * current error correction implementation doesn't deal with erasures very well, so it's better to provide a value + * for these ambiguous codewords instead of treating it as an erasure. The problem is that we don't know which of + * the ambiguous values to choose. We try decode using the first value, and if that fails, we use another of the + * ambiguous values and try to decode again. This usually only happens on very hard to read and decode barcodes, + * so decoding the normal barcodes is not affected by this. + * @param ecLevel + * @param codewords + * @param erasureArray contains the indexes of erasures + * @param ambiguousIndexes array with the indexes that have more than one most likely value + * @param ambiguousIndexValues two dimensional array that contains the ambiguous values. The first dimension must + * be the same length as the ambiguousIndexes array + * @return + * @throws FormatException + * @throws ChecksumException + */ + private static DecoderResult createDecoderResultFromAmbiguousValues(int ecLevel, + int[] codewords, + int[] erasureArray, + int[] ambiguousIndexes, + int[][] ambiguousIndexValues) + throws FormatException, ChecksumException { + int[] ambiguousIndexCount = new int[ambiguousIndexes.length]; + + int tries = 100; + while (tries-- > 0) { + for (int i = 0; i < ambiguousIndexCount.length; i++) { + codewords[ambiguousIndexes[i]] = ambiguousIndexValues[i][ambiguousIndexCount[i]]; + } + try { + return decodeCodewords(codewords, ecLevel, erasureArray); + } catch (ChecksumException ignored) { + // + } + if (ambiguousIndexCount.length == 0) { + throw ChecksumException.getChecksumInstance(); + } + for (int i = 0; i < ambiguousIndexCount.length; i++) { + if (ambiguousIndexCount[i] < ambiguousIndexValues[i].length - 1) { + ambiguousIndexCount[i]++; + break; + } else { + ambiguousIndexCount[i] = 0; + if (i == ambiguousIndexCount.length - 1) { + throw ChecksumException.getChecksumInstance(); + } + } + } + } + throw ChecksumException.getChecksumInstance(); + } + + private static BarcodeValue[][] createBarcodeMatrix(DetectionResult detectionResult) { + BarcodeValue[][] barcodeMatrix = new BarcodeValue[detectionResult.getBarcodeRowCount()][detectionResult + .getBarcodeColumnCount() + 2]; + for (int row = 0; row < barcodeMatrix.length; row++) { + for (int column = 0; column < barcodeMatrix[row].length; column++) { + barcodeMatrix[row][column] = new BarcodeValue(); + } + } int column = -1; for (DetectionResultColumn detectionResultColumn : detectionResult.getDetectionResultColumns()) { @@ -268,10 +341,10 @@ public final class PDF417ScanningDecoder { continue; } for (Codeword codeword : detectionResultColumn.getCodewords()) { - if (codeword == null) { + if (codeword == null || codeword.getRowNumber() == -1) { continue; } - barcodeMatrix.setValue(codeword.getRowNumber(), column, codeword.getValue()); + barcodeMatrix[codeword.getRowNumber()][column].setValue(codeword.getValue()); } } return barcodeMatrix; @@ -281,7 +354,9 @@ public final class PDF417ScanningDecoder { return barcodeColumn >= 0 && barcodeColumn <= detectionResult.getBarcodeColumnCount() + 1; } - private static int getStartColumn(DetectionResult detectionResult, int barcodeColumn, int imageRow, + private static int getStartColumn(DetectionResult detectionResult, + int barcodeColumn, + int imageRow, boolean leftToRight) { int offset = leftToRight ? 1 : -1; Codeword codeword = null; @@ -307,8 +382,10 @@ public final class PDF417ScanningDecoder { barcodeColumn -= offset; for (Codeword previousRowCodeword : detectionResult.getDetectionResultColumn(barcodeColumn).getCodewords()) { if (previousRowCodeword != null) { - return (leftToRight ? previousRowCodeword.getEndX() : previousRowCodeword.getStartX()) + offset * - skippedColumns * (previousRowCodeword.getEndX() - previousRowCodeword.getStartX()); + return (leftToRight ? previousRowCodeword.getEndX() : previousRowCodeword.getStartX()) + + offset * + skippedColumns * + (previousRowCodeword.getEndX() - previousRowCodeword.getStartX()); } } skippedColumns++; @@ -316,8 +393,14 @@ public final class PDF417ScanningDecoder { return leftToRight ? detectionResult.getBoundingBox().getMinX() : detectionResult.getBoundingBox().getMaxX(); } - private static Codeword detectCodeword(BitMatrix image, int minColumn, int maxColumn, boolean leftToRight, - int startColumn, int imageRow, int minCodewordWidth, int maxCodewordWidth) { + private static Codeword detectCodeword(BitMatrix image, + int minColumn, + int maxColumn, + boolean leftToRight, + int startColumn, + int imageRow, + int minCodewordWidth, + int maxCodewordWidth) { startColumn = adjustCodewordStartColumn(image, minColumn, maxColumn, leftToRight, startColumn, imageRow); // we usually know fairly exact now how long a codeword is. We should provide minimum and maximum expected length // and try to adjust the read pixels, e.g. remove single pixel errors or try to cut off exceeding pixels. @@ -368,8 +451,12 @@ public final class PDF417ScanningDecoder { return new Codeword(startColumn, endColumn, getCodewordBucketNumber(decodedValue), codeword); } - private static int[] getModuleBitCount(BitMatrix image, int minColumn, int maxColumn, boolean leftToRight, - int startColumn, int imageRow) { + private static int[] getModuleBitCount(BitMatrix image, + int minColumn, + int maxColumn, + boolean leftToRight, + int startColumn, + int imageRow) { int imageColumn = startColumn; int[] moduleBitCount = new int[8]; int moduleNumber = 0; @@ -396,11 +483,11 @@ public final class PDF417ScanningDecoder { return 2 << barcodeECLevel; } - private static int adjustCodewordStartColumn(BitMatrix image, - int minColumn, + private static int adjustCodewordStartColumn(BitMatrix image, + int minColumn, int maxColumn, - boolean leftToRight, - int codewordStartColumn, + boolean leftToRight, + int codewordStartColumn, int imageRow) { int correctedStartColumn = codewordStartColumn; int increment = leftToRight ? -1 : 1; @@ -424,23 +511,13 @@ public final class PDF417ScanningDecoder { codewordSize <= maxCodewordWidth + CODEWORD_SKEW_SIZE; } - private static int[] getErasureArray(Collection list) { - int[] result = new int[list.size()]; - int i = 0; - for (Integer integer : list) { - result[i++] = integer; - } - return result; - } - - private static DecoderResult decodeCodewords(int[] codewords, int ecLevel, int[] erasures) - throws FormatException, ChecksumException { + private static DecoderResult decodeCodewords(int[] codewords, int ecLevel, int[] erasures) throws FormatException, + ChecksumException { if (codewords.length == 0) { throw FormatException.getFormatInstance(); } int numECCodewords = 1 << (ecLevel + 1); - int correctedErrorsCount = correctErrors(codewords, erasures, numECCodewords); verifyCodewordCount(codewords, numECCodewords); @@ -457,16 +534,20 @@ public final class PDF417ScanningDecoder { * * @param codewords data and error correction codewords * @param erasures positions of any known erasures - * @param numECCodewords number of error correction codewards that were available in codewords + * @param numECCodewords number of error correction codewords that are available in codewords * @throws ChecksumException if error correction fails */ private static int correctErrors(int[] codewords, int[] erasures, int numECCodewords) throws ChecksumException { - if (erasures.length > numECCodewords / 2 + MAX_ERRORS || numECCodewords < 0 || numECCodewords > MAX_EC_CODEWORDS) { + if (erasures != null && + erasures.length > numECCodewords / 2 + MAX_ERRORS || + numECCodewords < 0 || + numECCodewords > MAX_EC_CODEWORDS) { // Too many errors or EC Codewords is corrupted throw ChecksumException.getChecksumInstance(); } return errorCorrection.decode(codewords, numECCodewords, erasures); } + /** * Verify that all is OK with the codeword array. * @@ -522,4 +603,24 @@ public final class PDF417ScanningDecoder { return (moduleBitCount[0] - moduleBitCount[2] + moduleBitCount[4] - moduleBitCount[6] + 9) % 9; } + public static String toString(BarcodeValue[][] barcodeMatrix) { + Formatter formatter = new Formatter(); + for (int row = 0; row < barcodeMatrix.length; row++) { + formatter.format("Row %2d: ", row); + for (int column = 0; column < barcodeMatrix[row].length; column++) { + BarcodeValue barcodeValue = barcodeMatrix[row][column]; + if (barcodeValue.getValue().length == 0) { + formatter.format(" ", (Object[]) null); + } else { + formatter.format("%4d(%2d)", barcodeValue.getValue()[0], + barcodeValue.getConfidence(barcodeValue.getValue()[0])); + } + } + formatter.format("\n"); + } + String result = formatter.toString(); + formatter.close(); + return result; + } + } diff --git a/core/src/com/google/zxing/pdf417/detector/Detector.java b/core/src/com/google/zxing/pdf417/detector/Detector.java index c733f50d7..8286b92be 100644 --- a/core/src/com/google/zxing/pdf417/detector/Detector.java +++ b/core/src/com/google/zxing/pdf417/detector/Detector.java @@ -38,8 +38,8 @@ import java.util.Map; */ public final class Detector { - private static final int[] INDEXES_START_PATTERN = { 0, 4, 1, 5 }; - private static final int[] INDEXES_STOP_PATTERN = { 6, 2, 7, 3 }; + private static final int[] INDEXES_START_PATTERN = {0, 4, 1, 5}; + private static final int[] INDEXES_STOP_PATTERN = {6, 2, 7, 3}; private static final int INTEGER_MATH_SHIFT = 8; private static final int PATTERN_MATCH_RESULT_SCALE_FACTOR = 1 << INTEGER_MATH_SHIFT; private static final int MAX_AVG_VARIANCE = (int) (PATTERN_MATCH_RESULT_SCALE_FACTOR * 0.42f); @@ -47,9 +47,9 @@ public final class Detector { // B S B S B S B S Bar/Space pattern // 11111111 0 1 0 1 0 1 000 - private static final int[] START_PATTERN = { 8, 1, 1, 1, 1, 1, 1, 3 }; + private static final int[] START_PATTERN = {8, 1, 1, 1, 1, 1, 1, 3}; // 1111111 0 1 000 1 0 1 00 1 - private static final int[] STOP_PATTERN = { 7, 1, 1, 3, 1, 1, 1, 2, 1 }; + private static final int[] STOP_PATTERN = {7, 1, 1, 3, 1, 1, 1, 2, 1}; private static final int MAX_PIXEL_DRIFT = 3; private static final int MAX_PATTERN_DRIFT = 5; // if we set the value too low, then we don't detect the correct height of the bar if the start patterns are damaged. @@ -60,20 +60,7 @@ public final class Detector { private static final int ROW_STEP = 5; private static final int BARCODE_MIN_HEIGHT = 10; - private final BinaryBitmap image; - - public Detector(BinaryBitmap image) { - this.image = image; - } - - /** - *

Detects a PDF417 Code in an image, simply.

- * - * @return {@link PDF417DetectorResult} encapsulating results of detecting a PDF417 Code - * @throws NotFoundException if no PDF417 Code can be found - */ - public PDF417DetectorResult detect(boolean multiple) throws NotFoundException { - return detect(null, multiple); + private Detector() { } /** @@ -85,7 +72,8 @@ public final class Detector { * @return {@link PDF417DetectorResult} encapsulating results of detecting a PDF417 code * @throws NotFoundException if no PDF417 Code can be found */ - PDF417DetectorResult detect(Map hints, boolean multiple) throws NotFoundException { + public static PDF417DetectorResult detect(BinaryBitmap image, Map hints, boolean multiple) + throws NotFoundException { // TODO detection improvement, tryHarder could try several different luminance thresholds/blackpoints or even // different binarizers //boolean tryHarder = hints != null && hints.containsKey(DecodeHintType.TRY_HARDER); @@ -107,12 +95,12 @@ public final class Detector { * @param bitMatrix bit matrix to detect barcodes in * @return List of ResultPoint arrays containing the coordinates of found barcodes */ - private List detect(boolean multiple, BitMatrix bitMatrix) { + private static List detect(boolean multiple, BitMatrix bitMatrix) { List barcodeCoordinates = new ArrayList(); int row = 0; int column = 0; boolean foundBarcodeInRow = false; - while (row < image.getHeight()) { + while (row < bitMatrix.getHeight()) { ResultPoint[] vertices = findVertices(bitMatrix, row, column); if (vertices[0] == null && vertices[3] == null) { @@ -224,8 +212,12 @@ public final class Detector { } } - private static ResultPoint[] findRowsWithPattern(BitMatrix matrix, int height, int width, int startRow, - int startColumn, int[] pattern) { + private static ResultPoint[] findRowsWithPattern(BitMatrix matrix, + int height, + int width, + int startRow, + int startColumn, + int[] pattern) { ResultPoint[] result = new ResultPoint[4]; boolean found = false; int[] counters = new int[pattern.length]; @@ -251,14 +243,15 @@ public final class Detector { // Last row of the current symbol that contains pattern if (found) { int skippedRowCount = 0; - int[] previousRowLoc = { (int) result[0].getX(), (int) result[1].getX() }; + int[] previousRowLoc = {(int) result[0].getX(), (int) result[1].getX()}; for (; stopRow < height; stopRow++) { int[] loc = findGuardPattern(matrix, previousRowLoc[0], stopRow, width, false, pattern, counters); // a found pattern is only considered to belong to the same barcode if the start and end positions // don't differ too much. Pattern drift should be not bigger than two for consecutive rows. With // a higher number of skipped rows drift could be larger. To keep it simple for now, we allow a slightly // larger drift and don't check for skipped rows. - if (loc != null && Math.abs(previousRowLoc[0] - loc[0]) < MAX_PATTERN_DRIFT && + if (loc != null && + Math.abs(previousRowLoc[0] - loc[0]) < MAX_PATTERN_DRIFT && Math.abs(previousRowLoc[1] - loc[1]) < MAX_PATTERN_DRIFT) { previousRowLoc = loc; skippedRowCount = 0; @@ -270,7 +263,7 @@ public final class Detector { } } } - stopRow -= skippedRowCount; + stopRow -= skippedRowCount + 1; result[2] = new ResultPoint(previousRowLoc[0], stopRow); result[3] = new ResultPoint(previousRowLoc[1], stopRow); } @@ -292,8 +285,13 @@ public final class Detector { * @param counters array of counters, as long as pattern, to re-use * @return start/end horizontal offset of guard pattern, as an array of two ints. */ - private static int[] findGuardPattern(BitMatrix matrix, int column, int row, int width, boolean whiteFirst, - int[] pattern, int[] counters) { + private static int[] findGuardPattern(BitMatrix matrix, + int column, + int row, + int width, + boolean whiteFirst, + int[] pattern, + int[] counters) { Arrays.fill(counters, 0, counters.length, 0); int patternLength = pattern.length; boolean isWhite = whiteFirst; @@ -313,7 +311,7 @@ public final class Detector { } else { if (counterPosition == patternLength - 1) { if (patternMatchVariance(counters, pattern, MAX_INDIVIDUAL_VARIANCE) < MAX_AVG_VARIANCE) { - return new int[] { patternStart, x }; + return new int[] {patternStart, x}; } patternStart += counters[0] + counters[1]; System.arraycopy(counters, 2, counters, 0, patternLength - 2); @@ -329,7 +327,7 @@ public final class Detector { } if (counterPosition == patternLength - 1) { if (patternMatchVariance(counters, pattern, MAX_INDIVIDUAL_VARIANCE) < MAX_AVG_VARIANCE) { - return new int[] { patternStart, x - 1 }; + return new int[] {patternStart, x - 1}; } } return null; diff --git a/core/test/data/blackbox/pdf417-2/24.bin b/core/test/data/blackbox/pdf417-2/24.bin index fc1f44706..4effe2e65 100755 Binary files a/core/test/data/blackbox/pdf417-2/24.bin and b/core/test/data/blackbox/pdf417-2/24.bin differ diff --git a/core/test/data/blackbox/pdf417-4/01-01.png b/core/test/data/blackbox/pdf417-4/01-01.png new file mode 100644 index 000000000..c1bf3295f Binary files /dev/null and b/core/test/data/blackbox/pdf417-4/01-01.png differ diff --git a/core/test/data/blackbox/pdf417-4/01.bin b/core/test/data/blackbox/pdf417-4/01.bin new file mode 100644 index 000000000..4effe2e65 Binary files /dev/null and b/core/test/data/blackbox/pdf417-4/01.bin differ diff --git a/core/test/data/blackbox/pdf417-4/02-01.png b/core/test/data/blackbox/pdf417-4/02-01.png new file mode 100644 index 000000000..12d12ce11 Binary files /dev/null and b/core/test/data/blackbox/pdf417-4/02-01.png differ diff --git a/core/test/data/blackbox/pdf417-4/02-02.png b/core/test/data/blackbox/pdf417-4/02-02.png new file mode 100644 index 000000000..55ac0bbca Binary files /dev/null and b/core/test/data/blackbox/pdf417-4/02-02.png differ diff --git a/core/test/data/blackbox/pdf417-4/02.bin b/core/test/data/blackbox/pdf417-4/02.bin new file mode 100644 index 000000000..9c07c8f6f Binary files /dev/null and b/core/test/data/blackbox/pdf417-4/02.bin differ diff --git a/core/test/src/com/google/zxing/common/AbstractBlackBoxTestCase.java b/core/test/src/com/google/zxing/common/AbstractBlackBoxTestCase.java index 1796cce15..2be51a675 100644 --- a/core/test/src/com/google/zxing/common/AbstractBlackBoxTestCase.java +++ b/core/test/src/com/google/zxing/common/AbstractBlackBoxTestCase.java @@ -296,7 +296,7 @@ public abstract class AbstractBlackBoxTestCase extends Assert { return true; } - private static String readFileAsString(File file, Charset charset) throws IOException { + protected static String readFileAsString(File file, Charset charset) throws IOException { StringBuilder result = new StringBuilder((int) file.length()); InputStreamReader reader = new InputStreamReader(new FileInputStream(file), charset); try { diff --git a/core/test/src/com/google/zxing/common/TestResult.java b/core/test/src/com/google/zxing/common/TestResult.java index 4c7bce5f5..313c5c6ae 100644 --- a/core/test/src/com/google/zxing/common/TestResult.java +++ b/core/test/src/com/google/zxing/common/TestResult.java @@ -16,7 +16,7 @@ package com.google.zxing.common; -final class TestResult { +public final class TestResult { private final int mustPassCount; private final int tryHarderCount; @@ -24,7 +24,7 @@ final class TestResult { private final int maxTryHarderMisreads; private final float rotation; - TestResult(int mustPassCount, int tryHarderCount, int maxMisreads, int maxTryHarderMisreads, float rotation) { + public TestResult(int mustPassCount, int tryHarderCount, int maxMisreads, int maxTryHarderMisreads, float rotation) { this.mustPassCount = mustPassCount; this.tryHarderCount = tryHarderCount; this.maxMisreads = maxMisreads; @@ -32,23 +32,23 @@ final class TestResult { this.rotation = rotation; } - int getMustPassCount() { + public int getMustPassCount() { return mustPassCount; } - int getTryHarderCount() { + public int getTryHarderCount() { return tryHarderCount; } - int getMaxMisreads() { + public int getMaxMisreads() { return maxMisreads; } - int getMaxTryHarderMisreads() { + public int getMaxTryHarderMisreads() { return maxTryHarderMisreads; } - float getRotation() { + public float getRotation() { return rotation; } diff --git a/core/test/src/com/google/zxing/pdf417/PDF417BlackBox2TestCase.java b/core/test/src/com/google/zxing/pdf417/PDF417BlackBox2TestCase.java index c40ff5268..376293226 100644 --- a/core/test/src/com/google/zxing/pdf417/PDF417BlackBox2TestCase.java +++ b/core/test/src/com/google/zxing/pdf417/PDF417BlackBox2TestCase.java @@ -29,9 +29,8 @@ public final class PDF417BlackBox2TestCase extends AbstractBlackBoxTestCase { public PDF417BlackBox2TestCase() { super("test/data/blackbox/pdf417-2", new MultiFormatReader(), BarcodeFormat.PDF_417); - // TODO this should be 25,25,0,0 - addTest(23, 23, 1, 1, 0.0f); - addTest(23, 23, 1, 1, 180.0f); + addTest(25, 25, 0, 0, 0.0f); + addTest(25, 25, 0, 0, 180.0f); } } diff --git a/core/test/src/com/google/zxing/pdf417/PDF417BlackBox4TestCase.java b/core/test/src/com/google/zxing/pdf417/PDF417BlackBox4TestCase.java new file mode 100644 index 000000000..d0688bbc9 --- /dev/null +++ b/core/test/src/com/google/zxing/pdf417/PDF417BlackBox4TestCase.java @@ -0,0 +1,237 @@ +/* + * Copyright 2013 ZXing authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.google.zxing.pdf417; + +import com.google.zxing.BarcodeFormat; +import com.google.zxing.BinaryBitmap; +import com.google.zxing.BufferedImageLuminanceSource; +import com.google.zxing.DecodeHintType; +import com.google.zxing.LuminanceSource; +import com.google.zxing.ReaderException; +import com.google.zxing.Result; +import com.google.zxing.ResultMetadataType; +import com.google.zxing.common.AbstractBlackBoxTestCase; +import com.google.zxing.common.HybridBinarizer; +import com.google.zxing.common.SummaryResults; +import com.google.zxing.common.TestResult; + +import org.junit.Test; + +import javax.imageio.ImageIO; + +import java.awt.image.BufferedImage; +import java.io.File; +import java.io.IOException; +import java.nio.charset.Charset; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collections; +import java.util.Comparator; +import java.util.EnumMap; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.Map.Entry; +import java.util.logging.Logger; + +/** + * This class tests Macro PDF417 barcode specific functionality. It ensures that information, which is split into + * several barcodes can be properly combined again to yield the original data content. + * + * @author Guenther Grau + * + */ +public final class PDF417BlackBox4TestCase extends AbstractBlackBoxTestCase { + private static final Logger log = Logger.getLogger(AbstractBlackBoxTestCase.class.getSimpleName()); + + private static final Charset UTF8 = Charset.forName("UTF-8"); + private static final Charset ISO88591 = Charset.forName("ISO-8859-1"); + private static final String TEST_BASE_PATH_SUFFIX = "test/data/blackbox/pdf417-4"; + private final PDF417Reader barcodeReader = new PDF417Reader(); + + private final List testResults = new ArrayList(); + private File testBase; + + public PDF417BlackBox4TestCase() { + super(TEST_BASE_PATH_SUFFIX, null, BarcodeFormat.PDF_417); + // A little workaround to prevent aggravation in my IDE + testBase = new File(TEST_BASE_PATH_SUFFIX); + if (!testBase.exists()) { + // try starting with 'core' since the test base is often given as the project root + testBase = new File("core/" + TEST_BASE_PATH_SUFFIX); + } + testResults.add(new TestResult(2, 2, 0, 0, 0.0f)); + } + + @Test + @Override + public void testBlackBox() throws IOException { + testPDF417BlackBoxCountingResults(true); + } + + public SummaryResults testPDF417BlackBoxCountingResults(boolean assertOnFailure) throws IOException { + assertFalse(testResults.isEmpty()); + + Map> imageFiles = getImageFileLists(); + int testCount = testResults.size(); + + int[] passedCounts = new int[testCount]; + int[] misreadCounts = new int[testCount]; + int[] tryHarderCounts = new int[testCount]; + int[] tryHaderMisreadCounts = new int[testCount]; + + for (Entry> testImageGroup : imageFiles.entrySet()) { + log.fine(String.format("Starting Image Group %s", testImageGroup.getKey())); + + String fileBaseName = testImageGroup.getKey(); + String expectedText = null; + File expectedTextFile = new File(testBase, fileBaseName + ".txt"); + if (expectedTextFile.exists()) { + expectedText = readFileAsString(expectedTextFile, UTF8); + } else { + expectedTextFile = new File(testBase, fileBaseName + ".bin"); + assertTrue(expectedTextFile.exists()); + expectedText = readFileAsString(expectedTextFile, ISO88591); + } + + for (int x = 0; x < testCount; x++) { + List results = new ArrayList(); + for (File imageFile : testImageGroup.getValue()) { + BufferedImage image = ImageIO.read(imageFile); + float rotation = testResults.get(x).getRotation(); + BufferedImage rotatedImage = rotateImage(image, rotation); + LuminanceSource source = new BufferedImageLuminanceSource(rotatedImage); + BinaryBitmap bitmap = new BinaryBitmap(new HybridBinarizer(source)); + + try { + results.addAll(Arrays.asList(decode(bitmap, false))); + } catch (ReaderException ignored) { + // ignore + } + } + Collections.sort(results, new Comparator() { + @Override + public int compare(Result arg0, Result arg1) { + PDF417ResultMetadata resultMetadata = getMeta(arg0); + PDF417ResultMetadata otherResultMetadata = getMeta(arg1); + return resultMetadata.getSegmentIndex() - otherResultMetadata.getSegmentIndex(); + } + }); + StringBuilder resultText = new StringBuilder(); + String fileId = null; + for (Result result : results) { + PDF417ResultMetadata resultMetadata = getMeta(result); + assertNotNull("resultMetadata", resultMetadata); + if (fileId == null) { + fileId = resultMetadata.getFileId(); + } + assertEquals("FileId", fileId, resultMetadata.getFileId()); + resultText.append(result.getText()); + } + assertEquals("ExpectedText", expectedText, resultText.toString()); + passedCounts[x]++; + tryHarderCounts[x]++; + } + } + + // Print the results of all tests first + int totalFound = 0; + int totalMustPass = 0; + int totalMisread = 0; + int totalMaxMisread = 0; + + int numberOfTests = imageFiles.keySet().size(); + for (int x = 0; x < testResults.size(); x++) { + TestResult testResult = testResults.get(x); + log.info(String.format("Rotation %d degrees:", (int) testResult.getRotation())); + log.info(String.format(" %d of %d images passed (%d required)", passedCounts[x], numberOfTests, + testResult.getMustPassCount())); + int failed = numberOfTests - passedCounts[x]; + log.info(String + .format(" %d failed due to misreads, %d not detected", misreadCounts[x], failed - misreadCounts[x])); + log.info(String.format(" %d of %d images passed with try harder (%d required)", tryHarderCounts[x], + numberOfTests, testResult.getTryHarderCount())); + failed = numberOfTests - tryHarderCounts[x]; + log.info(String.format(" %d failed due to misreads, %d not detected", tryHaderMisreadCounts[x], failed - + tryHaderMisreadCounts[x])); + totalFound += passedCounts[x] + tryHarderCounts[x]; + totalMustPass += testResult.getMustPassCount() + testResult.getTryHarderCount(); + totalMisread += misreadCounts[x] + tryHaderMisreadCounts[x]; + totalMaxMisread += testResult.getMaxMisreads() + testResult.getMaxTryHarderMisreads(); + } + + int totalTests = numberOfTests * testCount * 2; + log.info(String.format("Decoded %d images out of %d (%d%%, %d required)", totalFound, totalTests, totalFound * + 100 / + totalTests, totalMustPass)); + if (totalFound > totalMustPass) { + log.warning(String.format("+++ Test too lax by %d images", totalFound - totalMustPass)); + } else if (totalFound < totalMustPass) { + log.warning(String.format("--- Test failed by %d images", totalMustPass - totalFound)); + } + + if (totalMisread < totalMaxMisread) { + log.warning(String.format("+++ Test expects too many misreads by %d images", totalMaxMisread - totalMisread)); + } else if (totalMisread > totalMaxMisread) { + log.warning(String.format("--- Test had too many misreads by %d images", totalMisread - totalMaxMisread)); + } + + // Then run through again and assert if any failed + if (assertOnFailure) { + for (int x = 0; x < testCount; x++) { + TestResult testResult = testResults.get(x); + String label = "Rotation " + testResult.getRotation() + " degrees: Too many images failed"; + assertTrue(label, passedCounts[x] >= testResult.getMustPassCount()); + assertTrue("Try harder, " + label, tryHarderCounts[x] >= testResult.getTryHarderCount()); + label = "Rotation " + testResult.getRotation() + " degrees: Too many images misread"; + assertTrue(label, misreadCounts[x] <= testResult.getMaxMisreads()); + assertTrue("Try harder, " + label, tryHaderMisreadCounts[x] <= testResult.getMaxTryHarderMisreads()); + } + } + return new SummaryResults(totalFound, totalMustPass, totalTests); + } + + private static PDF417ResultMetadata getMeta(Result result) { + return result.getResultMetadata() == null ? null : (PDF417ResultMetadata) result.getResultMetadata().get( + ResultMetadataType.PDF417_EXTRA_METADATA); + } + + private Result[] decode(BinaryBitmap source, boolean tryHarder) throws ReaderException { + Map hints = new EnumMap(DecodeHintType.class); + if (tryHarder) { + hints.put(DecodeHintType.TRY_HARDER, Boolean.TRUE); + } + + return barcodeReader.decodeMultiple(source, hints); + } + + private Map> getImageFileLists() { + Map> result = new HashMap>(); + for (File file : getImageFiles()) { + String testImageFileName = file.getName(); + String fileBaseName = testImageFileName.substring(0, testImageFileName.indexOf('-')); + List files = result.get(fileBaseName); + if (files == null) { + files = new ArrayList(); + result.put(fileBaseName, files); + } + files.add(file); + } + return result; + } + +} diff --git a/core/test/src/com/google/zxing/pdf417/decoder/ec/ErrorCorrectionTestCase.java b/core/test/src/com/google/zxing/pdf417/decoder/ec/ErrorCorrectionTestCase.java index 3d99db1c5..fd77aad05 100644 --- a/core/test/src/com/google/zxing/pdf417/decoder/ec/ErrorCorrectionTestCase.java +++ b/core/test/src/com/google/zxing/pdf417/decoder/ec/ErrorCorrectionTestCase.java @@ -17,6 +17,7 @@ package com.google.zxing.pdf417.decoder.ec; import com.google.zxing.ChecksumException; + import org.junit.Test; import java.util.Random; @@ -32,25 +33,16 @@ public final class ErrorCorrectionTestCase extends AbstractErrorCorrectionTestCa //private static final int[] PDF417_TEST_WITH_EC = // { 5, 453, 178, 121, 239, 452, 327, 657, 619 }; - private static final int[] PDF417_TEST = - { 48,901, 56,141,627,856,330, 69,244,900, - 852,169,843,895,852,895,913,154,845,778, - 387, 89,869,901,219,474,543,650,169,201, - 9,160, 35, 70,900,900,900,900,900,900, - 900,900,900,900,900,900,900,900}; - private static final int[] PDF417_TEST_WITH_EC = - { 48,901, 56,141,627,856,330, 69,244,900, - 852,169,843,895,852,895,913,154,845,778, - 387, 89,869,901,219,474,543,650,169,201, - 9,160, 35, 70,900,900,900,900,900,900, - 900,900,900,900,900,900,900,900,769,843, - 591,910,605,206,706,917,371,469, 79,718, - 47,777,249,262,193,620,597,477,450,806, - 908,309,153,871,686,838,185,674, 68,679, - 691,794,497,479,234,250,496, 43,347,582, - 882,536,322,317,273,194,917,237,420,859, - 340,115,222,808,866,836,417,121,833,459, - 64,159}; + private static final int[] PDF417_TEST = { + 48, 901, 56, 141, 627, 856, 330, 69, 244, 900, 852, 169, 843, 895, 852, 895, 913, 154, 845, 778, 387, 89, 869, + 901, 219, 474, 543, 650, 169, 201, 9, 160, 35, 70, 900, 900, 900, 900, 900, 900, 900, 900, 900, 900, 900, 900, + 900, 900}; + private static final int[] PDF417_TEST_WITH_EC = { + 48, 901, 56, 141, 627, 856, 330, 69, 244, 900, 852, 169, 843, 895, 852, 895, 913, 154, 845, 778, 387, 89, 869, + 901, 219, 474, 543, 650, 169, 201, 9, 160, 35, 70, 900, 900, 900, 900, 900, 900, 900, 900, 900, 900, 900, 900, + 900, 900, 769, 843, 591, 910, 605, 206, 706, 917, 371, 469, 79, 718, 47, 777, 249, 262, 193, 620, 597, 477, 450, + 806, 908, 309, 153, 871, 686, 838, 185, 674, 68, 679, 691, 794, 497, 479, 234, 250, 496, 43, 347, 582, 882, 536, + 322, 317, 273, 194, 917, 237, 420, 859, 340, 115, 222, 808, 866, 836, 417, 121, 833, 459, 64, 159}; private static final int ECC_BYTES = PDF417_TEST_WITH_EC.length - PDF417_TEST.length; // Example is EC level 1 (s=1). The number of erasures (l) and substitutions (f) must obey: // l + 2f <= 2^(s+1) - 3 @@ -81,7 +73,7 @@ public final class ErrorCorrectionTestCase extends AbstractErrorCorrectionTestCa @Test public void testMaxErrors() throws ChecksumException { Random random = getRandom(); - for (int test : PDF417_TEST) { // # iterations is kind of arbitrary + for (int testIterations = 0; testIterations < 100; testIterations++) { // # iterations is kind of arbitrary int[] received = PDF417_TEST_WITH_EC.clone(); corrupt(received, MAX_ERRORS, random); checkDecode(received); @@ -137,4 +129,4 @@ public final class ErrorCorrectionTestCase extends AbstractErrorCorrectionTestCa } } -} \ No newline at end of file +}