I moved a chunk of the histogram/black point code out of BaseMonochromeBitmapSource...
authordswitkin <dswitkin@59b500cc-1b3d-0410-9834-0bbf25fbcc57>
Thu, 21 May 2009 19:56:25 +0000 (19:56 +0000)
committerdswitkin <dswitkin@59b500cc-1b3d-0410-9834-0bbf25fbcc57>
Thu, 21 May 2009 19:56:25 +0000 (19:56 +0000)
git-svn-id: http://zxing.googlecode.com/svn/trunk@945 59b500cc-1b3d-0410-9834-0bbf25fbcc57

android/src/com/google/zxing/client/android/YUVMonochromeBitmapSource.java
androidtest/src/com/google/zxing/client/androidtest/RGBMonochromeBitmapSource.java
core/src/com/google/zxing/MonochromeBitmapSource.java
core/src/com/google/zxing/common/BaseMonochromeBitmapSource.java
core/src/com/google/zxing/common/BlackPointEstimator.java
core/src/com/google/zxing/common/CroppedMonochromeBitmapSource.java
core/test/src/com/google/zxing/common/BlackPointEstimationMethodTestCase.java [deleted file]
core/test/src/com/google/zxing/common/BlackPointEstimatorTestCase.java [new file with mode: 0644]
javame/src/com/google/zxing/client/j2me/LCDUIImageMonochromeBitmapSource.java
javase/src/com/google/zxing/client/j2se/BufferedImageMonochromeBitmapSource.java

index 8aad467..adc7648 100755 (executable)
@@ -97,12 +97,12 @@ public final class YUVMonochromeBitmapSource extends BaseMonochromeBitmapSource
    * @return The luminance as an int, from 0-255
    */
   @Override
