Code tweaks and so forth with Daniel

git-svn-id: https://zxing.googlecode.com/svn/trunk@34 59b500cc-1b3d-0410-9834-0bbf25fbcc57
This commit is contained in:
srowen 2007-11-14 19:23:23 +00:00
parent 852bd08760
commit f7a402511c
5 changed files with 117 additions and 101 deletions

View file

@ -38,8 +38,7 @@ public final class MultiFormatReader implements Reader {
public Result decode(MonochromeBitmapSource image, Hashtable hints) public Result decode(MonochromeBitmapSource image, Hashtable hints)
throws ReaderException { throws ReaderException {
Hashtable possibleFormats = Hashtable possibleFormats = hints == null ? null : (Hashtable) hints.get(DecodeHintType.POSSIBLE_FORMATS);
hints == null ? null : (Hashtable) hints.get(DecodeHintType.POSSIBLE_FORMATS);
boolean tryUPC = false; boolean tryUPC = false;
boolean tryQR = false; boolean tryQR = false;
@ -51,7 +50,7 @@ public final class MultiFormatReader implements Reader {
} else if (possibleFormats.contains(BarcodeFormat.QR_CODE)) { } else if (possibleFormats.contains(BarcodeFormat.QR_CODE)) {
tryQR = true; tryQR = true;
} else { } else {
throw new ReaderException(); throw new ReaderException("POSSIBLE_FORMATS specifies no supported types");
} }
// UPC is much faster to decode, so try it first. // UPC is much faster to decode, so try it first.
@ -70,7 +69,7 @@ public final class MultiFormatReader implements Reader {
} }
} }
throw new ReaderException(); throw new ReaderException("Could not locate and decode a barcode in the image");
} }
} }

View file

@ -86,21 +86,21 @@ public final class BitArray {
for (int i = 0; i < max; i++) { for (int i = 0; i < max; i++) {
newBits[i] = 0; newBits[i] = 0;
} }
int size = this.size;
for (int i = 0; i < size; i++) { for (int i = 0; i < size; i++) {
if (this.get(size - i - 1)) { if (get(size - i - 1)) {
newBits[i >> 5] |= 1 << (i & 0x1F); newBits[i >> 5] |= 1 << (i & 0x1F);
} }
} }
bits = newBits; bits = newBits;
} }
private int[] makeArray(int size) { private static int[] makeArray(int size) {
int arraySize = size >> 5; int arraySize = size >> 5;
if ((size & 0x1F) != 0) { if ((size & 0x1F) != 0) {
arraySize++; arraySize++;
} }
int[] result = new int[arraySize]; return new int[arraySize];
return result;
} }
} }

View file

