Code tweaks and so forth with Daniel
[zxing.git] / core / src / com / google / zxing / upc / UPCDecoder.java
index 3e530e0..99928f3 100755 (executable)
@@ -19,8 +19,6 @@ package com.google.zxing.upc;
 import com.google.zxing.common.BitArray;
 import com.google.zxing.MonochromeBitmapSource;
 
-import java.util.Arrays;
-
 /**
  * This class takes a bitmap, and attempts to return a String which is the contents of the UPC
  * barcode in the image. It should be scale-invariant, but does not make any corrections for
@@ -28,36 +26,55 @@ import java.util.Arrays;
  *
  * @author dswitkin@google.com (Daniel Switkin)
  */
-public class UPCDecoder {
+final class UPCDecoder {
+
+  private static final byte[] BITMAP_SEARCH_PATTERN = { 50, 49, 51, 48, 52, 46, 54, 43, 57, 40, 60 };
+  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
+  };
+  private static final int TOLERANCE = 5;
+
+  private MonochromeBitmapSource bitmap;
+  private int width;
+  private int height;
+  private StringBuffer result;
+
   UPCDecoder(MonochromeBitmapSource bitmap) {
-       mBitmap = bitmap;
-    if (bitmap != null) {
-      mWidth = bitmap.getWidth();
-      mHeight = bitmap.getHeight();
-    }
+         this.bitmap = bitmap;
+    width = bitmap.getWidth();
+    height = bitmap.getHeight();
   }
 
   // To decode the image, we follow a search pattern defined in kBitmapSearchPattern. It is a
   // list of percentages which translate to row numbers to scan across. For each row, we scan
   // left to right, and if that fails, we reverse the row in place and try again to see if the
   // bar code was upside down.
-  public String decode() {
-    if (mBitmap == null) return "";
-
-    BitArray rowData = new BitArray(mWidth);
+  String decode() {
+    BitArray rowData = new BitArray(width);
     String longestResult = "";
     int found = -1;
-    for (int x = 0; x < kBitmapSearchPattern.length; x++) {
-      int row = mHeight * kBitmapSearchPattern[x] / 100;
-      mBitmap.getBlackRow(row, rowData, 0, mWidth);
+    for (int x = 0; x < BITMAP_SEARCH_PATTERN.length; x++) {
+      int row = height * BITMAP_SEARCH_PATTERN[x] / 100;
+      bitmap.getBlackRow(row, rowData, 0, width);
 
       if (decodeRow(rowData)) {
         found = x;
         break;
       }
       //Log("decode: row " + row + " normal result: " + mResult);
-      if (mResult.length() > longestResult.length()) {
-        longestResult = mResult;
+      if (result.length() > longestResult.length()) {
+        longestResult = result.toString();
       }
       
       rowData.reverse();
@@ -66,48 +83,60 @@ public class UPCDecoder {
         break;
       }
       //Log("decode: row " + row + " inverted result: " + mResult);
-      if (mResult.length() > longestResult.length()) {
-        longestResult = mResult;
+      if (result.length() > longestResult.length()) {
+        longestResult = result.toString();
       }
     }
     
-    if (found >= 0) return mResult;
-    else return "";
+    if (found >= 0) {
+      return result.toString();
+    } else {
+      return "";
+    }
   }
   
-  // UPC-A bar codes are made up of a left marker, six digits, a middle marker, six more digits,
-  // and an end marker, reading from left to right. For more information, see:
-  //
-  // http://en.wikipedia.org/wiki/Universal_Product_Code
-  //
-  // TODO: Add support for UPC-E Zero Compressed bar codes.
-  // TODO: Add support for EAN-13 (European Article Number) bar codes.
-  // FIXME: Don't trust the first result from findPattern() for the start sequence - resume from
-  // that spot and try to start again if finding digits fails.
+  /**
+   * 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(BitArray rowData) {
-    mResult = "";
-    int rowOffset = findPattern(rowData, 0, kStartEndPattern, false);
-    if (rowOffset < 0) return false;
+    // TODO: Add support for UPC-E Zero Compressed bar codes.
+    // TODO: Add support for EAN-13 (European Article Number) bar codes.
+    // FIXME: Don't trust the first result from findPattern() for the start sequence - resume from
+    // that spot and try to start again if finding digits fails.
+    result = new StringBuffer();
+    int rowOffset = findPattern(rowData, 0, START_END_PATTERN, false);
+    if (rowOffset < 0) {
+      return false;
+    }
     //Log("Start pattern ends at column " + rowOffset);
 
     rowOffset = decodeOneSide(rowData, rowOffset);
-    if (rowOffset < 0) return false;
+    if (rowOffset < 0) {
+      return false;
+    }
 
-    rowOffset = findPattern(rowData, rowOffset, kMiddlePattern, 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);
 
     rowOffset = decodeOneSide(rowData, rowOffset);
-    if (rowOffset < 0) return false;
+    if (rowOffset < 0) {
+      return false;
+    }
 
     // We could attempt to read the end pattern for sanity, but there's not much point.
     // UPC-A codes have 12 digits, so any other result is garbage.
-    return (mResult.length() == 12);
+    return result.length() == 12;
   }
 
   private int decodeOneSide(BitArray rowData, int rowOffset) {
     int[] counters = new int[4];
-    for (int x = 0; x < 6 && rowOffset < mWidth; x++) {
+    for (int x = 0; x < 6 && rowOffset < width; x++) {
       recordPattern(rowData, rowOffset, counters, 4);
       for (int y = 0; y < 4; y++) {
         rowOffset += counters[y];
@@ -116,7 +145,7 @@ public class UPCDecoder {
       if (c == '-') {
         return -1;
       } else {
-        mResult += c;
+        result.append(c);
       }
     }
     return rowOffset;
@@ -127,13 +156,14 @@ public class UPCDecoder {
   // begin on white or black based on the flag.
   private int findPattern(BitArray rowData, int rowOffset, byte[] pattern, boolean whiteFirst) {
     int[] counters = new int[pattern.length];
-    int width = mWidth;
+    int width = this.width;
     boolean isWhite = false;
-    for (; rowOffset < width; rowOffset++) {
+    while (rowOffset < width) {
       isWhite = !rowData.get(rowOffset);
       if (whiteFirst == isWhite) {
         break;
       }
+      rowOffset++;
     }
 
     int counterPosition = 0;
@@ -160,15 +190,19 @@ public class UPCDecoder {
     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.
+  /**
+   * Records a pattern of alternating white and black pixels, returning an array of how many
+   * pixels of each color were seen. The pattern begins immediately based on the color of the
+   * first pixel encountered, so a patternSize of 3 could result in WBW or BWB.
+   */
   private void recordPattern(BitArray rowData, int rowOffset, int[] counters, int patternSize) {
-    Arrays.fill(counters, 0);
+    for (int i = 0; i < counters.length; i++) {
+      counters[i] = 0;
+    }
     boolean isWhite = !rowData.get(rowOffset);
 
     int counterPosition = 0;
-    int width = mWidth;
+    int width = this.width;
     for (int x = rowOffset; x < width; x++) {
       boolean pixel = rowData.get(x);
       if ((!pixel && isWhite) || (pixel && !isWhite)) {
@@ -185,13 +219,15 @@ public class UPCDecoder {
     }
   }
 
-  // This is an optimized version of doesPatternMatch() which is specific to recognizing digits.
-  // The average is divided by 7 because there are 7 bits per digit, even though the color only
-  // alternates four times. kDigitPatterns has been premultiplied by 10 for efficiency. Notice
-  // that the contents of the counters array are modified to save an extra allocation, so don't
-  // use these values after returning from this call.
-  // TODO: add EAN even parity support
-  private char findDigit(int[] counters) {
+  /**
+   * 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 char findDigit(int[] counters) {
+    // TODO: add EAN even parity support
     int total = counters[0] + counters[1] + counters[2] + counters[3];
     int average = total * 10 / 7;
     for (int x = 0; x < 4; x++) {
@@ -201,60 +237,41 @@ public class UPCDecoder {
     for (int x = 0; x < 10; x++) {
       boolean match = true;
       for (int y = 0; y < 4; y++) {
-        int diff = counters[y] - kDigitPatterns[x][y];
-        if (diff > kTolerance || diff < -kTolerance) {
+        int diff = counters[y] - DIGIT_PATTERNS[x][y];
+        if (diff > TOLERANCE || diff < -TOLERANCE) {
           match = false;
           break;
         }
       }
-      if (match) return kDigits[x];
+      if (match) {
+        return (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.
-  // TODO: Remove the divide for performance.
-  private boolean doesPatternMatch(int[] counters, byte[] pattern) {
+  /**
+   * 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;
-    for (int x = 0; x < counters.length; x++) {
+    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 < counters.length; x++) {
+    for (int x = 0; x < numCounters; x++) {
       int scaledCounter = counters[x] * 100 / average;
       int scaledPattern = pattern[x] * 10;
-      if (scaledCounter < scaledPattern - kTolerance || scaledCounter > scaledPattern + kTolerance) {
+      if (scaledCounter < scaledPattern - TOLERANCE || scaledCounter > scaledPattern + TOLERANCE) {
         return false;
       }
     }
     return true;
   }
 
-  private static final byte[] kBitmapSearchPattern = { 50, 49, 51, 48, 52, 46, 54, 43, 57, 40, 60 };
-  private static final byte[] kStartEndPattern = { 1, 1, 1 };
-  private static final byte[] kMiddlePattern = { 1, 1, 1, 1, 1 };
-
-  private static final byte[][] kDigitPatterns = {
-    { 30, 20, 10, 10 }, // 0
-    { 20, 20, 20, 10 }, // 1
-    { 20, 10, 20, 20 }, // 2
-    { 10, 40, 10, 10 }, // 3
-    { 10, 10, 30, 20 }, // 4
-    { 10, 20, 30, 10 }, // 5
-    { 10, 10, 10, 40 }, // 6
-    { 10, 30, 10, 20 }, // 7
-    { 10, 20, 10, 30 }, // 8
-    { 30, 10, 10, 20 }  // 9
-  };
-
-  private static final char[] kDigits = { '0', '1', '2', '3', '4', '5', '6', '7', '8', '9' };
-  private static final int kTolerance = 5;
-
-  private MonochromeBitmapSource mBitmap;
-  private int mWidth;
-  private int mHeight;
-  private String mResult;
 }