/*
- * Copyright 2008 Google Inc.
+ * Copyright 2008 ZXing authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
package com.google.zxing.oned;
+import com.google.zxing.BarcodeFormat;
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;
+import java.util.Hashtable;
+
/**
* <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 Sean Owen
* @author alasdair@google.com (Alasdair Mackintosh)
*/
public abstract class AbstractUPCEANReader extends AbstractOneDReader implements UPCEANReader {
- private static final float MAX_VARIANCE = 0.4f;
+ private static final int MAX_AVG_VARIANCE = (int) (PATTERN_MATCH_RESULT_SCALE_FACTOR * 0.42f);
+ private static final int MAX_INDIVIDUAL_VARIANCE = (int) (PATTERN_MATCH_RESULT_SCALE_FACTOR * 0.7f);
/**
* Start/end guard pattern.
*/
- protected static final int[] START_END_PATTERN = {1, 1, 1,};
+ private 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};
+ 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 = {
+ static final int[][] L_PATTERNS = {
{3, 2, 1, 1}, // 0
{2, 2, 2, 1}, // 1
{2, 1, 2, 2}, // 2
/**
* 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 final int[][] L_AND_G_PATTERNS;
static {
L_AND_G_PATTERNS = new int[20][];
}
}
- static int[] findStartGuardPattern(final BitArray row) throws ReaderException {
+ private final StringBuffer decodeRowStringBuffer;
+
+ protected AbstractUPCEANReader() {
+ decodeRowStringBuffer = new StringBuffer(20);
+ }
+
+ static int[] findStartGuardPattern(BitArray row) throws ReaderException {
boolean foundStart = false;
int[] startRange = null;
int nextStart = 0;
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 = row.isRange(Math.max(0, start - 2 * (startRange[1] - start)), start, false);
+ // Make sure there is a quiet zone at least as big as the start pattern before the barcode. If
+ // this check would run off the left edge of the image, do not accept this barcode, as it is
+ // very likely to be a false positive.
+ int quietStart = start - (nextStart - start);
+ if (quietStart >= 0) {
+ foundStart = row.isRange(quietStart, start, false);
+ }
}
return startRange;
}
- public final Result decodeRow(int rowNumber, BitArray row) throws ReaderException {
+ public final Result decodeRow(int rowNumber, BitArray row, Hashtable hints) throws ReaderException {
return decodeRow(rowNumber, row, findStartGuardPattern(row));
}
public final Result decodeRow(int rowNumber, BitArray row, int[] startGuardRange) throws ReaderException {
-
- StringBuffer result = new StringBuffer();
-
+ StringBuffer result = decodeRowStringBuffer;
+ result.setLength(0);
int endStart = decodeMiddle(row, startGuardRange, result);
-
int[] endRange = decodeEnd(row, endStart);
- // Check for whitespace after the pattern
+ // Make sure there is a quiet zone at least as big as the end pattern after the barcode. The
+ // spec might want more whitespace, but in practice this is the maximum we can count on.
int end = endRange[1];
- if (!row.isRange(end, Math.min(row.getSize(), end + 2 * (end - endRange[0])), false)) {
- throw new ReaderException("Pattern not followed by whitespace");
+ int quietEnd = end + (end - endRange[0]);
+ if (quietEnd >= row.getSize() || !row.isRange(end, quietEnd, false)) {
+ throw ReaderException.getInstance();
}
String resultString = result.toString();
if (!checkChecksum(resultString)) {
- throw new ReaderException("Checksum failed");
+ throw ReaderException.getInstance();
}
- 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)});
+ float left = (float) (startGuardRange[1] + startGuardRange[0]) / 2.0f;
+ float right = (float) (endRange[1] + endRange[0]) / 2.0f;
+ return new Result(resultString,
+ null, // no natural byte representation for these barcodes
+ new ResultPoint[]{
+ new GenericResultPoint(left, (float) rowNumber),
+ new GenericResultPoint(right, (float) rowNumber)},
+ getBarcodeFormat());
+ }
+
+ abstract BarcodeFormat getBarcodeFormat();
+
+ /**
+ * @return {@link #checkStandardUPCEANChecksum(String)}
+ */
+ boolean checkChecksum(String s) throws ReaderException {
+ return checkStandardUPCEANChecksum(s);
}
/**
* @return true iff string of digits passes the UPC/EAN checksum algorithm
* @throws ReaderException if the string does not contain only digits
*/
- protected boolean checkChecksum(String s) throws ReaderException {
- int sum = 0;
+ public static boolean checkStandardUPCEANChecksum(String s) throws ReaderException {
int length = s.length();
+ if (length == 0) {
+ return false;
+ }
+
+ int sum = 0;
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");
+ throw ReaderException.getInstance();
}
sum += digit;
}
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");
+ throw ReaderException.getInstance();
}
sum += digit;
}
protected abstract int decodeMiddle(BitArray row, int[] startRange, StringBuffer resultString)
throws ReaderException;
- protected int[] decodeEnd(BitArray row, int endStart) throws ReaderException {
+ int[] decodeEnd(BitArray row, int endStart) throws ReaderException {
return findGuardPattern(row, endStart, false, START_END_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)
+ static int[] findGuardPattern(BitArray row, int rowOffset, boolean whiteFirst, int[] pattern)
throws ReaderException {
int patternLength = pattern.length;
int[] counters = new int[patternLength];
counters[counterPosition]++;
} else {
if (counterPosition == patternLength - 1) {
- if (patternMatchVariance(counters, pattern) < MAX_VARIANCE) {
+ if (patternMatchVariance(counters, pattern, MAX_INDIVIDUAL_VARIANCE) < MAX_AVG_VARIANCE) {
return new int[]{patternStart, x};
}
patternStart += counters[0] + counters[1];
isWhite = !isWhite;
}
}
- throw new ReaderException("Can't find pattern");
+ throw ReaderException.getInstance();
}
/**
* @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 {
+ 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 bestVariance = MAX_AVG_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);
+ int max = patterns.length;
+ for (int i = 0; i < max; i++) {
+ int[] pattern = patterns[i];
+ int variance = patternMatchVariance(counters, pattern, MAX_INDIVIDUAL_VARIANCE);
if (variance < bestVariance) {
bestVariance = variance;
- bestMatch = d;
+ bestMatch = i;
}
}
if (bestMatch >= 0) {
return bestMatch;
} else {
- throw new ReaderException("Could not match any digit in pattern");
+ throw ReaderException.getInstance();
}
}