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:
srowen@gmail.com 2013-03-25 15:06:58 +00:00
parent 843a181d73
commit a42d32ca15
9 changed files with 973 additions and 309 deletions

View file

@ -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

View file

@ -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,

View file

@ -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);
}
}

View 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;
}
}

View file

@ -1 +1 @@
Unknown - change me!
-ActiveBarcode-ABCDEFGHIJKLMNOPQRSTUVWXYZ

View file

@ -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

View file

@ -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);
}

View file

@ -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);
}
}

View file

@ -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);
}
}