Changed the 2D histogram calculation to sample four rows spread across the image...
[zxing.git] / core / src / com / google / zxing / common / BaseMonochromeBitmapSource.java
index b9de750..06f0513 100644 (file)
@@ -24,20 +24,28 @@ import com.google.zxing.ReaderException;
  */
 public abstract class BaseMonochromeBitmapSource implements MonochromeBitmapSource {
 
-  private static final int LUMINANCE_BITS = 5;
-  private static final int LUMINANCE_SHIFT = 8 - LUMINANCE_BITS;
-  private static final int LUMINANCE_BUCKETS = 1 << LUMINANCE_BITS;
-
+  private final int height;
+  private final int width;
   private int blackPoint;
   private BlackPointEstimationMethod lastMethod;
   private int lastArgument;
+  private int[] luminances = null;
 
-  protected BaseMonochromeBitmapSource() {
+  protected BaseMonochromeBitmapSource(int width, int height) {
+    this.height = height;
+    this.width = width;
     blackPoint = 0x7F;
     lastMethod = null;
     lastArgument = 0;
   }
 
+  private void initLuminances() {
+    if (luminances == null) {
+      int max = width > height ? width : height;
+      luminances = new int[max];
+    }
+  }
+
   public boolean isBlack(int x, int y) {
     return getLuminance(x, y) < blackPoint;
   }
@@ -49,15 +57,17 @@ public abstract class BaseMonochromeBitmapSource implements MonochromeBitmapSour
       row.clear();
     }
 
+    // Reuse the same int array each time
+    initLuminances();
+    int[] localLuminances = getLuminanceRow(y, luminances);
+
     // If the current decoder calculated the blackPoint based on one row, assume we're trying to
     // decode a 1D barcode, and apply some sharpening.
-    // TODO: We may want to add a fifth parameter to request the amount of shapening to be done.
-    cacheRowForLuminance(y);
     if (lastMethod.equals(BlackPointEstimationMethod.ROW_SAMPLING)) {
-      int left = getLuminance(startX, y);
-      int center = getLuminance(startX + 1, y);
+      int left = localLuminances[startX];
+      int center = localLuminances[startX + 1];
       for (int x = 1; x < getWidth - 1; x++) {
-        int right = getLuminance(startX + x + 1, y);
+        int right = localLuminances[startX + x + 1];
         // Simple -1 4 -1 box filter with a weight of 2
         int luminance = ((center << 2) - left - right) >> 1;
         if (luminance < blackPoint) {
@@ -68,7 +78,7 @@ public abstract class BaseMonochromeBitmapSource implements MonochromeBitmapSour
       }
     } else {
       for (int x = 0; x < getWidth; x++) {
-        if (getLuminance(startX + x, y) < blackPoint) {
+        if (localLuminances[startX + x] < blackPoint) {
           row.set(x);
         }
       }
@@ -76,32 +86,30 @@ public abstract class BaseMonochromeBitmapSource implements MonochromeBitmapSour
     return row;
   }
 
-  public void estimateBlackPoint(BlackPointEstimationMethod method, int argument) throws ReaderException {
-    if (!method.equals(lastMethod) || argument != lastArgument) {
-      int width = getWidth();
-      int height = getHeight();
-      int[] histogram = new int[LUMINANCE_BUCKETS];
-      if (method.equals(BlackPointEstimationMethod.TWO_D_SAMPLING)) {
-        int minDimension = width < height ? width : height;
-        int startX = (width - minDimension) >> 1;
-        int startY = (height - minDimension) >> 1;
-        for (int n = 0; n < minDimension; n++) {
-          int luminance = getLuminance(startX + n, startY + n);
-          histogram[luminance >> LUMINANCE_SHIFT]++;
-        }
-      } else if (method.equals(BlackPointEstimationMethod.ROW_SAMPLING)) {
-        if (argument < 0 || argument >= height) {
-          throw new IllegalArgumentException("Row is not within the image: " + argument);
-        }
-        cacheRowForLuminance(argument);
-        for (int x = 0; x < width; x++) {
-          int luminance = getLuminance(x, argument);
-          histogram[luminance >> LUMINANCE_SHIFT]++;
-        }
-      } else {
-        throw new IllegalArgumentException("Unknown method: " + method);
+  public BitArray getBlackColumn(int x, BitArray column, int startY, int getHeight) {
+    if (column == null || column.getSize() < getHeight) {
+      column = new BitArray(getHeight);
+    } else {
+      column.clear();
+    }
+
+    // Reuse the same int array each time
+    initLuminances();
+    int[] localLuminances = getLuminanceColumn(x, luminances);
+
+    // We don't handle "row sampling" specially here
+    for (int y = 0; y < getHeight; y++) {
+      if (localLuminances[startY + y] < blackPoint) {
+        column.set(y);
       }
-      blackPoint = BlackPointEstimator.estimate(histogram) << LUMINANCE_SHIFT;
+    }
+    return column;
+  }
+
+  public void estimateBlackPoint(BlackPointEstimationMethod method, int argument)
+      throws ReaderException {
+    if (!method.equals(lastMethod) || argument != lastArgument) {
+      blackPoint = BlackPointEstimator.estimate(this, method, argument);
       lastMethod = method;
       lastArgument = argument;
     }
@@ -119,16 +127,63 @@ public abstract class BaseMonochromeBitmapSource implements MonochromeBitmapSour
     return false;
   }
 
-  // These two methods should not need to exist because they are defined in the interface that
-  // this abstract class implements. However this seems to cause problems on some Nokias. 
-  // So we write these redundant declarations.
+  public final int getHeight() {
+    return height;
+  }
 
-  public abstract int getHeight();
+  public final int getWidth() {
+    return width;
+  }
 
-  public abstract int getWidth();
+  public String toString() {
+    StringBuffer result = new StringBuffer(height * (width + 1));
+    BitArray row = new BitArray(width);
+    for (int i = 0; i < height; i++) {
+      row = getBlackRow(i, row, 0, width);
+      for (int j = 0; j < width; j++) {
+        result.append(row.get(j) ? "X " : "  ");
+      }
+      result.append('\n');
+    }
+    return result.toString();
+  }
+
+
+  // These methods below should not need to exist because they are defined in the interface that
+  // this abstract class implements. However this seems to cause problems on some Nokias.
+  // So we write these redundant declarations.
 
+  /**
+   * Retrieves the luminance at the pixel x,y in the bitmap. This method is only used for estimating
+   * the black point and implementing getBlackRow() - it is not meant for decoding, hence it is not
+   * part of MonochromeBitmapSource itself, and is protected.
+   *
+   * @param x The x coordinate in the image.
+   * @param y The y coordinate in the image.
+   * @return The luminance value between 0 and 255.
+   */
   public abstract int getLuminance(int x, int y);
 
-  public abstract void cacheRowForLuminance(int y);
+  /**
+   * This is the main mechanism for retrieving luminance data. It is dramatically more efficient
+   * than repeatedly calling getLuminance(). As above, this is not meant for decoders.
+   *
+   * @param y The row to fetch
+   * @param row The array to write luminance values into. It is <b>strongly</b> suggested that you
+   *            allocate this yourself, making sure row.length >= getWidth(), and reuse the same
+   *            array on subsequent calls for performance. If you pass null, you will be flogged,
+   *            but then I will take pity on you and allocate a sufficient array internally.
+   * @return The array containing the luminance data. This is the same as row if it was usable.
+   */
+  public abstract int[] getLuminanceRow(int y, int[] row);
+
+  /**
+   * The same as getLuminanceRow(), but for columns.
+   *
+   * @param x The column to fetch
+   * @param column The array to write luminance values into. See above.
+   * @return The array containing the luminance data.
+   */
+  public abstract int[] getLuminanceColumn(int x, int[] column);
 
 }