Improved GridSampler API -- no need for reflection anymore. Reintroduced Android...
[zxing.git] / core / src / com / google / zxing / qrcode / detector / GridSampler.java
index b4bb478..880ad48 100644 (file)
@@ -28,42 +28,57 @@ import com.google.zxing.common.BitMatrix;
  * Imaging library, but which may not be available in other environments such as J2ME, and vice
  * versa.
  *
- * The implementation used can be controlled by calling {@link #setGridSamplerClassName(String)}
- * with the name of a class which implements this interface.
+ * The implementation used can be controlled by calling {@link #setGridSampler(GridSampler)}
+ * with an instance of a class which implements this interface.
  *
  * @author srowen@google.com (Sean Owen)
  */
 public abstract class GridSampler {
 
-  private static final String DEFAULT_IMPL_CLASS =
-      "com.google.zxing.qrcode.detector.DefaultGridSampler";
-
-  private static String gridSamplerClassName = DEFAULT_IMPL_CLASS;
   private static GridSampler gridSampler;
 
-  public static void setGridSamplerClassName(String className) {
-    if (className == null) {
+  /**
+   * Sets the implementation of {@link GridSampler} used by the library. One global
+   * instance is stored, which may sound problematic. But, the implementation provided
+   * ought to be appropriate for the entire platform, and all uses of this library
+   * in the whole lifetime of the JVM. For instance, an Android activity can swap in
+   * an implementation that takes advantage of native platform libraries.
+   * 
+   * @param newGridSampler
+   */
+  public static void setGridSampler(GridSampler newGridSampler) {
+    if (newGridSampler == null) {
       throw new IllegalArgumentException();
     }
-    gridSamplerClassName = className;
+    gridSampler = newGridSampler;
   }
 
+  /**
+   * @return the current implementation of {@link GridSampler}
+   */
   public static GridSampler getInstance() {
     if (gridSampler == null) {
-      try {
-        Class gridSamplerClass = Class.forName(gridSamplerClassName);
-        gridSampler = (GridSampler) gridSamplerClass.newInstance();
-      } catch (ClassNotFoundException cnfe) {
-        throw new RuntimeException(cnfe.toString());
-      } catch (IllegalAccessException iae) {
-        throw new RuntimeException(iae.toString());
-      } catch (InstantiationException ie) {
-        throw new RuntimeException(ie.toString());
-      }
+      gridSampler = new DefaultGridSampler();
     }
     return gridSampler;
   }
 
+  /**
+   * <p>Given an image, locations of a QR Code's finder patterns and bottom-right alignment pattern,
+   * and the presumed dimension in modules of the QR Code, implemntations of this method extract
+   * the QR Code from the image by sampling the points in the image which should correspond to the
+   * modules of the QR Code.</p>
+   *
+   * @param image image to sample
+   * @param topLeft top-left finder pattern location
+   * @param topRight top-right finder pattern location
+   * @param bottomLeft bottom-left finder pattern location
+   * @param alignmentPattern bottom-right alignment pattern location
+   * @param dimension dimension of QR Code
+   * @return {@link BitMatrix} representing QR Code's modules
+   * @throws ReaderException if QR Code cannot be reasonably sampled -- for example if the location
+   * of the finder patterns imply a transformation that would require sampling off the image
+   */
   protected abstract BitMatrix sampleGrid(MonochromeBitmapSource image,
                                           FinderPattern topLeft,
                                           FinderPattern topRight,
@@ -71,17 +86,42 @@ public abstract class GridSampler {
                                           AlignmentPattern alignmentPattern,
                                           int dimension) throws ReaderException;
 
-  protected static void checkEndpoint(MonochromeBitmapSource image, float[] points)
-      throws ReaderException {
-    int x = (int) points[0];
-    int y = (int) points[1];
-    if (x < 0 || x >= image.getWidth() || y < 0 || y >= image.getHeight()) {
+  /**
+   * <p>Checks a set of points that have been transformed to sample points on an image against
+   * the image's dimensions to see if the endpoints are even within the image.
+   * This method actually only checks the endpoints since the points are assumed to lie
+   * on a line.</p>
+   *
+   * <p>This method will actually "nudge" the endpoints back onto the image if they are found to be barely
+   * (less than 1 pixel) off the image. This accounts for imperfect detection of finder patterns in an image
+   * where the QR Code runs all the way to the image border.</p>
+   *
+   * @param image image into which the points should map
+   * @param points actual points in x1,y1,...,xn,yn form
+   * @throws ReaderException if an endpoint is lies outside the image boundaries
+   */
+  protected static void checkEndpoint(MonochromeBitmapSource image, float[] points) throws ReaderException {
+    int width = image.getWidth();
+    int height = image.getHeight();
+    checkOneEndpoint(points, (int) points[0], (int) points[1], width, height);
+    checkOneEndpoint(points, (int) points[points.length - 2], (int) points[points.length - 1], width, height);
+  }
+
+  private static void checkOneEndpoint(float[] points, int x, int y, int width, int height) throws ReaderException {
+    if (x < -1 || x > width || y < -1 || y > height) {
       throw new ReaderException("Transformed point out of bounds at " + x + ',' + y);
     }
-    x = (int) points[points.length - 2];
-    y = (int) points[points.length - 1];
-    if (x < 0 || x >= image.getWidth() || y < 0 || y >= image.getHeight()) {
-      throw new ReaderException("Transformed point out of bounds at " + x + ',' + y);
+    if (x == -1) {
+      points[0] = 0.0f;
+    }
+    if (y == -1) {
+      points[1] = 0.0f;
+    }
+    if (x == width) {
+      points[0] = width - 1;
+    }
+    if (y == height) {
+      points[1] = height - 1;
     }
   }