mirror of
https://github.com/zxing/zxing.git
synced 2025-01-26 02:23:25 -08:00
Added the initial version of my UPC reader and modified some common files
as necessary to get it building and used by the J2SE command line test. git-svn-id: https://zxing.googlecode.com/svn/trunk@31 59b500cc-1b3d-0410-9834-0bbf25fbcc57
This commit is contained in:
parent
fbb9d110de
commit
89c7787517
|
@ -17,6 +17,7 @@
|
|||
package com.google.zxing;
|
||||
|
||||
import com.google.zxing.qrcode.QRCodeReader;
|
||||
import com.google.zxing.upc.UPCReader;
|
||||
|
||||
import java.util.Hashtable;
|
||||
|
||||
|
@ -39,13 +40,37 @@ public final class MultiFormatReader implements Reader {
|
|||
throws ReaderException {
|
||||
Hashtable possibleFormats =
|
||||
hints == null ? null : (Hashtable) hints.get(DecodeHintType.POSSIBLE_FORMATS);
|
||||
// TODO for now we are only support QR Code so this behaves accordingly. This needs to
|
||||
// become more sophisticated
|
||||
if (possibleFormats == null || possibleFormats.contains(BarcodeFormat.QR_CODE)) {
|
||||
return new QRCodeReader().decode(image, hints);
|
||||
boolean tryUPC = false;
|
||||
boolean tryQR = false;
|
||||
|
||||
if (possibleFormats == null) {
|
||||
tryUPC = true;
|
||||
tryQR = true;
|
||||
} else if (possibleFormats.contains(BarcodeFormat.UPC)) {
|
||||
tryUPC = true;
|
||||
} else if (possibleFormats.contains(BarcodeFormat.QR_CODE)) {
|
||||
tryQR = true;
|
||||
} else {
|
||||
throw new ReaderException();
|
||||
}
|
||||
|
||||
// UPC is much faster to decode, so try it first.
|
||||
if (tryUPC) {
|
||||
try {
|
||||
return new UPCReader().decode(image, hints);
|
||||
} catch (ReaderException e) {
|
||||
}
|
||||
}
|
||||
|
||||
// Then fall through to QR codes.
|
||||
if (tryQR) {
|
||||
try {
|
||||
return new QRCodeReader().decode(image, hints);
|
||||
} catch (ReaderException e) {
|
||||
}
|
||||
}
|
||||
|
||||
throw new ReaderException();
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -23,14 +23,12 @@ package com.google.zxing.common;
|
|||
*/
|
||||
public final class BitArray {
|
||||
|
||||
private final int[] bits;
|
||||
private int[] bits;
|
||||
private int size;
|
||||
|
||||
public BitArray(int size) {
|
||||
int arraySize = size >> 5;
|
||||
if ((size & 0x1F) != 0) {
|
||||
arraySize++;
|
||||
}
|
||||
bits = new int[arraySize];
|
||||
this.size = size;
|
||||
this.bits = makeArray(size);
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -55,7 +53,7 @@ public final class BitArray {
|
|||
*
|
||||
* @param i first bit to set
|
||||
* @param newBits the new value of the next 32 bits. Note again that the least-significant bit
|
||||
* correponds to bit i, the next-least-significant to i+1, and so on.
|
||||
* corresponds to bit i, the next-least-significant to i+1, and so on.
|
||||
*/
|
||||
public void setBulk(int i, int newBits) {
|
||||
bits[i >> 5] = newBits;
|
||||
|
@ -78,5 +76,31 @@ public final class BitArray {
|
|||
public int[] getBitArray() {
|
||||
return bits;
|
||||
}
|
||||
|
||||
/**
|
||||
* Reverses all bits in the array.
|
||||
*/
|
||||
public void reverse() {
|
||||
int[] newBits = makeArray(size);
|
||||
int max = newBits.length;
|
||||
for (int i = 0; i < max; i++) {
|
||||
newBits[i] = 0;
|
||||
}
|
||||
for (int i = 0; i < size; i++) {
|
||||
if (this.get(size - i - 1)) {
|
||||
newBits[i >> 5] |= 1 << (i & 0x1F);
|
||||
}
|
||||
}
|
||||
bits = newBits;
|
||||
}
|
||||
|
||||
private int[] makeArray(int size) {
|
||||
int arraySize = size >> 5;
|
||||
if ((size & 0x1F) != 0) {
|
||||
arraySize++;
|
||||
}
|
||||
int[] result = new int[arraySize];
|
||||
return result;
|
||||
}
|
||||
|
||||
}
|
260
core/src/com/google/zxing/upc/UPCDecoder.java
Executable file
260
core/src/com/google/zxing/upc/UPCDecoder.java
Executable file
|
@ -0,0 +1,260 @@
|
|||
/*
|
||||
* Copyright 2007 Google Inc.
|
||||
*
|
||||
* 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.upc;
|
||||
|
||||
import com.google.zxing.common.BitArray;
|
||||
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
|
||||
* barcode in the image. It should be scale-invariant, but does not make any corrections for
|
||||
* rotation or skew.
|
||||
*
|
||||
* @author dswitkin@google.com (Daniel Switkin)
|
||||
*/
|
||||
public class UPCDecoder {
|
||||
UPCDecoder(MonochromeBitmapSource bitmap) {
|
||||
mBitmap = bitmap;
|
||||
if (bitmap != null) {
|
||||
mWidth = bitmap.getWidth();
|
||||
mHeight = bitmap.getHeight();
|
||||
}
|
||||
}
|
||||
|
||||
// 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
|
||||
// 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.
|
||||
public String decode() {
|
||||
if (mBitmap == null) return "";
|
||||
|
||||
BitArray rowData = new BitArray(mWidth);
|
||||
String longestResult = "";
|
||||
int found = -1;
|
||||
for (int x = 0; x < kBitmapSearchPattern.length; x++) {
|
||||
int row = mHeight * kBitmapSearchPattern[x] / 100;
|
||||
mBitmap.getBlackRow(row, rowData, 0, mWidth);
|
||||
|
||||
if (decodeRow(rowData)) {
|
||||
found = x;
|
||||
break;
|
||||
}
|
||||
//Log("decode: row " + row + " normal result: " + mResult);
|
||||
if (mResult.length() > longestResult.length()) {
|
||||
longestResult = mResult;
|
||||
}
|
||||
|
||||
rowData.reverse();
|
||||
if (decodeRow(rowData)) {
|
||||
found = x;
|
||||
break;
|
||||
}
|
||||
//Log("decode: row " + row + " inverted result: " + mResult);
|
||||
if (mResult.length() > longestResult.length()) {
|
||||
longestResult = mResult;
|
||||
}
|
||||
}
|
||||
|
||||
if (found >= 0) return mResult;
|
||||
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:
|
||||
//
|
||||
// http://en.wikipedia.org/wiki/Universal_Product_Code
|
||||
//
|
||||
// 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) {
|
||||
mResult = "";
|
||||
int rowOffset = findPattern(rowData, 0, kStartEndPattern, false);
|
||||
if (rowOffset < 0) return false;
|
||||
//Log("Start pattern ends at column " + rowOffset);
|
||||
|
||||
rowOffset = decodeOneSide(rowData, rowOffset);
|
||||
if (rowOffset < 0) return false;
|
||||
|
||||
rowOffset = findPattern(rowData, rowOffset, kMiddlePattern, true);
|
||||
if (rowOffset < 0) return false;
|
||||
//Log("Middle pattern ends at column " + rowOffset);
|
||||
|
||||
rowOffset = decodeOneSide(rowData, rowOffset);
|
||||
if (rowOffset < 0) return false;
|
||||
|
||||
// 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.
|
||||
return (mResult.length() == 12);
|
||||
}
|
||||
|
||||
private int decodeOneSide(BitArray rowData, int rowOffset) {
|
||||
int[] counters = new int[4];
|
||||
for (int x = 0; x < 6 && rowOffset < mWidth; x++) {
|
||||
recordPattern(rowData, rowOffset, counters, 4);
|
||||
for (int y = 0; y < 4; y++) {
|
||||
rowOffset += counters[y];
|
||||
}
|
||||
char c = findDigit(counters);
|
||||
if (c == '-') {
|
||||
return -1;
|
||||
} else {
|
||||
mResult += c;
|
||||
}
|
||||
}
|
||||
return rowOffset;
|
||||
}
|
||||
|
||||
// Returns the horizontal position just after the pattern was found if successful, otherwise
|
||||
// returns -1 if the pattern was not found. Searches are always left to right, and patterns
|
||||
// begin on white or black based on the flag.
|
||||
private int findPattern(BitArray rowData, int rowOffset, byte[] pattern, boolean whiteFirst) {
|
||||
int[] counters = new int[pattern.length];
|
||||
int width = mWidth;
|
||||
boolean isWhite = false;
|
||||
for (; rowOffset < width; rowOffset++) {
|
||||
isWhite = !rowData.get(rowOffset);
|
||||
if (whiteFirst == isWhite) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
int counterPosition = 0;
|
||||
for (int x = rowOffset; x < width; x++) {
|
||||
boolean pixel = rowData.get(x);
|
||||
if ((!pixel && isWhite) || (pixel && !isWhite)) {
|
||||
counters[counterPosition]++;
|
||||
} else {
|
||||
if (counterPosition == pattern.length - 1) {
|
||||
if (doesPatternMatch(counters, pattern)) {
|
||||
return x;
|
||||
}
|
||||
for (int y = 2; y < pattern.length; y++) {
|
||||
counters[y - 2] = counters[y];
|
||||
}
|
||||
counterPosition--;
|
||||
} else {
|
||||
counterPosition++;
|
||||
}
|
||||
counters[counterPosition] = 1;
|
||||
isWhite = !isWhite;
|
||||
}
|
||||
}
|
||||
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
|
||||
// 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) {
|
||||
Arrays.fill(counters, 0);
|
||||
boolean isWhite = !rowData.get(rowOffset);
|
||||
|
||||
int counterPosition = 0;
|
||||
int width = mWidth;
|
||||
for (int x = rowOffset; x < width; x++) {
|
||||
boolean pixel = rowData.get(x);
|
||||
if ((!pixel && isWhite) || (pixel && !isWhite)) {
|
||||
counters[counterPosition]++;
|
||||
} else {
|
||||
counterPosition++;
|
||||
if (counterPosition == patternSize) {
|
||||
return;
|
||||
} else {
|
||||
counters[counterPosition] = 1;
|
||||
isWhite = !isWhite;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 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
|
||||
// alternates four times. kDigitPatterns has been premultiplied by 10 for efficiency. Notice
|
||||
// that the contents of the counters array are modified to save an extra allocation, so don't
|
||||
// use these values after returning from this call.
|
||||
// TODO: add EAN even parity support
|
||||
private char findDigit(int[] counters) {
|
||||
int total = counters[0] + counters[1] + counters[2] + counters[3];
|
||||
int average = total * 10 / 7;
|
||||
for (int x = 0; x < 4; x++) {
|
||||
counters[x] = counters[x] * 100 / average;
|
||||
}
|
||||
|
||||
for (int x = 0; x < 10; x++) {
|
||||
boolean match = true;
|
||||
for (int y = 0; y < 4; y++) {
|
||||
int diff = counters[y] - kDigitPatterns[x][y];
|
||||
if (diff > kTolerance || diff < -kTolerance) {
|
||||
match = false;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (match) return kDigits[x];
|
||||
}
|
||||
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
|
||||
// interpreted as the wrong color.
|
||||
// TODO: Remove the divide for performance.
|
||||
private boolean doesPatternMatch(int[] counters, byte[] pattern) {
|
||||
int total = 0;
|
||||
for (int x = 0; x < counters.length; x++) {
|
||||
total += counters[x];
|
||||
}
|
||||
int average = total * 10 / counters.length;
|
||||
|
||||
for (int x = 0; x < counters.length; x++) {
|
||||
int scaledCounter = counters[x] * 100 / average;
|
||||
int scaledPattern = pattern[x] * 10;
|
||||
if (scaledCounter < scaledPattern - kTolerance || scaledCounter > scaledPattern + kTolerance) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
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;
|
||||
}
|
55
core/src/com/google/zxing/upc/UPCReader.java
Executable file
55
core/src/com/google/zxing/upc/UPCReader.java
Executable file
|
@ -0,0 +1,55 @@
|
|||
/*
|
||||
* Copyright 2007 Google Inc.
|
||||
*
|
||||
* 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.upc;
|
||||
|
||||
import com.google.zxing.MonochromeBitmapSource;
|
||||
import com.google.zxing.Reader;
|
||||
import com.google.zxing.ReaderException;
|
||||
import com.google.zxing.Result;
|
||||
import com.google.zxing.ResultPoint;
|
||||
|
||||
import java.util.Hashtable;
|
||||
|
||||
/**
|
||||
* A reader which decodes UPC-A barcodes.
|
||||
*
|
||||
* @author dswitkin@google.com (Daniel Switkin)
|
||||
*/
|
||||
public final class UPCReader implements Reader {
|
||||
|
||||
private static final ResultPoint[] NO_POINTS = new ResultPoint[0];
|
||||
|
||||
/**
|
||||
* Locates and decodes a UPC barcode in an image.
|
||||
*
|
||||
* @return a String representing the digits found
|
||||
* @throws ReaderException if a barcode cannot be found or decoded
|
||||
*/
|
||||
public Result decode(MonochromeBitmapSource image) throws ReaderException {
|
||||
return decode(image, null);
|
||||
}
|
||||
|
||||
public Result decode(MonochromeBitmapSource image, Hashtable hints)
|
||||
throws ReaderException {
|
||||
UPCDecoder decoder = new UPCDecoder(image);
|
||||
String result = decoder.decode();
|
||||
if (result == null || result.length() == 0) {
|
||||
throw new ReaderException("No UPC barcode found");
|
||||
}
|
||||
return new Result(result, NO_POINTS);
|
||||
}
|
||||
}
|
|
@ -53,9 +53,17 @@ public final class CommandLineRunner {
|
|||
|
||||
private static void decode(URI uri) throws IOException, ReaderException {
|
||||
BufferedImage image = ImageIO.read(uri.toURL());
|
||||
String result =
|
||||
new MultiFormatReader().decode(new BufferedImageMonochromeBitmapSource(image)).getText();
|
||||
System.out.println(result);
|
||||
if (image == null) {
|
||||
System.out.println(uri.toString() + ": Could not load image");
|
||||
return;
|
||||
}
|
||||
try {
|
||||
String result =
|
||||
new MultiFormatReader().decode(new BufferedImageMonochromeBitmapSource(image)).getText();
|
||||
System.out.println(uri.toString() + ": " + result);
|
||||
} catch (ReaderException e) {
|
||||
System.out.println(uri.toString() + ": No barcode found");
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
|
Loading…
Reference in a new issue