/*
- * Copyright 2007 Google Inc.
+ * Copyright 2007 ZXing authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
package com.google.zxing.qrcode.detector;
-import com.google.zxing.BlackPointEstimationMethod;
-import com.google.zxing.MonochromeBitmapSource;
-import com.google.zxing.ReaderException;
+import com.google.zxing.DecodeHintType;
+import com.google.zxing.FormatException;
+import com.google.zxing.NotFoundException;
import com.google.zxing.ResultPoint;
+import com.google.zxing.ResultPointCallback;
import com.google.zxing.common.BitMatrix;
+import com.google.zxing.common.DetectorResult;
+import com.google.zxing.common.GridSampler;
+import com.google.zxing.common.PerspectiveTransform;
import com.google.zxing.qrcode.decoder.Version;
+import java.util.Hashtable;
+
/**
* <p>Encapsulates logic that can detect a QR Code in an image, even if the QR Code
* is rotated or skewed, or partially obscured.</p>
*
- * @author srowen@google.com (Sean Owen)
+ * @author Sean Owen
*/
-public final class Detector {
+public class Detector {
- private final MonochromeBitmapSource image;
+ private final BitMatrix image;
+ private ResultPointCallback resultPointCallback;
- public Detector(MonochromeBitmapSource image) {
+ public Detector(BitMatrix image) {
this.image = image;
}
+ protected BitMatrix getImage() {
+ return image;
+ }
+
+ protected ResultPointCallback getResultPointCallback() {
+ return resultPointCallback;
+ }
+
/**
* <p>Detects a QR Code in an image, simply.</p>
*
* @return {@link DetectorResult} encapsulating results of detecting a QR Code
- * @throws ReaderException if no QR Code can be found
+ * @throws NotFoundException if no QR Code can be found
*/
- public DetectorResult detect() throws ReaderException {
+ public DetectorResult detect() throws NotFoundException, FormatException {
+ return detect(null);
+ }
- MonochromeBitmapSource image = this.image;
- if (!BlackPointEstimationMethod.TWO_D_SAMPLING.equals(image.getLastEstimationMethod())) {
- image.estimateBlackPoint(BlackPointEstimationMethod.TWO_D_SAMPLING, 0);
- }
+ /**
+ * <p>Detects a QR Code in an image, simply.</p>
+ *
+ * @param hints optional hints to detector
+ * @return {@link NotFoundException} encapsulating results of detecting a QR Code
+ * @throws NotFoundException if QR Code cannot be found
+ * @throws FormatException if a QR Code cannot be decoded
+ */
+ public DetectorResult detect(Hashtable hints) throws NotFoundException, FormatException {
- FinderPatternFinder finder = new FinderPatternFinder(image);
- FinderPatternInfo info = finder.find();
+ resultPointCallback = hints == null ? null :
+ (ResultPointCallback) hints.get(DecodeHintType.NEED_RESULT_POINT_CALLBACK);
+
+ FinderPatternFinder finder = new FinderPatternFinder(image, resultPointCallback);
+ FinderPatternInfo info = finder.find(hints);
+
+ return processFinderPatternInfo(info);
+ }
+
+ protected DetectorResult processFinderPatternInfo(FinderPatternInfo info)
+ throws NotFoundException, FormatException {
FinderPattern topLeft = info.getTopLeft();
FinderPattern topRight = info.getTopRight();
FinderPattern bottomLeft = info.getBottomLeft();
float moduleSize = calculateModuleSize(topLeft, topRight, bottomLeft);
+ if (moduleSize < 1.0f) {
+ throw NotFoundException.getNotFoundInstance();
+ }
int dimension = computeDimension(topLeft, topRight, bottomLeft, moduleSize);
Version provisionalVersion = Version.getProvisionalVersionForDimension(dimension);
int modulesBetweenFPCenters = provisionalVersion.getDimensionForVersion() - 7;
estAlignmentY,
(float) i);
break;
- } catch (ReaderException re) {
+ } catch (NotFoundException re) {
// try next round
}
}
- if (alignmentPattern == null) {
- throw new ReaderException("Could not find alignment pattern");
- }
-
+ // If we didn't find alignment pattern... well try anyway without it
}
- GridSampler sampler = GridSampler.getInstance();
- BitMatrix bits = sampler.sampleGrid(image, topLeft, topRight, bottomLeft, alignmentPattern, dimension);
+ PerspectiveTransform transform =
+ createTransform(topLeft, topRight, bottomLeft, alignmentPattern, dimension);
+
+ BitMatrix bits = sampleGrid(image, transform, dimension);
ResultPoint[] points;
if (alignmentPattern == null) {
return new DetectorResult(bits, points);
}
+ public PerspectiveTransform createTransform(ResultPoint topLeft,
+ ResultPoint topRight,
+ ResultPoint bottomLeft,
+ ResultPoint alignmentPattern,
+ int dimension) {
+ float dimMinusThree = (float) dimension - 3.5f;
+ float bottomRightX;
+ float bottomRightY;
+ float sourceBottomRightX;
+ float sourceBottomRightY;
+ if (alignmentPattern != null) {
+ bottomRightX = alignmentPattern.getX();
+ bottomRightY = alignmentPattern.getY();
+ sourceBottomRightX = sourceBottomRightY = dimMinusThree - 3.0f;
+ } else {
+ // Don't have an alignment pattern, just make up the bottom-right point
+ bottomRightX = (topRight.getX() - topLeft.getX()) + bottomLeft.getX();
+ bottomRightY = (topRight.getY() - topLeft.getY()) + bottomLeft.getY();
+ sourceBottomRightX = sourceBottomRightY = dimMinusThree;
+ }
+
+ return PerspectiveTransform.quadrilateralToQuadrilateral(
+ 3.5f,
+ 3.5f,
+ dimMinusThree,
+ 3.5f,
+ sourceBottomRightX,
+ sourceBottomRightY,
+ 3.5f,
+ dimMinusThree,
+ topLeft.getX(),
+ topLeft.getY(),
+ topRight.getX(),
+ topRight.getY(),
+ bottomRightX,
+ bottomRightY,
+ bottomLeft.getX(),
+ bottomLeft.getY());
+ }
+
+ private static BitMatrix sampleGrid(BitMatrix image,
+ PerspectiveTransform transform,
+ int dimension) throws NotFoundException {
+
+ GridSampler sampler = GridSampler.getInstance();
+ return sampler.sampleGrid(image, dimension, transform);
+ }
+
/**
* <p>Computes the dimension (number of modules on a size) of the QR Code based on the position
* of the finder patterns and estimated module size.</p>
*/
- private static int computeDimension(ResultPoint topLeft,
- ResultPoint topRight,
- ResultPoint bottomLeft,
- float moduleSize) throws ReaderException {
- int tltrCentersDimension = round(FinderPatternFinder.distance(topLeft, topRight) / moduleSize);
- int tlblCentersDimension = round(FinderPatternFinder.distance(topLeft, bottomLeft) / moduleSize);
+ protected static int computeDimension(ResultPoint topLeft,
+ ResultPoint topRight,
+ ResultPoint bottomLeft,
+ float moduleSize) throws NotFoundException {
+ int tltrCentersDimension = round(ResultPoint.distance(topLeft, topRight) / moduleSize);
+ int tlblCentersDimension = round(ResultPoint.distance(topLeft, bottomLeft) / moduleSize);
int dimension = ((tltrCentersDimension + tlblCentersDimension) >> 1) + 7;
switch (dimension & 0x03) { // mod 4
case 0:
dimension--;
break;
case 3:
- throw new ReaderException("Bad dimension: " + dimension);
+ throw NotFoundException.getNotFoundInstance();
}
return dimension;
}
* <p>Computes an average estimated module size based on estimated derived from the positions
* of the three finder patterns.</p>
*/
- private float calculateModuleSize(ResultPoint topLeft, ResultPoint topRight, ResultPoint bottomLeft) {
+ protected float calculateModuleSize(ResultPoint topLeft,
+ ResultPoint topRight,
+ ResultPoint bottomLeft) {
// Take the average
return (calculateModuleSizeOneWay(topLeft, topRight) +
calculateModuleSizeOneWay(topLeft, bottomLeft)) / 2.0f;
(int) pattern.getX(),
(int) pattern.getY());
if (Float.isNaN(moduleSizeEst1)) {
- return moduleSizeEst2;
+ return moduleSizeEst2 / 7.0f;
}
if (Float.isNaN(moduleSizeEst2)) {
- return moduleSizeEst1;
+ return moduleSizeEst1 / 7.0f;
}
// Average them, and divide by 7 since we've counted the width of 3 black modules,
// and 1 white and 1 black module on either side. Ergo, divide sum by 14.
*/
private float sizeOfBlackWhiteBlackRunBothWays(int fromX, int fromY, int toX, int toY) {
- float result = sizeOfBlackWhiteBlackRun(fromX, fromY, toX, toY);
+ float result = sizeOfBlackWhiteBlackRun(fromX, fromY, toX, toY);
- // Now count other way -- don't run off image though of course
- int otherToX = fromX - (toX - fromX);
- if (otherToX < 0) {
- // "to" should the be the first value not included, so, the first value off
- // the edge is -1
- otherToX = -1;
- } else if (otherToX >= image.getWidth()) {
- otherToX = image.getWidth();
- }
- int otherToY = fromY - (toY - fromY);
- if (otherToY < 0) {
- otherToY = -1;
- } else if (otherToY >= image.getHeight()) {
- otherToY = image.getHeight();
- }
- result += sizeOfBlackWhiteBlackRun(fromX, fromY, otherToX, otherToY);
- return result - 1.0f; // -1 because we counted the middle pixel twice
- }
+ // Now count other way -- don't run off image though of course
+ float scale = 1.0f;
+ int otherToX = fromX - (toX - fromX);
+ if (otherToX < 0) {
+ scale = (float) fromX / (float) (fromX - otherToX);
+ otherToX = 0;
+ } else if (otherToX > image.getWidth()) {
+ scale = (float) (image.getWidth() - fromX) / (float) (otherToX - fromX);
+ otherToX = image.getWidth();
+ }
+ int otherToY = (int) (fromY - (toY - fromY) * scale);
+
+ scale = 1.0f;
+ if (otherToY < 0) {
+ scale = (float) fromY / (float) (fromY - otherToY);
+ otherToY = 0;
+ } else if (otherToY > image.getHeight()) {
+ scale = (float) (image.getHeight() - fromY) / (float) (otherToY - fromY);
+ otherToY = image.getHeight();
+ }
+ otherToX = (int) (fromX + (otherToX - fromX) * scale);
+
+ result += sizeOfBlackWhiteBlackRun(fromX, fromY, otherToX, otherToY);
+ return result;
+ }
/**
* <p>This method traces a line from a point in the image, in the direction towards another point.
int realX = steep ? y : x;
int realY = steep ? x : y;
if (state == 1) { // In white pixels, looking for black
- if (image.isBlack(realX, realY)) {
+ if (image.get(realX, realY)) {
state++;
}
} else {
- if (!image.isBlack(realX, realY)) {
+ if (!image.get(realX, realY)) {
state++;
}
}
if (state == 3) { // Found black, white, black, and stumbled back onto white; done
int diffX = x - fromX;
int diffY = y - fromY;
+ if (xstep < 0) {
+ diffX++;
+ }
return (float) Math.sqrt((double) (diffX * diffX + diffY * diffY));
}
error += dy;
if (error > 0) {
+ if (y == toY) {
+ break;
+ }
y += ystep;
error -= dx;
}
* @param overallEstModuleSize estimated module size so far
* @param estAlignmentX x coordinate of center of area probably containing alignment pattern
* @param estAlignmentY y coordinate of above
- * @param allowanceFactor number of pixels in all directons to search from the center
+ * @param allowanceFactor number of pixels in all directions to search from the center
* @return {@link AlignmentPattern} if found, or null otherwise
- * @throws ReaderException if an unexpected error occurs during detection
+ * @throws NotFoundException if an unexpected error occurs during detection
*/
- private AlignmentPattern findAlignmentInRegion(float overallEstModuleSize,
- int estAlignmentX,
- int estAlignmentY,
- float allowanceFactor)
- throws ReaderException {
+ protected AlignmentPattern findAlignmentInRegion(float overallEstModuleSize,
+ int estAlignmentX,
+ int estAlignmentY,
+ float allowanceFactor)
+ throws NotFoundException {
// Look for an alignment pattern (3 modules in size) around where it
// should be
int allowance = (int) (allowanceFactor * overallEstModuleSize);
int alignmentAreaLeftX = Math.max(0, estAlignmentX - allowance);
int alignmentAreaRightX = Math.min(image.getWidth() - 1, estAlignmentX + allowance);
+ if (alignmentAreaRightX - alignmentAreaLeftX < overallEstModuleSize * 3) {
+ throw NotFoundException.getNotFoundInstance();
+ }
+
int alignmentAreaTopY = Math.max(0, estAlignmentY - allowance);
int alignmentAreaBottomY = Math.min(image.getHeight() - 1, estAlignmentY + allowance);
+ if (alignmentAreaBottomY - alignmentAreaTopY < overallEstModuleSize * 3) {
+ throw NotFoundException.getNotFoundInstance();
+ }
AlignmentPatternFinder alignmentFinder =
new AlignmentPatternFinder(
alignmentAreaTopY,
alignmentAreaRightX - alignmentAreaLeftX,
alignmentAreaBottomY - alignmentAreaTopY,
- overallEstModuleSize);
+ overallEstModuleSize,
+ resultPointCallback);
return alignmentFinder.find();
}
private static int round(float d) {
return (int) (d + 0.5f);
}
-
}