mirror of
https://github.com/zxing/zxing.git
synced 2025-03-05 20:48:51 -08:00
Major refactoring of 1D barcode code. Moved into com.google.zxing.oned package. Misc. other changes to support this refactoring
git-svn-id: https://zxing.googlecode.com/svn/trunk@159 59b500cc-1b3d-0410-9834-0bbf25fbcc57
This commit is contained in:
parent
e4c014d324
commit
8f7a3346cb
|
@ -25,8 +25,8 @@ public final class BarcodeFormat {
|
|||
|
||||
// No, we can't use an enum here. J2ME doesn't support it.
|
||||
|
||||
/** UPC 1D barcode format family. */
|
||||
public static final BarcodeFormat UPC = new BarcodeFormat();
|
||||
/** 1D barcode format family. */
|
||||
public static final BarcodeFormat ONED = new BarcodeFormat();
|
||||
/** QR Code 2D barcode format */
|
||||
public static final BarcodeFormat QR_CODE = new BarcodeFormat();
|
||||
/** DataMatrix 2D barcode format */
|
||||
|
|
|
@ -16,8 +16,8 @@
|
|||
|
||||
package com.google.zxing;
|
||||
|
||||
import com.google.zxing.oned.MultiFormatOneDReader;
|
||||
import com.google.zxing.qrcode.QRCodeReader;
|
||||
import com.google.zxing.upc.UPCReader;
|
||||
|
||||
import java.util.Hashtable;
|
||||
|
||||
|
@ -38,28 +38,24 @@ public final class MultiFormatReader implements Reader {
|
|||
throws ReaderException {
|
||||
Hashtable possibleFormats = hints == null ? null : (Hashtable) hints.get(DecodeHintType.POSSIBLE_FORMATS);
|
||||
|
||||
boolean tryUPC;
|
||||
boolean tryOneD;
|
||||
boolean tryQR;
|
||||
if (possibleFormats == null) {
|
||||
tryUPC = true;
|
||||
tryOneD = true;
|
||||
tryQR = true;
|
||||
} else {
|
||||
tryUPC = possibleFormats.contains(BarcodeFormat.UPC);
|
||||
tryOneD = possibleFormats.contains(BarcodeFormat.ONED);
|
||||
tryQR = possibleFormats.contains(BarcodeFormat.QR_CODE);
|
||||
}
|
||||
if (!(tryUPC || tryQR)) {
|
||||
if (!(tryOneD || tryQR)) {
|
||||
throw new ReaderException("POSSIBLE_FORMATS specifies no supported types");
|
||||
}
|
||||
|
||||
// Save the last exception as what we'll report if nothing decodes
|
||||
ReaderException savedRE = null;
|
||||
|
||||
// UPC is much faster to decode, so try it first.
|
||||
if (tryUPC) {
|
||||
if (tryOneD) {
|
||||
try {
|
||||
return new UPCReader().decode(image, hints);
|
||||
return new MultiFormatOneDReader().decode(image, hints);
|
||||
} catch (ReaderException re) {
|
||||
savedRE = re;
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -68,11 +64,10 @@ public final class MultiFormatReader implements Reader {
|
|||
try {
|
||||
return new QRCodeReader().decode(image, hints);
|
||||
} catch (ReaderException re) {
|
||||
savedRE = re;
|
||||
}
|
||||
}
|
||||
|
||||
throw savedRE;
|
||||
throw new ReaderException("No barcode was detected in this image.");
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -25,6 +25,7 @@ package com.google.zxing.common;
|
|||
* </p>
|
||||
*
|
||||
* @author srowen@google.com (Sean Owen)
|
||||
* @author dswitkin@google.com (Daniel Switkin)
|
||||
*/
|
||||
public final class BlackPointEstimator {
|
||||
|
||||
|
@ -37,10 +38,17 @@ public final class BlackPointEstimator {
|
|||
* count of the brightest luminance values that should be considered "black".</p>
|
||||
*
|
||||
* @param histogram an array of <em>counts</em> of luminance values
|
||||
* @param biasTowardsWhite values higher than 1.0 suggest that a higher black point is desirable (e.g.
|
||||
* more values are considered black); less than 1.0 suggests that lower is desirable. Must be greater
|
||||
* than 0.0; 1.0 is a good "default"
|
||||
* @return index within argument of bucket corresponding to brightest values which should be
|
||||
* considered "black"
|
||||
*/
|
||||
public static int estimate(int[] histogram) {
|
||||
public static int estimate(int[] histogram, float biasTowardsWhite) {
|
||||
|
||||
if (Float.isNaN(biasTowardsWhite) || biasTowardsWhite <= 0.0f) {
|
||||
throw new IllegalArgumentException("Illegal biasTowardsWhite: " + biasTowardsWhite);
|
||||
}
|
||||
|
||||
int numBuckets = histogram.length;
|
||||
|
||||
|
@ -79,7 +87,7 @@ public final class BlackPointEstimator {
|
|||
int bestValley = secondPeak - 1;
|
||||
int bestValleyScore = -1;
|
||||
for (int i = secondPeak - 1; i > firstPeak; i--) {
|
||||
int fromFirst = i - firstPeak;
|
||||
int fromFirst = (int) (biasTowardsWhite * (i - firstPeak));
|
||||
// Favor a "valley" that is not too close to either peak -- especially not the black peak --
|
||||
// and that has a low value of course
|
||||
int score = fromFirst * fromFirst * (secondPeak - i) * (256 - histogram[i]);
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
/*
|
||||
* Copyright 2007 Google Inc.
|
||||
* Copyright 2008 Google Inc.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
|
@ -14,19 +14,22 @@
|
|||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package com.google.zxing.upc;
|
||||
package com.google.zxing.common;
|
||||
|
||||
import com.google.zxing.ResultPoint;
|
||||
|
||||
/**
|
||||
* <p>Simple implementation of {@link ResultPoint} for applications that don't need
|
||||
* to use anything more complex.</p>
|
||||
*
|
||||
* @author dswitkin@google.com (Daniel Switkin)
|
||||
*/
|
||||
public final class UPCPoint implements ResultPoint {
|
||||
public final class GenericResultPoint implements ResultPoint {
|
||||
|
||||
private final float posX;
|
||||
private final float posY;
|
||||
|
||||
UPCPoint(float posX, float posY) {
|
||||
public GenericResultPoint(float posX, float posY) {
|
||||
this.posX = posX;
|
||||
this.posY = posY;
|
||||
}
|
153
core/src/com/google/zxing/oned/AbstractOneDReader.java
Normal file
153
core/src/com/google/zxing/oned/AbstractOneDReader.java
Normal file
|
@ -0,0 +1,153 @@
|
|||
/*
|
||||
* Copyright 2008 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.oned;
|
||||
|
||||
import com.google.zxing.BlackPointEstimationMethod;
|
||||
import com.google.zxing.MonochromeBitmapSource;
|
||||
import com.google.zxing.ReaderException;
|
||||
import com.google.zxing.Result;
|
||||
import com.google.zxing.common.BitArray;
|
||||
|
||||
import java.util.Hashtable;
|
||||
|
||||
/**
|
||||
* <p>Encapsulates functionality and implementation that is common to all families
|
||||
* of one-dimensional barcodes.</p>
|
||||
*
|
||||
* @author dswitkin@google.com (Daniel Switkin)
|
||||
* @author srowen@google.com (Sean Owen)
|
||||
*/
|
||||
public abstract class AbstractOneDReader implements OneDReader {
|
||||
|
||||
public final Result decode(MonochromeBitmapSource image) throws ReaderException {
|
||||
return decode(image, null);
|
||||
}
|
||||
|
||||
public final Result decode(MonochromeBitmapSource image, Hashtable hints) throws ReaderException {
|
||||
|
||||
int width = image.getWidth();
|
||||
int height = image.getHeight();
|
||||
|
||||
BitArray row = new BitArray(width);
|
||||
|
||||
// We're going to examine rows from the middle outward, searching alternately above and below the middle,
|
||||
// and farther out each time. rowStep is the number of rows between each successive attempt above and below
|
||||
// the middle. So we'd scan row middle, then middle - rowStep, then middle + rowStep,
|
||||
// then middle - 2*rowStep, etc.
|
||||
// rowStep is bigger as the image is taller, but is always at least 1. We've somewhat arbitrarily decided
|
||||
// that moving up and down by about 1/16 of the image is pretty good.
|
||||
int middle = height >> 1;
|
||||
int rowStep = Math.max(1, height >> 5);
|
||||
for (int x = 0; x < 11; x++) {
|
||||
|
||||
int rowStepsAboveOrBelow = (x + 1) >> 1;
|
||||
boolean isAbove = (x & 0x01) == 0; // i.e. is x even?
|
||||
int rowNumber = middle + rowStep * (isAbove ? rowStepsAboveOrBelow : -rowStepsAboveOrBelow);
|
||||
|
||||
image.estimateBlackPoint(BlackPointEstimationMethod.ROW_SAMPLING, rowNumber);
|
||||
image.getBlackRow(rowNumber, row, 0, width);
|
||||
|
||||
try {
|
||||
return decodeRow(rowNumber, row);
|
||||
} catch (ReaderException re) {
|
||||
// TODO re-enable this in a "try harder" mode?
|
||||
//row.reverse(); // try scanning the row backwards
|
||||
//try {
|
||||
// return decodeRow(rowNumber, row);
|
||||
//} catch (ReaderException re2) {
|
||||
// continue
|
||||
//}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
throw new ReaderException("No barcode found");
|
||||
}
|
||||
|
||||
protected static void recordPattern(BitArray row, int start, int[] counters) throws ReaderException {
|
||||
for (int i = 0; i < counters.length; i++) {
|
||||
counters[i] = 0;
|
||||
}
|
||||
int end = row.getSize();
|
||||
if (start >= end) {
|
||||
throw new ReaderException("Couldn't fully read a pattern");
|
||||
}
|
||||
boolean isWhite = !row.get(start);
|
||||
int counterPosition = 0;
|
||||
int i = start;
|
||||
while (i < end) {
|
||||
boolean pixel = row.get(i);
|
||||
if ((!pixel && isWhite) || (pixel && !isWhite)) {
|
||||
counters[counterPosition]++;
|
||||
} else {
|
||||
counterPosition++;
|
||||
if (counterPosition == counters.length) {
|
||||
break;
|
||||
} else {
|
||||
counters[counterPosition] = 1;
|
||||
isWhite = !isWhite;
|
||||
}
|
||||
}
|
||||
i++;
|
||||
}
|
||||
// If we read fully the last section of pixels and filled up our counters -- or filled
|
||||
// the last counter but ran off the side of the image, OK. Otherwise, a problem.
|
||||
if (!(counterPosition == counters.length || (counterPosition == counters.length - 1 && i == end))) {
|
||||
throw new ReaderException("Couldn't fully read a pattern");
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Determines how closely a set of observed counts of runs of black/white values matches a given
|
||||
* target pattern. For each counter, the ratio of the difference between it and the pattern value
|
||||
* is compared to the expected pattern value. This ratio is averaged across counters to produce
|
||||
* the return value. 0.0 means an exact match; higher values mean poorer matches.
|
||||
*
|
||||
* @param counters observed counters
|
||||
* @param pattern expected pattern
|
||||
* @return average variance between counters and pattern
|
||||
*/
|
||||
protected static float patternMatchVariance(int[] counters, int[] pattern) {
|
||||
int total = 0;
|
||||
int numCounters = counters.length;
|
||||
int patternLength = 0;
|
||||
for (int i = 0; i < numCounters; i++) {
|
||||
total += counters[i];
|
||||
patternLength += pattern[i];
|
||||
}
|
||||
float unitBarWidth = (float) total / (float) patternLength;
|
||||
|
||||
float totalVariance = 0.0f;
|
||||
for (int x = 0; x < numCounters; x++) {
|
||||
float scaledCounter = (float) counters[x] / unitBarWidth;
|
||||
float width = pattern[x];
|
||||
float abs = scaledCounter > width ? scaledCounter - width : width - scaledCounter;
|
||||
totalVariance += abs / width;
|
||||
}
|
||||
return totalVariance / (float) numCounters;
|
||||
}
|
||||
|
||||
/**
|
||||
* Fast round method.
|
||||
*
|
||||
* @return argument rounded to nearest int
|
||||
*/
|
||||
protected static int round(float f) {
|
||||
return (int) (f + 0.5f);
|
||||
}
|
||||
|
||||
}
|
256
core/src/com/google/zxing/oned/AbstractUPCEANReader.java
Normal file
256
core/src/com/google/zxing/oned/AbstractUPCEANReader.java
Normal file
|
@ -0,0 +1,256 @@
|
|||
/*
|
||||
* Copyright 2008 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.oned;
|
||||
|
||||
import com.google.zxing.Result;
|
||||
import com.google.zxing.ResultPoint;
|
||||
import com.google.zxing.ReaderException;
|
||||
import com.google.zxing.common.BitArray;
|
||||
import com.google.zxing.common.GenericResultPoint;
|
||||
|
||||
/**
|
||||
* <p>Encapsulates functionality and implementation that is common to UPC and EAN families
|
||||
* of one-dimensional barcodes.</p>
|
||||
*
|
||||
* @author dswitkin@google.com (Daniel Switkin)
|
||||
* @author srowen@google.com (Sean Owen)
|
||||
* @author alasdair@google.com (Alasdair Mackintosh)
|
||||
*/
|
||||
public abstract class AbstractUPCEANReader extends AbstractOneDReader implements UPCEANReader {
|
||||
|
||||
private static final float MAX_VARIANCE = 0.4f;
|
||||
|
||||
/** Start/end guard pattern. */
|
||||
protected static final int[] START_END_PATTERN = {1, 1, 1,};
|
||||
|
||||
/** Pattern marking the middle of a UPC/EAN pattern, separating the two halves. */
|
||||
protected static final int[] MIDDLE_PATTERN = {1, 1, 1, 1, 1};
|
||||
|
||||
/** "Odd", or "L" patterns used to encode UPC/EAN digits. */
|
||||
protected static final int[][] L_PATTERNS = {
|
||||
{3, 2, 1, 1}, // 0
|
||||
{2, 2, 2, 1}, // 1
|
||||
{2, 1, 2, 2}, // 2
|
||||
{1, 4, 1, 1}, // 3
|
||||
{1, 1, 3, 2}, // 4
|
||||
{1, 2, 3, 1}, // 5
|
||||
{1, 1, 1, 4}, // 6
|
||||
{1, 3, 1, 2}, // 7
|
||||
{1, 2, 1, 3}, // 8
|
||||
{3, 1, 1, 2} // 9
|
||||
};
|
||||
|
||||
/** As above but also including the "even", or "G" patterns used to encode UPC/EAN digits. */
|
||||
protected static final int[][] L_AND_G_PATTERNS;
|
||||
|
||||
static {
|
||||
L_AND_G_PATTERNS = new int[20][];
|
||||
for (int i = 0; i < 10; i++) {
|
||||
L_AND_G_PATTERNS[i] = L_PATTERNS[i];
|
||||
}
|
||||
for (int i = 10; i < 20; i++) {
|
||||
int[] widths = L_PATTERNS[i - 10];
|
||||
int[] reversedWidths = new int[widths.length];
|
||||
for (int j = 0; j < widths.length; j++) {
|
||||
reversedWidths[j] = widths[widths.length - j - 1];
|
||||
}
|
||||
L_AND_G_PATTERNS[i] = reversedWidths;
|
||||
}
|
||||
}
|
||||
|
||||
static int[] findStartGuardPattern(final BitArray row) throws ReaderException {
|
||||
boolean foundStart = false;
|
||||
int[] startRange = null;
|
||||
int nextStart = 0;
|
||||
while (!foundStart) {
|
||||
startRange = findGuardPattern(row, nextStart, false, START_END_PATTERN);
|
||||
int start = startRange[0];
|
||||
nextStart = startRange[1];
|
||||
// As a check, we want to see some white in front of this "start pattern",
|
||||
// maybe as wide as the start pattern itself?
|
||||
foundStart = isWhiteRange(row, Math.max(0, start - 2 * (startRange[1] - start)), start);
|
||||
}
|
||||
return startRange;
|
||||
}
|
||||
|
||||
public final Result decodeRow(int rowNumber, BitArray row) throws ReaderException {
|
||||
return decodeRow(rowNumber, row, findStartGuardPattern(row));
|
||||
}
|
||||
|
||||
public final Result decodeRow(int rowNumber, BitArray row, int[] startGuardRange) throws ReaderException {
|
||||
|
||||
StringBuffer result = new StringBuffer();
|
||||
|
||||
int endStart = decodeMiddle(row, startGuardRange, result);
|
||||
|
||||
int[] endRange = decodeEnd(row, endStart);
|
||||
|
||||
// Check for whitespace after the pattern
|
||||
int end = endRange[1];
|
||||
if (!isWhiteRange(row, end, Math.min(row.getSize(), end + 2 * (end - endRange[0])))) {
|
||||
throw new ReaderException("Pattern not followed by whitespace");
|
||||
}
|
||||
|
||||
String resultString = result.toString();
|
||||
if (!checkChecksum(resultString)) {
|
||||
throw new ReaderException("Checksum failed");
|
||||
}
|
||||
|
||||
return new Result(resultString, new ResultPoint[]{
|
||||
new GenericResultPoint((float) (startGuardRange[1] - startGuardRange[0]) / 2.0f, (float) rowNumber),
|
||||
new GenericResultPoint((float) (endRange[1] - endRange[0]) / 2.0f, (float) rowNumber)});
|
||||
}
|
||||
|
||||
/**
|
||||
* @return true iff row consists of white values in the range [start,end)
|
||||
*/
|
||||
protected static boolean isWhiteRange(BitArray row, int start, int end) {
|
||||
for (int i = start; i < end; i++) {
|
||||
if (row.get(i)) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Computes the UPC/EAN checksum on a string of digits
|
||||
* @param s
|
||||
* @return
|
||||
*/
|
||||
protected boolean checkChecksum(String s) throws ReaderException {
|
||||
int sum = 0;
|
||||
int length = s.length();
|
||||
for (int i = length - 2; i >= 0; i -= 2) {
|
||||
int digit = (int) s.charAt(i) - (int) '0';
|
||||
if (digit < 0 || digit > 9) {
|
||||
throw new ReaderException("Illegal character during checksum");
|
||||
}
|
||||
sum += digit;
|
||||
}
|
||||
sum *= 3;
|
||||
for (int i = length - 1; i >= 0; i -= 2) {
|
||||
int digit = (int) s.charAt(i) - (int) '0';
|
||||
if (digit < 0 || digit > 9) {
|
||||
throw new ReaderException("Illegal character during checksum");
|
||||
}
|
||||
sum += digit;
|
||||
}
|
||||
return sum % 10 == 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* Subclasses override this to decode the portion of a barcode between the start and end guard patterns.
|
||||
*
|
||||
* @param row row of black/white values to search
|
||||
* @param startRange start/end offset of start guard pattern
|
||||
* @param resultString {@link StringBuffer} to append decoded chars to
|
||||
* @return horizontal offset of first pixel after the "middle" that was decoded
|
||||
* @throws ReaderException if decoding could not complete successfully
|
||||
*/
|
||||
protected abstract int decodeMiddle(BitArray row, int[] startRange, StringBuffer resultString)
|
||||
throws ReaderException;
|
||||
|
||||
protected int[] decodeEnd(BitArray row, int endStart) throws ReaderException {
|
||||
return findGuardPattern(row, endStart, false, START_END_PATTERN);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param row row of black/white values to search
|
||||
* @param rowOffset position to start search
|
||||
* @param whiteFirst if true, indicates that the pattern specifies white/black/white/...
|
||||
* pixel counts, otherwise, it is interpreted as black/white/black/...
|
||||
* @param pattern pattern of counts of number of black and white pixels that are being
|
||||
* searched for as a pattern
|
||||
* @return start/end horizontal offset of guard pattern, as an array of two ints
|
||||
* @throws ReaderException if pattern is not found
|
||||
*/
|
||||
protected static int[] findGuardPattern(BitArray row, int rowOffset, boolean whiteFirst, int[] pattern)
|
||||
throws ReaderException {
|
||||
int patternLength = pattern.length;
|
||||
int[] counters = new int[patternLength];
|
||||
int width = row.getSize();
|
||||
boolean isWhite = false;
|
||||
while (rowOffset < width) {
|
||||
isWhite = !row.get(rowOffset);
|
||||
if (whiteFirst == isWhite) {
|
||||
break;
|
||||
}
|
||||
rowOffset++;
|
||||
}
|
||||
|
||||
int counterPosition = 0;
|
||||
int patternStart = rowOffset;
|
||||
for (int x = rowOffset; x < width; x++) {
|
||||
boolean pixel = row.get(x);
|
||||
if ((!pixel && isWhite) || (pixel && !isWhite)) {
|
||||
counters[counterPosition]++;
|
||||
} else {
|
||||
if (counterPosition == patternLength - 1) {
|
||||
if (patternMatchVariance(counters, pattern) < MAX_VARIANCE) {
|
||||
return new int[] {patternStart, x};
|
||||
}
|
||||
patternStart += counters[0] + counters[1];
|
||||
for (int y = 2; y < patternLength; y++) {
|
||||
counters[y - 2] = counters[y];
|
||||
}
|
||||
counterPosition--;
|
||||
} else {
|
||||
counterPosition++;
|
||||
}
|
||||
counters[counterPosition] = 1;
|
||||
isWhite = !isWhite;
|
||||
}
|
||||
}
|
||||
throw new ReaderException("Can't find pattern");
|
||||
}
|
||||
|
||||
/**
|
||||
* Attempts to decode a single UPC/EAN-encoded digit.
|
||||
*
|
||||
* @param row row of black/white values to decode
|
||||
* @param counters the counts of runs of observed black/white/black/... values
|
||||
* @param rowOffset horizontal offset to start decoding from
|
||||
* @param patterns the set of patterns to use to decode -- sometimes different encodings
|
||||
* for the digits 0-9 are used, and this indicates the encodings for 0 to 9 that should
|
||||
* be used
|
||||
* @return horizontal offset of first pixel beyond the decoded digit
|
||||
* @throws ReaderException if digit cannot be decoded
|
||||
*/
|
||||
protected static int decodeDigit(BitArray row,
|
||||
int[] counters,
|
||||
int rowOffset,
|
||||
int[][] patterns) throws ReaderException {
|
||||
recordPattern(row, rowOffset, counters);
|
||||
float bestVariance = MAX_VARIANCE; // worst variance we'll accept
|
||||
int bestMatch = -1;
|
||||
for (int d = 0; d < patterns.length; d++) {
|
||||
int[] pattern = patterns[d];
|
||||
float variance = patternMatchVariance(counters, pattern);
|
||||
if (variance < bestVariance) {
|
||||
bestVariance = variance;
|
||||
bestMatch = d;
|
||||
}
|
||||
}
|
||||
if (bestMatch >= 0) {
|
||||
return bestMatch;
|
||||
} else {
|
||||
throw new ReaderException("Could not match any digit in pattern");
|
||||
}
|
||||
}
|
||||
|
||||
}
|
404
core/src/com/google/zxing/oned/Code128Reader.java
Normal file
404
core/src/com/google/zxing/oned/Code128Reader.java
Normal file
|
@ -0,0 +1,404 @@
|
|||
/*
|
||||
* Copyright 2008 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.oned;
|
||||
|
||||
import com.google.zxing.Result;
|
||||
import com.google.zxing.ReaderException;
|
||||
import com.google.zxing.ResultPoint;
|
||||
import com.google.zxing.common.BitArray;
|
||||
import com.google.zxing.common.GenericResultPoint;
|
||||
|
||||
/**
|
||||
* <p>Decodes Code 128 barcodes.</p>
|
||||
*
|
||||
* @author srowen@google.com (Sean Owen)
|
||||
*/
|
||||
public final class Code128Reader extends AbstractOneDReader {
|
||||
|
||||
private static final int[][] CODE_PATTERNS = {
|
||||
{2, 1, 2, 2, 2, 2}, // 0
|
||||
{2, 2, 2, 1, 2, 2},
|
||||
{2, 2, 2, 2, 2, 1},
|
||||
{1, 2, 1, 2, 2, 3},
|
||||
{1, 2, 1, 3, 2, 2},
|
||||
{1, 3, 1, 2, 2, 2}, // 5
|
||||
{1, 2, 2, 2, 1, 3},
|
||||
{1, 2, 2, 3, 1, 2},
|
||||
{1, 3, 2, 2, 1, 2},
|
||||
{2, 2, 1, 2, 1, 3},
|
||||
{2, 2, 1, 3, 1, 2}, // 10
|
||||
{2, 3, 1, 2, 1, 2},
|
||||
{1, 1, 2, 2, 3, 2},
|
||||
{1, 2, 2, 1, 3, 2},
|
||||
{1, 2, 2, 2, 3, 1},
|
||||
{1, 1, 3, 2, 2, 2}, // 15
|
||||
{1, 2, 3, 1, 2, 2},
|
||||
{1, 2, 3, 2, 2, 1},
|
||||
{2, 2, 3, 2, 1, 1},
|
||||
{2, 2, 1, 1, 3, 2},
|
||||
{2, 2, 1, 2, 3, 1}, // 20
|
||||
{2, 1, 3, 2, 1, 2},
|
||||
{2, 2, 3, 1, 1, 2},
|
||||
{3, 1, 2, 1, 3, 1},
|
||||
{3, 1, 1, 2, 2, 2},
|
||||
{3, 2, 1, 1, 2, 2}, // 25
|
||||
{3, 2, 1, 2, 2, 1},
|
||||
{3, 1, 2, 2, 1, 2},
|
||||
{3, 2, 2, 1, 1, 2},
|
||||
{3, 2, 2, 2, 1, 1},
|
||||
{2, 1, 2, 1, 2, 3}, // 30
|
||||
{2, 1, 2, 3, 2, 1},
|
||||
{2, 3, 2, 1, 2, 1},
|
||||
{1, 1, 1, 3, 2, 3},
|
||||
{1, 3, 1, 1, 2, 3},
|
||||
{1, 3, 1, 3, 2, 1}, // 35
|
||||
{1, 1, 2, 3, 1, 3},
|
||||
{1, 3, 2, 1, 1, 3},
|
||||
{1, 3, 2, 3, 1, 1},
|
||||
{2, 1, 1, 3, 1, 3},
|
||||
{2, 3, 1, 1, 1, 3}, // 40
|
||||
{2, 3, 1, 3, 1, 1},
|
||||
{1, 1, 2, 1, 3, 3},
|
||||
{1, 1, 2, 3, 3, 1},
|
||||
{1, 3, 2, 1, 3, 1},
|
||||
{1, 1, 3, 1, 2, 3}, // 45
|
||||
{1, 1, 3, 3, 2, 1},
|
||||
{1, 3, 3, 1, 2, 1},
|
||||
{3, 1, 3, 1, 2, 1},
|
||||
{2, 1, 1, 3, 3, 1},
|
||||
{2, 3, 1, 1, 3, 1}, // 50
|
||||
{2, 1, 3, 1, 1, 3},
|
||||
{2, 1, 3, 3, 1, 1},
|
||||
{2, 1, 3, 1, 3, 1},
|
||||
{3, 1, 1, 1, 2, 3},
|
||||
{3, 1, 1, 3, 2, 1}, // 55
|
||||
{3, 3, 1, 1, 2, 1},
|
||||
{3, 1, 2, 1, 1, 3},
|
||||
{3, 1, 2, 3, 1, 1},
|
||||
{3, 3, 2, 1, 1, 1},
|
||||
{3, 1, 4, 1, 1, 1}, // 60
|
||||
{2, 2, 1, 4, 1, 1},
|
||||
{4, 3, 1, 1, 1, 1},
|
||||
{1, 1, 1, 2, 2, 4},
|
||||
{1, 1, 1, 4, 2, 2},
|
||||
{1, 2, 1, 1, 2, 4}, // 65
|
||||
{1, 2, 1, 4, 2, 1},
|
||||
{1, 4, 1, 1, 2, 2},
|
||||
{1, 4, 1, 2, 2, 1},
|
||||
{1, 1, 2, 2, 1, 4},
|
||||
{1, 1, 2, 4, 1, 2}, // 70
|
||||
{1, 2, 2, 1, 1, 4},
|
||||
{1, 2, 2, 4, 1, 1},
|
||||
{1, 4, 2, 1, 1, 2},
|
||||
{1, 4, 2, 2, 1, 1},
|
||||
{2, 4, 1, 2, 1, 1}, // 75
|
||||
{2, 2, 1, 1, 1, 4},
|
||||
{4, 1, 3, 1, 1, 1},
|
||||
{2, 4, 1, 1, 1, 2},
|
||||
{1, 3, 4, 1, 1, 1},
|
||||
{1, 1, 1, 2, 4, 2}, // 80
|
||||
{1, 2, 1, 1, 4, 2},
|
||||
{1, 2, 1, 2, 4, 1},
|
||||
{1, 1, 4, 2, 1, 2},
|
||||
{1, 2, 4, 1, 1, 2},
|
||||
{1, 2, 4, 2, 1, 1}, // 85
|
||||
{4, 1, 1, 2, 1, 2},
|
||||
{4, 2, 1, 1, 1, 2},
|
||||
{4, 2, 1, 2, 1, 1},
|
||||
{2, 1, 2, 1, 4, 1},
|
||||
{2, 1, 4, 1, 2, 1}, // 90
|
||||
{4, 1, 2, 1, 2, 1},
|
||||
{1, 1, 1, 1, 4, 3},
|
||||
{1, 1, 1, 3, 4, 1},
|
||||
{1, 3, 1, 1, 4, 1},
|
||||
{1, 1, 4, 1, 1, 3}, // 95
|
||||
{1, 1, 4, 3, 1, 1},
|
||||
{4, 1, 1, 1, 1, 3},
|
||||
{4, 1, 1, 3, 1, 1},
|
||||
{1, 1, 3, 1, 4, 1},
|
||||
{1, 1, 4, 1, 3, 1}, // 100
|
||||
{3, 1, 1, 1, 4, 1},
|
||||
{4, 1, 1, 1, 3, 1},
|
||||
{2, 1, 1, 4, 1, 2},
|
||||
{2, 1, 1, 2, 1, 4},
|
||||
{2, 1, 1, 2, 3, 2}, // 105
|
||||
{2, 3, 3, 1, 1, 1, 2}
|
||||
};
|
||||
|
||||
private static final float MAX_VARIANCE = 0.4f;
|
||||
|
||||
private static final int CODE_SHIFT = 98;
|
||||
|
||||
private static final int CODE_CODE_C = 99;
|
||||
private static final int CODE_CODE_B = 100;
|
||||
private static final int CODE_CODE_A = 101;
|
||||
|
||||
private static final int CODE_FNC_1 = 102;
|
||||
private static final int CODE_FNC_2 = 97;
|
||||
private static final int CODE_FNC_3 = 96;
|
||||
private static final int CODE_FNC_4_A = 101;
|
||||
private static final int CODE_FNC_4_B = 100;
|
||||
|
||||
private static final int CODE_START_A = 103;
|
||||
private static final int CODE_START_B = 104;
|
||||
private static final int CODE_START_C = 105;
|
||||
private static final int CODE_STOP = 106;
|
||||
|
||||
private static int[] findStartPattern(BitArray row) throws ReaderException {
|
||||
int width = row.getSize();
|
||||
int rowOffset = 0;
|
||||
while (rowOffset < width) {
|
||||
if (row.get(rowOffset)) {
|
||||
break;
|
||||
}
|
||||
rowOffset++;
|
||||
}
|
||||
|
||||
int counterPosition = 0;
|
||||
int[] counters = new int[6];
|
||||
int patternStart = rowOffset;
|
||||
boolean isWhite = false;
|
||||
int patternLength = counters.length;
|
||||
|
||||
for (int i = rowOffset; i < width; i++) {
|
||||
boolean pixel = row.get(i);
|
||||
if ((!pixel && isWhite) || (pixel && !isWhite)) {
|
||||
counters[counterPosition]++;
|
||||
} else {
|
||||
if (counterPosition == patternLength - 1) {
|
||||
for (int startCode = CODE_START_A; startCode <= CODE_START_C; startCode++) {
|
||||
if (patternMatchVariance(counters, CODE_PATTERNS[startCode]) < MAX_VARIANCE) {
|
||||
return new int[] {patternStart, i, startCode};
|
||||
}
|
||||
}
|
||||
patternStart += counters[0] + counters[1];
|
||||
for (int y = 2; y < patternLength; y++) {
|
||||
counters[y - 2] = counters[y];
|
||||
}
|
||||
counterPosition--;
|
||||
} else {
|
||||
counterPosition++;
|
||||
}
|
||||
counters[counterPosition] = 1;
|
||||
isWhite = !isWhite;
|
||||
}
|
||||
}
|
||||
throw new ReaderException("Can't find pattern");
|
||||
}
|
||||
|
||||
private static int decodeCode(BitArray row, int[] counters, int rowOffset) throws ReaderException {
|
||||
recordPattern(row, rowOffset, counters);
|
||||
float bestVariance = 0.4f; // worst variance we'll accept
|
||||
int bestMatch = -1;
|
||||
for (int d = 0; d < CODE_PATTERNS.length; d++) {
|
||||
int[] pattern = CODE_PATTERNS[d];
|
||||
float variance = patternMatchVariance(counters, pattern);
|
||||
if (variance < bestVariance) {
|
||||
bestVariance = variance;
|
||||
bestMatch = d;
|
||||
}
|
||||
}
|
||||
// TODO We're overlooking the fact that the STOP pattern has 7 values, not 6
|
||||
if (bestMatch >= 0) {
|
||||
return bestMatch;
|
||||
} else {
|
||||
throw new ReaderException("Could not match any code pattern");
|
||||
}
|
||||
}
|
||||
|
||||
public Result decodeRow(final int rowNumber, final BitArray row) throws ReaderException {
|
||||
|
||||
int[] startPatternInfo = findStartPattern(row);
|
||||
int startCode = startPatternInfo[2];
|
||||
int codeSet;
|
||||
switch (startCode) {
|
||||
case CODE_START_A:
|
||||
codeSet = CODE_CODE_A;
|
||||
break;
|
||||
case CODE_START_B:
|
||||
codeSet = CODE_CODE_B;
|
||||
break;
|
||||
case CODE_START_C:
|
||||
codeSet = CODE_CODE_C;
|
||||
break;
|
||||
default:
|
||||
throw new ReaderException("Illegal start code");
|
||||
}
|
||||
|
||||
boolean done = false;
|
||||
boolean isNextShifted = false;
|
||||
|
||||
StringBuffer result = new StringBuffer();
|
||||
int lastStart = startPatternInfo[0];
|
||||
int nextStart = startPatternInfo[1];
|
||||
int[] counters = new int[6];
|
||||
|
||||
int lastCode = 0;
|
||||
int code = 0;
|
||||
int checksumTotal = startCode;
|
||||
int multiplier = 0;
|
||||
|
||||
while (!done) {
|
||||
|
||||
boolean unshift = isNextShifted;
|
||||
isNextShifted = false;
|
||||
|
||||
lastCode = code;
|
||||
code = decodeCode(row, counters, nextStart);
|
||||
if (code != CODE_STOP) {
|
||||
multiplier++;
|
||||
checksumTotal += multiplier * code;
|
||||
}
|
||||
|
||||
lastStart = nextStart;
|
||||
for (int i = 0; i < counters.length; i++) {
|
||||
nextStart += counters[i];
|
||||
}
|
||||
|
||||
// Take care of illegal start codes
|
||||
switch (code) {
|
||||
case CODE_START_A:
|
||||
case CODE_START_B:
|
||||
case CODE_START_C:
|
||||
throw new ReaderException("Unexpected start code");
|
||||
}
|
||||
|
||||
switch (codeSet) {
|
||||
|
||||
case CODE_CODE_A:
|
||||
if (code < 64) {
|
||||
result.append((char) (' ' + code));
|
||||
} else if (code < 96) {
|
||||
result.append((char) (code - 64));
|
||||
} else {
|
||||
switch (code) {
|
||||
case CODE_FNC_1:
|
||||
case CODE_FNC_2:
|
||||
case CODE_FNC_3:
|
||||
case CODE_FNC_4_A:
|
||||
// do nothing?
|
||||
break;
|
||||
case CODE_SHIFT:
|
||||
isNextShifted = true;
|
||||
codeSet = CODE_CODE_B;
|
||||
break;
|
||||
case CODE_CODE_B:
|
||||
codeSet = CODE_CODE_B;
|
||||
break;
|
||||
case CODE_CODE_C:
|
||||
codeSet = CODE_CODE_C;
|
||||
break;
|
||||
case CODE_STOP:
|
||||
done = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
break;
|
||||
case CODE_CODE_B:
|
||||
if (code < 96) {
|
||||
result.append((char) (' ' + code));
|
||||
} else {
|
||||
switch (code) {
|
||||
case CODE_FNC_1:
|
||||
case CODE_FNC_2:
|
||||
case CODE_FNC_3:
|
||||
case CODE_FNC_4_B:
|
||||
// do nothing?
|
||||
break;
|
||||
case CODE_SHIFT:
|
||||
isNextShifted = true;
|
||||
codeSet = CODE_CODE_C;
|
||||
break;
|
||||
case CODE_CODE_A:
|
||||
codeSet = CODE_CODE_A;
|
||||
break;
|
||||
case CODE_CODE_C:
|
||||
codeSet = CODE_CODE_C;
|
||||
break;
|
||||
case CODE_STOP:
|
||||
done = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
break;
|
||||
case CODE_CODE_C:
|
||||
if (code < 100) {
|
||||
if (code < 10) {
|
||||
result.append('0');
|
||||
}
|
||||
result.append(code);
|
||||
} else {
|
||||
switch (code) {
|
||||
case CODE_FNC_1:
|
||||
// do nothing?
|
||||
break;
|
||||
case CODE_CODE_A:
|
||||
codeSet = CODE_CODE_A;
|
||||
break;
|
||||
case CODE_CODE_B:
|
||||
codeSet = CODE_CODE_B;
|
||||
break;
|
||||
case CODE_STOP:
|
||||
done = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
if (unshift) {
|
||||
switch (codeSet) {
|
||||
case CODE_CODE_A:
|
||||
codeSet = CODE_CODE_C;
|
||||
break;
|
||||
case CODE_CODE_B:
|
||||
codeSet = CODE_CODE_A;
|
||||
break;
|
||||
case CODE_CODE_C:
|
||||
codeSet = CODE_CODE_B;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
// Pull out from sum the value of the penultimate check code
|
||||
checksumTotal -= multiplier * lastCode;
|
||||
if (checksumTotal % 103 != lastCode) {
|
||||
throw new ReaderException("Checksum failed");
|
||||
}
|
||||
|
||||
// Need to pull out the check digits from string
|
||||
int resultLength = result.length();
|
||||
if (resultLength > 0) {
|
||||
if (codeSet == CODE_CODE_C) {
|
||||
result.delete(resultLength - 2, resultLength);
|
||||
} else {
|
||||
result.delete(resultLength - 1, resultLength);
|
||||
}
|
||||
}
|
||||
|
||||
String resultString = result.toString();
|
||||
return new Result(resultString,
|
||||
new ResultPoint[]{new GenericResultPoint((float) (startPatternInfo[1] - startPatternInfo[0]) / 2.0f,
|
||||
(float) rowNumber),
|
||||
new GenericResultPoint((float) (nextStart - lastStart) / 2.0f,
|
||||
(float) rowNumber)});
|
||||
|
||||
}
|
||||
|
||||
}
|
192
core/src/com/google/zxing/oned/Code39Reader.java
Normal file
192
core/src/com/google/zxing/oned/Code39Reader.java
Normal file
|
@ -0,0 +1,192 @@
|
|||
/*
|
||||
* Copyright 2008 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.oned;
|
||||
|
||||
import com.google.zxing.ReaderException;
|
||||
import com.google.zxing.Result;
|
||||
import com.google.zxing.ResultPoint;
|
||||
import com.google.zxing.common.BitArray;
|
||||
import com.google.zxing.common.GenericResultPoint;
|
||||
|
||||
/**
|
||||
* <p>Decodes Code 39 barcodes. This does not supported "Full ASCII Code 39" yet.</p>
|
||||
*
|
||||
* @author srowen@google.com (Sean Owen)
|
||||
*/
|
||||
public final class Code39Reader extends AbstractOneDReader {
|
||||
|
||||
private static final String ALPHABET_STRING = "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ-. *$/+%";
|
||||
private static final char[] ALPHABET = ALPHABET_STRING.toCharArray();
|
||||
|
||||
private static final int[] CHARACTER_ENCODINGS = {
|
||||
0x038, 0x121, 0x061, 0x160, 0x031, 0x130, 0x070, 0x025, 0x124, 0x064, // 0-9
|
||||
0x109, 0x049, 0x148, 0x019, 0x118, 0x058, 0x00D, 0x10C, 0x08C, 0x01C, // A-J
|
||||
0x103, 0x043, 0x142, 0x013, 0x112, 0x052, 0x007, 0x106, 0x046, 0x016, // K-T
|
||||
0x181, 0x0C1, 0x1C0, 0x091, 0x190, 0x0D0, 0x085, 0x184, 0x0C4, 0x094, // U-*
|
||||
0x0A8, 0x0A2, 0x08A, 0x02A // $-%
|
||||
};
|
||||
|
||||
private static final int ASTERISK_ENCODING = CHARACTER_ENCODINGS[39];
|
||||
|
||||
private final boolean usingCheckDigit;
|
||||
|
||||
/**
|
||||
* Creates a reader that assumes all encoded data is data, and does not treat the final
|
||||
* character as a check digit.
|
||||
*/
|
||||
public Code39Reader() {
|
||||
usingCheckDigit = false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a reader that can be configured to check the last character as a check digit.
|
||||
*
|
||||
* @param usingCheckDigit if true, treat the last data character as a check digit, not
|
||||
* data, and verify that the checksum passes
|
||||
*/
|
||||
public Code39Reader(boolean usingCheckDigit) {
|
||||
this.usingCheckDigit = usingCheckDigit;
|
||||
}
|
||||
|
||||
public Result decodeRow(final int rowNumber, final BitArray row) throws ReaderException {
|
||||
|
||||
int[] start = findAsteriskPattern(row);
|
||||
|
||||
int nextStart = start[1];
|
||||
|
||||
int end = row.getSize();
|
||||
|
||||
// Read off white space
|
||||
while (nextStart < end && !row.get(nextStart)) {
|
||||
nextStart++;
|
||||
}
|
||||
|
||||
StringBuffer result = new StringBuffer();
|
||||
int[] counters = new int[9];
|
||||
char decodedChar;
|
||||
int lastStart;
|
||||
do {
|
||||
recordPattern(row, nextStart, counters);
|
||||
int pattern = toNarrowWidePattern(counters);
|
||||
decodedChar = patternToChar(pattern);
|
||||
result.append(decodedChar);
|
||||
lastStart = nextStart;
|
||||
for (int i = 0; i < counters.length; i++) {
|
||||
nextStart += counters[i];
|
||||
}
|
||||
// Read off white space
|
||||
while (nextStart < end && !row.get(nextStart)) {
|
||||
nextStart++;
|
||||
}
|
||||
} while (decodedChar != '*');
|
||||
result.deleteCharAt(result.length() - 1); // remove asterisk
|
||||
|
||||
if (usingCheckDigit) {
|
||||
int max = result.length() - 1;
|
||||
int total = 0;
|
||||
for (int i = 0; i < max; i++) {
|
||||
total += ALPHABET_STRING.indexOf(result.charAt(i));
|
||||
}
|
||||
if (total % 43 != ALPHABET_STRING.indexOf(result.charAt(max))) {
|
||||
throw new ReaderException("Checksum failed");
|
||||
}
|
||||
result.deleteCharAt(max);
|
||||
}
|
||||
|
||||
String resultString = result.toString();
|
||||
return new Result(resultString,
|
||||
new ResultPoint[]{new GenericResultPoint((float) (start[1] - start[0]) / 2.0f, (float) rowNumber),
|
||||
new GenericResultPoint((float) (nextStart - lastStart) / 2.0f, (float) rowNumber)});
|
||||
|
||||
}
|
||||
|
||||
private static int[] findAsteriskPattern(BitArray row) throws ReaderException {
|
||||
int width = row.getSize();
|
||||
int rowOffset = 0;
|
||||
while (rowOffset < width) {
|
||||
if (row.get(rowOffset)) {
|
||||
break;
|
||||
}
|
||||
rowOffset++;
|
||||
}
|
||||
|
||||
int counterPosition = 0;
|
||||
int[] counters = new int[9];
|
||||
int patternStart = rowOffset;
|
||||
boolean isWhite = false;
|
||||
int patternLength = counters.length;
|
||||
|
||||
for (int i = rowOffset; i < width; i++) {
|
||||
boolean pixel = row.get(i);
|
||||
if ((!pixel && isWhite) || (pixel && !isWhite)) {
|
||||
counters[counterPosition]++;
|
||||
} else {
|
||||
if (counterPosition == patternLength - 1) {
|
||||
try {
|
||||
if (toNarrowWidePattern(counters) == ASTERISK_ENCODING) {
|
||||
return new int[] {patternStart, i};
|
||||
}
|
||||
} catch (ReaderException re) {
|
||||
// no match, continue
|
||||
}
|
||||
patternStart += counters[0] + counters[1];
|
||||
for (int y = 2; y < patternLength; y++) {
|
||||
counters[y - 2] = counters[y];
|
||||
}
|
||||
counterPosition--;
|
||||
} else {
|
||||
counterPosition++;
|
||||
}
|
||||
counters[counterPosition] = 1;
|
||||
isWhite = !isWhite;
|
||||
}
|
||||
}
|
||||
throw new ReaderException("Can't find pattern");
|
||||
}
|
||||
|
||||
private static int toNarrowWidePattern(int[] counters) throws ReaderException {
|
||||
int minCounter = Integer.MAX_VALUE;
|
||||
for (int i = 0; i < counters.length; i++) {
|
||||
if (counters[i] < minCounter) {
|
||||
minCounter = counters[i];
|
||||
}
|
||||
}
|
||||
int maxNarrowCounter = (int) (minCounter * 1.5f);
|
||||
int wideCounters = 0;
|
||||
int pattern = 0;
|
||||
for (int i = 0; i < counters.length; i++) {
|
||||
if (counters[i] > maxNarrowCounter) {
|
||||
pattern |= 1 << (counters.length - 1 - i);
|
||||
wideCounters++;
|
||||
}
|
||||
}
|
||||
if (wideCounters != 3) {
|
||||
throw new ReaderException("Can't find 3 wide bars/spaces out of 9");
|
||||
}
|
||||
return pattern;
|
||||
}
|
||||
|
||||
private static char patternToChar(int pattern) throws ReaderException {
|
||||
for (int i = 0; i < CHARACTER_ENCODINGS.length; i++) {
|
||||
if (CHARACTER_ENCODINGS[i] == pattern) {
|
||||
return ALPHABET[i];
|
||||
}
|
||||
}
|
||||
throw new ReaderException("Pattern did not match character encoding");
|
||||
}
|
||||
|
||||
}
|
123
core/src/com/google/zxing/oned/EAN13Reader.java
Normal file
123
core/src/com/google/zxing/oned/EAN13Reader.java
Normal file
|
@ -0,0 +1,123 @@
|
|||
/*
|
||||
* Copyright 2008 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.oned;
|
||||
|
||||
import com.google.zxing.ReaderException;
|
||||
import com.google.zxing.common.BitArray;
|
||||
|
||||
/**
|
||||
* <p>Implements decoding of the EAN-13 format.</p>
|
||||
*
|
||||
* @author dswitkin@google.com (Daniel Switkin)
|
||||
* @author srowen@google.com (Sean Owen)
|
||||
* @author alasdair@google.com (Alasdair Mackintosh)
|
||||
*/
|
||||
public final class EAN13Reader extends AbstractUPCEANReader {
|
||||
|
||||
// For an EAN-13 barcode, the first digit is represented by the parities used
|
||||
// to encode the next six digits, according to the table below. For example,
|
||||
// if the barcode is 5 123456 789012 then the value of the first digit is
|
||||
// signified by using odd for '1', even for '2', even for '3', odd for '4',
|
||||
// odd for '5', and even for '6'. See http://en.wikipedia.org/wiki/EAN-13
|
||||
//
|
||||
// Parity of next 6 digits
|
||||
// Digit 0 1 2 3 4 5
|
||||
// 0 Odd Odd Odd Odd Odd Odd
|
||||
// 1 Odd Odd Even Odd Even Even
|
||||
// 2 Odd Odd Even Even Odd Even
|
||||
// 3 Odd Odd Even Even Even Odd
|
||||
// 4 Odd Even Odd Odd Even Even
|
||||
// 5 Odd Even Even Odd Odd Even
|
||||
// 6 Odd Even Even Even Odd Odd
|
||||
// 7 Odd Even Odd Even Odd Even
|
||||
// 8 Odd Even Odd Even Even Odd
|
||||
// 9 Odd Even Even Odd Even Odd
|
||||
//
|
||||
// Note that the encoding for '0' uses the same parity as a UPC barcode. Hence
|
||||
// a UPC barcode can be converted to an EAN-13 barcode by prepending a 0.
|
||||
//
|
||||
// The encodong is represented by the following array, which is a bit pattern
|
||||
// using Odd = 0 and Even = 1. For example, 5 is represented by:
|
||||
//
|
||||
// Odd Even Even Odd Odd Even
|
||||
// in binary:
|
||||
// 0 1 1 0 0 1 == 0x19
|
||||
//
|
||||
private static final int[] FIRST_DIGIT_ENCODINGS = {
|
||||
0x00, 0x0B, 0x0D, 0xE, 0x13, 0x19, 0x1C, 0x15, 0x16, 0x1A
|
||||
};
|
||||
|
||||
protected int decodeMiddle(BitArray row, int[] startRange, StringBuffer resultString) throws ReaderException {
|
||||
|
||||
int[] counters = new int[4];
|
||||
int end = row.getSize();
|
||||
int rowOffset = startRange[1];
|
||||
|
||||
int lgPatternFound = 0;
|
||||
|
||||
for (int x = 0; x < 6 && rowOffset < end; x++) {
|
||||
int bestMatch = decodeDigit(row, counters, rowOffset, L_AND_G_PATTERNS);
|
||||
resultString.append((char) ('0' + bestMatch % 10));
|
||||
for (int i = 0; i < counters.length; i++) {
|
||||
rowOffset += counters[i];
|
||||
}
|
||||
if (bestMatch >= 10) {
|
||||
lgPatternFound |= 1 << (5 - x);
|
||||
}
|
||||
}
|
||||
|
||||
determineFirstDigit(resultString, lgPatternFound);
|
||||
|
||||
int[] middleRange = findGuardPattern(row, rowOffset, true, MIDDLE_PATTERN);
|
||||
rowOffset = middleRange[1];
|
||||
|
||||
for (int x = 0; x < 6 && rowOffset < end; x++) {
|
||||
int bestMatch = decodeDigit(row, counters, rowOffset, L_PATTERNS);
|
||||
resultString.append((char) ('0' + bestMatch));
|
||||
for (int i = 0; i < counters.length; i++) {
|
||||
rowOffset += counters[i];
|
||||
}
|
||||
}
|
||||
|
||||
return rowOffset;
|
||||
}
|
||||
|
||||
/**
|
||||
* Based on pattern of odd-even ('L' and 'G') patterns used to encoded the explicitly-encoded digits
|
||||
* in a barcode, determines the implicitly encoded first digit and adds it to the result string.
|
||||
*
|
||||
* @param resultString string to insert decoded first digit into
|
||||
* @param lgPatternFound int whose bits indicates the pattern of odd/even L/G patterns used to
|
||||
* encode digits
|
||||
* @throws ReaderException if first digit cannot be determined
|
||||
*/
|
||||
private static void determineFirstDigit(StringBuffer resultString, int lgPatternFound) throws ReaderException {
|
||||
for (int d = 0; d < 10; d++) {
|
||||
if (lgPatternFound == FIRST_DIGIT_ENCODINGS[d]) {
|
||||
// OK, if the first digit is a 0, then this is effectively also a UPC-A code.
|
||||
// I think it's best (?) to go ahead and treat it as if it had matched as UPC-A, and return a result
|
||||
// *without* the leading 0
|
||||
if (d != 0) {
|
||||
resultString.insert(0, (char) ('0' + d));
|
||||
}
|
||||
return;
|
||||
}
|
||||
}
|
||||
throw new ReaderException("Unable to determine first digit");
|
||||
}
|
||||
|
||||
}
|
57
core/src/com/google/zxing/oned/EAN8Reader.java
Normal file
57
core/src/com/google/zxing/oned/EAN8Reader.java
Normal file
|
@ -0,0 +1,57 @@
|
|||
/*
|
||||
* Copyright 2008 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.oned;
|
||||
|
||||
import com.google.zxing.ReaderException;
|
||||
import com.google.zxing.common.BitArray;
|
||||
|
||||
/**
|
||||
* <p>Implements decoding of the EAN-8 format.</p>
|
||||
*
|
||||
* @author srowen@google.com (Sean Owen)
|
||||
*/
|
||||
public final class EAN8Reader extends AbstractUPCEANReader {
|
||||
|
||||
protected int decodeMiddle(BitArray row, int[] startRange, StringBuffer result) throws ReaderException {
|
||||
|
||||
int[] counters = new int[4];
|
||||
int end = row.getSize();
|
||||
int rowOffset = startRange[1];
|
||||
|
||||
for (int x = 0; x < 4 && rowOffset < end; x++) {
|
||||
int bestMatch = decodeDigit(row, counters, rowOffset, L_PATTERNS);
|
||||
result.append((char) ('0' + bestMatch));
|
||||
for (int i = 0; i < counters.length; i++) {
|
||||
rowOffset += counters[i];
|
||||
}
|
||||
}
|
||||
|
||||
int[] middleRange = findGuardPattern(row, rowOffset, true, MIDDLE_PATTERN);
|
||||
rowOffset = middleRange[1];
|
||||
|
||||
for (int x = 0; x < 4 && rowOffset < end; x++) {
|
||||
int bestMatch = decodeDigit(row, counters, rowOffset, L_PATTERNS);
|
||||
result.append((char) ('0' + bestMatch));
|
||||
for (int i = 0; i < counters.length; i++) {
|
||||
rowOffset += counters[i];
|
||||
}
|
||||
}
|
||||
|
||||
return rowOffset;
|
||||
}
|
||||
|
||||
}
|
45
core/src/com/google/zxing/oned/MultiFormatOneDReader.java
Normal file
45
core/src/com/google/zxing/oned/MultiFormatOneDReader.java
Normal file
|
@ -0,0 +1,45 @@
|
|||
/*
|
||||
* Copyright 2008 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.oned;
|
||||
|
||||
import com.google.zxing.ReaderException;
|
||||
import com.google.zxing.Result;
|
||||
import com.google.zxing.common.BitArray;
|
||||
|
||||
/**
|
||||
* @author dswitkin@google.com (Daniel Switkin)
|
||||
* @author srowen@google.com (Sean Owen)
|
||||
*/
|
||||
public final class MultiFormatOneDReader extends AbstractOneDReader {
|
||||
|
||||
private final OneDReader[] readers = new OneDReader[]{
|
||||
new MultiFormatUPCEANReader(), new Code39Reader(), new Code128Reader()
|
||||
};
|
||||
|
||||
public Result decodeRow(int rowNumber, BitArray row) throws ReaderException {
|
||||
ReaderException saved = null;
|
||||
for (int i = 0; i < readers.length; i++) {
|
||||
try {
|
||||
return readers[i].decodeRow(rowNumber, row);
|
||||
} catch (ReaderException re) {
|
||||
saved = re;
|
||||
}
|
||||
}
|
||||
throw saved;
|
||||
}
|
||||
|
||||
}
|
65
core/src/com/google/zxing/oned/MultiFormatUPCEANReader.java
Normal file
65
core/src/com/google/zxing/oned/MultiFormatUPCEANReader.java
Normal file
|
@ -0,0 +1,65 @@
|
|||
/*
|
||||
* Copyright 2008 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.oned;
|
||||
|
||||
import com.google.zxing.ReaderException;
|
||||
import com.google.zxing.Result;
|
||||
import com.google.zxing.common.BitArray;
|
||||
|
||||
/**
|
||||
* <p>A reader that can read all available UPC/EAN formats. If a caller wants to try to
|
||||
* read all such formats, it is most efficent to use this implementation rather than invoke
|
||||
* individual readers.</p>
|
||||
*
|
||||
* @author srowen@google.com (Sean Owen)
|
||||
*/
|
||||
public final class MultiFormatUPCEANReader extends AbstractOneDReader {
|
||||
|
||||
/**
|
||||
* Reader implementations to which this implementation delegates, in the order
|
||||
* they will be attempted. Order is important.
|
||||
*/
|
||||
private final UPCEANReader[] readers = new UPCEANReader[] {
|
||||
new EAN13Reader(), new UPCAReader(), new EAN8Reader(), new UPCEReader()
|
||||
};
|
||||
|
||||
public Result decodeRow(int rowNumber, BitArray row) throws ReaderException {
|
||||
int[] startGuardPattern = AbstractUPCEANReader.findStartGuardPattern(row);
|
||||
return decodeRow(rowNumber, row, startGuardPattern);
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* @param rowNumber
|
||||
* @param row
|
||||
* @param startGuardRange
|
||||
* @return
|
||||
* @throws ReaderException
|
||||
*/
|
||||
public Result decodeRow(int rowNumber, BitArray row, int[] startGuardRange) throws ReaderException {
|
||||
ReaderException saved = null;
|
||||
for (int i = 0; i < readers.length; i++) {
|
||||
try {
|
||||
return readers[i].decodeRow(rowNumber, row, startGuardRange);
|
||||
} catch (ReaderException re) {
|
||||
saved = re;
|
||||
}
|
||||
}
|
||||
throw saved;
|
||||
}
|
||||
|
||||
}
|
43
core/src/com/google/zxing/oned/OneDReader.java
Normal file
43
core/src/com/google/zxing/oned/OneDReader.java
Normal file
|
@ -0,0 +1,43 @@
|
|||
/*
|
||||
* Copyright 2008 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.oned;
|
||||
|
||||
import com.google.zxing.Reader;
|
||||
import com.google.zxing.ReaderException;
|
||||
import com.google.zxing.Result;
|
||||
import com.google.zxing.common.BitArray;
|
||||
|
||||
/**
|
||||
* <p>{@link Reader}s which also implement this interface read one-dimensional barcode
|
||||
* formats, and expose additional functionality that is specific to this type of barcode.</p>
|
||||
*
|
||||
* @author srowen@google.com (Sean Owen)
|
||||
*/
|
||||
public interface OneDReader extends Reader {
|
||||
|
||||
/**
|
||||
* <p>Attempts to decode a one-dimensional barcode format given a single row of
|
||||
* an image.</p>
|
||||
*
|
||||
* @param rowNumber row number from top of the row
|
||||
* @param row the black/white pixel data of the row
|
||||
* @return {@link Result} containing encoded string and start/end of barcode
|
||||
* @throws ReaderException if an error occurs or barcode cannot be found
|
||||
*/
|
||||
Result decodeRow(int rowNumber, BitArray row) throws ReaderException;
|
||||
|
||||
}
|
57
core/src/com/google/zxing/oned/UPCAReader.java
Normal file
57
core/src/com/google/zxing/oned/UPCAReader.java
Normal file
|
@ -0,0 +1,57 @@
|
|||
/*
|
||||
* Copyright 2008 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.oned;
|
||||
|
||||
import com.google.zxing.ReaderException;
|
||||
import com.google.zxing.common.BitArray;
|
||||
|
||||
/**
|
||||
* <p>Implements decoding of the UPC-A format.</p>
|
||||
*
|
||||
* @author dswitkin@google.com (Daniel Switkin)
|
||||
* @author srowen@google.com (Sean Owen)
|
||||
*/
|
||||
public final class UPCAReader extends AbstractUPCEANReader {
|
||||
|
||||
protected int decodeMiddle(BitArray row, int[] startRange, StringBuffer resultString) throws ReaderException {
|
||||
int middleStart = decodeDigits(row, startRange[1], resultString);
|
||||
int[] middleRange = findGuardPattern(row, middleStart, true, MIDDLE_PATTERN);
|
||||
return decodeDigits(row, middleRange[1], resultString);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param row row of black/white values to decode
|
||||
* @param start horizontal offset from which decoding starts
|
||||
* @param result {@link StringBuffer} to append decoded digits to
|
||||
* @return horizontal offset of first pixel after the six decoded digits
|
||||
* @throws ReaderException if six digits could not be decoded from the row
|
||||
*/
|
||||
private static int decodeDigits(BitArray row, int start, StringBuffer result) throws ReaderException {
|
||||
int[] counters = new int[4];
|
||||
int end = row.getSize();
|
||||
int rowOffset = start;
|
||||
for (int x = 0; x < 6 && rowOffset < end; x++) {
|
||||
int bestMatch = decodeDigit(row, counters, rowOffset, L_PATTERNS);
|
||||
result.append((char) ('0' + bestMatch));
|
||||
for (int i = 0; i < counters.length; i++) {
|
||||
rowOffset += counters[i];
|
||||
}
|
||||
}
|
||||
return rowOffset;
|
||||
}
|
||||
|
||||
}
|
38
core/src/com/google/zxing/oned/UPCEANReader.java
Normal file
38
core/src/com/google/zxing/oned/UPCEANReader.java
Normal file
|
@ -0,0 +1,38 @@
|
|||
/*
|
||||
* Copyright 2008 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.oned;
|
||||
|
||||
import com.google.zxing.ReaderException;
|
||||
import com.google.zxing.Result;
|
||||
import com.google.zxing.common.BitArray;
|
||||
|
||||
/**
|
||||
* <p>This interfaces captures addtional functionality that readers of
|
||||
* UPC/EAN family of barcodes should expose.</p>
|
||||
*
|
||||
* @author srowen@google.com (Sean Owen)
|
||||
*/
|
||||
public interface UPCEANReader extends OneDReader {
|
||||
|
||||
/**
|
||||
* <p>Like {@link #decodeRow(int, com.google.zxing.common.BitArray)}, but
|
||||
* allows caller to inform method about where the UPC/EAN start pattern is
|
||||
* found. This allows this to be computed once and reused across many implementations.</p>
|
||||
*/
|
||||
Result decodeRow(int rowNumber, BitArray row, int[] startGuardRange) throws ReaderException;
|
||||
|
||||
}
|
136
core/src/com/google/zxing/oned/UPCEReader.java
Normal file
136
core/src/com/google/zxing/oned/UPCEReader.java
Normal file
|
@ -0,0 +1,136 @@
|
|||
/*
|
||||
* Copyright 2008 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.oned;
|
||||
|
||||
import com.google.zxing.ReaderException;
|
||||
import com.google.zxing.common.BitArray;
|
||||
|
||||
/**
|
||||
* <p>Implements decoding of the UPC-E format.</p>
|
||||
* <p/>
|
||||
* <p><a href="http://www.barcodeisland.com/upce.phtml">This</a> is a great reference for
|
||||
* UPC-E information.</p>
|
||||
*
|
||||
* @author srowen@google.com (Sean Owen)
|
||||
*/
|
||||
public final class UPCEReader extends AbstractUPCEANReader {
|
||||
|
||||
/**
|
||||
* The pattern that marks the middle, and end, of a UPC-E pattern.
|
||||
* There is no "second half" to a UPC-E barcode.
|
||||
*/
|
||||
private static final int[] MIDDLE_END_PATTERN = {1, 1, 1, 1, 1, 1};
|
||||
|
||||
/**
|
||||
* See {@link #L_AND_G_PATTERNS}; these values similarly represent patterns of
|
||||
* even-odd parity encodings of digits that imply both the number system (0 or 1)
|
||||
* used, and the check digit.
|
||||
*/
|
||||
private static final int[][] NUMSYS_AND_CHECK_DIGIT_PATTERNS = {
|
||||
{0x38, 0x34, 0x32, 0x31, 0x2C, 0x26, 0x23, 0x2A, 0x29, 0x25},
|
||||
{0x07, 0x0B, 0x0D, 0x0E, 0x13, 0x19, 0x1C, 0x15, 0x16, 0x1A}
|
||||
};
|
||||
|
||||
protected int decodeMiddle(BitArray row, int[] startRange, StringBuffer result) throws ReaderException {
|
||||
|
||||
int[] counters = new int[4];
|
||||
int end = row.getSize();
|
||||
int rowOffset = startRange[1];
|
||||
|
||||
int lgPatternFound = 0;
|
||||
|
||||
for (int x = 0; x < 6 && rowOffset < end; x++) {
|
||||
int bestMatch = decodeDigit(row, counters, rowOffset, L_AND_G_PATTERNS);
|
||||
result.append((char) ('0' + bestMatch % 10));
|
||||
for (int i = 0; i < counters.length; i++) {
|
||||
rowOffset += counters[i];
|
||||
}
|
||||
if (bestMatch >= 10) {
|
||||
lgPatternFound |= 1 << (5 - x);
|
||||
}
|
||||
}
|
||||
|
||||
determineNumSysAndCheckDigit(result, lgPatternFound);
|
||||
|
||||
return rowOffset;
|
||||
}
|
||||
|
||||
protected int[] decodeEnd(BitArray row, int endStart) throws ReaderException {
|
||||
return findGuardPattern(row, endStart, true, MIDDLE_END_PATTERN);
|
||||
}
|
||||
|
||||
protected boolean checkChecksum(String s) throws ReaderException {
|
||||
return super.checkChecksum(convertUPCEtoUPCA(s));
|
||||
}
|
||||
|
||||
private static void determineNumSysAndCheckDigit(StringBuffer resultString, int lgPatternFound)
|
||||
throws ReaderException {
|
||||
|
||||
for (int numSys = 0; numSys <= 1; numSys++) {
|
||||
for (int d = 0; d < 10; d++) {
|
||||
if (lgPatternFound == NUMSYS_AND_CHECK_DIGIT_PATTERNS[numSys][d]) {
|
||||
resultString.insert(0, (char) ('0' + numSys));
|
||||
resultString.append((char) ('0' + d));
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
throw new ReaderException("Unable to determine number system and check digit");
|
||||
}
|
||||
|
||||
/**
|
||||
* Expands a UPC-E value back into its full, equivalent UPC-A code value.
|
||||
*
|
||||
* @param upce UPC-E code as string of digits
|
||||
* @return equivalent UPC-A code as string of digits
|
||||
*/
|
||||
private static String convertUPCEtoUPCA(String upce) {
|
||||
char[] upceChars = new char[6];
|
||||
upce.getChars(1, 7, upceChars, 0);
|
||||
StringBuffer result = new StringBuffer(12);
|
||||
result.append(upce.charAt(0));
|
||||
char lastChar = upceChars[5];
|
||||
switch (lastChar) {
|
||||
case '0':
|
||||
case '1':
|
||||
case '2':
|
||||
result.append(upceChars, 0, 2);
|
||||
result.append(lastChar);
|
||||
result.append("0000");
|
||||
result.append(upceChars, 2, 3);
|
||||
break;
|
||||
case '3':
|
||||
result.append(upceChars, 0, 3);
|
||||
result.append("00000");
|
||||
result.append(upceChars, 3, 2);
|
||||
break;
|
||||
case '4':
|
||||
result.append(upceChars, 0, 4);
|
||||
result.append("00000");
|
||||
result.append(upceChars[4]);
|
||||
break;
|
||||
default:
|
||||
result.append(upceChars, 0, 5);
|
||||
result.append("0000");
|
||||
result.append(lastChar);
|
||||
break;
|
||||
}
|
||||
result.append(upce.charAt(7));
|
||||
return result.toString();
|
||||
}
|
||||
|
||||
}
|
|
@ -1,449 +0,0 @@
|
|||
/*
|
||||
* 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.BlackPointEstimationMethod;
|
||||
import com.google.zxing.MonochromeBitmapSource;
|
||||
import com.google.zxing.ResultPoint;
|
||||
import com.google.zxing.common.BitArray;
|
||||
|
||||
/**
|
||||
* 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)
|
||||
*/
|
||||
final class UPCDecoder {
|
||||
|
||||
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
|
||||
};
|
||||
|
||||
/** Alternative even-parity patterns for EAN-13 barcodes. */
|
||||
private static final byte[][] EVEN_PARITY_PATTERNS = {
|
||||
{ 10, 10, 20, 30 }, // 0
|
||||
{ 10, 20, 20, 20 }, // 1
|
||||
{ 20, 20, 10, 20 }, // 2
|
||||
{ 10, 10, 40, 10 }, // 3
|
||||
{ 20, 30, 10, 10 }, // 4
|
||||
{ 10, 30, 20, 10 }, // 5
|
||||
{ 40, 10, 10, 10 }, // 6
|
||||
{ 20, 10, 30, 10 }, // 7
|
||||
{ 30, 10, 20, 10 }, // 8
|
||||
{ 20, 10, 10, 30 } // 9
|
||||
};
|
||||
|
||||
// For an EAN-13 barcode, the first digit is represented by the parities used
|
||||
// to encode the next six digits, according to the table below. For example,
|
||||
// if the barcode is 5 123456 789012 then the value of the first digit is
|
||||
// signified by using odd for '1', even for '2', even for '3', odd for '4',
|
||||
// odd for '5', and even for '6'. See http://en.wikipedia.org/wiki/EAN-13
|
||||
//
|
||||
// Parity of next 6 digits
|
||||
// Digit 0 1 2 3 4 5
|
||||
// 0 Odd Odd Odd Odd Odd Odd
|
||||
// 1 Odd Odd Even Odd Even Even
|
||||
// 2 Odd Odd Even Even Odd Even
|
||||
// 3 Odd Odd Even Even Even Odd
|
||||
// 4 Odd Even Odd Odd Even Even
|
||||
// 5 Odd Even Even Odd Odd Even
|
||||
// 6 Odd Even Even Even Odd Odd
|
||||
// 7 Odd Even Odd Even Odd Even
|
||||
// 8 Odd Even Odd Even Even Odd
|
||||
// 9 Odd Even Even Odd Even Odd
|
||||
//
|
||||
// Note that the encoding for '0' uses the same parity as a UPC barcode. Hence
|
||||
// a UPC barcode can be converted to an EAN-13 barcode by prepending a 0.
|
||||
//
|
||||
// The encodong is represented by the following array, which is a bit pattern
|
||||
// using Odd = 0 and Even = 1. For example, 5 is represented by:
|
||||
//
|
||||
// Odd Even Even Odd Odd Even
|
||||
// in binary:
|
||||
// 0 1 1 0 0 1 == 0x19
|
||||
//
|
||||
private static final byte[] FIRST_DIGIT_ENCODINGS = {
|
||||
0x00, 0x0B, 0x0D, 0xE, 0x13, 0x19, 0x1C, 0x15, 0x16, 0x1A
|
||||
};
|
||||
|
||||
|
||||
// Parity types indicating how a given digit is encoded
|
||||
private static final int UNKNOWN_PARITY = 0;
|
||||
private static final int ODD_PARITY = 1;
|
||||
private static final int EVEN_PARITY = 2;
|
||||
|
||||
/**
|
||||
* Utility class for returning a matched character. Defines the character
|
||||
* plus the parity used for encoding it.
|
||||
*/
|
||||
private static class CharResult {
|
||||
public char character; // the encoded character
|
||||
public int parity; // one of the parity types above
|
||||
}
|
||||
|
||||
private static final int TOLERANCE = 5;
|
||||
private static final UPCPoint[] NO_POINTS = new UPCPoint[0];
|
||||
|
||||
private MonochromeBitmapSource bitmap;
|
||||
private int width;
|
||||
private int height;
|
||||
private StringBuffer result;
|
||||
private UPCPoint[] points;
|
||||
|
||||
UPCDecoder(MonochromeBitmapSource bitmap) {
|
||||
this.bitmap = bitmap;
|
||||
width = bitmap.getWidth();
|
||||
height = bitmap.getHeight();
|
||||
points = NO_POINTS;
|
||||
}
|
||||
|
||||
/**
|
||||
* To decode the image, we attempt to decode rows across from the middle outwards.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() {
|
||||
BitArray rowData = new BitArray(width);
|
||||
//String longestResult = "";
|
||||
boolean found = false;
|
||||
|
||||
// We're going to examine rows from the middle outward, searching alternately above and below the middle,
|
||||
// and farther out each time. rowStep is the number of rows between each successive attempt above and below
|
||||
// the middle. So we'd scan row middle, then middle - rowStep, then middle + rowStep,
|
||||
// then middle - 2*rowStep, etc.
|
||||
// rowStep is bigger as the image is taller, but is always at least 1. We've somewhat arbitrarily decided
|
||||
// that moving up and down by about 1/32 of the image is pretty good.
|
||||
int middle = height >> 1;
|
||||
int rowStep = Math.max(1, height >> 5);
|
||||
for (int x = 0; x < 11; x++) {
|
||||
|
||||
int rowStepsAboveOrBelow = (x + 1) >> 1;
|
||||
boolean isAbove = (x & 0x01) == 0; // i.e. is x even?
|
||||
int row = middle + rowStep * (isAbove ? rowStepsAboveOrBelow : -rowStepsAboveOrBelow);
|
||||
|
||||
bitmap.estimateBlackPoint(BlackPointEstimationMethod.ROW_SAMPLING, row);
|
||||
bitmap.getBlackRow(row, rowData, 0, width);
|
||||
|
||||
if (decodeRow(row, rowData)) {
|
||||
found = true;
|
||||
break;
|
||||
}
|
||||
//Log("decode: row " + row + " normal result: " + result);
|
||||
//if (result.length() > longestResult.length()) {
|
||||
// longestResult = result.toString();
|
||||
//}
|
||||
|
||||
rowData.reverse();
|
||||
if (decodeRow(row, rowData)) {
|
||||
found = true;
|
||||
break;
|
||||
}
|
||||
//Log("decode: row " + row + " inverted result: " + result);
|
||||
//if (result.length() > longestResult.length()) {
|
||||
// longestResult = result.toString();
|
||||
//}
|
||||
}
|
||||
|
||||
if (found) {
|
||||
return result.toString();
|
||||
} else {
|
||||
return "";
|
||||
}
|
||||
}
|
||||
|
||||
public ResultPoint[] getPoints() {
|
||||
return points;
|
||||
}
|
||||
|
||||
/**
|
||||
* 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:
|
||||
* <a href="http://en.wikipedia.org/wiki/Universal_Product_Code">
|
||||
* http://en.wikipedia.org/wiki/Universal_Product_Code</a>
|
||||
*/
|
||||
private boolean decodeRow(int row, BitArray rowData) {
|
||||
// TODO: Add support for UPC-E Zero Compressed 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.
|
||||
result = new StringBuffer();
|
||||
int rowOffset = findPattern(rowData, 0, START_END_PATTERN, false);
|
||||
if (rowOffset < 0) {
|
||||
return false;
|
||||
}
|
||||
int startOffset = rowOffset;
|
||||
//Log("Start pattern ends at column " + rowOffset);
|
||||
|
||||
rowOffset = decodeOneSide(rowData, rowOffset, true);
|
||||
if (rowOffset < 0) {
|
||||
return false;
|
||||
}
|
||||
|
||||
rowOffset = findPattern(rowData, rowOffset, MIDDLE_PATTERN, true);
|
||||
if (rowOffset < 0) {
|
||||
return false;
|
||||
}
|
||||
//Log("Middle pattern ends at column " + rowOffset);
|
||||
|
||||
// Pass in false for checkBothParities(). For an EAN-13 barcode, only the
|
||||
// left had side will use mixed parities.
|
||||
rowOffset = decodeOneSide(rowData, rowOffset, false);
|
||||
if (rowOffset < 0) {
|
||||
return false;
|
||||
}
|
||||
|
||||
boolean verified = verifyResult();
|
||||
if (verified) {
|
||||
points = new UPCPoint[] { new UPCPoint(startOffset, row), new UPCPoint(rowOffset, row) };
|
||||
}
|
||||
return verified;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Verifies the checksum. This is computed by adding up digits in the even
|
||||
* indices (0, 2, 4...) then adding the digits in the odd indices (1, 3, 5..)
|
||||
* and multiplying by 3. The total, plus the final checksum digit, should be
|
||||
* divisible by 10.
|
||||
*
|
||||
* Note that for a UPC barcode, we add the additional '0' to the front
|
||||
* (converting it to a EAN-13 code) for purposes of calculating the checksum
|
||||
*/
|
||||
private boolean verifyResult() {
|
||||
// TODO - handle compressed barcodes.
|
||||
|
||||
// length is 12 for UPC and 13 for EAN-13
|
||||
if (result.length() != 12 && result.length() != 13) {
|
||||
return false;
|
||||
}
|
||||
|
||||
int checksum = 0;
|
||||
int end = result.length() - 2;
|
||||
int factor = 3;
|
||||
// Calculate from penultimate digit down to first. This avoids having to
|
||||
// account for the optional '0' on the front, which won't actually affect
|
||||
// the calculation.
|
||||
for (int i = end; i >= 0; i--) {
|
||||
int value = (result.charAt(i) - (int)'0') * factor;
|
||||
checksum += value;
|
||||
factor = factor == 3 ? 1 : 3;
|
||||
}
|
||||
int endValue = (result.charAt(end + 1) - (int)'0');
|
||||
//Log("checksum + endValue = " + (checksum + endValue));
|
||||
return (checksum + endValue) % 10 == 0;
|
||||
}
|
||||
|
||||
private int decodeOneSide(BitArray rowData, int rowOffset, boolean checkBothParities) {
|
||||
int[] counters = new int[4];
|
||||
byte firstDigitPattern = 0;
|
||||
CharResult foundChar = new CharResult();
|
||||
for (int x = 0; x < 6 && rowOffset < width; x++) {
|
||||
recordPattern(rowData, rowOffset, counters, 4);
|
||||
for (int y = 0; y < 4; y++) {
|
||||
rowOffset += counters[y];
|
||||
}
|
||||
findDigit(counters, foundChar, checkBothParities);
|
||||
if (foundChar.parity == UNKNOWN_PARITY) {
|
||||
return -1;
|
||||
}
|
||||
if (foundChar.parity == EVEN_PARITY) {
|
||||
firstDigitPattern |= 1 << (5 - x);
|
||||
}
|
||||
result.append(foundChar.character);
|
||||
}
|
||||
// If checkBothParities is true then we're potentially looking at the left
|
||||
// hand side of an EAN-13 barcode, where the first digit is encoded by the
|
||||
// parity pattern. In that case, calculate the first digit by checking
|
||||
// the parity patterns.
|
||||
if (checkBothParities) {
|
||||
char firstDigit = '-';
|
||||
for (int i = 0; i < FIRST_DIGIT_ENCODINGS.length; i++) {
|
||||
if (firstDigitPattern == FIRST_DIGIT_ENCODINGS[i]) {
|
||||
firstDigit = (char)((int)'0' + i);
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (firstDigit == '-') {
|
||||
return -1;
|
||||
}
|
||||
if (firstDigit != '0') {
|
||||
result.insert(0, firstDigit);
|
||||
}
|
||||
}
|
||||
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 patternLength = pattern.length;
|
||||
int[] counters = new int[patternLength];
|
||||
int width = this.width;
|
||||
boolean isWhite = false;
|
||||
while (rowOffset < width) {
|
||||
isWhite = !rowData.get(rowOffset);
|
||||
if (whiteFirst == isWhite) {
|
||||
break;
|
||||
}
|
||||
rowOffset++;
|
||||
}
|
||||
|
||||
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 == patternLength - 1) {
|
||||
if (doesPatternMatch(counters, pattern)) {
|
||||
return x;
|
||||
}
|
||||
for (int y = 2; y < patternLength; 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) {
|
||||
for (int i = 0; i < counters.length; i++) {
|
||||
counters[i] = 0;
|
||||
}
|
||||
boolean isWhite = !rowData.get(rowOffset);
|
||||
|
||||
int counterPosition = 0;
|
||||
int width = this.width;
|
||||
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.
|
||||
*/
|
||||
private static void findDigit(int[] counters, CharResult result, boolean checkBothParities) {
|
||||
result.parity = UNKNOWN_PARITY;
|
||||
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;
|
||||
byte[] pattern = DIGIT_PATTERNS[x];
|
||||
for (int y = 0; y < 4; y++) {
|
||||
int diff = counters[y] - pattern[y];
|
||||
if (diff > TOLERANCE || diff < -TOLERANCE) {
|
||||
match = false;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (match) {
|
||||
result.parity = ODD_PARITY;
|
||||
result.character = (char)((int)'0' + x);
|
||||
return;
|
||||
}
|
||||
}
|
||||
// If first pattern didn't match, look for even parity patterns, as used in
|
||||
// EAN-13 barcodes.
|
||||
if (checkBothParities) {
|
||||
for (int x = 0; x < 10; x++) {
|
||||
boolean match = true;
|
||||
byte[] pattern = EVEN_PARITY_PATTERNS[x];
|
||||
for (int y = 0; y < 4; y++) {
|
||||
int diff = counters[y] - pattern[y];
|
||||
if (diff > TOLERANCE || diff < -TOLERANCE) {
|
||||
match = false;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (match) {
|
||||
result.parity = EVEN_PARITY;
|
||||
result.character = (char)((int)'0' + 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.
|
||||
*/
|
||||
private static boolean doesPatternMatch(int[] counters, byte[] pattern) {
|
||||
// TODO: Remove the divide for performance.
|
||||
int total = 0;
|
||||
int numCounters = counters.length;
|
||||
for (int x = 0; x < numCounters; x++) {
|
||||
total += counters[x];
|
||||
}
|
||||
int average = total * 10 / counters.length;
|
||||
|
||||
for (int x = 0; x < numCounters; x++) {
|
||||
int scaledCounter = counters[x] * 100 / average;
|
||||
int scaledPattern = pattern[x] * 10;
|
||||
if (scaledCounter < scaledPattern - TOLERANCE || scaledCounter > scaledPattern + TOLERANCE) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
}
|
|
@ -1,53 +0,0 @@
|
|||
/*
|
||||
* 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 java.util.Hashtable;
|
||||
|
||||
/**
|
||||
* A reader which decodes UPC-A barcodes.
|
||||
*
|
||||
* @author dswitkin@google.com (Daniel Switkin)
|
||||
*/
|
||||
public final class UPCReader implements Reader {
|
||||
|
||||
/**
|
||||
* 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, decoder.getPoints());
|
||||
}
|
||||
|
||||
}
|
|
@ -17,7 +17,6 @@
|
|||
package com.google.zxing;
|
||||
|
||||
import com.google.zxing.common.AbstractBlackBoxTestCase;
|
||||
import com.google.zxing.upc.UPCReader;
|
||||
|
||||
import java.io.File;
|
||||
|
||||
|
@ -27,7 +26,7 @@ import java.io.File;
|
|||
public final class UPCReaderBlackBox2TestCase extends AbstractBlackBoxTestCase {
|
||||
|
||||
public UPCReaderBlackBox2TestCase() {
|
||||
super(new File("test/data/blackbox/upc2"), new UPCReader(), 0.5);
|
||||
super(new File("test/data/blackbox/upc2"), new MultiFormatReader(), 0.66);
|
||||
}
|
||||
|
||||
}
|
|
@ -16,6 +16,7 @@
|
|||
|
||||
package com.google.zxing.qrcode;
|
||||
|
||||
import com.google.zxing.MultiFormatReader;
|
||||
import com.google.zxing.common.AbstractBlackBoxTestCase;
|
||||
|
||||
import java.io.File;
|
||||
|
@ -26,7 +27,7 @@ import java.io.File;
|
|||
public final class QRCodeReaderBlackBoxTestCase extends AbstractBlackBoxTestCase {
|
||||
|
||||
public QRCodeReaderBlackBoxTestCase() {
|
||||
super(new File("test/data/blackbox/qrcode1"), new QRCodeReader(), 0.5);
|
||||
super(new File("test/data/blackbox/qrcode1"), new MultiFormatReader(), 0.5);
|
||||
}
|
||||
|
||||
}
|
|
@ -16,6 +16,7 @@
|
|||
|
||||
package com.google.zxing.qrcode;
|
||||
|
||||
import com.google.zxing.MultiFormatReader;
|
||||
import com.google.zxing.common.AbstractBlackBoxTestCase;
|
||||
|
||||
import java.io.File;
|
||||
|
@ -26,7 +27,7 @@ import java.io.File;
|
|||
public final class QRCodeReaderExternalTestCase extends AbstractBlackBoxTestCase {
|
||||
|
||||
public QRCodeReaderExternalTestCase() {
|
||||
super(new File("test/data/blackbox/qrcode2"), new QRCodeReader(), 1.0);
|
||||
super(new File("test/data/blackbox/qrcode2"), new MultiFormatReader(), 1.0);
|
||||
}
|
||||
|
||||
}
|
|
@ -16,6 +16,7 @@
|
|||
|
||||
package com.google.zxing.upc;
|
||||
|
||||
import com.google.zxing.MultiFormatReader;
|
||||
import com.google.zxing.common.AbstractBlackBoxTestCase;
|
||||
|
||||
import java.io.File;
|
||||
|
@ -26,7 +27,7 @@ import java.io.File;
|
|||
public final class UPCReaderBlackBoxTestCase extends AbstractBlackBoxTestCase {
|
||||
|
||||
public UPCReaderBlackBoxTestCase() {
|
||||
super(new File("test/data/blackbox/upc1"), new UPCReader(), 0.5);
|
||||
super(new File("test/data/blackbox/upc1"), new MultiFormatReader(), 0.5);
|
||||
}
|
||||
|
||||
}
|
|
@ -80,6 +80,7 @@ public final class LCDUIImageMonochromeBitmapSource implements MonochromeBitmapS
|
|||
public void estimateBlackPoint(BlackPointEstimationMethod method, int argument) {
|
||||
if (!method.equals(lastMethod) || argument != lastArgument) {
|
||||
int[] histogram = new int[LUMINANCE_BUCKETS];
|
||||
float biasTowardsWhite = 1.0f;
|
||||
if (method.equals(BlackPointEstimationMethod.TWO_D_SAMPLING)) {
|
||||
int minDimension = width < height ? width : height;
|
||||
for (int n = 0, offset = 0; n < minDimension; n++, offset += width + 1) {
|
||||
|
@ -89,6 +90,7 @@ public final class LCDUIImageMonochromeBitmapSource implements MonochromeBitmapS
|
|||
if (argument < 0 || argument >= height) {
|
||||
throw new IllegalArgumentException("Row is not within the image: " + argument);
|
||||
}
|
||||
biasTowardsWhite = 2.0f;
|
||||
int offset = argument * width;
|
||||
for (int x = 0; x < width; x++) {
|
||||
histogram[computeRGBLuminance(rgbPixels[offset + x]) >> LUMINANCE_SHIFT]++;
|
||||
|
@ -96,7 +98,7 @@ public final class LCDUIImageMonochromeBitmapSource implements MonochromeBitmapS
|
|||
} else {
|
||||
throw new IllegalArgumentException("Unknown method: " + method);
|
||||
}
|
||||
blackPoint = BlackPointEstimator.estimate(histogram) << LUMINANCE_SHIFT;
|
||||
blackPoint = BlackPointEstimator.estimate(histogram, biasTowardsWhite) << LUMINANCE_SHIFT;
|
||||
lastMethod = method;
|
||||
lastArgument = argument;
|
||||
}
|
||||
|
|
|
@ -80,6 +80,7 @@ public final class BufferedImageMonochromeBitmapSource implements MonochromeBitm
|
|||
int width = image.getWidth();
|
||||
int height = image.getHeight();
|
||||
int[] histogram = new int[LUMINANCE_BUCKETS];
|
||||
float biasTowardsWhite = 1.0f;
|
||||
if (method.equals(BlackPointEstimationMethod.TWO_D_SAMPLING)) {
|
||||
int minDimension = width < height ? width : height;
|
||||
int startI = height == minDimension ? 0 : (height - width) >> 1;
|
||||
|
@ -92,6 +93,7 @@ public final class BufferedImageMonochromeBitmapSource implements MonochromeBitm
|
|||
if (argument < 0 || argument >= height) {
|
||||
throw new IllegalArgumentException("Row is not within the image: " + argument);
|
||||
}
|
||||
biasTowardsWhite = 2.0f;
|
||||
int[] rgbArray = new int[width];
|
||||
image.getRGB(0, argument, width, 1, rgbArray, 0, width);
|
||||
for (int x = 0; x < width; x++) {
|
||||
|
@ -100,7 +102,7 @@ public final class BufferedImageMonochromeBitmapSource implements MonochromeBitm
|
|||
} else {
|
||||
throw new IllegalArgumentException("Unknown method: " + method);
|
||||
}
|
||||
blackPoint = BlackPointEstimator.estimate(histogram) << LUMINANCE_SHIFT;
|
||||
blackPoint = BlackPointEstimator.estimate(histogram, biasTowardsWhite) << LUMINANCE_SHIFT;
|
||||
lastMethod = method;
|
||||
lastArgument = argument;
|
||||
}
|
||||
|
|
Loading…
Reference in a new issue