-  protected int getLuminance(int x, int y) {
+  public int getLuminance(int x, int y) {
     return mYUVData[(y + mCropTop) * mDataWidth + x + mCropLeft] & 0xff;
   }
 
   @Override
-  protected int[] getLuminanceRow(int y, int[] row) {
+  public int[] getLuminanceRow(int y, int[] row) {
     int width = getWidth();
     if (row == null || row.length < width) {
       row = new int[width];
@@ -116,7 +116,7 @@ public final class YUVMonochromeBitmapSource extends BaseMonochromeBitmapSource
   }
 
   @Override
-  protected int[] getLuminanceColumn(int x, int[] column) {
+  public int[] getLuminanceColumn(int x, int[] column) {
     int height = getHeight();
     if (column == null || column.length < height) {
       column = new int[height];
index 8f1c249..6065bcb 100644 (file)
@@ -67,12 +67,12 @@ public final class RGBMonochromeBitmapSource extends BaseMonochromeBitmapSource
   }
 
   @Override
-  protected int getLuminance(int x, int y) {
+  public int getLuminance(int x, int y) {
     return mLuminances[y * getWidth() + x] & 0xff;
   }
 
   @Override
-  protected int[] getLuminanceRow(int y, int[] row) {
+  public int[] getLuminanceRow(int y, int[] row) {
     int width = getWidth();
     if (row == null || row.length < width) {
       row = new int[width];
@@ -85,7 +85,7 @@ public final class RGBMonochromeBitmapSource extends BaseMonochromeBitmapSource
   }
 
   @Override
-  protected int[] getLuminanceColumn(int x, int[] column) {
+  public int[] getLuminanceColumn(int x, int[] column) {
     int width = getWidth();
     int height = getHeight();
     if (column == null || column.length < height) {
index 64155a1..c88ac24 100644 (file)
@@ -101,4 +101,37 @@ public interface MonochromeBitmapSource {
    */
   boolean isRotateSupported();
 
+  /**
+   * 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);
+
+  /**
+   * 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);
+
 }
index 0120391..2e16a73 100644 (file)
@@ -24,16 +24,12 @@ 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;
+  private int[] luminances = null;
 
   protected BaseMonochromeBitmapSource(int width, int height) {
     this.height = height;
@@ -113,30 +109,7 @@ public abstract class BaseMonochromeBitmapSource implements MonochromeBitmapSour
   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);
-        }
-        initLuminances();
-        luminances = getLuminanceRow(argument, luminances);
-        for (int x = 0; x < width; x++) {
-          histogram[luminances[x] >> LUMINANCE_SHIFT]++;
-        }
-      } else {
-        throw new IllegalArgumentException("Unknown method");
-      }
-      blackPoint = BlackPointEstimator.estimate(histogram) << LUMINANCE_SHIFT;
+      blackPoint = BlackPointEstimator.estimate(this, method, argument);
       lastMethod = method;
       lastArgument = argument;
     }
@@ -189,7 +162,7 @@ public abstract class BaseMonochromeBitmapSource implements MonochromeBitmapSour
    * @param y The y coordinate in the image.
    * @return The luminance value between 0 and 255.
    */
-  protected abstract int getLuminance(int x, int y);
+  public abstract int getLuminance(int x, int y);
 
   /**
    * This is the main mechanism for retrieving luminance data. It is dramatically more efficient
@@ -202,7 +175,7 @@ public abstract class BaseMonochromeBitmapSource implements MonochromeBitmapSour
    *            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.
    */
-  protected abstract int[] getLuminanceRow(int y, int[] row);
+  public abstract int[] getLuminanceRow(int y, int[] row);
 
   /**
    * The same as getLuminanceRow(), but for columns.
@@ -211,6 +184,6 @@ public abstract class BaseMonochromeBitmapSource implements MonochromeBitmapSour
    * @param column The array to write luminance values into. See above.
    * @return The array containing the luminance data.
    */
-  protected abstract int[] getLuminanceColumn(int x, int[] column);
+  public abstract int[] getLuminanceColumn(int x, int[] column);
 
 }
index 9f218dd..9fdc04b 100644 (file)
@@ -16,6 +16,8 @@
 \r
 package com.google.zxing.common;\r
 \r
+import com.google.zxing.BlackPointEstimationMethod;\r
+import com.google.zxing.MonochromeBitmapSource;\r
 import com.google.zxing.ReaderException;\r
 \r
 /**\r
@@ -26,37 +28,97 @@ import com.google.zxing.ReaderException;
  * <a href="http://webdiis.unizar.es/~neira/12082/thresholding.pdf">http://webdiis.unizar.es/~neira/12082/thresholding.pdf</a>.\r
  * </p>\r
  *\r
+ * NOTE: This class is not threadsafe.\r
+ *\r
  * @author Sean Owen\r
  * @author dswitkin@google.com (Daniel Switkin)\r
  */\r
 public final class BlackPointEstimator {\r
 \r
+  private static final int LUMINANCE_BITS = 5;\r
+  private static final int LUMINANCE_SHIFT = 8 - LUMINANCE_BITS;\r
+  private static final int LUMINANCE_BUCKETS = 1 << LUMINANCE_BITS;\r
+\r
+  private static int[] luminances = null;\r
+  private static int[] histogram = null;\r
+\r
   private BlackPointEstimator() {\r
   }\r
 \r
+  private static void initArrays(int luminanceSize) {\r
+    if (luminances == null || luminances.length < luminanceSize) {\r
+      luminances = new int[luminanceSize];\r
+    }\r
+    if (histogram == null) {\r
+      histogram = new int[LUMINANCE_BUCKETS];\r
+    } else {\r
+      for (int x = 0; x < LUMINANCE_BUCKETS; x++) {\r
+        histogram[x] = 0;\r
+      }\r
+    }\r
+  }\r
+\r
+  /**\r
+   * Calculates the black point for the supplied bitmap.\r
+   *\r
+   * @param source The bitmap to analyze.\r
+   * @param method The pixel sampling technique to use.\r
+   * @param argument The row index in the case of ROW_SAMPLING, otherwise ignored.\r
+   * @return The black point as an integer 0-255.\r
+   * @throws ReaderException An exception thrown if the blackpoint cannot be determined.\r
+   */\r
+  public static int estimate(MonochromeBitmapSource source, BlackPointEstimationMethod method,\r
+      int argument) throws ReaderException {\r
+    int width = source.getWidth();\r
+    int height = source.getHeight();\r
+    initArrays(width);\r
+\r
+    if (method.equals(BlackPointEstimationMethod.TWO_D_SAMPLING)) {\r
+      int minDimension = width < height ? width : height;\r
+      int startX = (width - minDimension) >> 1;\r
+      int startY = (height - minDimension) >> 1;\r
+      for (int n = 0; n < minDimension; n++) {\r
+        int luminance = source.getLuminance(startX + n, startY + n);\r
+        histogram[luminance >> LUMINANCE_SHIFT]++;\r
+      }\r
+    } else if (method.equals(BlackPointEstimationMethod.ROW_SAMPLING)) {\r
+      if (argument < 0 || argument >= height) {\r
+        throw new IllegalArgumentException("Row is not within the image: " + argument);\r
+      }\r
+\r
+      luminances = source.getLuminanceRow(argument, luminances);\r
+      for (int x = 0; x < width; x++) {\r
+        histogram[luminances[x] >> LUMINANCE_SHIFT]++;\r
+      }\r
+    } else {\r
+      throw new IllegalArgumentException("Unknown method");\r
+    }\r
+    return findBestValley(histogram) << LUMINANCE_SHIFT;\r
+  }\r
+\r
   /**\r
    * <p>Given an array of <em>counts</em> of luminance values (i.e. a histogram), this method\r
    * decides which bucket of values corresponds to the black point -- which bucket contains the\r
    * count of the brightest luminance values that should be considered "black".</p>\r
    *\r
-   * @param histogram an array of <em>counts</em> of luminance values\r
+   * @param buckets an array of <em>counts</em> of luminance values\r
    * @return index within argument of bucket corresponding to brightest values which should be\r
    *         considered "black"\r
    * @throws ReaderException if "black" and "white" appear to be very close in luminance\r
    */\r
-  public static int estimate(int[] histogram) throws ReaderException{\r
-    int numBuckets = histogram.length;\r
+  public static int findBestValley(int[] buckets) throws ReaderException {\r
+    int numBuckets = buckets.length;\r
     int maxBucketCount = 0;\r
     // Find tallest peak in histogram\r
     int firstPeak = 0;\r
     int firstPeakSize = 0;\r
     for (int i = 0; i < numBuckets; i++) {\r
-      if (histogram[i] > firstPeakSize) {\r
+      if (buckets[i] > firstPeakSize) {\r
         firstPeak = i;\r
-        firstPeakSize = histogram[i];\r
+        firstPeakSize = buckets[i];\r
       }\r
-      if (histogram[i] > maxBucketCount) {\r
-        maxBucketCount = histogram[i];\r
+      if (buckets[i] > maxBucketCount) {\r
+        maxBucketCount = buckets[i];\r
       }\r
     }\r
 \r
@@ -67,7 +129,7 @@ public final class BlackPointEstimator {
     for (int i = 0; i < numBuckets; i++) {\r
       int distanceToBiggest = i - firstPeak;\r
       // Encourage more distant second peaks by multiplying by square of distance\r
-      int score = histogram[i] * distanceToBiggest * distanceToBiggest;\r
+      int score = buckets[i] * distanceToBiggest * distanceToBiggest;\r
       if (score > secondPeakScore) {\r
         secondPeak = i;\r
         secondPeakScore = score;\r
@@ -81,7 +143,7 @@ public final class BlackPointEstimator {
       secondPeak = temp;\r
     }\r
 \r
-    // Kind of aribtrary; if the two peaks are very close, then we figure there is so little\r
+    // Kind of arbitrary; if the two peaks are very close, then we figure there is so little\r
     // dynamic range in the image, that discriminating black and white is too error-prone.\r
     // Decoding the image/line is either pointless, or may in some cases lead to a false positive\r
     // for 1D formats, which are relatively lenient.\r
@@ -97,7 +159,7 @@ public final class BlackPointEstimator {
       int fromFirst = i - firstPeak;\r
       // Favor a "valley" that is not too close to either peak -- especially not the black peak --\r
       // and that has a low value of course\r
-      int score = fromFirst * fromFirst * (secondPeak - i) * (maxBucketCount - histogram[i]);\r
+      int score = fromFirst * fromFirst * (secondPeak - i) * (maxBucketCount - buckets[i]);\r
       if (score > bestValleyScore) {\r
         bestValley = i;\r
         bestValleyScore = score;\r
index c7d625d..a3adf57 100644 (file)
@@ -95,4 +95,16 @@ public final class CroppedMonochromeBitmapSource implements MonochromeBitmapSour
     return delegate.isRotateSupported();
   }
 
+  public int getLuminance(int x, int y) {
+    return delegate.getLuminance(x, y);
+  }
+
+  public int[] getLuminanceRow(int y, int[] row) {
+    return delegate.getLuminanceRow(y, row);
+  }
+
+  public int[] getLuminanceColumn(int x, int[] column) {
+    return delegate.getLuminanceColumn(x, column);
+  }
+
 }
diff --git a/core/test/src/com/google/zxing/common/BlackPointEstimationMethodTestCase.java b/core/test/src/com/google/zxing/common/BlackPointEstimationMethodTestCase.java
deleted file mode 100644 (file)
index 2878a97..0000000
+++ /dev/null
@@ -1,43 +0,0 @@
-/*
- * 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.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.google.zxing.common;
-
-import com.google.zxing.ReaderException;
-import junit.framework.TestCase;
-
-/**
- * @author Sean Owen
- */
-public final class BlackPointEstimationMethodTestCase extends TestCase {
-
-  public void testBasic() throws ReaderException {
-    int[] histogram = { 0, 0, 11, 43, 37, 18, 3, 1, 0, 0, 13, 36, 24, 0, 11, 2 };
-    int point = BlackPointEstimator.estimate(histogram);
-    assertEquals(8, point);
-  }
-
-  public void testTooLittleRange() {
-    try {
-      int[] histogram = { 0, 0, 0, 0, 0, 0, 1, 43, 48, 18, 3, 1, 0, 0, 0, 0 };
-      BlackPointEstimator.estimate(histogram);
-      fail("Should have thrown an exception");
-    } catch (ReaderException re) {
-      // good
-    }
-  }
-
-}
\ No newline at end of file
diff --git a/core/test/src/com/google/zxing/common/BlackPointEstimatorTestCase.java b/core/test/src/com/google/zxing/common/BlackPointEstimatorTestCase.java
new file mode 100644 (file)
index 0000000..d780e9f
--- /dev/null
@@ -0,0 +1,43 @@
+/*
+ * 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.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.google.zxing.common;
+
+import com.google.zxing.ReaderException;
+import junit.framework.TestCase;
+
+/**
+ * @author Sean Owen
+ */
+public final class BlackPointEstimatorTestCase extends TestCase {
+
+  public void testBasic() throws ReaderException {
+    int[] histogram = { 0, 0, 11, 43, 37, 18, 3, 1, 0, 0, 13, 36, 24, 0, 11, 2 };
+    int point = BlackPointEstimator.findBestValley(histogram);
+    assertEquals(8, point);
+  }
+
+  public void testTooLittleRange() {
+    try {
+      int[] histogram = { 0, 0, 0, 0, 0, 0, 1, 43, 48, 18, 3, 1, 0, 0, 0, 0 };
+      BlackPointEstimator.findBestValley(histogram);
+      fail("Should have thrown an exception");
+    } catch (ReaderException re) {
+      // good
+    }
+  }
+
+}
\ No newline at end of file
index bd6b122..0d65ba3 100644 (file)
@@ -38,7 +38,7 @@ public final class LCDUIImageMonochromeBitmapSource extends BaseMonochromeBitmap
   }
 
   // This is expensive and should be used very sparingly.
-  protected int getLuminance(int x, int y) {
+  public int getLuminance(int x, int y) {
     image.getRGB(pixelHolder, 0, getWidth(), x, y, 1, 1);
     int pixel = pixelHolder[0];
 
@@ -60,7 +60,7 @@ public final class LCDUIImageMonochromeBitmapSource extends BaseMonochromeBitmap
   }
 
   // For efficiency, the RGB data and the luminance data share the same array.
-  protected int[] getLuminanceRow(int y, int[] row) {
+  public int[] getLuminanceRow(int y, int[] row) {
     int width = getWidth();
     if (row == null || row.length < width) {
       row = new int[width];
@@ -75,7 +75,7 @@ public final class LCDUIImageMonochromeBitmapSource extends BaseMonochromeBitmap
     return row;
   }
 
-  protected int[] getLuminanceColumn(int x, int[] column) {
+  public int[] getLuminanceColumn(int x, int[] column) {
     int height = getHeight();
     if (column == null || column.length < height) {
       column = new int[height];
index 5c6d75b..be7ad9a 100644 (file)
@@ -123,7 +123,7 @@ public final class BufferedImageMonochromeBitmapSource extends BaseMonochromeBit
    * where R, G, and B are values in [0,1].
    */
   @Override
-  protected int getLuminance(int x, int y) {
+  public int getLuminance(int x, int y) {
     int pixel = image.getRGB(left + x, top + y);
     // Coefficients add up to 1024 to make the divide into a fast shift
     return (306 * ((pixel >> 16) & 0xFF) +
@@ -132,7 +132,7 @@ public final class BufferedImageMonochromeBitmapSource extends BaseMonochromeBit
   }
 
   @Override
-  protected int[] getLuminanceRow(int y, int[] row) {
+  public int[] getLuminanceRow(int y, int[] row) {
     int width = getWidth();
     if (row == null || row.length < width) {
       row = new int[width];
@@ -148,7 +148,7 @@ public final class BufferedImageMonochromeBitmapSource extends BaseMonochromeBit
   }
 
   @Override
-  protected int[] getLuminanceColumn(int x, int[] column) {
+  public int[] getLuminanceColumn(int x, int[] column) {
     int height = getHeight();
     if (column == null || column.length < height) {
       column = new int[height];