mirror of
https://github.com/zxing/zxing.git
synced 2025-01-12 11:47:26 -08:00
PDF417 improvements from Hartmut, Christoph
git-svn-id: https://zxing.googlecode.com/svn/trunk@2594 59b500cc-1b3d-0410-9834-0bbf25fbcc57
This commit is contained in:
parent
843a181d73
commit
a42d32ca15
2
AUTHORS
2
AUTHORS
|
@ -20,6 +20,7 @@ Brian Brown (Google)
|
|||
Bruce Allen
|
||||
Chang Hyun Park
|
||||
Christian Brunschen (Google)
|
||||
Christoph Schulz (creatale GmbH)
|
||||
crowdin.net
|
||||
Daniel Switkin (Google)
|
||||
Dave MacLachlan (Google)
|
||||
|
@ -39,6 +40,7 @@ Fred Lin (Anobiit)
|
|||
gcstang
|
||||
Guillaume Le Biller
|
||||
Hannes Erven
|
||||
Hartmut Neubauer (Schweers Informationstechnologie GmbH)
|
||||
hosigumayuugi
|
||||
hypest (Barcorama project)
|
||||
Ian W. Davis
|
||||
|
|
|
@ -26,11 +26,8 @@ import com.google.zxing.common.BitMatrix;
|
|||
*
|
||||
* @author SITA Lab (kevin.osullivan@sita.aero)
|
||||
*/
|
||||
final class BitMatrixParser {
|
||||
public final class BitMatrixParser {
|
||||
|
||||
private static final int[] NO_ERRORS = new int[0];
|
||||
|
||||
private static final int MAX_ROW_DIFFERENCE = 6;
|
||||
private static final int MAX_ROWS = 90;
|
||||
//private static final int MAX_COLUMNS = 30;
|
||||
// Maximum Codewords (Data + Error)
|
||||
|
@ -38,7 +35,7 @@ final class BitMatrixParser {
|
|||
private static final int MODULES_IN_SYMBOL = 17;
|
||||
|
||||
private final BitMatrix bitMatrix;
|
||||
private int rows = 0;
|
||||
//private int rows = 0;
|
||||
//private int columns = 0;
|
||||
|
||||
private int leftColumnECData = 0;
|
||||
|
@ -61,138 +58,47 @@ final class BitMatrixParser {
|
|||
* @return an array of codewords.
|
||||
*/
|
||||
int[] readCodewords() throws FormatException {
|
||||
int width = bitMatrix.getWidth();
|
||||
//int width = bitMatrix.getWidth();
|
||||
int height = bitMatrix.getHeight();
|
||||
|
||||
erasures = new int[MAX_CW_CAPACITY];
|
||||
|
||||
// Get the number of pixels in a module across the X dimension
|
||||
//float moduleWidth = bitMatrix.getModuleWidth();
|
||||
float moduleWidth = 1.0f; // Image has been sampled and reduced
|
||||
|
||||
int[] rowCounters = new int[width];
|
||||
int[] codewords = new int[MAX_CW_CAPACITY];
|
||||
int next = 0;
|
||||
int matchingConsecutiveScans = 0;
|
||||
boolean rowInProgress = false;
|
||||
int rowNumber = 0;
|
||||
int rowHeight = 0;
|
||||
for (int i = 1; i < height; i++) {
|
||||
for (int i = 0; i < height; i++) {
|
||||
if (rowNumber >= MAX_ROWS) {
|
||||
// Something is wrong, since we have exceeded
|
||||
// the maximum rows in the specification.
|
||||
throw FormatException.getFormatInstance();
|
||||
}
|
||||
int rowDifference = 0;
|
||||
// Scan a line of modules and check the
|
||||
// difference between this and the previous line
|
||||
for (int j = 0; j < width; j++) {
|
||||
// Accumulate differences between this line and the
|
||||
// previous line.
|
||||
if (bitMatrix.get(j, i) != bitMatrix.get(j, i - 1)) {
|
||||
rowDifference++;
|
||||
}
|
||||
}
|
||||
if (rowDifference <= moduleWidth * MAX_ROW_DIFFERENCE) {
|
||||
for (int j = 0; j < width; j++) {
|
||||
// Accumulate the black pixels on this line
|
||||
if (bitMatrix.get(j, i)) {
|
||||
rowCounters[j]++;
|
||||
}
|
||||
}
|
||||
// Increment the number of consecutive rows of pixels
|
||||
// that are more or less the same
|
||||
matchingConsecutiveScans++;
|
||||
// Height of a row is a multiple of the module size in pixels
|
||||
// It's supposed to be >= 3x module width, but, accept anything >= 2x
|
||||
if ((matchingConsecutiveScans + 1) >= 2.0f * moduleWidth) {
|
||||
// We have some previous matches as well as a match here
|
||||
// Set processing a unique row.
|
||||
rowInProgress = true;
|
||||
}
|
||||
} else {
|
||||
if (rowInProgress) {
|
||||
// Process Row
|
||||
next = processRow(rowCounters, rowNumber, rowHeight, codewords, next);
|
||||
if (next == -1) {
|
||||
// Something is wrong, since we have exceeded
|
||||
// the maximum columns in the specification.
|
||||
throw FormatException.getFormatInstance();
|
||||
}
|
||||
// Reinitialize the row counters.
|
||||
for (int j = 0; j < rowCounters.length; j++) {
|
||||
rowCounters[j] = 0;
|
||||
}
|
||||
rowNumber++;
|
||||
rowHeight = 0;
|
||||
}
|
||||
matchingConsecutiveScans = 0;
|
||||
rowInProgress = false;
|
||||
}
|
||||
rowHeight++;
|
||||
}
|
||||
// Check for a row that was in progress before we exited above.
|
||||
if (rowInProgress) {
|
||||
// Process Row
|
||||
if (rowNumber >= MAX_ROWS) {
|
||||
// Something is wrong, since we have exceeded
|
||||
// the maximum rows in the specification.
|
||||
throw FormatException.getFormatInstance();
|
||||
}
|
||||
next = processRow(rowCounters, rowNumber, rowHeight, codewords, next);
|
||||
next = processRow(rowNumber, codewords, next);
|
||||
rowNumber++;
|
||||
rows = rowNumber;
|
||||
}
|
||||
}
|
||||
erasures = trimArray(erasures, eraseCount);
|
||||
return trimArray(codewords, next);
|
||||
}
|
||||
|
||||
/**
|
||||
* Trim the array to the required size.
|
||||
*
|
||||
* @param array the array
|
||||
* @param size the size to trim it to
|
||||
* @return the new trimmed array
|
||||
*/
|
||||
private static int[] trimArray(int[] array, int size) {
|
||||
if (size < 0) {
|
||||
throw new IllegalArgumentException();
|
||||
}
|
||||
if (size == 0) {
|
||||
return NO_ERRORS;
|
||||
}
|
||||
int[] a = new int[size];
|
||||
System.arraycopy(array, 0, a, 0, size);
|
||||
return a;
|
||||
}
|
||||
|
||||
/**
|
||||
* Convert the symbols in the row to codewords.
|
||||
* Each PDF417 symbol character consists of four bar elements and four space
|
||||
* elements, each of which can be one to six modules wide. The four bar and
|
||||
* four space elements shall measure 17 modules in total.
|
||||
*
|
||||
* @param rowCounters an array containing the counts of black pixels for each column
|
||||
* in the row.
|
||||
* @param rowNumber the current row number of codewords.
|
||||
* @param rowHeight the height of this row in pixels.
|
||||
* @param codewords the codeword array to save codewords into.
|
||||
* @param next the next available index into the codewords array.
|
||||
* @return the next available index into the codeword array after processing
|
||||
* this row.
|
||||
*/
|
||||
int processRow(int[] rowCounters, int rowNumber, int rowHeight, int[] codewords, int next)
|
||||
throws FormatException {
|
||||
int processRow(int rowNumber, int[] codewords, int next) throws FormatException {
|
||||
int width = bitMatrix.getWidth();
|
||||
int columnNumber = 0;
|
||||
long symbol = 0;
|
||||
for (int i = 0; i < width; i += MODULES_IN_SYMBOL) {
|
||||
// This happens in real life and is almost surely a rare misdecode
|
||||
if (i + MODULES_IN_SYMBOL > rowCounters.length) {
|
||||
throw FormatException.getFormatInstance();
|
||||
}
|
||||
for (int mask = MODULES_IN_SYMBOL - 1; mask >= 0; mask--) {
|
||||
if (rowCounters[i + (MODULES_IN_SYMBOL - 1 - mask)] >= rowHeight >>> 1) {
|
||||
if (bitMatrix.get(i + (MODULES_IN_SYMBOL - 1 - mask), rowNumber)) {
|
||||
symbol |= 1L << mask;
|
||||
}
|
||||
}
|
||||
|
@ -227,8 +133,9 @@ final class BitMatrixParser {
|
|||
--next;
|
||||
if (ecLevel < 0 && rowNumber % 3 == 2) {
|
||||
rightColumnECData = codewords[next];
|
||||
if (rightColumnECData == leftColumnECData && leftColumnECData != 0) {
|
||||
ecLevel = ((rightColumnECData % 30) - rows % 3) / 3;
|
||||
if (rightColumnECData == leftColumnECData && leftColumnECData > 0) {
|
||||
//ecLevel = ((rightColumnECData % 30) - rows % 3) / 3;
|
||||
ecLevel = (rightColumnECData % 30) / 3;
|
||||
}
|
||||
}
|
||||
codewords[next] = 0;
|
||||
|
@ -236,21 +143,34 @@ final class BitMatrixParser {
|
|||
return next;
|
||||
}
|
||||
|
||||
/**
|
||||
* Trim the array to the required size.
|
||||
*
|
||||
* @param array the array
|
||||
* @param size the size to trim it to
|
||||
* @return the new trimmed array
|
||||
*/
|
||||
private static int[] trimArray(int[] array, int size) {
|
||||
if (size < 0) {
|
||||
throw new IllegalArgumentException();
|
||||
}
|
||||
int[] a = new int[size];
|
||||
System.arraycopy(array, 0, a, 0, size);
|
||||
return a;
|
||||
}
|
||||
|
||||
/**
|
||||
* Translate the symbol into a codeword.
|
||||
*
|
||||
* @return the codeword corresponding to the symbol.
|
||||
*/
|
||||
private static int getCodeword(long symbol) {
|
||||
public static int getCodeword(long symbol) {
|
||||
long sym = symbol & 0x3FFFF;
|
||||
int i = findCodewordIndex(sym);
|
||||
if (i == -1) {
|
||||
return -1;
|
||||
} else {
|
||||
long cw = CODEWORD_TABLE[i] - 1;
|
||||
cw %= 929;
|
||||
return (int) cw;
|
||||
}
|
||||
return (CODEWORD_TABLE[i] - 1) % 929;
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -292,7 +212,7 @@ final class BitMatrixParser {
|
|||
* specification. The index of a symbol in this table corresponds to the
|
||||
* index into the codeword table.
|
||||
*/
|
||||
private static final int[] SYMBOL_TABLE = {0x1025e, 0x1027a, 0x1029e,
|
||||
public static final int[] SYMBOL_TABLE = {0x1025e, 0x1027a, 0x1029e,
|
||||
0x102bc, 0x102f2, 0x102f4, 0x1032e, 0x1034e, 0x1035c, 0x10396,
|
||||
0x103a6, 0x103ac, 0x10422, 0x10428, 0x10436, 0x10442, 0x10444,
|
||||
0x10448, 0x10450, 0x1045e, 0x10466, 0x1046c, 0x1047a, 0x10482,
|
||||
|
|
|
@ -23,6 +23,7 @@ import com.google.zxing.ResultPoint;
|
|||
import com.google.zxing.common.BitMatrix;
|
||||
import com.google.zxing.common.DetectorResult;
|
||||
import com.google.zxing.common.GridSampler;
|
||||
import com.google.zxing.common.PerspectiveTransform;
|
||||
import com.google.zxing.common.detector.MathUtils;
|
||||
|
||||
import java.util.Arrays;
|
||||
|
@ -34,6 +35,8 @@ import java.util.Map;
|
|||
*
|
||||
* @author SITA Lab (kevin.osullivan@sita.aero)
|
||||
* @author dswitkin@google.com (Daniel Switkin)
|
||||
* @author Schweers Informationstechnologie GmbH (hartmut.neubauer@schweers.de)
|
||||
* @author creatale GmbH (christoph.schulz@creatale.de)
|
||||
*/
|
||||
public final class Detector {
|
||||
|
||||
|
@ -41,7 +44,6 @@ public final class Detector {
|
|||
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_INDIVIDUAL_VARIANCE = (int) (PATTERN_MATCH_RESULT_SCALE_FACTOR * 0.8f);
|
||||
private static final int SKEW_THRESHOLD = 3;
|
||||
|
||||
// B S B S B S B S Bar/Space pattern
|
||||
// 11111111 0 1 0 1 0 1 000
|
||||
|
@ -84,18 +86,17 @@ public final class Detector {
|
|||
// Fetch the 1 bit matrix once up front.
|
||||
BitMatrix matrix = image.getBlackMatrix();
|
||||
|
||||
boolean tryHarder = hints != null && hints.containsKey(DecodeHintType.TRY_HARDER);
|
||||
|
||||
// Try to find the vertices assuming the image is upright.
|
||||
ResultPoint[] vertices = findVertices(matrix, tryHarder);
|
||||
int rowStep = 8;
|
||||
ResultPoint[] vertices = findVertices(matrix, rowStep);
|
||||
if (vertices == null) {
|
||||
// Maybe the image is rotated 180 degrees?
|
||||
vertices = findVertices180(matrix, tryHarder);
|
||||
vertices = findVertices180(matrix, rowStep);
|
||||
if (vertices != null) {
|
||||
correctCodeWordVertices(vertices, true);
|
||||
correctVertices(matrix, vertices, true);
|
||||
}
|
||||
} else {
|
||||
correctCodeWordVertices(vertices, false);
|
||||
correctVertices(matrix, vertices, false);
|
||||
}
|
||||
|
||||
if (vertices == null) {
|
||||
|
@ -107,18 +108,22 @@ public final class Detector {
|
|||
throw NotFoundException.getNotFoundInstance();
|
||||
}
|
||||
|
||||
int dimension = computeDimension(vertices[4], vertices[6],
|
||||
vertices[5], vertices[7], moduleWidth);
|
||||
int dimension = computeDimension(vertices[12], vertices[14],
|
||||
vertices[13], vertices[15], moduleWidth);
|
||||
if (dimension < 1) {
|
||||
throw NotFoundException.getNotFoundInstance();
|
||||
}
|
||||
|
||||
int ydimension = computeYDimension(vertices[4], vertices[6], vertices[5], vertices[7], moduleWidth);
|
||||
ydimension = ydimension > dimension ? ydimension : dimension;
|
||||
int yDimension = Math.max(computeYDimension(vertices[12], vertices[14],
|
||||
vertices[13], vertices[15], moduleWidth), dimension);
|
||||
|
||||
// Deskew and sample image.
|
||||
BitMatrix bits = sampleGrid(matrix, vertices[4], vertices[5], vertices[6], vertices[7], dimension, ydimension);
|
||||
return new DetectorResult(bits, new ResultPoint[]{vertices[5], vertices[4], vertices[6], vertices[7]});
|
||||
// Deskew and over-sample image.
|
||||
BitMatrix linesMatrix = sampleLines(vertices, dimension, yDimension);
|
||||
BitMatrix linesGrid = new LinesSampler(linesMatrix, dimension).sample();
|
||||
|
||||
//TODO: verify vertex indices.
|
||||
return new DetectorResult(linesGrid, new ResultPoint[]{
|
||||
vertices[5], vertices[4], vertices[6], vertices[7]});
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -126,6 +131,7 @@ public final class Detector {
|
|||
* and Stop patterns as locators.
|
||||
*
|
||||
* @param matrix the scanned barcode image.
|
||||
* @param rowStep the step size for iterating rows (every n-th row).
|
||||
* @return an array containing the vertices:
|
||||
* vertices[0] x, y top left barcode
|
||||
* vertices[1] x, y bottom left barcode
|
||||
|
@ -136,17 +142,15 @@ public final class Detector {
|
|||
* vertices[6] x, y top right codeword area
|
||||
* vertices[7] x, y bottom right codeword area
|
||||
*/
|
||||
private static ResultPoint[] findVertices(BitMatrix matrix, boolean tryHarder) {
|
||||
private static ResultPoint[] findVertices(BitMatrix matrix, int rowStep) {
|
||||
int height = matrix.getHeight();
|
||||
int width = matrix.getWidth();
|
||||
|
||||
ResultPoint[] result = new ResultPoint[8];
|
||||
ResultPoint[] result = new ResultPoint[16];
|
||||
boolean found = false;
|
||||
|
||||
int[] counters = new int[START_PATTERN.length];
|
||||
|
||||
int rowStep = Math.max(1, height >> (tryHarder ? 9 : 7));
|
||||
|
||||
// Top Left
|
||||
for (int i = 0; i < height; i += rowStep) {
|
||||
int[] loc = findGuardPattern(matrix, 0, i, width, false, START_PATTERN, counters);
|
||||
|
@ -207,9 +211,9 @@ public final class Detector {
|
|||
* and Stop patterns as locators. This assumes that the image is rotated 180
|
||||
* degrees and if it locates the start and stop patterns at it will re-map
|
||||
* the vertices for a 0 degree rotation.
|
||||
* TODO: Change assumption about barcode location.
|
||||
*
|
||||
* @param matrix the scanned barcode image.
|
||||
* @param rowStep the step size for iterating rows (every n-th row).
|
||||
* @return an array containing the vertices:
|
||||
* vertices[0] x, y top left barcode
|
||||
* vertices[1] x, y bottom left barcode
|
||||
|
@ -220,18 +224,19 @@ public final class Detector {
|
|||
* vertices[6] x, y top right codeword area
|
||||
* vertices[7] x, y bottom right codeword area
|
||||
*/
|
||||
private static ResultPoint[] findVertices180(BitMatrix matrix, boolean tryHarder) {
|
||||
private static ResultPoint[] findVertices180(BitMatrix matrix, int rowStep) {
|
||||
|
||||
// TODO: Change assumption about barcode location.
|
||||
|
||||
int height = matrix.getHeight();
|
||||
int width = matrix.getWidth();
|
||||
int halfWidth = width >> 1;
|
||||
|
||||
ResultPoint[] result = new ResultPoint[8];
|
||||
ResultPoint[] result = new ResultPoint[16];
|
||||
boolean found = false;
|
||||
|
||||
int[] counters = new int[START_PATTERN_REVERSE.length];
|
||||
|
||||
int rowStep = Math.max(1, height >> (tryHarder ? 9 : 7));
|
||||
|
||||
// Top Left
|
||||
for (int i = height - 1; i > 0; i -= rowStep) {
|
||||
int[] loc = findGuardPattern(matrix, halfWidth, i, halfWidth, true, START_PATTERN_REVERSE, counters);
|
||||
|
@ -287,175 +292,6 @@ public final class Detector {
|
|||
return found ? result : null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Because we scan horizontally to detect the start and stop patterns, the vertical component of
|
||||
* the codeword coordinates will be slightly wrong if there is any skew or rotation in the image.
|
||||
* This method moves those points back onto the edges of the theoretically perfect bounding
|
||||
* quadrilateral if needed.
|
||||
*
|
||||
* @param vertices The eight vertices located by findVertices().
|
||||
*/
|
||||
private static void correctCodeWordVertices(ResultPoint[] vertices, boolean upsideDown) {
|
||||
|
||||
float v0x = vertices[0].getX();
|
||||
float v0y = vertices[0].getY();
|
||||
float v2x = vertices[2].getX();
|
||||
float v2y = vertices[2].getY();
|
||||
float v4x = vertices[4].getX();
|
||||
float v4y = vertices[4].getY();
|
||||
float v6x = vertices[6].getX();
|
||||
float v6y = vertices[6].getY();
|
||||
|
||||
float skew = v4y - v6y;
|
||||
if (upsideDown) {
|
||||
skew = -skew;
|
||||
}
|
||||
if (skew > SKEW_THRESHOLD) {
|
||||
// Fix v4
|
||||
float deltax = v6x - v0x;
|
||||
float deltay = v6y - v0y;
|
||||
float delta2 = deltax * deltax + deltay * deltay;
|
||||
float correction = (v4x - v0x) * deltax / delta2;
|
||||
vertices[4] = new ResultPoint(v0x + correction * deltax, v0y + correction * deltay);
|
||||
} else if (-skew > SKEW_THRESHOLD) {
|
||||
// Fix v6
|
||||
float deltax = v2x - v4x;
|
||||
float deltay = v2y - v4y;
|
||||
float delta2 = deltax * deltax + deltay * deltay;
|
||||
float correction = (v2x - v6x) * deltax / delta2;
|
||||
vertices[6] = new ResultPoint(v2x - correction * deltax, v2y - correction * deltay);
|
||||
}
|
||||
|
||||
float v1x = vertices[1].getX();
|
||||
float v1y = vertices[1].getY();
|
||||
float v3x = vertices[3].getX();
|
||||
float v3y = vertices[3].getY();
|
||||
float v5x = vertices[5].getX();
|
||||
float v5y = vertices[5].getY();
|
||||
float v7x = vertices[7].getX();
|
||||
float v7y = vertices[7].getY();
|
||||
|
||||
skew = v7y - v5y;
|
||||
if (upsideDown) {
|
||||
skew = -skew;
|
||||
}
|
||||
if (skew > SKEW_THRESHOLD) {
|
||||
// Fix v5
|
||||
float deltax = v7x - v1x;
|
||||
float deltay = v7y - v1y;
|
||||
float delta2 = deltax * deltax + deltay * deltay;
|
||||
float correction = (v5x - v1x) * deltax / delta2;
|
||||
vertices[5] = new ResultPoint(v1x + correction * deltax, v1y + correction * deltay);
|
||||
} else if (-skew > SKEW_THRESHOLD) {
|
||||
// Fix v7
|
||||
float deltax = v3x - v5x;
|
||||
float deltay = v3y - v5y;
|
||||
float delta2 = deltax * deltax + deltay * deltay;
|
||||
float correction = (v3x - v7x) * deltax / delta2;
|
||||
vertices[7] = new ResultPoint(v3x - correction * deltax, v3y - correction * deltay);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* <p>Estimates module size (pixels in a module) based on the Start and End
|
||||
* finder patterns.</p>
|
||||
*
|
||||
* @param vertices an array of vertices:
|
||||
* vertices[0] x, y top left barcode
|
||||
* vertices[1] x, y bottom left barcode
|
||||
* vertices[2] x, y top right barcode
|
||||
* vertices[3] x, y bottom right barcode
|
||||
* vertices[4] x, y top left codeword area
|
||||
* vertices[5] x, y bottom left codeword area
|
||||
* vertices[6] x, y top right codeword area
|
||||
* vertices[7] x, y bottom right codeword area
|
||||
* @return the module size.
|
||||
*/
|
||||
private static float computeModuleWidth(ResultPoint[] vertices) {
|
||||
float pixels1 = ResultPoint.distance(vertices[0], vertices[4]);
|
||||
float pixels2 = ResultPoint.distance(vertices[1], vertices[5]);
|
||||
float moduleWidth1 = (pixels1 + pixels2) / (17 * 2.0f);
|
||||
float pixels3 = ResultPoint.distance(vertices[6], vertices[2]);
|
||||
float pixels4 = ResultPoint.distance(vertices[7], vertices[3]);
|
||||
float moduleWidth2 = (pixels3 + pixels4) / (18 * 2.0f);
|
||||
return (moduleWidth1 + moduleWidth2) / 2.0f;
|
||||
}
|
||||
|
||||
/**
|
||||
* Computes the dimension (number of modules in a row) of the PDF417 Code
|
||||
* based on vertices of the codeword area and estimated module size.
|
||||
*
|
||||
* @param topLeft of codeword area
|
||||
* @param topRight of codeword area
|
||||
* @param bottomLeft of codeword area
|
||||
* @param bottomRight of codeword are
|
||||
* @param moduleWidth estimated module size
|
||||
* @return the number of modules in a row.
|
||||
*/
|
||||
private static int computeDimension(ResultPoint topLeft,
|
||||
ResultPoint topRight,
|
||||
ResultPoint bottomLeft,
|
||||
ResultPoint bottomRight,
|
||||
float moduleWidth) {
|
||||
int topRowDimension = MathUtils.round(ResultPoint.distance(topLeft, topRight) / moduleWidth);
|
||||
int bottomRowDimension = MathUtils.round(ResultPoint.distance(bottomLeft, bottomRight) / moduleWidth);
|
||||
return ((((topRowDimension + bottomRowDimension) >> 1) + 8) / 17) * 17;
|
||||
}
|
||||
|
||||
/**
|
||||
* Computes the y dimension (number of modules in a column) of the PDF417 Code
|
||||
* based on vertices of the codeword area and estimated module size.
|
||||
*
|
||||
* @param topLeft of codeword area
|
||||
* @param topRight of codeword area
|
||||
* @param bottomLeft of codeword area
|
||||
* @param bottomRight of codeword are
|
||||
* @param moduleWidth estimated module size
|
||||
* @return the number of modules in a row.
|
||||
*/
|
||||
private static int computeYDimension(ResultPoint topLeft,
|
||||
ResultPoint topRight,
|
||||
ResultPoint bottomLeft,
|
||||
ResultPoint bottomRight,
|
||||
float moduleWidth) {
|
||||
int leftColumnDimension = MathUtils.round(ResultPoint.distance(topLeft, bottomLeft) / moduleWidth);
|
||||
int rightColumnDimension = MathUtils.round(ResultPoint.distance(topRight, bottomRight) / moduleWidth);
|
||||
return (leftColumnDimension + rightColumnDimension) >> 1;
|
||||
}
|
||||
|
||||
private static BitMatrix sampleGrid(BitMatrix matrix,
|
||||
ResultPoint topLeft,
|
||||
ResultPoint bottomLeft,
|
||||
ResultPoint topRight,
|
||||
ResultPoint bottomRight,
|
||||
int xdimension,
|
||||
int ydimension) throws NotFoundException {
|
||||
|
||||
// Note that unlike the QR Code sampler, we didn't find the center of modules, but the
|
||||
// very corners. So there is no 0.5f here; 0.0f is right.
|
||||
GridSampler sampler = GridSampler.getInstance();
|
||||
|
||||
return sampler.sampleGrid(
|
||||
matrix,
|
||||
xdimension, ydimension,
|
||||
0.0f, // p1ToX
|
||||
0.0f, // p1ToY
|
||||
xdimension, // p2ToX
|
||||
0.0f, // p2ToY
|
||||
xdimension, // p3ToX
|
||||
ydimension, // p3ToY
|
||||
0.0f, // p4ToX
|
||||
ydimension, // p4ToY
|
||||
topLeft.getX(), // p1FromX
|
||||
topLeft.getY(), // p1FromY
|
||||
topRight.getX(), // p2FromX
|
||||
topRight.getY(), // p2FromY
|
||||
bottomRight.getX(), // p3FromX
|
||||
bottomRight.getY(), // p3FromY
|
||||
bottomLeft.getX(), // p4FromX
|
||||
bottomLeft.getY()); // p4FromY
|
||||
}
|
||||
|
||||
/**
|
||||
* @param matrix row of black/white values to search
|
||||
* @param column x position to start search
|
||||
|
@ -550,4 +386,267 @@ public final class Detector {
|
|||
return totalVariance / total;
|
||||
}
|
||||
|
||||
/**
|
||||
* <p>Correct the vertices by searching for top and bottom vertices of wide
|
||||
* bars, then locate the intersections between the upper and lower horizontal
|
||||
* line and the inner vertices vertical lines.</p>
|
||||
*
|
||||
* @param matrix the scanned barcode image.
|
||||
* @param vertices the vertices vector is extended and the new members are:
|
||||
* vertices[ 8] x,y point on upper border of left wide bar
|
||||
* vertices[ 9] x,y point on lower border of left wide bar
|
||||
* vertices[10] x,y point on upper border of right wide bar
|
||||
* vertices[11] x,y point on lower border of right wide bar
|
||||
* vertices[12] x,y final top left codeword area
|
||||
* vertices[13] x,y final bottom left codeword area
|
||||
* vertices[14] x,y final top right codeword area
|
||||
* vertices[15] x,y final bottom right codeword area
|
||||
* @param upsideDown true if rotated by 180 degree.
|
||||
*/
|
||||
private static void correctVertices(BitMatrix matrix,
|
||||
ResultPoint[] vertices,
|
||||
boolean upsideDown) throws NotFoundException {
|
||||
|
||||
boolean isLowLeft = Math.abs(vertices[4].getY() - vertices[5].getY()) < 20.0;
|
||||
boolean isLowRight = Math.abs(vertices[6].getY() - vertices[7].getY()) < 20.0;
|
||||
if (isLowLeft || isLowRight) {
|
||||
throw NotFoundException.getNotFoundInstance();
|
||||
} else {
|
||||
findWideBarTopBottom(matrix, vertices, 0, 0, 8, 17, upsideDown ? 1 : -1);
|
||||
findWideBarTopBottom(matrix, vertices, 1, 0, 8, 17, upsideDown ? -1 : 1);
|
||||
findWideBarTopBottom(matrix, vertices, 2, 11, 7, 18, upsideDown ? 1 : -1);
|
||||
findWideBarTopBottom(matrix, vertices, 3, 11, 7, 18, upsideDown ? -1 : 1);
|
||||
findCrossingPoint(vertices, 12, 4, 5, 8, 10, matrix);
|
||||
findCrossingPoint(vertices, 13, 4, 5, 9, 11, matrix);
|
||||
findCrossingPoint(vertices, 14, 6, 7, 8, 10, matrix);
|
||||
findCrossingPoint(vertices, 15, 6, 7, 9, 11, matrix);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* <p>Locate the top or bottom of one of the two wide black bars of a guard pattern.</p>
|
||||
*
|
||||
* <p>Warning: it only searches along the y axis, so the return points would not be
|
||||
* right if the barcode is too curved.</p>
|
||||
*
|
||||
* @param matrix The bit matrix.
|
||||
* @param vertices The 16 vertices located by findVertices(); the result
|
||||
* points are stored into vertices[8], ... , vertices[11].
|
||||
* @param offsetVertex The offset of the outer vertex and the inner
|
||||
* vertex (+ 4) to be corrected and (+ 8) where the result is stored.
|
||||
* @param startWideBar start of a wide bar.
|
||||
* @param lenWideBar length of wide bar.
|
||||
* @param lenPattern length of the pattern.
|
||||
* @param rowStep +1 if corner should be exceeded towards the bottom, -1 towards the top.
|
||||
*/
|
||||
private static void findWideBarTopBottom(BitMatrix matrix,
|
||||
ResultPoint[] vertices,
|
||||
int offsetVertex,
|
||||
int startWideBar,
|
||||
int lenWideBar,
|
||||
int lenPattern,
|
||||
int rowStep) {
|
||||
ResultPoint verticeStart = vertices[offsetVertex];
|
||||
ResultPoint verticeEnd = vertices[offsetVertex + 4];
|
||||
|
||||
// Start horizontally at the middle of the bar.
|
||||
int endWideBar = startWideBar + lenWideBar;
|
||||
float barDiff = verticeEnd.getX() - verticeStart.getX();
|
||||
float barStart = verticeStart.getX() + (barDiff * startWideBar) / lenPattern;
|
||||
float barEnd = verticeStart.getX() + (barDiff * endWideBar) / lenPattern;
|
||||
int x = Math.round((barStart + barEnd) / 2.0f);
|
||||
|
||||
// Start vertically between the preliminary vertices.
|
||||
int yStart = Math.round(verticeStart.getY());
|
||||
int y = yStart;
|
||||
|
||||
// Find offset of thin bar to the right as additional safeguard.
|
||||
int nextBarX = (int)Math.max(barStart, barEnd) + 1;
|
||||
while (nextBarX < matrix.getWidth()) {
|
||||
if (!matrix.get(nextBarX - 1, y) && matrix.get(nextBarX, y)) {
|
||||
break;
|
||||
}
|
||||
nextBarX++;
|
||||
}
|
||||
nextBarX -= x;
|
||||
|
||||
boolean isEnd = false;
|
||||
while (!isEnd) {
|
||||
if (matrix.get(x, y)) {
|
||||
// If the thin bar to the right ended, stop as well
|
||||
isEnd = !matrix.get(x + nextBarX, y) && !matrix.get(x + nextBarX + 1, y);
|
||||
y += rowStep;
|
||||
if (y <= 0 || y >= matrix.getHeight() - 1) {
|
||||
// End of barcode image reached.
|
||||
isEnd = true;
|
||||
}
|
||||
} else {
|
||||
// Look sidewise whether black bar continues? (in the case the image is skewed)
|
||||
if (x > 0 && matrix.get(x - 1, y)) {
|
||||
x--;
|
||||
} else if (x < matrix.getWidth() - 1 && matrix.get(x + 1, y)) {
|
||||
x++;
|
||||
} else {
|
||||
// End of pattern regarding big bar and big gap reached.
|
||||
isEnd = true;
|
||||
if (y != yStart) {
|
||||
// Turn back one step, because target has been exceeded.
|
||||
y -= rowStep;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
vertices[offsetVertex + 8] = new ResultPoint(x, y);
|
||||
}
|
||||
|
||||
/**
|
||||
* <p>Finds the intersection of two lines.</p>
|
||||
*
|
||||
* @param vertices The reference of the vertices vector
|
||||
* @param idxResult Index of result point inside the vertices vector.
|
||||
* @param idxLineA1
|
||||
* @param idxLineA2 Indices two points inside the vertices vector that define the first line.
|
||||
* @param idxLineB1
|
||||
* @param idxLineB2 Indices two points inside the vertices vector that define the second line.
|
||||
* @param matrix: bit matrix, here only for testing whether the result is inside the matrix.
|
||||
* @return Returns true when the result is valid and lies inside the matrix. Otherwise throws an
|
||||
* exception.
|
||||
*/
|
||||
private static void findCrossingPoint(ResultPoint[] vertices,
|
||||
int idxResult,
|
||||
int idxLineA1, int idxLineA2,
|
||||
int idxLineB1, int idxLineB2,
|
||||
BitMatrix matrix) throws NotFoundException {
|
||||
ResultPoint result = intersection(vertices[idxLineA1], vertices[idxLineA2],
|
||||
vertices[idxLineB1], vertices[idxLineB2]);
|
||||
if (result == null) {
|
||||
throw NotFoundException.getNotFoundInstance();
|
||||
}
|
||||
|
||||
int x = Math.round(result.getX());
|
||||
int y = Math.round(result.getY());
|
||||
if (x < 0 || x >= matrix.getWidth() || y < 0 || y >= matrix.getHeight()) {
|
||||
throw NotFoundException.getNotFoundInstance();
|
||||
}
|
||||
|
||||
vertices[idxResult] = result;
|
||||
}
|
||||
|
||||
/**
|
||||
* Computes the intersection between two lines.
|
||||
*/
|
||||
private static ResultPoint intersection(ResultPoint a1, ResultPoint a2, ResultPoint b1, ResultPoint b2) {
|
||||
float dxa = a1.getX() - a2.getX();
|
||||
float dxb = b1.getX() - b2.getX();
|
||||
float dya = a1.getY() - a2.getY();
|
||||
float dyb = b1.getY() - b2.getY();
|
||||
|
||||
float p = a1.getX() * a2.getY() - a1.getY() * a2.getX();
|
||||
float q = b1.getX() * b2.getY() - b1.getY() * b2.getX();
|
||||
float denom = dxa * dyb - dya * dxb;
|
||||
if (denom == 0) {
|
||||
// Lines don't intersect
|
||||
return null;
|
||||
}
|
||||
|
||||
float x = (p * dxb - dxa * q) / denom;
|
||||
float y = (p * dyb - dya * q) / denom;
|
||||
|
||||
return new ResultPoint(x, y);
|
||||
}
|
||||
|
||||
/**
|
||||
* <p>Estimates module size (pixels in a module) based on the Start and End
|
||||
* finder patterns.</p>
|
||||
*
|
||||
* @param vertices an array of vertices:
|
||||
* vertices[0] x, y top left barcode
|
||||
* vertices[1] x, y bottom left barcode
|
||||
* vertices[2] x, y top right barcode
|
||||
* vertices[3] x, y bottom right barcode
|
||||
* vertices[4] x, y top left codeword area
|
||||
* vertices[5] x, y bottom left codeword area
|
||||
* vertices[6] x, y top right codeword area
|
||||
* vertices[7] x, y bottom right codeword area
|
||||
* @return the module size.
|
||||
*/
|
||||
private static float computeModuleWidth(ResultPoint[] vertices) {
|
||||
float pixels1 = ResultPoint.distance(vertices[0], vertices[4]);
|
||||
float pixels2 = ResultPoint.distance(vertices[1], vertices[5]);
|
||||
float moduleWidth1 = (pixels1 + pixels2) / (17 * 2.0f);
|
||||
float pixels3 = ResultPoint.distance(vertices[6], vertices[2]);
|
||||
float pixels4 = ResultPoint.distance(vertices[7], vertices[3]);
|
||||
float moduleWidth2 = (pixels3 + pixels4) / (18 * 2.0f);
|
||||
return (moduleWidth1 + moduleWidth2) / 2.0f;
|
||||
}
|
||||
|
||||
/**
|
||||
* Computes the dimension (number of modules in a row) of the PDF417 Code
|
||||
* based on vertices of the codeword area and estimated module size.
|
||||
*
|
||||
* @param topLeft of codeword area
|
||||
* @param topRight of codeword area
|
||||
* @param bottomLeft of codeword area
|
||||
* @param bottomRight of codeword are
|
||||
* @param moduleWidth estimated module size
|
||||
* @return the number of modules in a row.
|
||||
*/
|
||||
private static int computeDimension(ResultPoint topLeft,
|
||||
ResultPoint topRight,
|
||||
ResultPoint bottomLeft,
|
||||
ResultPoint bottomRight,
|
||||
float moduleWidth) {
|
||||
int topRowDimension = MathUtils.round(ResultPoint.distance(topLeft, topRight) / moduleWidth);
|
||||
int bottomRowDimension = MathUtils.round(ResultPoint.distance(bottomLeft, bottomRight) / moduleWidth);
|
||||
return ((((topRowDimension + bottomRowDimension) >> 1) + 8) / 17) * 17;
|
||||
}
|
||||
|
||||
/**
|
||||
* Computes the y dimension (number of modules in a column) of the PDF417 Code
|
||||
* based on vertices of the codeword area and estimated module size.
|
||||
*
|
||||
* @param topLeft of codeword area
|
||||
* @param topRight of codeword area
|
||||
* @param bottomLeft of codeword area
|
||||
* @param bottomRight of codeword are
|
||||
* @param moduleWidth estimated module size
|
||||
* @return the number of modules in a row.
|
||||
*/
|
||||
private static int computeYDimension(ResultPoint topLeft,
|
||||
ResultPoint topRight,
|
||||
ResultPoint bottomLeft,
|
||||
ResultPoint bottomRight,
|
||||
float moduleWidth) {
|
||||
int leftColumnDimension = MathUtils.round(ResultPoint.distance(topLeft, bottomLeft) / moduleWidth);
|
||||
int rightColumnDimension = MathUtils.round(ResultPoint.distance(topRight, bottomRight) / moduleWidth);
|
||||
return (leftColumnDimension + rightColumnDimension) >> 1;
|
||||
}
|
||||
|
||||
/**
|
||||
* Deskew and over-sample image.
|
||||
*
|
||||
* @param vertices vertices from findVertices()
|
||||
* @param dimension x dimension
|
||||
* @param yDimension y dimension
|
||||
* @return an over-sampled BitMatrix.
|
||||
*/
|
||||
private BitMatrix sampleLines(ResultPoint[] vertices, int dimension, int yDimension) throws NotFoundException {
|
||||
int sampleDimensionX = dimension * 8;
|
||||
int sampleDimensionY = yDimension * 4;
|
||||
|
||||
PerspectiveTransform transform = PerspectiveTransform
|
||||
.quadrilateralToQuadrilateral(0.0f, 0.0f,
|
||||
sampleDimensionX, 0.0f, 0.0f,
|
||||
sampleDimensionY, sampleDimensionX,
|
||||
sampleDimensionY, vertices[12].getX(),
|
||||
vertices[12].getY(), vertices[14].getX(),
|
||||
vertices[14].getY(), vertices[13].getX(),
|
||||
vertices[13].getY(), vertices[15].getX(),
|
||||
vertices[15].getY());
|
||||
|
||||
return GridSampler.getInstance().sampleGrid(image.getBlackMatrix(),
|
||||
sampleDimensionX, sampleDimensionY, transform);
|
||||
}
|
||||
|
||||
}
|
||||
|
|
643
core/src/com/google/zxing/pdf417/detector/LinesSampler.java
Normal file
643
core/src/com/google/zxing/pdf417/detector/LinesSampler.java
Normal file
|
@ -0,0 +1,643 @@
|
|||
/*
|
||||
* 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.detector;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
import com.google.zxing.common.BitMatrix;
|
||||
import com.google.zxing.pdf417.decoder.BitMatrixParser;
|
||||
|
||||
/**
|
||||
* <p>Encapsulates logic that detects valid codewords from a deskewed lines matrix.
|
||||
* To sample the grid several properties of PDF417 are used:</p>
|
||||
*
|
||||
* <ul>
|
||||
* <li>each codeword/symbol has 17 modules</li>
|
||||
* <li>each codeword/symbol begins with a black bar and ends with a white bar</li>
|
||||
* <li>each codeword consists of 4 black bars + 4 white bars</li>
|
||||
* <li>all valid codewords are known (obviously)</li>
|
||||
* </ul>
|
||||
|
||||
* @author creatale GmbH (christoph.schulz@creatale.de)
|
||||
*/
|
||||
public final class LinesSampler {
|
||||
|
||||
private static final int MODULES_IN_SYMBOL = 17;
|
||||
private static final int BARS_IN_SYMBOL = 8;
|
||||
private static final int BARCODE_START_OFFSET = 2;
|
||||
private static final float[] RATIOS_TABLE;
|
||||
static {
|
||||
// Pre-computes and outputs the symbol ratio table.
|
||||
float[][] table = new float[BitMatrixParser.SYMBOL_TABLE.length][BARS_IN_SYMBOL];
|
||||
RATIOS_TABLE = new float[BitMatrixParser.SYMBOL_TABLE.length * BARS_IN_SYMBOL];
|
||||
int x = 0;
|
||||
for (int i = 0; i < BitMatrixParser.SYMBOL_TABLE.length; i++) {
|
||||
int currentSymbol = BitMatrixParser.SYMBOL_TABLE[i];
|
||||
int currentBit = currentSymbol & 0x1;
|
||||
for (int j = 0; j < BARS_IN_SYMBOL; j++) {
|
||||
float size = 0.0f;
|
||||
while ((currentSymbol & 0x1) == currentBit) {
|
||||
size += 1.0f;
|
||||
currentSymbol >>= 1;
|
||||
}
|
||||
currentBit = currentSymbol & 0x1;
|
||||
table[i][BARS_IN_SYMBOL - j - 1] = size / MODULES_IN_SYMBOL;
|
||||
}
|
||||
for (int j = 0; j < BARS_IN_SYMBOL; j++) {
|
||||
RATIOS_TABLE[x] = table[i][j];
|
||||
x++;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private final BitMatrix linesMatrix;
|
||||
private final int symbolsPerLine;
|
||||
private final int dimension;
|
||||
|
||||
public LinesSampler(BitMatrix linesMatrix, int dimension) {
|
||||
this.linesMatrix = linesMatrix;
|
||||
this.symbolsPerLine = dimension / MODULES_IN_SYMBOL;
|
||||
this.dimension = dimension;
|
||||
}
|
||||
|
||||
/**
|
||||
* Samples a grid from a lines matrix.
|
||||
*
|
||||
* @return the potentially decodable bit matrix.
|
||||
*/
|
||||
public BitMatrix sample() {
|
||||
List<Float> symbolWidths = findSymbolWidths();
|
||||
|
||||
int[][] codewords = new int[linesMatrix.getHeight()][];
|
||||
int[][] clusterNumbers = new int[linesMatrix.getHeight()][];
|
||||
linesMatrixToCodewords(codewords, clusterNumbers, symbolWidths);
|
||||
|
||||
List<List<Map<Integer, Integer>>> votes = distributeVotes(codewords, clusterNumbers);
|
||||
|
||||
List<List<Integer>> detectedCodeWords = new ArrayList<List<Integer>>();
|
||||
resize3(detectedCodeWords, votes.size());
|
||||
for (int i = 0; i < votes.size(); i++) {
|
||||
resize4(detectedCodeWords.get(i), votes.get(i).size());
|
||||
for (int j = 0; j < votes.get(i).size(); j++) {
|
||||
if (!votes.get(i).get(j).isEmpty()) {
|
||||
detectedCodeWords.get(i).set(j, getValueWithMaxVotes(votes.get(i).get(j)).getVote());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
List<Integer> insertLinesAt = findMissingLines(detectedCodeWords);
|
||||
|
||||
int rowCount = decodeRowCount(detectedCodeWords, insertLinesAt);
|
||||
resize3(detectedCodeWords, rowCount);
|
||||
|
||||
return codewordsToBitMatrix(detectedCodeWords, dimension, detectedCodeWords.size());
|
||||
}
|
||||
|
||||
/**
|
||||
* Use the following property of PDF417 barcodes to detect symbols:
|
||||
* Every symbol starts with a black module and every symbol is 17 modules
|
||||
* wide, therefore there have to be columns in the line matrix that are
|
||||
* completely composed of black pixels.
|
||||
*
|
||||
* @return array containing with symbol widths.
|
||||
*/
|
||||
private List<Float> findSymbolWidths() {
|
||||
float expectedSymbolWidth;
|
||||
if (symbolsPerLine > 0) {
|
||||
expectedSymbolWidth = linesMatrix.getWidth() / (float) symbolsPerLine;
|
||||
} else {
|
||||
expectedSymbolWidth = linesMatrix.getWidth();
|
||||
}
|
||||
|
||||
List<Float> symbolWidths = new ArrayList<Float>();
|
||||
int symbolStart = 0;
|
||||
boolean lastWasSymbolStart = true;
|
||||
int[] blackCount = new int[linesMatrix.getWidth()];
|
||||
for (int x = BARCODE_START_OFFSET; x < linesMatrix.getWidth(); x++) {
|
||||
for (int y = 0; y < linesMatrix.getHeight(); y++) {
|
||||
if (linesMatrix.get(x, y)) {
|
||||
blackCount[x]++;
|
||||
}
|
||||
}
|
||||
if (blackCount[x] == linesMatrix.getHeight()) {
|
||||
if (!lastWasSymbolStart) {
|
||||
float currentWidth = x - symbolStart;
|
||||
// Make sure we really found a symbol by asserting a minimal size of
|
||||
// 75% of the expected symbol width. This might break highly distorted
|
||||
// barcodes, but fixes an issue with barcodes where there is a full black
|
||||
// column from top to bottom within a symbol.
|
||||
if (currentWidth > 0.75 * expectedSymbolWidth) {
|
||||
// The actual symbol width might be slightly bigger than the expected
|
||||
// symbol width, but if we are more than half an expected symbol width
|
||||
// bigger, we assume that we missed one or more symbols and assume that
|
||||
// they were the expected symbol width.
|
||||
while (currentWidth > 1.5 * expectedSymbolWidth) {
|
||||
symbolWidths.add(expectedSymbolWidth);
|
||||
currentWidth -= expectedSymbolWidth;
|
||||
}
|
||||
symbolWidths.add(currentWidth);
|
||||
lastWasSymbolStart = true;
|
||||
symbolStart = x;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
if (lastWasSymbolStart) {
|
||||
lastWasSymbolStart = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// The last symbol ends at the right edge of the matrix, where there usually is no black bar.
|
||||
float currentWidth = linesMatrix.getWidth() - symbolStart;
|
||||
while (currentWidth > 1.5 * expectedSymbolWidth) {
|
||||
symbolWidths.add(expectedSymbolWidth);
|
||||
currentWidth -= expectedSymbolWidth;
|
||||
}
|
||||
symbolWidths.add(currentWidth);
|
||||
|
||||
return symbolWidths;
|
||||
}
|
||||
|
||||
private void linesMatrixToCodewords(int[][] codewords, int[][] clusterNumbers, List<Float> symbolWidths) {
|
||||
for (int y = 0; y < linesMatrix.getHeight(); y++) {
|
||||
codewords[y] = new int[symbolsPerLine];
|
||||
clusterNumbers[y] = new int [symbolsPerLine];
|
||||
Arrays.fill(clusterNumbers[y], 0, clusterNumbers[y].length, -1);
|
||||
List<Integer> barWidths = new ArrayList<Integer>();
|
||||
// Run-length encode the bars in the scanned linesMatrix.
|
||||
// We assume that the first bar is black, as determined by the PDF417 standard.
|
||||
// Filter small white bars at the beginning of the barcode.
|
||||
// Small white bars may occur due to small deviations in scan line sampling.
|
||||
barWidths.add(BARCODE_START_OFFSET);
|
||||
boolean isSetBar = true;
|
||||
for (int x = BARCODE_START_OFFSET; x < linesMatrix.getWidth(); x++) {
|
||||
if (linesMatrix.get(x, y)) {
|
||||
if (!isSetBar) {
|
||||
isSetBar = true;
|
||||
barWidths.add(0);
|
||||
}
|
||||
} else {
|
||||
if (isSetBar) {
|
||||
isSetBar = false;
|
||||
barWidths.add(0);
|
||||
}
|
||||
|
||||
}
|
||||
int lastIndex = barWidths.size() - 1;
|
||||
barWidths.set(lastIndex, barWidths.get(lastIndex) + 1);
|
||||
}
|
||||
|
||||
// Find the symbols in the line by counting bar lengths until we reach symbolWidth.
|
||||
// We make sure, that the last bar of a symbol is always white, as determined by the PDF417 standard.
|
||||
// This helps to reduce the amount of errors done during the symbol recognition.
|
||||
// The symbolWidth usually is not constant over the width of the barcode.
|
||||
int[] cwStarts = new int[symbolsPerLine];
|
||||
cwStarts[0] = 0;
|
||||
int cwCount = 1;
|
||||
int cwWidth = 0;
|
||||
for (int i = 0; i < barWidths.size() && cwCount < symbolsPerLine; i++) {
|
||||
cwWidth += barWidths.get(i);
|
||||
if ((float)cwWidth > symbolWidths.get(cwCount - 1)) {
|
||||
if ((i % 2) == 1) { // check if bar is white
|
||||
i++;
|
||||
}
|
||||
if (i < barWidths.size()) {
|
||||
cwWidth = barWidths.get(i);
|
||||
}
|
||||
cwStarts[cwCount] = i;
|
||||
cwCount++;
|
||||
}
|
||||
}
|
||||
|
||||
float[][] cwRatios = new float[symbolsPerLine][BARS_IN_SYMBOL];
|
||||
// Distribute bar widths to modules of a codeword.
|
||||
for (int i = 0; i < symbolsPerLine; i++) {
|
||||
int cwStart = cwStarts[i];
|
||||
int cwEnd = (i == symbolsPerLine - 1) ? barWidths.size() : cwStarts[i + 1];
|
||||
int cwLength = cwEnd - cwStart;
|
||||
|
||||
if (cwLength < 7 || cwLength > 9) {
|
||||
// We try to recover symbols with 7 or 9 bars and spaces with heuristics, but everything else is beyond repair.
|
||||
continue;
|
||||
}
|
||||
|
||||
// For symbols with 9 bar length simply ignore the last bar.
|
||||
float cwWidthF = 0.0f;
|
||||
for (int j = 0; j < Math.min(BARS_IN_SYMBOL, cwLength); ++j) {
|
||||
cwWidthF += (float)barWidths.get(cwStart + j);
|
||||
}
|
||||
|
||||
// If there were only 7 bars and spaces detected use the following heuristic:
|
||||
// Assume the length of the symbol is symbolWidth and the last (unrecognized) bar uses all remaining space.
|
||||
if (cwLength == 7) {
|
||||
for (int j = 0; j < cwLength; ++j) {
|
||||
cwRatios[i][j] = (float)barWidths.get(cwStart + j) / symbolWidths.get(i);
|
||||
}
|
||||
cwRatios[i][7] = (symbolWidths.get(i) - cwWidthF) / symbolWidths.get(i);
|
||||
} else {
|
||||
for (int j = 0; j < cwRatios[i].length; ++j) {
|
||||
cwRatios[i][j] = barWidths.get(cwStart + j) / cwWidthF;
|
||||
}
|
||||
}
|
||||
|
||||
float bestMatchError = Float.MAX_VALUE;
|
||||
int bestMatch = 0;
|
||||
|
||||
// Search for the most possible codeword by comparing the ratios of bar size to symbol width.
|
||||
// The sum of the squared differences is used as similarity metric.
|
||||
// (Picture it as the square euclidian distance in the space of eight tuples where a tuple represents the bar ratios.)
|
||||
for (int j = 0; j < BitMatrixParser.SYMBOL_TABLE.length; j++) {
|
||||
float error = 0.0f;
|
||||
for (int k = 0; k < BARS_IN_SYMBOL; k++) {
|
||||
float diff = RATIOS_TABLE[j * BARS_IN_SYMBOL + k] - cwRatios[i][k];
|
||||
error += diff * diff;
|
||||
}
|
||||
if (error < bestMatchError) {
|
||||
bestMatchError = error;
|
||||
bestMatch = BitMatrixParser.SYMBOL_TABLE[j];
|
||||
}
|
||||
}
|
||||
codewords[y][i] = bestMatch;
|
||||
clusterNumbers[y][i] = calculateClusterNumber(bestMatch);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private List<List<Map<Integer, Integer>>> distributeVotes(int[][] codewords, int[][] clusterNumbers) {
|
||||
// Matrix of votes for codewords which are possible at this position.
|
||||
List<List<Map<Integer, Integer>>> votes = new ArrayList<List<Map<Integer, Integer>>>();
|
||||
votes.add(new ArrayList<Map<Integer,Integer>>());
|
||||
resize2(votes.get(0), symbolsPerLine);
|
||||
|
||||
int currentRow = 0;
|
||||
Map<Integer, Integer> clusterNumberVotes = new HashMap<Integer, Integer>();
|
||||
int lastLineClusterNumber = -1;
|
||||
|
||||
for (int y = 0; y < codewords.length; y++) {
|
||||
// Vote for the most probable cluster number for this row.
|
||||
clusterNumberVotes.clear();
|
||||
for (int i = 0; i < codewords[y].length; i++) {
|
||||
if (clusterNumbers[y][i] != -1) {
|
||||
clusterNumberVotes.put(clusterNumbers[y][i], defaultValue(clusterNumberVotes.get(clusterNumbers[y][i]), 0) + 1);
|
||||
}
|
||||
}
|
||||
|
||||
// Ignore lines where no codeword could be read.
|
||||
if (!clusterNumberVotes.isEmpty()) {
|
||||
VoteResult voteResult = getValueWithMaxVotes(clusterNumberVotes);
|
||||
boolean lineClusterNumberIsIndecisive = voteResult.isIndecisive();
|
||||
int lineClusterNumber = voteResult.getVote();
|
||||
|
||||
// If there are to few votes on the lines cluster number, we keep the old one.
|
||||
// This avoids switching lines because of damaged inter line readings, but
|
||||
// may cause problems for barcodes with four or less rows.
|
||||
if (lineClusterNumberIsIndecisive) {
|
||||
lineClusterNumber = lastLineClusterNumber;
|
||||
}
|
||||
|
||||
if ((lineClusterNumber != ((lastLineClusterNumber + 3) % 9)) && (lastLineClusterNumber != -1)) {
|
||||
lineClusterNumber = lastLineClusterNumber;
|
||||
}
|
||||
|
||||
// Ignore broken lines at the beginning of the barcode.
|
||||
if ((lineClusterNumber == 0 && lastLineClusterNumber == -1) || (lastLineClusterNumber != -1)) {
|
||||
if ((lineClusterNumber == ((lastLineClusterNumber + 3) % 9)) && (lastLineClusterNumber != -1)) {
|
||||
currentRow++;
|
||||
if (votes.size() < currentRow + 1) {
|
||||
resize1(votes, currentRow + 1);
|
||||
resize2(votes.get(currentRow), symbolsPerLine);
|
||||
}
|
||||
}
|
||||
|
||||
if ((lineClusterNumber == ((lastLineClusterNumber + 6) % 9)) && (lastLineClusterNumber != -1)) {
|
||||
currentRow += 2;
|
||||
if (votes.size() < currentRow + 1) {
|
||||
resize1(votes, currentRow + 1);
|
||||
resize2(votes.get(currentRow), symbolsPerLine);
|
||||
}
|
||||
}
|
||||
|
||||
for (int i = 0; i < codewords[y].length; i++) {
|
||||
if (clusterNumbers[y][i] != -1) {
|
||||
if (clusterNumbers[y][i] == lineClusterNumber) {
|
||||
Map<Integer, Integer> votesMap = votes.get(currentRow).get(i);
|
||||
votesMap.put(codewords[y][i], defaultValue(votesMap.get(codewords[y][i]), 0) + 1);
|
||||
} else if (clusterNumbers[y][i] == ((lineClusterNumber + 3) % 9)) {
|
||||
if (votes.size() < currentRow + 2) {
|
||||
resize1(votes, currentRow + 2);
|
||||
resize2(votes.get(currentRow + 1), symbolsPerLine);
|
||||
}
|
||||
Map<Integer, Integer> votesMap = votes.get(currentRow + 1).get(i);
|
||||
votesMap.put(codewords[y][i], defaultValue(votesMap.get(codewords[y][i]), 0) + 1);
|
||||
} else if ((clusterNumbers[y][i] == ((lineClusterNumber + 6) % 9)) && (currentRow > 0)) {
|
||||
Map<Integer, Integer> votesMap = votes.get(currentRow - 1).get(i);
|
||||
votesMap.put(codewords[y][i], defaultValue(votesMap.get(codewords[y][i]), 0) + 1);
|
||||
}
|
||||
}
|
||||
}
|
||||
lastLineClusterNumber = lineClusterNumber;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return votes;
|
||||
}
|
||||
|
||||
private List<Integer> findMissingLines(List<List<Integer>> detectedCodeWords) {
|
||||
List<Integer> insertLinesAt = new ArrayList<Integer>();
|
||||
if (detectedCodeWords.size() > 1) {
|
||||
for (int i = 0; i < detectedCodeWords.size() - 1; i++) {
|
||||
int clusterNumberRow = -1;
|
||||
for (int j = 0; j < detectedCodeWords.get(i).size() && clusterNumberRow == -1; j++) {
|
||||
int clusterNumber = calculateClusterNumber(detectedCodeWords.get(i).get(j));
|
||||
if (clusterNumber != -1) {
|
||||
clusterNumberRow = clusterNumber;
|
||||
}
|
||||
}
|
||||
if (i == 0) {
|
||||
// The first line must have the cluster number 0. Insert empty lines to match this.
|
||||
if (clusterNumberRow > 0) {
|
||||
insertLinesAt.add(0);
|
||||
if (clusterNumberRow > 3) {
|
||||
insertLinesAt.add(0);
|
||||
}
|
||||
}
|
||||
}
|
||||
int clusterNumberNextRow = -1;
|
||||
for (int j = 0; j < detectedCodeWords.get(i + 1).size() && clusterNumberNextRow == -1; j++) {
|
||||
int clusterNumber = calculateClusterNumber(detectedCodeWords.get(i + 1).get(j));
|
||||
if (clusterNumber != -1) {
|
||||
clusterNumberNextRow = clusterNumber;
|
||||
}
|
||||
}
|
||||
if ((clusterNumberRow + 3) % 9 != clusterNumberNextRow
|
||||
&& clusterNumberRow != -1
|
||||
&& clusterNumberNextRow != -1) {
|
||||
// The cluster numbers are not consecutive. Insert an empty line between them.
|
||||
insertLinesAt.add(i + 1);
|
||||
if (clusterNumberRow == clusterNumberNextRow) {
|
||||
// There may be two lines missing. This is detected when two consecutive lines have the same cluster number.
|
||||
insertLinesAt.add(i + 1);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
for (int i = 0; i < insertLinesAt.size(); i++) {
|
||||
List<Integer> v = new ArrayList<Integer>();
|
||||
for (int j = 0; j < symbolsPerLine; ++j) {
|
||||
v.add(0);
|
||||
}
|
||||
detectedCodeWords.add(insertLinesAt.get(i) + i, v);
|
||||
}
|
||||
|
||||
return insertLinesAt;
|
||||
}
|
||||
|
||||
private int decodeRowCount(List<List<Integer>> detectedCodeWords, List<Integer> insertLinesAt) {
|
||||
// Use the information in the first and last column to determin the number of rows and find more missing rows.
|
||||
// For missing rows insert blank space, so the error correction can try to fill them in.
|
||||
|
||||
insertLinesAt.clear();
|
||||
Map<Integer,Integer> rowCountVotes = new HashMap<Integer, Integer>();
|
||||
Map<Integer,Integer> ecLevelVotes = new HashMap<Integer, Integer>();
|
||||
Map<Integer,Integer> rowNumberVotes = new HashMap<Integer, Integer>();
|
||||
int lastRowNumber = -1;
|
||||
|
||||
for (int i = 0; i + 2 < detectedCodeWords.size(); i += 3) {
|
||||
rowNumberVotes.clear();
|
||||
int firstCodewordDecodedLeft = -1;
|
||||
if (detectedCodeWords.get(i).get(0) != 0) {
|
||||
firstCodewordDecodedLeft = BitMatrixParser.getCodeword(detectedCodeWords.get(i).get(0));
|
||||
}
|
||||
int secondCodewordDecodedLeft = -1;
|
||||
if (detectedCodeWords.get(i + 1).get(0) != 0) {
|
||||
secondCodewordDecodedLeft = BitMatrixParser.getCodeword(detectedCodeWords.get(i + 1).get(0));
|
||||
}
|
||||
int thirdCodewordDecodedLeft = -1;
|
||||
if (detectedCodeWords.get(i + 2).get(0) != 0) {
|
||||
thirdCodewordDecodedLeft = BitMatrixParser.getCodeword(detectedCodeWords.get(i + 2).get(0));
|
||||
}
|
||||
|
||||
int firstCodewordDecodedRight = -1;
|
||||
if (detectedCodeWords.get(i).get(detectedCodeWords.get(i).size() - 1) != 0) {
|
||||
firstCodewordDecodedRight = BitMatrixParser.getCodeword(detectedCodeWords.get(i).get(detectedCodeWords.get(i).size() - 1));
|
||||
}
|
||||
int secondCodewordDecodedRight = -1;
|
||||
if (detectedCodeWords.get(i + 1).get(detectedCodeWords.get(i + 1).size() - 1) != 0) {
|
||||
secondCodewordDecodedRight = BitMatrixParser.getCodeword(detectedCodeWords.get(i + 1).get(detectedCodeWords.get(i + 1).size() - 1));
|
||||
}
|
||||
int thirdCodewordDecodedRight = -1;
|
||||
if (detectedCodeWords.get(i + 2).get(detectedCodeWords.get(i + 2).size() - 1) != 0) {
|
||||
thirdCodewordDecodedRight = BitMatrixParser.getCodeword(detectedCodeWords.get(i + 2).get(detectedCodeWords.get(i + 2).size() - 1));
|
||||
}
|
||||
|
||||
if (firstCodewordDecodedLeft != -1 && secondCodewordDecodedLeft != -1) {
|
||||
int leftRowCount = ((firstCodewordDecodedLeft % 30) * 3) + ((secondCodewordDecodedLeft % 30) % 3);
|
||||
int leftECLevel = (secondCodewordDecodedLeft % 30) / 3;
|
||||
|
||||
rowCountVotes.put(leftRowCount, defaultValue(rowCountVotes.get(leftRowCount), 0) + 1);
|
||||
ecLevelVotes.put(leftECLevel, defaultValue(ecLevelVotes.get(leftECLevel), 0) + 1);
|
||||
}
|
||||
|
||||
if (secondCodewordDecodedRight != -1 && thirdCodewordDecodedRight != -1) {
|
||||
int rightRowCount = ((secondCodewordDecodedRight % 30) * 3) + ((thirdCodewordDecodedRight % 30) % 3);
|
||||
int rightECLevel = (thirdCodewordDecodedRight % 30) / 3;
|
||||
|
||||
rowCountVotes.put(rightRowCount, defaultValue(rowCountVotes.get(rightRowCount), 0) + 1);
|
||||
ecLevelVotes.put(rightECLevel, defaultValue(ecLevelVotes.get(rightECLevel), 0) + 1);
|
||||
}
|
||||
|
||||
if (firstCodewordDecodedLeft != -1) {
|
||||
int rowNumber = firstCodewordDecodedLeft / 30;
|
||||
rowNumberVotes.put(rowNumber, defaultValue(rowNumberVotes.get(rowNumber), 0) + 1);
|
||||
}
|
||||
if (secondCodewordDecodedLeft != -1) {
|
||||
int rowNumber = secondCodewordDecodedLeft / 30;
|
||||
rowNumberVotes.put(rowNumber, defaultValue(rowNumberVotes.get(rowNumber), 0) + 1);
|
||||
}
|
||||
if (thirdCodewordDecodedLeft != -1) {
|
||||
int rowNumber = thirdCodewordDecodedLeft / 30;
|
||||
rowNumberVotes.put(rowNumber, defaultValue(rowNumberVotes.get(rowNumber), 0) + 1);
|
||||
}
|
||||
if (firstCodewordDecodedRight != -1) {
|
||||
int rowNumber = firstCodewordDecodedRight / 30;
|
||||
rowNumberVotes.put(rowNumber, defaultValue(rowNumberVotes.get(rowNumber), 0) + 1);
|
||||
}
|
||||
if (secondCodewordDecodedRight != -1) {
|
||||
int rowNumber = secondCodewordDecodedRight / 30;
|
||||
rowNumberVotes.put(rowNumber, defaultValue(rowNumberVotes.get(rowNumber), 0) + 1);
|
||||
}
|
||||
if (thirdCodewordDecodedRight != -1) {
|
||||
int rowNumber = thirdCodewordDecodedRight / 30;
|
||||
rowNumberVotes.put(rowNumber, defaultValue(rowNumberVotes.get(rowNumber), 0) + 1);
|
||||
}
|
||||
int rowNumber = getValueWithMaxVotes(rowNumberVotes).getVote();
|
||||
if (lastRowNumber + 1 < rowNumber) {
|
||||
for (int j = lastRowNumber + 1; j < rowNumber; j++) {
|
||||
insertLinesAt.add(i);
|
||||
insertLinesAt.add(i);
|
||||
insertLinesAt.add(i);
|
||||
}
|
||||
}
|
||||
lastRowNumber = rowNumber;
|
||||
}
|
||||
|
||||
for (int i = 0; i < insertLinesAt.size(); i++) {
|
||||
List<Integer> v = new ArrayList<Integer>();
|
||||
for (int j = 0; j < symbolsPerLine; ++j) {
|
||||
v.add(0);
|
||||
}
|
||||
detectedCodeWords.add(insertLinesAt.get(i) + i, v);
|
||||
}
|
||||
|
||||
int rowCount = getValueWithMaxVotes(rowCountVotes).getVote();
|
||||
//int ecLevel = getValueWithMaxVotes(ecLevelVotes).getVote();
|
||||
|
||||
rowCount += 1;
|
||||
return rowCount;
|
||||
}
|
||||
|
||||
|
||||
|
||||
private static class VoteResult {
|
||||
private boolean indecisive;
|
||||
private int vote;
|
||||
boolean isIndecisive() {
|
||||
return indecisive;
|
||||
}
|
||||
void setIndecisive(boolean indecisive) {
|
||||
this.indecisive = indecisive;
|
||||
}
|
||||
int getVote() {
|
||||
return vote;
|
||||
}
|
||||
void setVote(int vote) {
|
||||
this.vote = vote;
|
||||
}
|
||||
}
|
||||
|
||||
private static VoteResult getValueWithMaxVotes(Map<Integer, Integer> votes) {
|
||||
VoteResult result = new VoteResult();
|
||||
int maxVotes = 0;
|
||||
for (Map.Entry<Integer, Integer> entry : votes.entrySet()) {
|
||||
if (entry.getValue() > maxVotes) {
|
||||
maxVotes = entry.getValue();
|
||||
result.setVote(entry.getKey());
|
||||
result.setIndecisive(false);
|
||||
} else if (entry.getValue() == maxVotes) {
|
||||
result.setIndecisive(true);
|
||||
}
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
private static BitMatrix codewordsToBitMatrix(List<List<Integer>> codewords, int dimension, int yDimension) {
|
||||
BitMatrix result = new BitMatrix(dimension, yDimension);
|
||||
for (int i = 0; i < codewords.size(); i++) {
|
||||
for (int j = 0; j < codewords.get(i).size(); j++) {
|
||||
int moduleOffset = j * MODULES_IN_SYMBOL;
|
||||
for (int k = 0; k < MODULES_IN_SYMBOL; k++) {
|
||||
if ((codewords.get(i).get(j) & (1 << (MODULES_IN_SYMBOL - k - 1))) > 0) {
|
||||
result.set(moduleOffset + k, i);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
private static int calculateClusterNumber(int codeword) {
|
||||
if (codeword == 0) {
|
||||
return -1;
|
||||
}
|
||||
int barNumber = 0;
|
||||
boolean blackBar = true;
|
||||
int clusterNumber = 0;
|
||||
for (int i = 0; i < MODULES_IN_SYMBOL; i++) {
|
||||
if ((codeword & (1 << i)) > 0) {
|
||||
if (!blackBar) {
|
||||
blackBar = true;
|
||||
barNumber++;
|
||||
}
|
||||
if (barNumber % 2 == 0) {
|
||||
clusterNumber++;
|
||||
} else {
|
||||
clusterNumber--;
|
||||
}
|
||||
} else {
|
||||
if (blackBar) {
|
||||
blackBar = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
return (clusterNumber + 9) % 9;
|
||||
}
|
||||
|
||||
private static void resize1(List<List<Map<Integer,Integer>>> list, int size) {
|
||||
// Delete some
|
||||
for (int i = size; i < list.size(); i++) {
|
||||
list.remove(i);
|
||||
}
|
||||
// Append some.
|
||||
for (int i = list.size(); i < size; i++) {
|
||||
list.add(new ArrayList<Map<Integer,Integer>>());
|
||||
}
|
||||
}
|
||||
|
||||
private static void resize2(List<Map<Integer,Integer>> list, int size) {
|
||||
// Delete some
|
||||
for (int i = size; i < list.size(); i++) {
|
||||
list.remove(i);
|
||||
}
|
||||
// Append some.
|
||||
for (int i = list.size(); i < size; i++) {
|
||||
list.add(new HashMap<Integer, Integer>());
|
||||
}
|
||||
}
|
||||
|
||||
private static void resize3(List<List<Integer>> list, int size) {
|
||||
// Delete some
|
||||
for (int i = size; i < list.size(); i++) {
|
||||
list.remove(i);
|
||||
}
|
||||
// Append some.
|
||||
for (int i = list.size(); i < size; i++) {
|
||||
list.add(new ArrayList<Integer>());
|
||||
}
|
||||
}
|
||||
|
||||
private static void resize4(List<Integer> list, int size) {
|
||||
// Delete some
|
||||
for (int i = size; i < list.size(); i++) {
|
||||
list.remove(i);
|
||||
}
|
||||
// Append some.
|
||||
for (int i = list.size(); i < size; i++) {
|
||||
list.add(0);
|
||||
}
|
||||
}
|
||||
|
||||
private static <T> T defaultValue(T value, T d) {
|
||||
return value == null ? d : value;
|
||||
}
|
||||
|
||||
}
|
|
@ -1 +1 @@
|
|||
Unknown - change me!
|
||||
-ActiveBarcode-ABCDEFGHIJKLMNOPQRSTUVWXYZ
|
|
@ -1 +1 @@
|
|||
Unknown - change me!
|
||||
-ActiveBarcode-ABCDEFGHIJKLMNOPQRSTUVWXYZ1234567890-ActiveBarcode-ABCDEFGHIJKLMNOPQRSTUVWXYZ1234567890-ActiveBarcode-ABCDEFGHIJKLMNOPQRSTUVWXYZ1234567890-ActiveBarcode-ABCDEFGHIJKLMNOPQRSTUVWXYZ1234567890-ActiveBarcode-ABCDEFGHIJKLMNOPQRSTUVWXYZ1234567890-ActiveBarcode-ABCDEFGHIJKLMNOPQRSTUVWXYZ1234567890-ActiveBarcode-ABCDEFGHIJKLMNOPQRSTUVWXYZ1234567890-ActiveBarcode-ABCDEFGHIJKLMNOPQRSTUVWXYZ1234567890-ActiveBarcode-ABCDEFGHIJKLMNOPQRSTUVWXYZ1234567890-ActiveBarcode-ABCDEFGHIJKLMNOPQRSTUVWXYZ1234567890-ActiveBarcode-ABCDEFGHIJKLMNOPQRSTUVWXYZ1234567890-ActiveBarcode-ABCDEFGHIJKLMNOPQRSTUVWXYZ1234567890
|
|
@ -29,7 +29,7 @@ public final class PDF417BlackBox1TestCase extends AbstractBlackBoxTestCase {
|
|||
|
||||
public PDF417BlackBox1TestCase() {
|
||||
super("test/data/blackbox/pdf417", new MultiFormatReader(), BarcodeFormat.PDF_417);
|
||||
addTest(4, 4, 0.0f);
|
||||
addTest(6, 6, 0.0f);
|
||||
addTest(4, 4, 180.0f);
|
||||
}
|
||||
|
||||
|
|
|
@ -29,8 +29,8 @@ public final class PDF417BlackBox2TestCase extends AbstractBlackBoxTestCase {
|
|||
|
||||
public PDF417BlackBox2TestCase() {
|
||||
super("test/data/blackbox/pdf417-2", new MultiFormatReader(), BarcodeFormat.PDF_417);
|
||||
addTest(19, 19, 0, 0, 0.0f);
|
||||
addTest(17, 17, 0, 0, 180.0f);
|
||||
addTest(23, 23, 0, 0, 0.0f);
|
||||
addTest(22, 22, 0, 0, 180.0f);
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -24,8 +24,8 @@ public final class PDF417BlackBox3TestCase extends AbstractBlackBoxTestCase {
|
|||
|
||||
public PDF417BlackBox3TestCase() {
|
||||
super("test/data/blackbox/pdf417-3", new MultiFormatReader(), BarcodeFormat.PDF_417);
|
||||
addTest(4, 4, 0, 0, 0.0f);
|
||||
addTest(4, 4, 0, 0, 180.0f);
|
||||
addTest(9, 9, 0, 0, 0.0f);
|
||||
addTest(9, 9, 0, 0, 180.0f);
|
||||
}
|
||||
|
||||
}
|
||||
|
|
Loading…
Reference in a new issue