mirror of
https://github.com/zxing/zxing.git
synced 2025-03-05 20:48:51 -08:00
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:
parent
a6f8559478
commit
8fbcb53126
|
@ -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<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.
|
||||
*
|
||||
|
@ -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};
|
||||
}
|
||||
|
|
|
@ -84,7 +84,7 @@ public final class PDF417Reader implements Reader, MultipleBarcodeReader {
|
|||
private static Result[] decode(BinaryBitmap image, Map<DecodeHintType, ?> hints, boolean multiple)
|
||||
throws NotFoundException, FormatException, ChecksumException {
|
||||
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()) {
|
||||
DecoderResult decoderResult = PDF417ScanningDecoder.decode(detectorResult.getBits(), points[4], points[5],
|
||||
points[6], points[7], getMinCodewordWidth(points), getMaxCodewordWidth(points));
|
||||
|
|
|
@ -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();
|
||||
}
|
||||
|
||||
}
|
|
@ -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<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);
|
||||
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<Integer> result = new ArrayList<Integer>();
|
||||
for (Entry<Integer,Integer> 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) {
|
||||
|
|
|
@ -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() {
|
||||
|
|
|
@ -76,4 +76,9 @@ final class Codeword {
|
|||
this.rowNumber = rowNumber;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return rowNumber + "|" + value;
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -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();
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -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<Integer> erasures = new ArrayList<Integer>();
|
||||
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 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<Integer> 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;
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
||||
/**
|
||||
* <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);
|
||||
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<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
|
||||
// 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<ResultPoint[]> detect(boolean multiple, BitMatrix bitMatrix) {
|
||||
private static List<ResultPoint[]> detect(boolean multiple, BitMatrix bitMatrix) {
|
||||
List<ResultPoint[]> barcodeCoordinates = new ArrayList<ResultPoint[]>();
|
||||
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;
|
||||
|
|
Binary file not shown.
BIN
core/test/data/blackbox/pdf417-4/01-01.png
Normal file
BIN
core/test/data/blackbox/pdf417-4/01-01.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 345 KiB |
BIN
core/test/data/blackbox/pdf417-4/01.bin
Normal file
BIN
core/test/data/blackbox/pdf417-4/01.bin
Normal file
Binary file not shown.
BIN
core/test/data/blackbox/pdf417-4/02-01.png
Normal file
BIN
core/test/data/blackbox/pdf417-4/02-01.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 1.7 MiB |
BIN
core/test/data/blackbox/pdf417-4/02-02.png
Normal file
BIN
core/test/data/blackbox/pdf417-4/02-02.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 1.5 MiB |
BIN
core/test/data/blackbox/pdf417-4/02.bin
Normal file
BIN
core/test/data/blackbox/pdf417-4/02.bin
Normal file
Binary file not shown.
|
@ -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 {
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
||||
}
|
|
@ -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
|
|||
}
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
|
Loading…
Reference in a new issue