@ -19,8 +19,6 @@ package com.google.zxing.upc;
import com.google.zxing.common.BitArray; import com.google.zxing.common.BitArray;
import com.google.zxing.MonochromeBitmapSource; import com.google.zxing.MonochromeBitmapSource;
import java.util.Arrays;
/** /**
* This class takes a bitmap, and attempts to return a String which is the contents of the UPC * This class takes a bitmap, and attempts to return a String which is the contents of the UPC
* barcode in the image. It should be scale-invariant, but does not make any corrections for * barcode in the image. It should be scale-invariant, but does not make any corrections for
@ -28,36 +26,55 @@ import java.util.Arrays;
* *
* @author dswitkin@google.com (Daniel Switkin) * @author dswitkin@google.com (Daniel Switkin)
*/ */
public class UPCDecoder { final class UPCDecoder {
private static final byte[] BITMAP_SEARCH_PATTERN = { 50, 49, 51, 48, 52, 46, 54, 43, 57, 40, 60 };
private static final byte[] START_END_PATTERN = { 1, 1, 1 };
private static final byte[] MIDDLE_PATTERN = { 1, 1, 1, 1, 1 };
private static final byte[][] DIGIT_PATTERNS = {
{ 30, 20, 10, 10 }, // 0
{ 20, 20, 20, 10 }, // 1
{ 20, 10, 20, 20 }, // 2
{ 10, 40, 10, 10 }, // 3
{ 10, 10, 30, 20 }, // 4
{ 10, 20, 30, 10 }, // 5
{ 10, 10, 10, 40 }, // 6
{ 10, 30, 10, 20 }, // 7
{ 10, 20, 10, 30 }, // 8
{ 30, 10, 10, 20 } // 9
};
private static final int TOLERANCE = 5;
private MonochromeBitmapSource bitmap;
private int width;
private int height;
private StringBuffer result;
UPCDecoder(MonochromeBitmapSource bitmap) { UPCDecoder(MonochromeBitmapSource bitmap) {
mBitmap = bitmap; this.bitmap = bitmap;
if (bitmap != null) { width = bitmap.getWidth();
mWidth = bitmap.getWidth(); height = bitmap.getHeight();
mHeight = bitmap.getHeight();
}
} }
// To decode the image, we follow a search pattern defined in kBitmapSearchPattern. It is a // To decode the image, we follow a search pattern defined in kBitmapSearchPattern. It is a
// list of percentages which translate to row numbers to scan across. For each row, we scan // list of percentages which translate to row numbers to scan across. For each row, we scan
// left to right, and if that fails, we reverse the row in place and try again to see if the // left to right, and if that fails, we reverse the row in place and try again to see if the
// bar code was upside down. // bar code was upside down.
public String decode() { String decode() {
if (mBitmap == null) return ""; BitArray rowData = new BitArray(width);
BitArray rowData = new BitArray(mWidth);
String longestResult = ""; String longestResult = "";
int found = -1; int found = -1;
for (int x = 0; x < kBitmapSearchPattern.length; x++) { for (int x = 0; x < BITMAP_SEARCH_PATTERN.length; x++) {
int row = mHeight * kBitmapSearchPattern[x] / 100; int row = height * BITMAP_SEARCH_PATTERN[x] / 100;
mBitmap.getBlackRow(row, rowData, 0, mWidth); bitmap.getBlackRow(row, rowData, 0, width);
if (decodeRow(rowData)) { if (decodeRow(rowData)) {
found = x; found = x;
break; break;
} }
//Log("decode: row " + row + " normal result: " + mResult); //Log("decode: row " + row + " normal result: " + mResult);
if (mResult.length() > longestResult.length()) { if (result.length() > longestResult.length()) {
longestResult = mResult; longestResult = result.toString();
} }
rowData.reverse(); rowData.reverse();
@ -66,48 +83,60 @@ public class UPCDecoder {
break; break;
} }
//Log("decode: row " + row + " inverted result: " + mResult); //Log("decode: row " + row + " inverted result: " + mResult);
if (mResult.length() > longestResult.length()) { if (result.length() > longestResult.length()) {
longestResult = mResult; longestResult = result.toString();
} }
} }
if (found >= 0) return mResult; if (found >= 0) {
else return ""; return result.toString();
} else {
return "";
}
} }
// UPC-A bar codes are made up of a left marker, six digits, a middle marker, six more digits, /**
// and an end marker, reading from left to right. For more information, see: * UPC-A bar codes are made up of a left marker, six digits, a middle marker, six more digits,
// * and an end marker, reading from left to right. For more information, see:
// http://en.wikipedia.org/wiki/Universal_Product_Code * <a href="http://en.wikipedia.org/wiki/Universal_Product_Code">
// * http://en.wikipedia.org/wiki/Universal_Product_Code</a>
// TODO: Add support for UPC-E Zero Compressed bar codes. */
// TODO: Add support for EAN-13 (European Article Number) bar codes.
// FIXME: Don't trust the first result from findPattern() for the start sequence - resume from
// that spot and try to start again if finding digits fails.
private boolean decodeRow(BitArray rowData) { private boolean decodeRow(BitArray rowData) {
mResult = ""; // TODO: Add support for UPC-E Zero Compressed bar codes.
int rowOffset = findPattern(rowData, 0, kStartEndPattern, false); // TODO: Add support for EAN-13 (European Article Number) bar codes.
if (rowOffset < 0) return false; // FIXME: Don't trust the first result from findPattern() for the start sequence - resume from
// that spot and try to start again if finding digits fails.
result = new StringBuffer();
int rowOffset = findPattern(rowData, 0, START_END_PATTERN, false);
if (rowOffset < 0) {
return false;
}
//Log("Start pattern ends at column " + rowOffset); //Log("Start pattern ends at column " + rowOffset);
rowOffset = decodeOneSide(rowData, rowOffset); rowOffset = decodeOneSide(rowData, rowOffset);
if (rowOffset < 0) return false; if (rowOffset < 0) {
return false;
}
rowOffset = findPattern(rowData, rowOffset, kMiddlePattern, true); rowOffset = findPattern(rowData, rowOffset, MIDDLE_PATTERN, true);
if (rowOffset < 0) return false; if (rowOffset < 0) {
return false;
}
//Log("Middle pattern ends at column " + rowOffset); //Log("Middle pattern ends at column " + rowOffset);
rowOffset = decodeOneSide(rowData, rowOffset); rowOffset = decodeOneSide(rowData, rowOffset);
if (rowOffset < 0) return false; if (rowOffset < 0) {
return false;
}
// We could attempt to read the end pattern for sanity, but there's not much point. // We could attempt to read the end pattern for sanity, but there's not much point.
// UPC-A codes have 12 digits, so any other result is garbage. // UPC-A codes have 12 digits, so any other result is garbage.
return (mResult.length() == 12); return result.length() == 12;
} }
private int decodeOneSide(BitArray rowData, int rowOffset) { private int decodeOneSide(BitArray rowData, int rowOffset) {
int[] counters = new int[4]; int[] counters = new int[4];
for (int x = 0; x < 6 && rowOffset < mWidth; x++) { for (int x = 0; x < 6 && rowOffset < width; x++) {
recordPattern(rowData, rowOffset, counters, 4); recordPattern(rowData, rowOffset, counters, 4);
for (int y = 0; y < 4; y++) { for (int y = 0; y < 4; y++) {
rowOffset += counters[y]; rowOffset += counters[y];
@ -116,7 +145,7 @@ public class UPCDecoder {
if (c == '-') { if (c == '-') {
return -1; return -1;
} else { } else {
mResult += c; result.append(c);
} }
} }
return rowOffset; return rowOffset;
@ -127,13 +156,14 @@ public class UPCDecoder {
// begin on white or black based on the flag. // begin on white or black based on the flag.
private int findPattern(BitArray rowData, int rowOffset, byte[] pattern, boolean whiteFirst) { private int findPattern(BitArray rowData, int rowOffset, byte[] pattern, boolean whiteFirst) {
int[] counters = new int[pattern.length]; int[] counters = new int[pattern.length];
int width = mWidth; int width = this.width;
boolean isWhite = false; boolean isWhite = false;
for (; rowOffset < width; rowOffset++) { while (rowOffset < width) {
isWhite = !rowData.get(rowOffset); isWhite = !rowData.get(rowOffset);
if (whiteFirst == isWhite) { if (whiteFirst == isWhite) {
break; break;
} }
rowOffset++;
} }
int counterPosition = 0; int counterPosition = 0;
@ -160,15 +190,19 @@ public class UPCDecoder {
return -1; return -1;
} }
// Records a pattern of alternating white and black pixels, returning an array of how many /**
// pixels of each color were seen. The pattern begins immediately based on the color of the * Records a pattern of alternating white and black pixels, returning an array of how many
// first pixel encountered, so a patternSize of 3 could result in WBW or BWB. * pixels of each color were seen. The pattern begins immediately based on the color of the
* first pixel encountered, so a patternSize of 3 could result in WBW or BWB.
*/
private void recordPattern(BitArray rowData, int rowOffset, int[] counters, int patternSize) { private void recordPattern(BitArray rowData, int rowOffset, int[] counters, int patternSize) {
Arrays.fill(counters, 0); for (int i = 0; i < counters.length; i++) {
counters[i] = 0;
}
boolean isWhite = !rowData.get(rowOffset); boolean isWhite = !rowData.get(rowOffset);
int counterPosition = 0; int counterPosition = 0;
int width = mWidth; int width = this.width;
for (int x = rowOffset; x < width; x++) { for (int x = rowOffset; x < width; x++) {
boolean pixel = rowData.get(x); boolean pixel = rowData.get(x);
if ((!pixel && isWhite) || (pixel && !isWhite)) { if ((!pixel && isWhite) || (pixel && !isWhite)) {
@ -185,13 +219,15 @@ public class UPCDecoder {
} }
} }
// This is an optimized version of doesPatternMatch() which is specific to recognizing digits. /**
// The average is divided by 7 because there are 7 bits per digit, even though the color only * This is an optimized version of doesPatternMatch() which is specific to recognizing digits.
// alternates four times. kDigitPatterns has been premultiplied by 10 for efficiency. Notice * The average is divided by 7 because there are 7 bits per digit, even though the color only
// that the contents of the counters array are modified to save an extra allocation, so don't * alternates four times. kDigitPatterns has been premultiplied by 10 for efficiency. Notice
// use these values after returning from this call. * that the contents of the counters array are modified to save an extra allocation, so don't
// TODO: add EAN even parity support * use these values after returning from this call.
private char findDigit(int[] counters) { */
private static char findDigit(int[] counters) {
// TODO: add EAN even parity support
int total = counters[0] + counters[1] + counters[2] + counters[3]; int total = counters[0] + counters[1] + counters[2] + counters[3];
int average = total * 10 / 7; int average = total * 10 / 7;
for (int x = 0; x < 4; x++) { for (int x = 0; x < 4; x++) {
@ -201,60 +237,41 @@ public class UPCDecoder {
for (int x = 0; x < 10; x++) { for (int x = 0; x < 10; x++) {
boolean match = true; boolean match = true;
for (int y = 0; y < 4; y++) { for (int y = 0; y < 4; y++) {
int diff = counters[y] - kDigitPatterns[x][y]; int diff = counters[y] - DIGIT_PATTERNS[x][y];
if (diff > kTolerance || diff < -kTolerance) { if (diff > TOLERANCE || diff < -TOLERANCE) {
match = false; match = false;
break; break;
} }
} }
if (match) return kDigits[x]; if (match) {
return (char) ((int) '0' + x);
}
} }
return '-'; return '-';
} }
// Finds whether the given set of pixel counters matches the requested pattern. Taking an /**
// average based on the number of counters offers some robustness when antialiased edges get * Finds whether the given set of pixel counters matches the requested pattern. Taking an
// interpreted as the wrong color. * average based on the number of counters offers some robustness when antialiased edges get
// TODO: Remove the divide for performance. * interpreted as the wrong color.
private boolean doesPatternMatch(int[] counters, byte[] pattern) { */
private static boolean doesPatternMatch(int[] counters, byte[] pattern) {
// TODO: Remove the divide for performance.
int total = 0; int total = 0;
for (int x = 0; x < counters.length; x++) { int numCounters = counters.length;
for (int x = 0; x < numCounters; x++) {
total += counters[x]; total += counters[x];
} }
int average = total * 10 / counters.length; int average = total * 10 / counters.length;
for (int x = 0; x < counters.length; x++) { for (int x = 0; x < numCounters; x++) {
int scaledCounter = counters[x] * 100 / average; int scaledCounter = counters[x] * 100 / average;
int scaledPattern = pattern[x] * 10; int scaledPattern = pattern[x] * 10;
if (scaledCounter < scaledPattern - kTolerance || scaledCounter > scaledPattern + kTolerance) { if (scaledCounter < scaledPattern - TOLERANCE || scaledCounter > scaledPattern + TOLERANCE) {
return false; return false;
} }
} }
return true; return true;
} }
private static final byte[] kBitmapSearchPattern = { 50, 49, 51, 48, 52, 46, 54, 43, 57, 40, 60 };
private static final byte[] kStartEndPattern = { 1, 1, 1 };
private static final byte[] kMiddlePattern = { 1, 1, 1, 1, 1 };
private static final byte[][] kDigitPatterns = {
{ 30, 20, 10, 10 }, // 0
{ 20, 20, 20, 10 }, // 1
{ 20, 10, 20, 20 }, // 2
{ 10, 40, 10, 10 }, // 3
{ 10, 10, 30, 20 }, // 4
{ 10, 20, 30, 10 }, // 5
{ 10, 10, 10, 40 }, // 6
{ 10, 30, 10, 20 }, // 7
{ 10, 20, 10, 30 }, // 8
{ 30, 10, 10, 20 } // 9
};
private static final char[] kDigits = { '0', '1', '2', '3', '4', '5', '6', '7', '8', '9' };
private static final int kTolerance = 5;
private MonochromeBitmapSource mBitmap;
private int mWidth;
private int mHeight;
private String mResult;
} }

View file

@ -52,4 +52,5 @@ public final class UPCReader implements Reader {
} }
return new Result(result, NO_POINTS); return new Result(result, NO_POINTS);
} }
} }

View file

@ -51,15 +51,14 @@ public final class CommandLineRunner {
} }
} }
private static void decode(URI uri) throws IOException, ReaderException { private static void decode(URI uri) throws IOException {
BufferedImage image = ImageIO.read(uri.toURL()); BufferedImage image = ImageIO.read(uri.toURL());
if (image == null) { if (image == null) {
System.out.println(uri.toString() + ": Could not load image"); System.err.println(uri.toString() + ": Could not load image");
return; return;
} }
try { try {
String result = String result = new MultiFormatReader().decode(new BufferedImageMonochromeBitmapSource(image)).getText();
new MultiFormatReader().decode(new BufferedImageMonochromeBitmapSource(image)).getText();
System.out.println(uri.toString() + ": " + result); System.out.println(uri.toString() + ": " + result);
} catch (ReaderException e) { } catch (ReaderException e) {
System.out.println(uri.toString() + ": No barcode found"); System.out.println(uri.toString() + ": No barcode found");