More small changes from code inspection
[zxing.git] / core / src / com / google / zxing / oned / AbstractUPCEANReader.java
index 26262fd..5336e0d 100644 (file)
@@ -1,5 +1,5 @@
 /*
- * 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.
@@ -30,12 +30,13 @@ import java.util.Hashtable;
  * 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.
@@ -83,7 +84,13 @@ public abstract class AbstractUPCEANReader extends AbstractOneDReader implements
     }
   }
 
-  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;
@@ -91,9 +98,13 @@ public abstract class AbstractUPCEANReader extends AbstractOneDReader implements
       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;
   }
@@ -103,34 +114,43 @@ public abstract class AbstractUPCEANReader extends AbstractOneDReader implements
   }
 
   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,
+    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((float) (startGuardRange[1] - startGuardRange[0]) / 2.0f, (float) rowNumber),
-            new GenericResultPoint((float) (endRange[1] - endRange[0]) / 2.0f, (float) rowNumber)},
+            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);
+  }
+
   /**
    * Computes the UPC/EAN checksum on a string of digits, and reports
    * whether the checksum is correct or not.
@@ -139,13 +159,17 @@ public abstract class AbstractUPCEANReader extends AbstractOneDReader implements
    * @return true iff string of digits passes the UPC/EAN checksum algorithm
    * @throws ReaderException if the string does not contain only digits
    */
-  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;
     }
@@ -153,7 +177,7 @@ public abstract class AbstractUPCEANReader extends AbstractOneDReader implements
     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;
     }
@@ -208,7 +232,7 @@ public abstract class AbstractUPCEANReader extends AbstractOneDReader implements
         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];
@@ -225,7 +249,7 @@ public abstract class AbstractUPCEANReader extends AbstractOneDReader implements
         isWhite = !isWhite;
       }
     }
-    throw new ReaderException("Can't find pattern");
+    throw ReaderException.getInstance();
   }
 
   /**
@@ -240,25 +264,24 @@ public abstract class AbstractUPCEANReader extends AbstractOneDReader implements
    * @return horizontal offset of first pixel beyond the decoded digit
    * @throws ReaderException if digit cannot be decoded
    */
-  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();
     }
   }