- Fixed a crash when parsing a particular VCard with a blank entry.
[zxing.git] / core / src / com / google / zxing / oned / AbstractOneDReader.java
index 85a8aed..b09dd71 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.
@@ -22,7 +22,9 @@ import com.google.zxing.MonochromeBitmapSource;
 import com.google.zxing.ReaderException;
 import com.google.zxing.Result;
 import com.google.zxing.ResultMetadataType;
+import com.google.zxing.ResultPoint;
 import com.google.zxing.common.BitArray;
+import com.google.zxing.common.GenericResultPoint;
 
 import java.util.Hashtable;
 
@@ -35,18 +37,21 @@ import java.util.Hashtable;
  */
 public abstract class AbstractOneDReader implements OneDReader {
 
+  private static final int INTEGER_MATH_SHIFT = 8;
+  static final int PATTERN_MATCH_RESULT_SCALE_FACTOR = 1 << INTEGER_MATH_SHIFT;
+
   public final Result decode(MonochromeBitmapSource image) throws ReaderException {
     return decode(image, null);
   }
 
   public final Result decode(MonochromeBitmapSource image, Hashtable hints) throws ReaderException {
-    boolean tryHarder = hints != null && hints.containsKey(DecodeHintType.TRY_HARDER);
     try {
-      return doDecode(image, hints, tryHarder);
+      return doDecode(image, hints);
     } catch (ReaderException re) {
+      boolean tryHarder = hints != null && hints.containsKey(DecodeHintType.TRY_HARDER);
       if (tryHarder && image.isRotateSupported()) {
         MonochromeBitmapSource rotatedImage = image.rotateCounterClockwise();
-        Result result = doDecode(rotatedImage, hints, tryHarder);
+        Result result = doDecode(rotatedImage, hints);
         // Record that we found it rotated 90 degrees CCW / 270 degrees CW
         Hashtable metadata = result.getResultMetadata();
         int orientation = 270;
@@ -62,33 +67,35 @@ public abstract class AbstractOneDReader implements OneDReader {
     }
   }
 
-  private Result doDecode(MonochromeBitmapSource image, Hashtable hints, boolean tryHarder) throws ReaderException {
-
+  /**
+   * 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; we try more of the
+   * image if "trying harder".
+   *
+   * @param image The image to decode
+   * @param hints Any hints that were requested
+   * @return The contents of the decoded barcode
+   * @throws ReaderException Any spontaneous errors which occur
+   */
+  private Result doDecode(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; we try more of the image if
-    // "trying harder"
     int middle = height >> 1;
+    boolean tryHarder = hints != null && hints.containsKey(DecodeHintType.TRY_HARDER);
     int rowStep = Math.max(1, height >> (tryHarder ? 7 : 4));
     int maxLines;
-    //if (tryHarder || barcodesToSkip > 0) {
     if (tryHarder) {
-      maxLines = height; // Look at the whole image; looking for more than one barcode
+      maxLines = height; // Look at the whole image, not just the center
     } else {
-      maxLines = 7;
+      maxLines = 9; // Nine rows spaced 1/16 apart is roughly the middle half of the image
     }
 
-    Hashtable lastResults = null;
-    boolean skippingSomeBarcodes = hints != null && hints.containsKey(DecodeHintType.SKIP_N_BARCODES);
-
     for (int x = 0; x < maxLines; x++) {
 
       // Scanning from the middle out. Determine which row we're looking at next:
@@ -108,58 +115,46 @@ public abstract class AbstractOneDReader implements OneDReader {
       }
       image.getBlackRow(rowNumber, row, 0, width);
 
-      // We may try twice for each row, if "trying harder":
+      // While we have the image data in a BitArray, it's fairly cheap to reverse it in place to
+      // handle decoding upside down barcodes.
       for (int attempt = 0; attempt < 2; attempt++) {
-
         if (attempt == 1) { // trying again?
-          if (tryHarder) { // only if "trying harder"
-            row.reverse(); // reverse the row and continue
-          } else {
-            break;
-          }
+          row.reverse(); // reverse the row and continue
         }
-
         try {
-
           // Look for a barcode
           Result result = decodeRow(rowNumber, row, hints);
-
-          if (lastResults != null && lastResults.containsKey(result.getText())) {
-            // Just saw the last barcode again, proceed
-            continue;
+          // We found our barcode
+          if (attempt == 1) {
+            // But it was upside down, so note that
+            result.putMetadata(ResultMetadataType.ORIENTATION, new Integer(180));
+            // And remember to flip the result points horizontally.
+            ResultPoint[] points = result.getResultPoints();
+            points[0] = new GenericResultPoint(width - points[0].getX() - 1, points[0].getY());
+            points[1] = new GenericResultPoint(width - points[1].getX() - 1, points[1].getY());
           }
-
-          if (skippingSomeBarcodes) { // See if we should skip and keep looking
-            int oldValue = ((Integer) hints.get(DecodeHintType.SKIP_N_BARCODES)).intValue();
-            if (oldValue > 1) {
-              hints.put(DecodeHintType.SKIP_N_BARCODES, new Integer(oldValue - 1));
-            } else {
-              hints.remove(DecodeHintType.SKIP_N_BARCODES);
-              skippingSomeBarcodes = false;
-            }
-            if (lastResults == null) {
-              lastResults = new Hashtable(3);
-            }
-            lastResults.put(result.getText(), Boolean.TRUE); // Remember what we just saw
-          } else {
-            // We found our barcode
-            if (attempt == 1) {
-              // But it was upside down, so note that
-              result.putMetadata(ResultMetadataType.ORIENTATION, new Integer(180));
-            }
-            return result;
-          }
-
+          return result;
         } catch (ReaderException re) {
           // continue -- just couldn't decode this row
         }
-
       }
     }
 
     throw new ReaderException("No barcode found");
   }
 
+  /**
+   * Records the size of successive runs of white and black pixels in a row, starting at a given point.
+   * The values are recorded in the given array, and the number of runs recorded is equal to the size
+   * of the array. If the row starts on a white pixel at the given start point, then the first count
+   * recorded is the run of white pixels starting from that point; likewise it is the count of a run
+   * of black pixels if the row begin on a black pixels at that point.
+   *
+   * @param row row to count from
+   * @param start offset into row to start at
+   * @param counters array into which to record counts
+   * @throws ReaderException if counters cannot be filled entirely from row before running out of pixels
+   */
   static void recordPattern(BitArray row, int start, int[] counters) throws ReaderException {
     int numCounters = counters.length;
     for (int i = 0; i < numCounters; i++) {
@@ -196,31 +191,54 @@ public abstract class AbstractOneDReader implements OneDReader {
 
   /**
    * Determines how closely a set of observed counts of runs of black/white values matches a given
-   * target pattern. This is reported as the ratio of the total variance from the expected pattern proportions
-   * across all pattern elements, to the length of the pattern.
+   * target pattern. This is reported as the ratio of the total variance from the expected pattern
+   * proportions across all pattern elements, to the length of the pattern.
    *
    * @param counters observed counters
    * @param pattern expected pattern
-   * @return average variance between counters and pattern
+   * @param maxIndividualVariance The most any counter can differ before we give up
+   * @return ratio of total variance between counters and pattern compared to total pattern size,
+   *  where the ratio has been multiplied by 256. So, 0 means no variance (perfect match); 256 means
+   *  the total variance between counters and patterns equals the pattern length, higher values mean
+   *  even more variance
    */
-  static float patternMatchVariance(int[] counters, int[] pattern) {
-    int total = 0;
+  static int patternMatchVariance(int[] counters, int[] pattern, int maxIndividualVariance) {
     int numCounters = counters.length;
+    int total = 0;
     int patternLength = 0;
     for (int i = 0; i < numCounters; i++) {
       total += counters[i];
       patternLength += pattern[i];
     }
-    float unitBarWidth = (float) total / (float) patternLength;
+    if (total < patternLength) {
+      // If we don't even have one pixel per unit of bar width, assume this is too small
+      // to reliably match, so fail:
+      return Integer.MAX_VALUE;
+    }
+    // We're going to fake floating-point math in integers. We just need to use more bits.
+    // Scale up patternLength so that intermediate values below like scaledCounter will have
+    // more "significant digits"
+    int unitBarWidth = (total << INTEGER_MATH_SHIFT) / patternLength;
+    maxIndividualVariance = (maxIndividualVariance * unitBarWidth) >> INTEGER_MATH_SHIFT;
 
-    float totalVariance = 0.0f;
+    int totalVariance = 0;
     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;
+      int counter = counters[x] << INTEGER_MATH_SHIFT;
+      int scaledPattern = pattern[x] * unitBarWidth;
+      int variance = counter > scaledPattern ? counter - scaledPattern : scaledPattern - counter;
+      if (variance > maxIndividualVariance) {
+        return Integer.MAX_VALUE;
+      }
+      totalVariance += variance; 
     }
-    return totalVariance / (float) patternLength;
+    return totalVariance / total;
   }
 
+  // This declaration should not be necessary, since this class is
+  // abstract and so does not have to provide an implementation for every
+  // method of an interface it implements, but it is causing NoSuchMethodError
+  // issues on some Nokia JVMs. So we add this superfluous declaration:
+
+  public abstract Result decodeRow(int rowNumber, BitArray row, Hashtable hints) throws ReaderException;
+
 }