More PDF417 changes from Guenther

git-svn-id: https://zxing.googlecode.com/svn/trunk@2708 59b500cc-1b3d-0410-9834-0bbf25fbcc57
This commit is contained in:
srowen@gmail.com 2013-05-03 20:57:14 +00:00
parent a6f8559478
commit 8fbcb53126
22 changed files with 728 additions and 316 deletions

View file

@ -15,6 +15,8 @@
*/ */
package com.google.zxing.pdf417; package com.google.zxing.pdf417;
import java.util.Collection;
/** /**
* @author SITA Lab (kevin.osullivan@sita.aero) * @author SITA Lab (kevin.osullivan@sita.aero)
* @author Guenther Grau * @author Guenther Grau
@ -22,8 +24,9 @@ package com.google.zxing.pdf417;
public final class PDF417Common { public final class PDF417Common {
public static final int NUMBER_OF_CODEWORDS = 929; public static final int NUMBER_OF_CODEWORDS = 929;
// Maximum Codewords (Data + Error). Was designed to be the same as the number of codewords // Maximum Codewords (Data + Error).
public static final int MAX_CODEWORDS_IN_BARCODE = NUMBER_OF_CODEWORDS; 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; public static final int MAX_ROWS_IN_BARCODE = 90;
// One left row indication column + max 30 data columns + one right row indicator column // One left row indication column + max 30 data columns + one right row indicator column
public static final int MAX_CODEWORDS_IN_ROW = 32; 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 MODULES_IN_STOP_PATTERN = 18;
public static final int BARS_IN_MODULE = 8; public static final int BARS_IN_MODULE = 8;
private static final int[] EMPTY_INT_ARRAY = {};
private PDF417Common() { private PDF417Common() {
} }
@ -41,7 +46,19 @@ public final class PDF417Common {
} }
return bitCountSum; return bitCountSum;
} }
public static int[] toIntArray(Collection<Integer> 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. * 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, 0x1f9d4, 0x1fa1a, 0x1fa2e, 0x1fa32, 0x1fa34, 0x1fa4e, 0x1fa5c, 0x1fa62, 0x1fa64, 0x1fa68, 0x1fa76, 0x1fa8e,
0x1fa9c, 0x1fab8, 0x1fac2, 0x1fac4, 0x1fac8, 0x1fad0, 0x1fade, 0x1fae6, 0x1faec, 0x1fb16, 0x1fb26, 0x1fb2c, 0x1fa9c, 0x1fab8, 0x1fac2, 0x1fac4, 0x1fac8, 0x1fad0, 0x1fade, 0x1fae6, 0x1faec, 0x1fb16, 0x1fb26, 0x1fb2c,
0x1fb3a, 0x1fb46, 0x1fb4c, 0x1fb58, 0x1fb6e, 0x1fb72, 0x1fb74, 0x1fb8a, 0x1fb92, 0x1fb94, 0x1fba2, 0x1fba4, 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. * 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, 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, 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, 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};
} }

View file

@ -84,7 +84,7 @@ public final class PDF417Reader implements Reader, MultipleBarcodeReader {
private static Result[] decode(BinaryBitmap image, Map<DecodeHintType, ?> hints, boolean multiple) private static Result[] decode(BinaryBitmap image, Map<DecodeHintType, ?> hints, boolean multiple)
throws NotFoundException, FormatException, ChecksumException { throws NotFoundException, FormatException, ChecksumException {
List<Result> results = new ArrayList<Result>(); List<Result> results = new ArrayList<Result>();
PDF417DetectorResult detectorResult = new Detector(image).detect(multiple); PDF417DetectorResult detectorResult = Detector.detect(image, hints, multiple);
for (ResultPoint[] points : detectorResult.getPoints()) { for (ResultPoint[] points : detectorResult.getPoints()) {
DecoderResult decoderResult = PDF417ScanningDecoder.decode(detectorResult.getBits(), points[4], points[5], DecoderResult decoderResult = PDF417ScanningDecoder.decode(detectorResult.getBits(), points[4], points[5],
points[6], points[7], getMinCodewordWidth(points), getMaxCodewordWidth(points)); points[6], points[7], getMinCodewordWidth(points), getMaxCodewordWidth(points));

View file

@ -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<String,BarcodeValue> values = new HashMap<String,BarcodeValue>();
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();
}
}

View file

@ -16,6 +16,10 @@
package com.google.zxing.pdf417.decoder; 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.HashMap;
import java.util.Map; import java.util.Map;
import java.util.Map.Entry; import java.util.Map.Entry;
@ -24,10 +28,13 @@ import java.util.Map.Entry;
* @author Guenther Grau * @author Guenther Grau
*/ */
final class BarcodeValue { final class BarcodeValue {
private final Map<Integer,Integer> values = new HashMap<Integer,Integer>(); private final Map<Integer,Integer> values = new HashMap<Integer,Integer>();
public void setValue(int value) { /**
* Add an occurrence of a value
* @param value
*/
void setValue(int value) {
Integer confidence = values.get(value); Integer confidence = values.get(value);
if (confidence == null) { if (confidence == null) {
confidence = 0; confidence = 0;
@ -36,24 +43,23 @@ final class BarcodeValue {
values.put(value, confidence); 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; int maxConfidence = -1;
Integer result = null; Collection<Integer> result = new ArrayList<Integer>();
boolean ambiguous = false;
for (Entry<Integer,Integer> entry : values.entrySet()) { for (Entry<Integer,Integer> entry : values.entrySet()) {
if (entry.getValue() > maxConfidence) { if (entry.getValue() > maxConfidence) {
maxConfidence = entry.getValue(); maxConfidence = entry.getValue();
result = entry.getKey(); result.clear();
ambiguous = false; result.add(entry.getKey());
// TODO fix this clause? } else if (entry.getValue() == maxConfidence) {
//} else if (entry.getValue() > maxConfidence) { result.add(entry.getKey());
// ambigous = true;
} }
} }
if (ambiguous) { return PDF417Common.toIntArray(result);
return null;
}
return result;
} }
public Integer getConfidence(int value) { public Integer getConfidence(int value) {

View file

@ -24,7 +24,7 @@ import com.google.zxing.common.BitMatrix;
* @author Guenther Grau * @author Guenther Grau
*/ */
final class BoundingBox { final class BoundingBox {
private BitMatrix image; private BitMatrix image;
private ResultPoint topLeft; private ResultPoint topLeft;
private ResultPoint bottomLeft; private ResultPoint bottomLeft;
@ -40,8 +40,10 @@ final class BoundingBox {
ResultPoint bottomLeft, ResultPoint bottomLeft,
ResultPoint topRight, ResultPoint topRight,
ResultPoint bottomRight) throws NotFoundException { ResultPoint bottomRight) throws NotFoundException {
if ((topLeft == null && topRight == null) || (bottomLeft == null && bottomRight == null) || if ((topLeft == null && topRight == null) ||
(topLeft != null && bottomLeft == null) || (topRight != null && bottomRight == null)) { (bottomLeft == null && bottomRight == null) ||
(topLeft != null && bottomLeft == null) ||
(topRight != null && bottomRight == null)) {
throw NotFoundException.getNotFoundInstance(); throw NotFoundException.getNotFoundInstance();
} }
init(image, topLeft, bottomLeft, topRight, bottomRight); init(image, topLeft, bottomLeft, topRight, bottomRight);
@ -51,11 +53,10 @@ final class BoundingBox {
init(boundingBox.image, boundingBox.topLeft, boundingBox.bottomLeft, boundingBox.topRight, boundingBox.bottomRight); init(boundingBox.image, boundingBox.topLeft, boundingBox.bottomLeft, boundingBox.topRight, boundingBox.bottomRight);
} }
private void init(BitMatrix image, private void init(BitMatrix image,
ResultPoint topLeft, ResultPoint topLeft,
ResultPoint bottomLeft, ResultPoint bottomLeft,
ResultPoint topRight,
ResultPoint topRight,
ResultPoint bottomRight) { ResultPoint bottomRight) {
this.image = image; this.image = image;
this.topLeft = topLeft; this.topLeft = topLeft;
@ -75,7 +76,12 @@ final class BoundingBox {
return new BoundingBox(leftBox.image, leftBox.topLeft, leftBox.bottomLeft, rightBox.topRight, rightBox.bottomRight); 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) { if (missingStartRows > 0) {
ResultPoint top = isLeft ? topLeft : topRight; ResultPoint top = isLeft ? topLeft : topRight;
int newMinY = (int) top.getY() - missingStartRows; int newMinY = (int) top.getY() - missingStartRows;
@ -85,27 +91,29 @@ final class BoundingBox {
// TODO use existing points to better interpolate the new x positions // TODO use existing points to better interpolate the new x positions
ResultPoint newTop = new ResultPoint(top.getX(), newMinY); ResultPoint newTop = new ResultPoint(top.getX(), newMinY);
if (isLeft) { if (isLeft) {
topLeft = newTop; newTopLeft = newTop;
} else { } else {
topRight = newTop; newTopRight = newTop;
} }
} }
if (missingEndRows > 0) { if (missingEndRows > 0) {
ResultPoint bottom = isLeft ? bottomLeft : bottomRight; ResultPoint bottom = isLeft ? bottomLeft : bottomRight;
int newMaxY = (int) bottom.getY() - missingStartRows; int newMaxY = (int) bottom.getY() + missingEndRows;
if (newMaxY >= image.getHeight()) { if (newMaxY >= image.getHeight()) {
newMaxY = image.getHeight() - 1; newMaxY = image.getHeight() - 1;
} }
// TODO use existing points to better interpolate the new x positions // TODO use existing points to better interpolate the new x positions
ResultPoint newBottom = new ResultPoint(bottom.getX(), newMaxY); ResultPoint newBottom = new ResultPoint(bottom.getX(), newMaxY);
if (isLeft) { if (isLeft) {
bottomLeft = newBottom; newBottomLeft = newBottom;
} else { } else {
bottomRight = newBottom; newBottomRight = newBottom;
} }
} }
calculateMinMaxValues(); calculateMinMaxValues();
return new BoundingBox(image, newTopLeft, newBottomLeft, newTopRight, newBottomRight);
} }
private void calculateMinMaxValues() { private void calculateMinMaxValues() {

View file

@ -76,4 +76,9 @@ final class Codeword {
this.rowNumber = rowNumber; this.rowNumber = rowNumber;
} }
@Override
public String toString() {
return rowNumber + "|" + value;
}
} }

View file

@ -16,16 +16,20 @@
package com.google.zxing.pdf417.decoder; package com.google.zxing.pdf417.decoder;
import com.google.zxing.pdf417.PDF417Common;
import java.util.Formatter;
/** /**
* @author Guenther Grau * @author Guenther Grau
*/ */
final class DetectionResult { final class DetectionResult {
private static final int ADJUST_ROW_NUMBER_SKIP = 2; private static final int ADJUST_ROW_NUMBER_SKIP = 2;
private final BarcodeMetadata barcodeMetadata; private final BarcodeMetadata barcodeMetadata;
private final DetectionResultColumn[] detectionResultColumns; private final DetectionResultColumn[] detectionResultColumns;
private final BoundingBox boundingBox; private BoundingBox boundingBox;
private final int barcodeColumnCount; private final int barcodeColumnCount;
DetectionResult(BarcodeMetadata barcodeMetadata, BoundingBox boundingBox) { DetectionResult(BarcodeMetadata barcodeMetadata, BoundingBox boundingBox) {
@ -35,44 +39,10 @@ final class DetectionResult {
detectionResultColumns = new DetectionResultColumn[barcodeColumnCount + 2]; 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() { DetectionResultColumn[] getDetectionResultColumns() {
adjustIndicatorColumnRowNumbers(detectionResultColumns[0]); adjustIndicatorColumnRowNumbers(detectionResultColumns[0]);
adjustIndicatorColumnRowNumbers(detectionResultColumns[barcodeColumnCount + 1]); adjustIndicatorColumnRowNumbers(detectionResultColumns[barcodeColumnCount + 1]);
int unadjustedCodewordCount = 900; int unadjustedCodewordCount = PDF417Common.MAX_CODEWORDS_IN_BARCODE;
int previousUnadjustedCount; int previousUnadjustedCount;
do { do {
previousUnadjustedCount = unadjustedCodewordCount; previousUnadjustedCount = unadjustedCodewordCount;
@ -81,6 +51,13 @@ final class DetectionResult {
return detectionResultColumns; 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 // 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 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 // we should also fill the rows top to bottom and bottom to top
@ -108,6 +85,7 @@ final class DetectionResult {
} }
private int adjustRowNumbersByRow() { private int adjustRowNumbersByRow() {
adjustRowNumbersFromBothRI();
// TODO we should only do full row adjustments if row numbers of left and right row indicator column match. // 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 // 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 // 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(); 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() { private int adjustRowNumbersFromRRI() {
if (detectionResultColumns[barcodeColumnCount + 1] == null) { if (detectionResultColumns[barcodeColumnCount + 1] == null) {
return 0; return 0;
@ -128,9 +131,7 @@ final class DetectionResult {
} }
int rowIndicatorRowNumber = codewords[codewordsRow].getRowNumber(); int rowIndicatorRowNumber = codewords[codewordsRow].getRowNumber();
int invalidRowCounts = 0; int invalidRowCounts = 0;
for (int barcodeColumn = barcodeColumnCount + 1; for (int barcodeColumn = barcodeColumnCount + 1; barcodeColumn > 0 && invalidRowCounts < ADJUST_ROW_NUMBER_SKIP; barcodeColumn--) {
barcodeColumn > 0 && invalidRowCounts < ADJUST_ROW_NUMBER_SKIP;
barcodeColumn--) {
Codeword codeword = detectionResultColumns[barcodeColumn].getCodewords()[codewordsRow]; Codeword codeword = detectionResultColumns[barcodeColumn].getCodewords()[codewordsRow];
if (codeword != null) { if (codeword != null) {
invalidRowCounts = adjustRowNumberIfValid(rowIndicatorRowNumber, invalidRowCounts, codeword); invalidRowCounts = adjustRowNumberIfValid(rowIndicatorRowNumber, invalidRowCounts, codeword);
@ -155,9 +156,7 @@ final class DetectionResult {
} }
int rowIndicatorRowNumber = codewords[codewordsRow].getRowNumber(); int rowIndicatorRowNumber = codewords[codewordsRow].getRowNumber();
int invalidRowCounts = 0; int invalidRowCounts = 0;
for (int barcodeColumn = 1; for (int barcodeColumn = 1; barcodeColumn < barcodeColumnCount + 1 && invalidRowCounts < ADJUST_ROW_NUMBER_SKIP; barcodeColumn++) {
barcodeColumn < barcodeColumnCount + 1 && invalidRowCounts < ADJUST_ROW_NUMBER_SKIP;
barcodeColumn++) {
Codeword codeword = detectionResultColumns[barcodeColumn].getCodewords()[codewordsRow]; Codeword codeword = detectionResultColumns[barcodeColumn].getCodewords()[codewordsRow];
if (codeword != null) { if (codeword != null) {
invalidRowCounts = adjustRowNumberIfValid(rowIndicatorRowNumber, invalidRowCounts, codeword); invalidRowCounts = adjustRowNumberIfValid(rowIndicatorRowNumber, invalidRowCounts, codeword);
@ -171,7 +170,6 @@ final class DetectionResult {
} }
private static int adjustRowNumberIfValid(int rowIndicatorRowNumber, int invalidRowCounts, Codeword codeword) { private static int adjustRowNumberIfValid(int rowIndicatorRowNumber, int invalidRowCounts, Codeword codeword) {
if (codeword == null) { if (codeword == null) {
return invalidRowCounts; return invalidRowCounts;
} }
@ -252,8 +250,48 @@ final class DetectionResult {
return barcodeMetadata.getErrorCorrectionLevel(); return barcodeMetadata.getErrorCorrectionLevel();
} }
public void setBoundingBox(BoundingBox boundingBox) {
this.boundingBox = boundingBox;
}
BoundingBox getBoundingBox() { BoundingBox getBoundingBox() {
return boundingBox; 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;
}
} }

View file

@ -16,6 +16,8 @@
package com.google.zxing.pdf417.decoder; package com.google.zxing.pdf417.decoder;
import java.util.Formatter;
/** /**
* @author Guenther Grau * @author Guenther Grau
*/ */
@ -37,14 +39,14 @@ class DetectionResultColumn {
return codeword; return codeword;
} }
for (int i = 1; i < MAX_NEARBY_DISTANCE; i++) { for (int i = 1; i < MAX_NEARBY_DISTANCE; i++) {
int nearImageRow = getCodewordsIndex(imageRow) - i; int nearImageRow = imageRowToCodewordIndex(imageRow) - i;
if (nearImageRow >= 0) { if (nearImageRow >= 0) {
codeword = codewords[nearImageRow]; codeword = codewords[nearImageRow];
if (codeword != null) { if (codeword != null) {
return codeword; return codeword;
} }
} }
nearImageRow = getCodewordsIndex(imageRow) + i; nearImageRow = imageRowToCodewordIndex(imageRow) + i;
if (nearImageRow < codewords.length) { if (nearImageRow < codewords.length) {
codeword = codewords[nearImageRow]; codeword = codewords[nearImageRow];
if (codeword != null) { if (codeword != null) {
@ -55,20 +57,20 @@ class DetectionResultColumn {
return null; return null;
} }
final int getCodewordsIndex(int imageRow) { final int imageRowToCodewordIndex(int imageRow) {
return imageRow - boundingBox.getMinY(); return imageRow - boundingBox.getMinY();
} }
final int getImageRow(int codewordIndex) { final int codewordIndexToImageRow(int codewordIndex) {
return boundingBox.getMinY() + codewordIndex; return boundingBox.getMinY() + codewordIndex;
} }
final void setCodeword(int imageRow, Codeword codeword) { final void setCodeword(int imageRow, Codeword codeword) {
codewords[getCodewordsIndex(imageRow)] = codeword; codewords[imageRowToCodewordIndex(imageRow)] = codeword;
} }
final Codeword getCodeword(int imageRow) { final Codeword getCodeword(int imageRow) {
return codewords[getCodewordsIndex(imageRow)]; return codewords[imageRowToCodewordIndex(imageRow)];
} }
final BoundingBox getBoundingBox() { final BoundingBox getBoundingBox() {
@ -79,4 +81,20 @@ class DetectionResultColumn {
return codewords; 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;
}
} }

View file

@ -17,15 +17,13 @@
package com.google.zxing.pdf417.decoder; package com.google.zxing.pdf417.decoder;
import com.google.zxing.ResultPoint; import com.google.zxing.ResultPoint;
import com.google.zxing.pdf417.PDF417Common;
/** /**
* @author Guenther Grau * @author Guenther Grau
*/ */
final class DetectionResultRowIndicatorColumn extends DetectionResultColumn { 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; private final boolean isLeft;
DetectionResultRowIndicatorColumn(BoundingBox boundingBox, boolean isLeft) { DetectionResultRowIndicatorColumn(BoundingBox boundingBox, boolean isLeft) {
@ -41,32 +39,22 @@ final class DetectionResultRowIndicatorColumn extends DetectionResultColumn {
} }
} }
int[] getRowHeights() { // TODO implement properly
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 maybe we should add missing codewords to store the correct row number to make // TODO maybe we should add missing codewords to store the correct row number to make
// finding row numbers for other columns easier // finding row numbers for other columns easier
// use row height count to make detection of invalid row numbers more reliable // 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(); BoundingBox boundingBox = getBoundingBox();
ResultPoint top = isLeft ? boundingBox.getTopLeft() : boundingBox.getTopRight(); ResultPoint top = isLeft ? boundingBox.getTopLeft() : boundingBox.getTopRight();
ResultPoint bottom = isLeft ? boundingBox.getBottomLeft() : boundingBox.getBottomRight(); ResultPoint bottom = isLeft ? boundingBox.getBottomLeft() : boundingBox.getBottomRight();
int firstRow = getCodewordsIndex((int) top.getY()); int firstRow = imageRowToCodewordIndex((int) top.getY());
int lastRow = getCodewordsIndex((int) bottom.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(); float averageRowHeight = (lastRow - firstRow) / (float) barcodeMetadata.getRowCount();
Codeword[] codewords = getCodewords();
int barcodeRow = -1; int barcodeRow = -1;
int maxRowHeight = 1; int maxRowHeight = 1;
int currentRowHeight = 0; int currentRowHeight = 0;
@ -76,11 +64,6 @@ final class DetectionResultRowIndicatorColumn extends DetectionResultColumn {
} }
Codeword codeword = codewords[codewordsRow]; 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; // float expectedRowNumber = (codewordsRow - firstRow) / averageRowHeight;
// if (Math.abs(codeword.getRowNumber() - expectedRowNumber) > 2) { // if (Math.abs(codeword.getRowNumber() - expectedRowNumber) > 2) {
// SimpleLog.log(LEVEL.WARNING, // SimpleLog.log(LEVEL.WARNING,
@ -129,6 +112,63 @@ final class DetectionResultRowIndicatorColumn extends DetectionResultColumn {
return (int) (averageRowHeight + 0.5); 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() { BarcodeMetadata getBarcodeMetadata() {
Codeword[] codewords = getCodewords(); Codeword[] codewords = getCodewords();
BarcodeValue barcodeColumnCount = new BarcodeValue(); BarcodeValue barcodeColumnCount = new BarcodeValue();
@ -158,15 +198,18 @@ final class DetectionResultRowIndicatorColumn extends DetectionResultColumn {
break; break;
} }
} }
if ((barcodeColumnCount.getValue() == null) || (barcodeRowCountUpperPart.getValue() == null) || // Maybe we should check if we have ambiguous values?
(barcodeRowCountLowerPart.getValue() == null) || (barcodeECLevel.getValue() == null) || if ((barcodeColumnCount.getValue().length == 0) ||
barcodeColumnCount.getValue() < 1 || (barcodeRowCountUpperPart.getValue().length == 0) ||
barcodeRowCountUpperPart.getValue() + barcodeRowCountLowerPart.getValue() < MIN_BARCODE_ROWS || (barcodeRowCountLowerPart.getValue().length == 0) ||
barcodeRowCountUpperPart.getValue() + barcodeRowCountLowerPart.getValue() > MAX_BARCODE_ROWS) { (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; return null;
} }
BarcodeMetadata barcodeMetadata = new BarcodeMetadata(barcodeColumnCount.getValue(), BarcodeMetadata barcodeMetadata = new BarcodeMetadata(barcodeColumnCount.getValue()[0],
barcodeRowCountUpperPart.getValue(), barcodeRowCountLowerPart.getValue(), barcodeECLevel.getValue()); barcodeRowCountUpperPart.getValue()[0], barcodeRowCountLowerPart.getValue()[0], barcodeECLevel.getValue()[0]);
removeIncorrectCodewords(codewords, barcodeMetadata); removeIncorrectCodewords(codewords, barcodeMetadata);
return barcodeMetadata; return barcodeMetadata;
} }
@ -213,4 +256,9 @@ final class DetectionResultRowIndicatorColumn extends DetectionResultColumn {
return isLeft; return isLeft;
} }
@Override
public String toString() {
return "IsLeft: " + isLeft + '\n' + super.toString();
}
} }

View file

@ -27,14 +27,14 @@ import com.google.zxing.pdf417.decoder.ec.ErrorCorrection;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Collection; import java.util.Collection;
import java.util.Formatter;
import java.util.List;
/** /**
* @author Guenther Grau * @author Guenther Grau
*/ */
public final class PDF417ScanningDecoder { 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 CODEWORD_SKEW_SIZE = 2;
private static final int MAX_ERRORS = 3; private static final int MAX_ERRORS = 3;
@ -44,44 +44,41 @@ public final class PDF417ScanningDecoder {
private PDF417ScanningDecoder() { private PDF417ScanningDecoder() {
} }
// TODO don't pass in minCodewordWidth and maxCodewordWidth, pass in barcode columns for start and stop pattern // 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. // 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 // 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. // 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 imageTopLeft,
ResultPoint imageBottomLeft, ResultPoint imageBottomLeft,
ResultPoint imageTopRight, ResultPoint imageTopRight,
ResultPoint imageBottomRight, ResultPoint imageBottomRight,
int minCodewordWidth, int minCodewordWidth,
int maxCodewordWidth) int maxCodewordWidth) throws NotFoundException, FormatException, ChecksumException {
throws NotFoundException, FormatException, ChecksumException {
BoundingBox boundingBox = new BoundingBox(image, imageTopLeft, imageBottomLeft, imageTopRight, imageBottomRight); BoundingBox boundingBox = new BoundingBox(image, imageTopLeft, imageBottomLeft, imageTopRight, imageBottomRight);
DetectionResultRowIndicatorColumn leftRowIndicatorColumn = null; DetectionResultRowIndicatorColumn leftRowIndicatorColumn = null;
DetectionResultRowIndicatorColumn rightRowIndicatorColumn = null; DetectionResultRowIndicatorColumn rightRowIndicatorColumn = null;
DetectionResult detectionResult = null; DetectionResult detectionResult = null;
boolean again = true; for (int i = 0; i < 2; i++) {
while (again) {
again = false;
if (imageTopLeft != null) { if (imageTopLeft != null) {
leftRowIndicatorColumn = getRowIndicatorColumn(image, boundingBox, imageTopLeft, true, minCodewordWidth, leftRowIndicatorColumn = getRowIndicatorColumn(image, boundingBox, imageTopLeft, true, minCodewordWidth,
maxCodewordWidth); maxCodewordWidth);
leftRowIndicatorColumn.setRowNumbers();
} }
if (imageTopRight != null) { if (imageTopRight != null) {
rightRowIndicatorColumn = getRowIndicatorColumn(image, boundingBox, imageTopRight, false, minCodewordWidth, rightRowIndicatorColumn = getRowIndicatorColumn(image, boundingBox, imageTopRight, false, minCodewordWidth,
maxCodewordWidth); maxCodewordWidth);
rightRowIndicatorColumn.setRowNumbers();
} }
detectionResult = merge(leftRowIndicatorColumn, rightRowIndicatorColumn); detectionResult = merge(leftRowIndicatorColumn, rightRowIndicatorColumn);
if (detectionResult == null) { if (detectionResult == null) {
throw NotFoundException.getNotFoundInstance(); throw NotFoundException.getNotFoundInstance();
} }
if (detectionResult.getBoundingBox().getMinY() < boundingBox.getMinY() || if (i == 0 &&
detectionResult.getBoundingBox().getMaxY() > boundingBox.getMaxY()) { (detectionResult.getBoundingBox().getMinY() < boundingBox.getMinY() || detectionResult.getBoundingBox()
.getMaxY() > boundingBox.getMaxY())) {
boundingBox = detectionResult.getBoundingBox(); boundingBox = detectionResult.getBoundingBox();
again = true; } else {
detectionResult.setBoundingBox(boundingBox);
break;
} }
} }
int maxBarcodeColumn = detectionResult.getBarcodeColumnCount() + 1; int maxBarcodeColumn = detectionResult.getBarcodeColumnCount() + 1;
@ -105,7 +102,7 @@ public final class PDF417ScanningDecoder {
int startColumn = -1; int startColumn = -1;
int previousStartColumn = startColumn; int previousStartColumn = startColumn;
// TODO start at a row for which we know the start position, then detect upwards and downwards from there. // 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); startColumn = getStartColumn(detectionResult, barcodeColumn, imageRow, leftToRight);
if (startColumn < 0 || startColumn > boundingBox.getMaxX()) { if (startColumn < 0 || startColumn > boundingBox.getMaxX()) {
if (previousStartColumn == -1) { if (previousStartColumn == -1) {
@ -136,20 +133,13 @@ public final class PDF417ScanningDecoder {
if (barcodeMetadata == null) { if (barcodeMetadata == null) {
return null; return null;
} }
BoundingBox boundingBox = getBoundingBox(leftRowIndicatorColumn, rightRowIndicatorColumn); BoundingBox boundingBox = BoundingBox.merge(adjustBoundingBox(leftRowIndicatorColumn),
adjustBoundingBox(rightRowIndicatorColumn));
return new DetectionResult(barcodeMetadata, boundingBox); return new DetectionResult(barcodeMetadata, boundingBox);
} }
private static BoundingBox getBoundingBox(DetectionResultRowIndicatorColumn leftRowIndicatorColumn, private static BoundingBox adjustBoundingBox(DetectionResultRowIndicatorColumn rowIndicatorColumn)
DetectionResultRowIndicatorColumn rightRowIndicatorColumn)
throws NotFoundException { throws NotFoundException {
BoundingBox box1 = adjustBoundingBox(leftRowIndicatorColumn);
BoundingBox box2 = adjustBoundingBox(rightRowIndicatorColumn);
return BoundingBox.merge(box1, box2);
}
private static BoundingBox adjustBoundingBox(DetectionResultRowIndicatorColumn rowIndicatorColumn) {
if (rowIndicatorColumn == null) { if (rowIndicatorColumn == null) {
return null; return null;
} }
@ -157,23 +147,27 @@ public final class PDF417ScanningDecoder {
int maxRowHeight = getMax(rowHeights); int maxRowHeight = getMax(rowHeights);
int missingStartRows = 0; int missingStartRows = 0;
for (int rowHeight : rowHeights) { for (int rowHeight : rowHeights) {
if (rowHeight < maxRowHeight - ROW_HEIGHT_SKEW) { missingStartRows += maxRowHeight - rowHeight;
missingStartRows++; if (rowHeight > 0) {
} else {
break; break;
} }
} }
Codeword[] codewords = rowIndicatorColumn.getCodewords();
for (int row = 0; missingStartRows > 0 && codewords[row] == null; row++) {
missingStartRows--;
}
int missingEndRows = 0; int missingEndRows = 0;
for (int row = rowHeights.length - 1; row >= 0; row--) { for (int row = rowHeights.length - 1; row >= 0; row--) {
if (rowHeights[row] < maxRowHeight - ROW_HEIGHT_SKEW) { missingEndRows += maxRowHeight - rowHeights[row];
missingEndRows++; if (rowHeights[row] > 0) {
} else {
break; 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()); rowIndicatorColumn.isLeft());
return rowIndicatorColumn.getBoundingBox();
} }
private static int getMax(int[] values) { private static int getMax(int[] values) {
@ -203,9 +197,12 @@ public final class PDF417ScanningDecoder {
return leftBarcodeMetadata; return leftBarcodeMetadata;
} }
private static DetectionResultRowIndicatorColumn getRowIndicatorColumn(BitMatrix image, BoundingBox boundingBox, private static DetectionResultRowIndicatorColumn getRowIndicatorColumn(BitMatrix image,
ResultPoint startPoint, boolean leftToRight, BoundingBox boundingBox,
int minCodewordWidth, int maxCodewordWidth) { ResultPoint startPoint,
boolean leftToRight,
int minCodewordWidth,
int maxCodewordWidth) {
DetectionResultRowIndicatorColumn rowIndicatorColumn = new DetectionResultRowIndicatorColumn(boundingBox, DetectionResultRowIndicatorColumn rowIndicatorColumn = new DetectionResultRowIndicatorColumn(boundingBox,
leftToRight); leftToRight);
for (int i = 0; i < 2; i++) { for (int i = 0; i < 2; i++) {
@ -228,38 +225,114 @@ public final class PDF417ScanningDecoder {
return rowIndicatorColumn; return rowIndicatorColumn;
} }
private static DecoderResult createDecoderResult(DetectionResult detectionResult) throws NotFoundException, private static void adjustCodewordCount(DetectionResult detectionResult, BarcodeValue[][] barcodeMatrix)
FormatException, ChecksumException { throws NotFoundException {
BarcodeMatrix barcodeMatrix = createBarcodeMatrix(detectionResult); int[] numberOfCodewords = barcodeMatrix[0][1].getValue();
Integer numberOfCodewords = barcodeMatrix.getValue(0, 1); int calculatedNumberOfCodewords = detectionResult.getBarcodeColumnCount() *
int calculatedNumberOfCodewords = detectionResult.getBarcodeColumnCount() * detectionResult.getBarcodeRowCount() - detectionResult.getBarcodeRowCount() -
getNumberOfECCodeWords(detectionResult.getBarcodeECLevel()); getNumberOfECCodeWords(detectionResult.getBarcodeECLevel());
if (numberOfCodewords == null) { if (numberOfCodewords.length == 0) {
if (calculatedNumberOfCodewords < 1 || calculatedNumberOfCodewords > PDF417Common.MAX_CODEWORDS_IN_BARCODE) { if (calculatedNumberOfCodewords < 1 || calculatedNumberOfCodewords > PDF417Common.MAX_CODEWORDS_IN_BARCODE) {
throw NotFoundException.getNotFoundInstance(); throw NotFoundException.getNotFoundInstance();
} }
barcodeMatrix.setValue(0, 1, calculatedNumberOfCodewords); barcodeMatrix[0][1].setValue(calculatedNumberOfCodewords);
} else if (numberOfCodewords != calculatedNumberOfCodewords) { } else if (numberOfCodewords[0] != calculatedNumberOfCodewords) {
// The calculated one is more reliable as it is derived from the row indicator column // The calculated one is more reliable as it is derived from the row indicator columns
numberOfCodewords = calculatedNumberOfCodewords; barcodeMatrix[0][1].setValue(calculatedNumberOfCodewords);
} }
}
private static DecoderResult createDecoderResult(DetectionResult detectionResult) throws FormatException,
ChecksumException, NotFoundException {
BarcodeValue[][] barcodeMatrix = createBarcodeMatrix(detectionResult);
adjustCodewordCount(detectionResult, barcodeMatrix);
Collection<Integer> erasures = new ArrayList<Integer>(); Collection<Integer> erasures = new ArrayList<Integer>();
int[] codewords = new int[detectionResult.getBarcodeRowCount() * detectionResult.getBarcodeColumnCount()]; int[] codewords = new int[detectionResult.getBarcodeRowCount() * detectionResult.getBarcodeColumnCount()];
List<int[]> ambiguousIndexValuesList = new ArrayList<int[]>();
List<Integer> ambiguousIndexesList = new ArrayList<Integer>();
for (int row = 0; row < detectionResult.getBarcodeRowCount(); row++) { for (int row = 0; row < detectionResult.getBarcodeRowCount(); row++) {
for (int column = 0; column < detectionResult.getBarcodeColumnCount(); column++) { for (int column = 0; column < detectionResult.getBarcodeColumnCount(); column++) {
Integer codeword = barcodeMatrix.getValue(row, column + 1); int[] values = barcodeMatrix[row][column + 1].getValue();
if (codeword == null) { int codewordIndex = row * detectionResult.getBarcodeColumnCount() + column;
erasures.add(row * detectionResult.getBarcodeColumnCount() + column); if (values.length == 0) {
erasures.add(codewordIndex);
} else if (values.length == 1) {
codewords[codewordIndex] = values[0];
} else { } 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; int column = -1;
for (DetectionResultColumn detectionResultColumn : detectionResult.getDetectionResultColumns()) { for (DetectionResultColumn detectionResultColumn : detectionResult.getDetectionResultColumns()) {
@ -268,10 +341,10 @@ public final class PDF417ScanningDecoder {
continue; continue;
} }
for (Codeword codeword : detectionResultColumn.getCodewords()) { for (Codeword codeword : detectionResultColumn.getCodewords()) {
if (codeword == null) { if (codeword == null || codeword.getRowNumber() == -1) {
continue; continue;
} }
barcodeMatrix.setValue(codeword.getRowNumber(), column, codeword.getValue()); barcodeMatrix[codeword.getRowNumber()][column].setValue(codeword.getValue());
} }
} }
return barcodeMatrix; return barcodeMatrix;
@ -281,7 +354,9 @@ public final class PDF417ScanningDecoder {
return barcodeColumn >= 0 && barcodeColumn <= detectionResult.getBarcodeColumnCount() + 1; 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) { boolean leftToRight) {
int offset = leftToRight ? 1 : -1; int offset = leftToRight ? 1 : -1;
Codeword codeword = null; Codeword codeword = null;
@ -307,8 +382,10 @@ public final class PDF417ScanningDecoder {
barcodeColumn -= offset; barcodeColumn -= offset;
for (Codeword previousRowCodeword : detectionResult.getDetectionResultColumn(barcodeColumn).getCodewords()) { for (Codeword previousRowCodeword : detectionResult.getDetectionResultColumn(barcodeColumn).getCodewords()) {
if (previousRowCodeword != null) { if (previousRowCodeword != null) {
return (leftToRight ? previousRowCodeword.getEndX() : previousRowCodeword.getStartX()) + offset * return (leftToRight ? previousRowCodeword.getEndX() : previousRowCodeword.getStartX()) +
skippedColumns * (previousRowCodeword.getEndX() - previousRowCodeword.getStartX()); offset *
skippedColumns *
(previousRowCodeword.getEndX() - previousRowCodeword.getStartX());
} }
} }
skippedColumns++; skippedColumns++;
@ -316,8 +393,14 @@ public final class PDF417ScanningDecoder {
return leftToRight ? detectionResult.getBoundingBox().getMinX() : detectionResult.getBoundingBox().getMaxX(); return leftToRight ? detectionResult.getBoundingBox().getMinX() : detectionResult.getBoundingBox().getMaxX();
} }
private static Codeword detectCodeword(BitMatrix image, int minColumn, int maxColumn, boolean leftToRight, private static Codeword detectCodeword(BitMatrix image,
int startColumn, int imageRow, int minCodewordWidth, int maxCodewordWidth) { int minColumn,
int maxColumn,
boolean leftToRight,
int startColumn,
int imageRow,
int minCodewordWidth,
int maxCodewordWidth) {
startColumn = adjustCodewordStartColumn(image, minColumn, maxColumn, leftToRight, startColumn, imageRow); 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 // 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. // 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); return new Codeword(startColumn, endColumn, getCodewordBucketNumber(decodedValue), codeword);
} }
private static int[] getModuleBitCount(BitMatrix image, int minColumn, int maxColumn, boolean leftToRight, private static int[] getModuleBitCount(BitMatrix image,
int startColumn, int imageRow) { int minColumn,
int maxColumn,
boolean leftToRight,
int startColumn,
int imageRow) {
int imageColumn = startColumn; int imageColumn = startColumn;
int[] moduleBitCount = new int[8]; int[] moduleBitCount = new int[8];
int moduleNumber = 0; int moduleNumber = 0;
@ -396,11 +483,11 @@ public final class PDF417ScanningDecoder {
return 2 << barcodeECLevel; return 2 << barcodeECLevel;
} }
private static int adjustCodewordStartColumn(BitMatrix image, private static int adjustCodewordStartColumn(BitMatrix image,
int minColumn, int minColumn,
int maxColumn, int maxColumn,
boolean leftToRight, boolean leftToRight,
int codewordStartColumn, int codewordStartColumn,
int imageRow) { int imageRow) {
int correctedStartColumn = codewordStartColumn; int correctedStartColumn = codewordStartColumn;
int increment = leftToRight ? -1 : 1; int increment = leftToRight ? -1 : 1;
@ -424,23 +511,13 @@ public final class PDF417ScanningDecoder {
codewordSize <= maxCodewordWidth + CODEWORD_SKEW_SIZE; codewordSize <= maxCodewordWidth + CODEWORD_SKEW_SIZE;
} }
private static int[] getErasureArray(Collection<Integer> list) { private static DecoderResult decodeCodewords(int[] codewords, int ecLevel, int[] erasures) throws FormatException,
int[] result = new int[list.size()]; ChecksumException {
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 {
if (codewords.length == 0) { if (codewords.length == 0) {
throw FormatException.getFormatInstance(); throw FormatException.getFormatInstance();
} }
int numECCodewords = 1 << (ecLevel + 1); int numECCodewords = 1 << (ecLevel + 1);
int correctedErrorsCount = correctErrors(codewords, erasures, numECCodewords); int correctedErrorsCount = correctErrors(codewords, erasures, numECCodewords);
verifyCodewordCount(codewords, numECCodewords); verifyCodewordCount(codewords, numECCodewords);
@ -457,16 +534,20 @@ public final class PDF417ScanningDecoder {
* *
* @param codewords data and error correction codewords * @param codewords data and error correction codewords
* @param erasures positions of any known erasures * @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 * @throws ChecksumException if error correction fails
*/ */
private static int correctErrors(int[] codewords, int[] erasures, int numECCodewords) throws ChecksumException { 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 // Too many errors or EC Codewords is corrupted
throw ChecksumException.getChecksumInstance(); throw ChecksumException.getChecksumInstance();
} }
return errorCorrection.decode(codewords, numECCodewords, erasures); return errorCorrection.decode(codewords, numECCodewords, erasures);
} }
/** /**
* Verify that all is OK with the codeword array. * 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; 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;
}
} }

View file

@ -38,8 +38,8 @@ import java.util.Map;
*/ */
public final class Detector { public final class Detector {
private static final int[] INDEXES_START_PATTERN = { 0, 4, 1, 5 }; 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_STOP_PATTERN = {6, 2, 7, 3};
private static final int INTEGER_MATH_SHIFT = 8; 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 PATTERN_MATCH_RESULT_SCALE_FACTOR = 1 << INTEGER_MATH_SHIFT;
private static final int MAX_AVG_VARIANCE = (int) (PATTERN_MATCH_RESULT_SCALE_FACTOR * 0.42f); 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 // B S B S B S B S Bar/Space pattern
// 11111111 0 1 0 1 0 1 000 // 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 // 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_PIXEL_DRIFT = 3;
private static final int MAX_PATTERN_DRIFT = 5; 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. // 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 ROW_STEP = 5;
private static final int BARCODE_MIN_HEIGHT = 10; private static final int BARCODE_MIN_HEIGHT = 10;
private final BinaryBitmap image; private Detector() {
public Detector(BinaryBitmap image) {
this.image = image;
}
/**
* <p>Detects a PDF417 Code in an image, simply.</p>
*
* @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);
} }
/** /**
@ -85,7 +72,8 @@ public final class Detector {
* @return {@link PDF417DetectorResult} encapsulating results of detecting a PDF417 code * @return {@link PDF417DetectorResult} encapsulating results of detecting a PDF417 code
* @throws NotFoundException if no PDF417 Code can be found * @throws NotFoundException if no PDF417 Code can be found
*/ */
PDF417DetectorResult detect(Map<DecodeHintType,?> hints, boolean multiple) throws NotFoundException { public static PDF417DetectorResult detect(BinaryBitmap image, Map<DecodeHintType,?> hints, boolean multiple)
throws NotFoundException {
// TODO detection improvement, tryHarder could try several different luminance thresholds/blackpoints or even // TODO detection improvement, tryHarder could try several different luminance thresholds/blackpoints or even
// different binarizers // different binarizers
//boolean tryHarder = hints != null && hints.containsKey(DecodeHintType.TRY_HARDER); //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 * @param bitMatrix bit matrix to detect barcodes in
* @return List of ResultPoint arrays containing the coordinates of found barcodes * @return List of ResultPoint arrays containing the coordinates of found barcodes
*/ */
private List<ResultPoint[]> detect(boolean multiple, BitMatrix bitMatrix) { private static List<ResultPoint[]> detect(boolean multiple, BitMatrix bitMatrix) {
List<ResultPoint[]> barcodeCoordinates = new ArrayList<ResultPoint[]>(); List<ResultPoint[]> barcodeCoordinates = new ArrayList<ResultPoint[]>();
int row = 0; int row = 0;
int column = 0; int column = 0;
boolean foundBarcodeInRow = false; boolean foundBarcodeInRow = false;
while (row < image.getHeight()) { while (row < bitMatrix.getHeight()) {
ResultPoint[] vertices = findVertices(bitMatrix, row, column); ResultPoint[] vertices = findVertices(bitMatrix, row, column);
if (vertices[0] == null && vertices[3] == null) { 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, private static ResultPoint[] findRowsWithPattern(BitMatrix matrix,
int startColumn, int[] pattern) { int height,
int width,
int startRow,
int startColumn,
int[] pattern) {
ResultPoint[] result = new ResultPoint[4]; ResultPoint[] result = new ResultPoint[4];
boolean found = false; boolean found = false;
int[] counters = new int[pattern.length]; int[] counters = new int[pattern.length];
@ -251,14 +243,15 @@ public final class Detector {
// Last row of the current symbol that contains pattern // Last row of the current symbol that contains pattern
if (found) { if (found) {
int skippedRowCount = 0; 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++) { for (; stopRow < height; stopRow++) {
int[] loc = findGuardPattern(matrix, previousRowLoc[0], stopRow, width, false, pattern, counters); 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 // 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 // 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 // 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. // 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) { Math.abs(previousRowLoc[1] - loc[1]) < MAX_PATTERN_DRIFT) {
previousRowLoc = loc; previousRowLoc = loc;
skippedRowCount = 0; skippedRowCount = 0;
@ -270,7 +263,7 @@ public final class Detector {
} }
} }
} }
stopRow -= skippedRowCount; stopRow -= skippedRowCount + 1;
result[2] = new ResultPoint(previousRowLoc[0], stopRow); result[2] = new ResultPoint(previousRowLoc[0], stopRow);
result[3] = new ResultPoint(previousRowLoc[1], 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 * @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. * @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, private static int[] findGuardPattern(BitMatrix matrix,
int[] pattern, int[] counters) { int column,
int row,
int width,
boolean whiteFirst,
int[] pattern,
int[] counters) {
Arrays.fill(counters, 0, counters.length, 0); Arrays.fill(counters, 0, counters.length, 0);
int patternLength = pattern.length; int patternLength = pattern.length;
boolean isWhite = whiteFirst; boolean isWhite = whiteFirst;
@ -313,7 +311,7 @@ public final class Detector {
} else { } else {
if (counterPosition == patternLength - 1) { if (counterPosition == patternLength - 1) {
if (patternMatchVariance(counters, pattern, MAX_INDIVIDUAL_VARIANCE) < MAX_AVG_VARIANCE) { 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]; patternStart += counters[0] + counters[1];
System.arraycopy(counters, 2, counters, 0, patternLength - 2); System.arraycopy(counters, 2, counters, 0, patternLength - 2);
@ -329,7 +327,7 @@ public final class Detector {
} }
if (counterPosition == patternLength - 1) { if (counterPosition == patternLength - 1) {
if (patternMatchVariance(counters, pattern, MAX_INDIVIDUAL_VARIANCE) < MAX_AVG_VARIANCE) { if (patternMatchVariance(counters, pattern, MAX_INDIVIDUAL_VARIANCE) < MAX_AVG_VARIANCE) {
return new int[] { patternStart, x - 1 }; return new int[] {patternStart, x - 1};
} }
} }
return null; return null;

Binary file not shown.

After

Width:  |  Height:  |  Size: 345 KiB

Binary file not shown.

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.7 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.5 MiB

Binary file not shown.

View file

@ -296,7 +296,7 @@ public abstract class AbstractBlackBoxTestCase extends Assert {
return true; 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()); StringBuilder result = new StringBuilder((int) file.length());
InputStreamReader reader = new InputStreamReader(new FileInputStream(file), charset); InputStreamReader reader = new InputStreamReader(new FileInputStream(file), charset);
try { try {

View file

@ -16,7 +16,7 @@
package com.google.zxing.common; package com.google.zxing.common;
final class TestResult { public final class TestResult {
private final int mustPassCount; private final int mustPassCount;
private final int tryHarderCount; private final int tryHarderCount;
@ -24,7 +24,7 @@ final class TestResult {
private final int maxTryHarderMisreads; private final int maxTryHarderMisreads;
private final float rotation; 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.mustPassCount = mustPassCount;
this.tryHarderCount = tryHarderCount; this.tryHarderCount = tryHarderCount;
this.maxMisreads = maxMisreads; this.maxMisreads = maxMisreads;
@ -32,23 +32,23 @@ final class TestResult {
this.rotation = rotation; this.rotation = rotation;
} }
int getMustPassCount() { public int getMustPassCount() {
return mustPassCount; return mustPassCount;
} }
int getTryHarderCount() { public int getTryHarderCount() {
return tryHarderCount; return tryHarderCount;
} }
int getMaxMisreads() { public int getMaxMisreads() {
return maxMisreads; return maxMisreads;
} }
int getMaxTryHarderMisreads() { public int getMaxTryHarderMisreads() {
return maxTryHarderMisreads; return maxTryHarderMisreads;
} }
float getRotation() { public float getRotation() {
return rotation; return rotation;
} }

View file

@ -29,9 +29,8 @@ public final class PDF417BlackBox2TestCase extends AbstractBlackBoxTestCase {
public PDF417BlackBox2TestCase() { public PDF417BlackBox2TestCase() {
super("test/data/blackbox/pdf417-2", new MultiFormatReader(), BarcodeFormat.PDF_417); super("test/data/blackbox/pdf417-2", new MultiFormatReader(), BarcodeFormat.PDF_417);
// TODO this should be 25,25,0,0 addTest(25, 25, 0, 0, 0.0f);
addTest(23, 23, 1, 1, 0.0f); addTest(25, 25, 0, 0, 180.0f);
addTest(23, 23, 1, 1, 180.0f);
} }
} }

View file

@ -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<TestResult> testResults = new ArrayList<TestResult>();
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<String,List<File>> 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<String,List<File>> 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<Result> results = new ArrayList<Result>();
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<Result>() {
@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<DecodeHintType,Object> hints = new EnumMap<DecodeHintType,Object>(DecodeHintType.class);
if (tryHarder) {
hints.put(DecodeHintType.TRY_HARDER, Boolean.TRUE);
}
return barcodeReader.decodeMultiple(source, hints);
}
private Map<String,List<File>> getImageFileLists() {
Map<String,List<File>> result = new HashMap<String,List<File>>();
for (File file : getImageFiles()) {
String testImageFileName = file.getName();
String fileBaseName = testImageFileName.substring(0, testImageFileName.indexOf('-'));
List<File> files = result.get(fileBaseName);
if (files == null) {
files = new ArrayList<File>();
result.put(fileBaseName, files);
}
files.add(file);
}
return result;
}
}

View file

@ -17,6 +17,7 @@
package com.google.zxing.pdf417.decoder.ec; package com.google.zxing.pdf417.decoder.ec;
import com.google.zxing.ChecksumException; import com.google.zxing.ChecksumException;
import org.junit.Test; import org.junit.Test;
import java.util.Random; import java.util.Random;
@ -32,25 +33,16 @@ public final class ErrorCorrectionTestCase extends AbstractErrorCorrectionTestCa
//private static final int[] PDF417_TEST_WITH_EC = //private static final int[] PDF417_TEST_WITH_EC =
// { 5, 453, 178, 121, 239, 452, 327, 657, 619 }; // { 5, 453, 178, 121, 239, 452, 327, 657, 619 };
private static final int[] PDF417_TEST = private static final int[] PDF417_TEST = {
{ 48,901, 56,141,627,856,330, 69,244,900, 48, 901, 56, 141, 627, 856, 330, 69, 244, 900, 852, 169, 843, 895, 852, 895, 913, 154, 845, 778, 387, 89, 869,
852,169,843,895,852,895,913,154,845,778, 901, 219, 474, 543, 650, 169, 201, 9, 160, 35, 70, 900, 900, 900, 900, 900, 900, 900, 900, 900, 900, 900, 900,
387, 89,869,901,219,474,543,650,169,201, 900, 900};
9,160, 35, 70,900,900,900,900,900,900, private static final int[] PDF417_TEST_WITH_EC = {
900,900,900,900,900,900,900,900}; 48, 901, 56, 141, 627, 856, 330, 69, 244, 900, 852, 169, 843, 895, 852, 895, 913, 154, 845, 778, 387, 89, 869,
private static final int[] PDF417_TEST_WITH_EC = 901, 219, 474, 543, 650, 169, 201, 9, 160, 35, 70, 900, 900, 900, 900, 900, 900, 900, 900, 900, 900, 900, 900,
{ 48,901, 56,141,627,856,330, 69,244,900, 900, 900, 769, 843, 591, 910, 605, 206, 706, 917, 371, 469, 79, 718, 47, 777, 249, 262, 193, 620, 597, 477, 450,
852,169,843,895,852,895,913,154,845,778, 806, 908, 309, 153, 871, 686, 838, 185, 674, 68, 679, 691, 794, 497, 479, 234, 250, 496, 43, 347, 582, 882, 536,
387, 89,869,901,219,474,543,650,169,201, 322, 317, 273, 194, 917, 237, 420, 859, 340, 115, 222, 808, 866, 836, 417, 121, 833, 459, 64, 159};
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; 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: // Example is EC level 1 (s=1). The number of erasures (l) and substitutions (f) must obey:
// l + 2f <= 2^(s+1) - 3 // l + 2f <= 2^(s+1) - 3
@ -81,7 +73,7 @@ public final class ErrorCorrectionTestCase extends AbstractErrorCorrectionTestCa
@Test @Test
public void testMaxErrors() throws ChecksumException { public void testMaxErrors() throws ChecksumException {
Random random = getRandom(); 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(); int[] received = PDF417_TEST_WITH_EC.clone();
corrupt(received, MAX_ERRORS, random); corrupt(received, MAX_ERRORS, random);
checkDecode(received); checkDecode(received);
@ -137,4 +129,4 @@ public final class ErrorCorrectionTestCase extends AbstractErrorCorrectionTestCa
} }
} }
} }