Draft of 'thinking' visualization for barcode scanning. Works for 1D and QR codes.

git-svn-id: https://zxing.googlecode.com/svn/trunk@1125 59b500cc-1b3d-0410-9834-0bbf25fbcc57
This commit is contained in:
srowen 2009-11-23 16:42:31 +00:00
parent aee98771a2
commit d1973dce0a
17 changed files with 240 additions and 70 deletions

View file

@ -19,6 +19,7 @@
<color name="encode_view">#ffffffff</color>
<color name="help_button_view">#ffcccccc</color>
<color name="help_view">#ff404040</color>
<color name="possible_result_points">#c0ffff00</color>
<color name="result_image_border">#ffffffff</color>
<color name="result_minor_text">#ffc0c0c0</color>
<color name="result_points">#c000ff00</color>

View file

@ -125,6 +125,10 @@ public final class CaptureActivity extends Activity implements SurfaceHolder.Cal
}
};
ViewfinderView getViewfinderView() {
return viewfinderView;
}
public Handler getHandler() {
return handler;
}

View file

@ -42,10 +42,12 @@ public final class CaptureActivityHandler extends Handler {
DONE
}
CaptureActivityHandler(CaptureActivity activity, String decodeMode,
boolean beginScanning) {
CaptureActivityHandler(CaptureActivity activity,
String decodeMode,
boolean beginScanning) {
this.activity = activity;
decodeThread = new DecodeThread(activity, decodeMode);
decodeThread = new DecodeThread(activity, decodeMode,
new ViewfinderResultPointCallback(activity.getViewfinderView()));
decodeThread.start();
state = State.SUCCESS;

View file

@ -22,6 +22,7 @@ import com.google.zxing.DecodeHintType;
import com.google.zxing.MultiFormatReader;
import com.google.zxing.ReaderException;
import com.google.zxing.Result;
import com.google.zxing.ResultPointCallback;
import com.google.zxing.common.GlobalHistogramBinarizer;
import android.content.SharedPreferences;
@ -47,10 +48,12 @@ final class DecodeThread extends Thread {
private Handler handler;
private final CaptureActivity activity;
private final MultiFormatReader multiFormatReader;
private final ResultPointCallback resultPointCallback;
DecodeThread(CaptureActivity activity, String mode) {
DecodeThread(CaptureActivity activity, String mode, ResultPointCallback resultPointCallback) {
this.activity = activity;
multiFormatReader = new MultiFormatReader();
this.resultPointCallback = resultPointCallback;
// The prefs can't change while the thread is running, so pick them up once here.
if (mode == null || mode.length() == 0) {
@ -101,39 +104,27 @@ final class DecodeThread extends Thread {
}
private void setDecodeProductMode() {
Hashtable<DecodeHintType, Object> hints = new Hashtable<DecodeHintType, Object>(3);
Vector<BarcodeFormat> vector = new Vector<BarcodeFormat>(4);
vector.addElement(BarcodeFormat.UPC_A);
vector.addElement(BarcodeFormat.UPC_E);
vector.addElement(BarcodeFormat.EAN_13);
vector.addElement(BarcodeFormat.EAN_8);
hints.put(DecodeHintType.POSSIBLE_FORMATS, vector);
multiFormatReader.setHints(hints);
doSetDecodeMode(BarcodeFormat.UPC_A,
BarcodeFormat.UPC_E,
BarcodeFormat.EAN_13,
BarcodeFormat.EAN_8);
}
/**
* Select the 1D formats we want this client to decode by hand.
*/
private void setDecode1DMode() {
Hashtable<DecodeHintType, Object> hints = new Hashtable<DecodeHintType, Object>(3);
Vector<BarcodeFormat> vector = new Vector<BarcodeFormat>(7);
vector.addElement(BarcodeFormat.UPC_A);
vector.addElement(BarcodeFormat.UPC_E);
vector.addElement(BarcodeFormat.EAN_13);
vector.addElement(BarcodeFormat.EAN_8);
vector.addElement(BarcodeFormat.CODE_39);
vector.addElement(BarcodeFormat.CODE_128);
vector.addElement(BarcodeFormat.ITF);
hints.put(DecodeHintType.POSSIBLE_FORMATS, vector);
multiFormatReader.setHints(hints);
doSetDecodeMode(BarcodeFormat.UPC_A,
BarcodeFormat.UPC_E,
BarcodeFormat.EAN_13,
BarcodeFormat.EAN_8,
BarcodeFormat.CODE_39,
BarcodeFormat.CODE_128,
BarcodeFormat.ITF);
}
private void setDecodeQRMode() {
Hashtable<DecodeHintType, Object> hints = new Hashtable<DecodeHintType, Object>(3);
Vector<BarcodeFormat> vector = new Vector<BarcodeFormat>(1);
vector.addElement(BarcodeFormat.QR_CODE);
hints.put(DecodeHintType.POSSIBLE_FORMATS, vector);
multiFormatReader.setHints(hints);
doSetDecodeMode(BarcodeFormat.QR_CODE);
}
/**
@ -141,17 +132,24 @@ final class DecodeThread extends Thread {
* explicitly set which formats are available.
*/
private void setDecodeAllMode() {
doSetDecodeMode(BarcodeFormat.UPC_A,
BarcodeFormat.UPC_E,
BarcodeFormat.EAN_13,
BarcodeFormat.EAN_8,
BarcodeFormat.CODE_39,
BarcodeFormat.CODE_128,
BarcodeFormat.ITF,
BarcodeFormat.QR_CODE);
}
private void doSetDecodeMode(BarcodeFormat... formats) {
Hashtable<DecodeHintType, Object> hints = new Hashtable<DecodeHintType, Object>(3);
Vector<BarcodeFormat> vector = new Vector<BarcodeFormat>(8);
vector.addElement(BarcodeFormat.UPC_A);
vector.addElement(BarcodeFormat.UPC_E);
vector.addElement(BarcodeFormat.EAN_13);
vector.addElement(BarcodeFormat.EAN_8);
vector.addElement(BarcodeFormat.CODE_39);
vector.addElement(BarcodeFormat.CODE_128);
vector.addElement(BarcodeFormat.ITF);
vector.addElement(BarcodeFormat.QR_CODE);
Vector<BarcodeFormat> vector = new Vector<BarcodeFormat>(formats.length);
for (BarcodeFormat format : formats) {
vector.addElement(format);
}
hints.put(DecodeHintType.POSSIBLE_FORMATS, vector);
hints.put(DecodeHintType.NEED_RESULT_POINT_CALLBACK, resultPointCallback);
multiFormatReader.setHints(hints);
}

View file

@ -0,0 +1,34 @@
/*
* Copyright (C) 2009 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.client.android;
import com.google.zxing.ResultPoint;
import com.google.zxing.ResultPointCallback;
final class ViewfinderResultPointCallback implements ResultPointCallback {
private final ViewfinderView viewfinderView;
ViewfinderResultPointCallback(ViewfinderView viewfinderView) {
this.viewfinderView = viewfinderView;
}
public void foundPossibleResultPoint(ResultPoint point) {
viewfinderView.addPossibleResultPoint(point);
}
}

View file

@ -24,6 +24,10 @@ import android.graphics.Paint;
import android.graphics.Rect;
import android.util.AttributeSet;
import android.view.View;
import com.google.zxing.ResultPoint;
import java.util.Collection;
import java.util.HashSet;
/**
* This view is overlaid on top of the camera preview. It adds the viewfinder rectangle and partial
@ -34,15 +38,18 @@ import android.view.View;
public final class ViewfinderView extends View {
private static final int[] SCANNER_ALPHA = {0, 64, 128, 192, 255, 192, 128, 64};
private static final long ANIMATION_DELAY = 100L;
private static final int OPAQUE = 0xFF;
private final Paint paint;
private final Rect box;
private Bitmap resultBitmap;
private final int maskColor;
private final int resultColor;
private final int frameColor;
private final int laserColor;
private final int resultPointColor;
private int scannerAlpha;
private Collection<ResultPoint> possibleResultPoints;
private Collection<ResultPoint> lastPossibleResultPoints;
// This constructor is used when the class is built from an XML resource.
public ViewfinderView(Context context, AttributeSet attrs) {
@ -50,13 +57,14 @@ public final class ViewfinderView extends View {
// Initialize these once for performance rather than calling them every time in onDraw().
paint = new Paint();
box = new Rect();
Resources resources = getResources();
maskColor = resources.getColor(R.color.viewfinder_mask);
resultColor = resources.getColor(R.color.result_view);
frameColor = resources.getColor(R.color.viewfinder_frame);
laserColor = resources.getColor(R.color.viewfinder_laser);
resultPointColor = resources.getColor(R.color.possible_result_points);
scannerAlpha = 0;
possibleResultPoints = new HashSet<ResultPoint>(5);
}
@Override
@ -70,42 +78,55 @@ public final class ViewfinderView extends View {
// Draw the exterior (i.e. outside the framing rect) darkened
paint.setColor(resultBitmap != null ? resultColor : maskColor);
box.set(0, 0, width, frame.top);
canvas.drawRect(box, paint);
box.set(0, frame.top, frame.left, frame.bottom + 1);
canvas.drawRect(box, paint);
box.set(frame.right + 1, frame.top, width, frame.bottom + 1);
canvas.drawRect(box, paint);
box.set(0, frame.bottom + 1, width, height);
canvas.drawRect(box, paint);
canvas.drawRect(0, 0, width, frame.top, paint);
canvas.drawRect(0, frame.top, frame.left, frame.bottom + 1, paint);
canvas.drawRect(frame.right + 1, frame.top, width, frame.bottom + 1, paint);
canvas.drawRect(0, frame.bottom + 1, width, height, paint);
if (resultBitmap != null) {
// Draw the opaque result bitmap over the scanning rectangle
paint.setAlpha(255);
paint.setAlpha(OPAQUE);
canvas.drawBitmap(resultBitmap, frame.left, frame.top, paint);
} else {
// Draw a two pixel solid black border inside the framing rect
paint.setColor(frameColor);
box.set(frame.left, frame.top, frame.right + 1, frame.top + 2);
canvas.drawRect(box, paint);
box.set(frame.left, frame.top + 2, frame.left + 2, frame.bottom - 1);
canvas.drawRect(box, paint);
box.set(frame.right - 1, frame.top, frame.right + 1, frame.bottom - 1);
canvas.drawRect(box, paint);
box.set(frame.left, frame.bottom - 1, frame.right + 1, frame.bottom + 1);
canvas.drawRect(box, paint);
canvas.drawRect(frame.left, frame.top, frame.right + 1, frame.top + 2, paint);
canvas.drawRect(frame.left, frame.top + 2, frame.left + 2, frame.bottom - 1, paint);
canvas.drawRect(frame.right - 1, frame.top, frame.right + 1, frame.bottom - 1, paint);
canvas.drawRect(frame.left, frame.bottom - 1, frame.right + 1, frame.bottom + 1, paint);
// Draw a red "laser scanner" line through the middle to show decoding is active
paint.setColor(laserColor);
paint.setAlpha(SCANNER_ALPHA[scannerAlpha]);
scannerAlpha = (scannerAlpha + 1) % SCANNER_ALPHA.length;
int middle = frame.height() / 2 + frame.top;
box.set(frame.left + 2, middle - 1, frame.right - 1, middle + 2);
canvas.drawRect(box, paint);
canvas.drawRect(frame.left + 2, middle - 1, frame.right - 1, middle + 2, paint);
Collection<ResultPoint> currentPossible = possibleResultPoints;
Collection<ResultPoint> currentLast = lastPossibleResultPoints;
if (currentPossible.isEmpty()) {
lastPossibleResultPoints = null;
} else {
possibleResultPoints = new HashSet<ResultPoint>(5);
lastPossibleResultPoints = currentPossible;
paint.setAlpha(OPAQUE);
paint.setColor(resultPointColor);
for (ResultPoint point : currentPossible) {
canvas.drawCircle(frame.left + point.getX(), frame.top + point.getY(), 6.0f, paint);
}
}
if (currentLast != null) {
paint.setAlpha(OPAQUE / 2);
paint.setColor(resultPointColor);
for (ResultPoint point : currentLast) {
canvas.drawCircle(frame.left + point.getX(), frame.top + point.getY(), 3.0f, paint);
}
}
// Request another update at the animation interval, but only repaint the laser line,
// not the entire viewfinder mask.
postInvalidateDelayed(ANIMATION_DELAY, box.left, box.top, box.right, box.bottom);
postInvalidateDelayed(ANIMATION_DELAY, frame.left, frame.top, frame.right, frame.bottom);
}
}
@ -123,4 +144,9 @@ public final class ViewfinderView extends View {
resultBitmap = barcode;
invalidate();
}
public void addPossibleResultPoint(ResultPoint point) {
possibleResultPoints.add(point);
}
}

View file

@ -62,6 +62,12 @@ public final class DecodeHintType {
*/
public static final DecodeHintType ASSUME_CODE_39_CHECK_DIGIT = new DecodeHintType();
/**
* The caller needs to be notified via callback when a possible {@link ResultPoint}
* is found. Maps to a {@link ResultPointCallback}.
*/
public static final DecodeHintType NEED_RESULT_POINT_CALLBACK = new DecodeHintType();
private DecodeHintType() {
}

View file

@ -0,0 +1,29 @@
/*
* Copyright 2009 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;
/**
* Callback which is invoked when a possible result point (significant
* point in the barcode image such as a corner) is found.
*
* @see DecodeHintType#NEED_RESULT_POINT_CALLBACK
*/
public interface ResultPointCallback {
void foundPossibleResultPoint(ResultPoint point);
}

View file

@ -19,6 +19,7 @@ package com.google.zxing.multi.qrcode.detector;
import com.google.zxing.DecodeHintType;
import com.google.zxing.ReaderException;
import com.google.zxing.ResultPoint;
import com.google.zxing.ResultPointCallback;
import com.google.zxing.common.Collections;
import com.google.zxing.common.Comparator;
import com.google.zxing.common.BitMatrix;
@ -90,6 +91,10 @@ final class MultiFinderPatternFinder extends FinderPatternFinder {
super(image);
}
MultiFinderPatternFinder(BitMatrix image, ResultPointCallback resultPointCallback) {
super(image, resultPointCallback);
}
/**
* @return the 3 best {@link FinderPattern}s from our list of candidates. The "best" are
* those that have been detected at least {@link #CENTER_QUORUM} times, and whose module

View file

@ -118,6 +118,14 @@ public abstract class AbstractOneDReader implements OneDReader {
for (int attempt = 0; attempt < 2; attempt++) {
if (attempt == 1) { // trying again?
row.reverse(); // reverse the row and continue
// This means we will only ever draw result points *once* in the life of this method
// since we want to avoid drawing the wrong points after flipping the row, and,
// don't want to clutter with noise from every single row scan -- just the scans
// that start on the center line.
if (hints != null && hints.containsKey(DecodeHintType.NEED_RESULT_POINT_CALLBACK)) {
hints = (Hashtable) hints.clone();
hints.remove(DecodeHintType.NEED_RESULT_POINT_CALLBACK);
}
}
try {
// Look for a barcode

View file

@ -17,9 +17,11 @@
package com.google.zxing.oned;
import com.google.zxing.BarcodeFormat;
import com.google.zxing.DecodeHintType;
import com.google.zxing.ReaderException;
import com.google.zxing.Result;
import com.google.zxing.ResultPoint;
import com.google.zxing.ResultPointCallback;
import com.google.zxing.common.BitArray;
import java.util.Hashtable;
@ -110,16 +112,40 @@ public abstract class AbstractUPCEANReader extends AbstractOneDReader implements
public final Result decodeRow(int rowNumber, BitArray row, Hashtable hints)
throws ReaderException {
return decodeRow(rowNumber, row, findStartGuardPattern(row));
return decodeRow(rowNumber, row, findStartGuardPattern(row), hints);
}
public final Result decodeRow(int rowNumber, BitArray row, int[] startGuardRange)
public final Result decodeRow(int rowNumber, BitArray row, int[] startGuardRange, Hashtable hints)
throws ReaderException {
ResultPointCallback resultPointCallback = hints == null ? null :
(ResultPointCallback) hints.get(DecodeHintType.NEED_RESULT_POINT_CALLBACK);
if (resultPointCallback != null) {
resultPointCallback.foundPossibleResultPoint(new ResultPoint(
(startGuardRange[0] + startGuardRange[1]) / 2.0f, rowNumber
));
}
StringBuffer result = decodeRowStringBuffer;
result.setLength(0);
int endStart = decodeMiddle(row, startGuardRange, result);
if (resultPointCallback != null) {
resultPointCallback.foundPossibleResultPoint(new ResultPoint(
endStart, rowNumber
));
}
int[] endRange = decodeEnd(row, endStart);
if (resultPointCallback != null) {
resultPointCallback.foundPossibleResultPoint(new ResultPoint(
(endRange[0] + endRange[1]) / 2.0f, rowNumber
));
}
// Make sure there is a quiet zone at least as big as the end pattern after the barcode. The
// spec might want more whitespace, but in practice this is the maximum we can count on.
int end = endRange[1];

View file

@ -68,7 +68,7 @@ public final class MultiFormatUPCEANReader extends AbstractOneDReader {
UPCEANReader reader = (UPCEANReader) readers.elementAt(i);
Result result;
try {
result = reader.decodeRow(rowNumber, row, startGuardPattern);
result = reader.decodeRow(rowNumber, row, startGuardPattern, hints);
} catch (ReaderException re) {
continue;
}

View file

@ -34,8 +34,9 @@ public final class UPCAReader implements UPCEANReader {
private final UPCEANReader ean13Reader = new EAN13Reader();
public Result decodeRow(int rowNumber, BitArray row, int[] startGuardRange) throws ReaderException {
return maybeReturnResult(ean13Reader.decodeRow(rowNumber, row, startGuardRange));
public Result decodeRow(int rowNumber, BitArray row, int[] startGuardRange, Hashtable hints)
throws ReaderException {
return maybeReturnResult(ean13Reader.decodeRow(rowNumber, row, startGuardRange, hints));
}
public Result decodeRow(int rowNumber, BitArray row, Hashtable hints) throws ReaderException {

View file

@ -20,6 +20,8 @@ import com.google.zxing.ReaderException;
import com.google.zxing.Result;
import com.google.zxing.common.BitArray;
import java.util.Hashtable;
/**
* <p>This interfaces captures additional functionality that readers of
* UPC/EAN family of barcodes should expose.</p>
@ -33,6 +35,7 @@ public interface UPCEANReader extends OneDReader {
* allows caller to inform method about where the UPC/EAN start pattern is
* found. This allows this to be computed once and reused across many implementations.</p>
*/
Result decodeRow(int rowNumber, BitArray row, int[] startGuardRange) throws ReaderException;
Result decodeRow(int rowNumber, BitArray row, int[] startGuardRange, Hashtable hints)
throws ReaderException;
}

View file

@ -17,6 +17,8 @@
package com.google.zxing.qrcode.detector;
import com.google.zxing.ReaderException;
import com.google.zxing.ResultPoint;
import com.google.zxing.ResultPointCallback;
import com.google.zxing.common.BitMatrix;
import java.util.Vector;
@ -45,6 +47,7 @@ final class AlignmentPatternFinder {
private final int height;
private final float moduleSize;
private final int[] crossCheckStateCount;
private final ResultPointCallback resultPointCallback;
/**
* <p>Creates a finder that will look in a portion of the whole image.</p>
@ -61,7 +64,8 @@ final class AlignmentPatternFinder {
int startY,
int width,
int height,
float moduleSize) {
float moduleSize,
ResultPointCallback resultPointCallback) {
this.image = image;
this.possibleCenters = new Vector(5);
this.startX = startX;
@ -70,6 +74,7 @@ final class AlignmentPatternFinder {
this.height = height;
this.moduleSize = moduleSize;
this.crossCheckStateCount = new int[3];
this.resultPointCallback = resultPointCallback;
}
/**
@ -262,7 +267,11 @@ final class AlignmentPatternFinder {
}
}
// Hadn't found this before; save it
possibleCenters.addElement(new AlignmentPattern(centerJ, centerI, estimatedModuleSize));
ResultPoint point = new AlignmentPattern(centerJ, centerI, estimatedModuleSize);
possibleCenters.addElement(point);
if (resultPointCallback != null) {
resultPointCallback.foundPossibleResultPoint(point);
}
}
return null;
}

View file

@ -16,8 +16,10 @@
package com.google.zxing.qrcode.detector;
import com.google.zxing.DecodeHintType;
import com.google.zxing.ReaderException;
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;
@ -34,6 +36,7 @@ import java.util.Hashtable;
public class Detector {
private final BitMatrix image;
private ResultPointCallback resultPointCallback;
public Detector(BitMatrix image) {
this.image = image;
@ -62,7 +65,10 @@ public class Detector {
*/
public DetectorResult detect(Hashtable hints) throws ReaderException {
FinderPatternFinder finder = new FinderPatternFinder(image);
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);
@ -347,7 +353,8 @@ public class Detector {
alignmentAreaTopY,
alignmentAreaRightX - alignmentAreaLeftX,
alignmentAreaBottomY - alignmentAreaTopY,
overallEstModuleSize);
overallEstModuleSize,
resultPointCallback);
return alignmentFinder.find();
}

View file

@ -19,6 +19,7 @@ package com.google.zxing.qrcode.detector;
import com.google.zxing.DecodeHintType;
import com.google.zxing.ReaderException;
import com.google.zxing.ResultPoint;
import com.google.zxing.ResultPointCallback;
import com.google.zxing.common.Collections;
import com.google.zxing.common.Comparator;
import com.google.zxing.common.BitMatrix;
@ -45,6 +46,7 @@ public class FinderPatternFinder {
private final Vector possibleCenters;
private boolean hasSkipped;
private final int[] crossCheckStateCount;
private final ResultPointCallback resultPointCallback;
/**
* <p>Creates a finder that will search the image for three finder patterns.</p>
@ -52,9 +54,14 @@ public class FinderPatternFinder {
* @param image image to search
*/
public FinderPatternFinder(BitMatrix image) {
this(image, null);
}
public FinderPatternFinder(BitMatrix image, ResultPointCallback resultPointCallback) {
this.image = image;
this.possibleCenters = new Vector();
this.crossCheckStateCount = new int[5];
this.resultPointCallback = resultPointCallback;
}
protected BitMatrix getImage() {
@ -401,7 +408,11 @@ public class FinderPatternFinder {
}
}
if (!found) {
possibleCenters.addElement(new FinderPattern(centerJ, centerI, estimatedModuleSize));
ResultPoint point = new FinderPattern(centerJ, centerI, estimatedModuleSize);
possibleCenters.addElement(point);
if (resultPointCallback != null) {
resultPointCallback.foundPossibleResultPoint(point);
}
}
return true;
}