Rewrote the Android M3 client to do continuous decoding, which means you no longer have to push the shutter button. Now you can just place the barcode in the viewfinder and it will display the contents as soon as it decodes them. That also means you no longer get "barcode not found" error dialogs which is a big improvement. Also made sure that capturing debug JPEGs uses unique filenames.

git-svn-id: https://zxing.googlecode.com/svn/trunk@352 59b500cc-1b3d-0410-9834-0bbf25fbcc57
This commit is contained in:
dswitkin 2008-04-08 15:25:13 +00:00
parent 1c9b906442
commit e9ed806d30
8 changed files with 439 additions and 294 deletions

View file

@ -15,6 +15,17 @@
limitations under the License.
-->
<resources>
<item type="id" name="decoding_succeeded_message"/>
<item type="id" name="decoding_failed_message"/>
<item type="id" name="preview"/>
<item type="id" name="decode"/>
<item type="id" name="save"/>
<item type="id" name="restart_preview"/>
<item type="id" name="quit"/>
<item type="id" name="set_decode_all_mode"/>
<item type="id" name="set_decode_1D_mode"/>
<item type="id" name="set_decode_QR_mode"/>
<item type="id" name="decode_started"/>
<item type="id" name="decode_succeeded"/>
<item type="id" name="decode_failed"/>
<item type="id" name="save_succeeded"/>
<item type="id" name="save_failed"/>
</resources>

View file

@ -32,8 +32,6 @@ import com.google.zxing.ResultPoint;
import com.google.zxing.client.result.ParsedReaderResult;
import com.google.zxing.client.result.ParsedReaderResultType;
import java.util.Date;
/**
* The barcode reader activity itself. This is loosely based on the CameraPreview
* example included in the Android SDK.
@ -45,10 +43,10 @@ public final class BarcodeReaderCaptureActivity extends Activity {
private CameraManager cameraManager;
private CameraSurfaceView surfaceView;
private WorkerThread workerThread;
private Date decodeStart;
private CameraThread cameraThread;
private static final int ABOUT_ID = Menu.FIRST;
private static final int HELP_ID = Menu.FIRST + 1;
@Override
public void onCreate(Bundle icicle) {
@ -64,9 +62,8 @@ public final class BarcodeReaderCaptureActivity extends Activity {
cameraManager = new CameraManager(getApplication());
surfaceView = new CameraSurfaceView(getApplication(), cameraManager);
setContentView(surfaceView);
workerThread = new WorkerThread(this, surfaceView, cameraManager, messageHandler);
workerThread.requestPreviewLoop();
workerThread.start();
cameraThread = new CameraThread(this, surfaceView, cameraManager, messageHandler);
cameraThread.start();
// TODO re-enable this when issues with Matrix.setPolyToPoly() are resolved
//GridSampler.setGridSampler(new AndroidGraphicsGridSampler());
@ -84,36 +81,38 @@ public final class BarcodeReaderCaptureActivity extends Activity {
protected void onResume() {
super.onResume();
cameraManager.openDriver();
if (workerThread == null) {
workerThread = new WorkerThread(this, surfaceView, cameraManager, messageHandler);
workerThread.requestPreviewLoop();
workerThread.start();
if (cameraThread == null) {
cameraThread = new CameraThread(this, surfaceView, cameraManager, messageHandler);
cameraThread.start();
}
}
@Override
protected void onPause() {
super.onPause();
if (workerThread != null) {
workerThread.requestExitAndWait();
workerThread = null;
if (cameraThread != null) {
Message quit = Message.obtain(cameraThread.handler, R.id.quit);
quit.sendToTarget();
cameraThread = null;
}
cameraManager.closeDriver();
}
@Override
public boolean onKeyDown(int keyCode, KeyEvent event) {
if (keyCode == KeyEvent.KEYCODE_DPAD_CENTER) {
decodeStart = new Date();
workerThread.requestStillAndDecode();
} else if (keyCode == KeyEvent.KEYCODE_Q) {
decodeStart = new Date();
workerThread.requestStillAndDecodeQR();
} else if (keyCode == KeyEvent.KEYCODE_U) {
decodeStart = new Date();
workerThread.requestStillAndDecode1D();
if (keyCode == KeyEvent.KEYCODE_A) {
cameraThread.setDecodeAllMode();
} else if (keyCode == KeyEvent.KEYCODE_C) {
workerThread.requestStillAndSave();
Message save = Message.obtain(cameraThread.handler, R.id.save);
save.sendToTarget();
} else if (keyCode == KeyEvent.KEYCODE_P) {
cameraManager.setUsePreviewForDecode(true);
} else if (keyCode == KeyEvent.KEYCODE_Q) {
cameraThread.setDecodeQRMode();
} else if (keyCode == KeyEvent.KEYCODE_S) {
cameraManager.setUsePreviewForDecode(false);
} else if (keyCode == KeyEvent.KEYCODE_U) {
cameraThread.setDecode1DMode();
} else {
return super.onKeyDown(keyCode, event);
}
@ -124,18 +123,24 @@ public final class BarcodeReaderCaptureActivity extends Activity {
public boolean onCreateOptionsMenu(Menu menu) {
super.onCreateOptionsMenu(menu);
menu.add(0, ABOUT_ID, R.string.menu_about);
menu.add(0, HELP_ID, R.string.menu_help);
return true;
}
@Override
public boolean onOptionsItemSelected(Menu.Item item) {
Context context = getApplication();
switch (item.getId()) {
case ABOUT_ID:
Context context = getApplication();
showAlert(context.getString(R.string.title_about),
context.getString(R.string.msg_about),
context.getString(R.string.button_ok), null, true, null);
break;
case HELP_ID:
showAlert(context.getString(R.string.title_help),
context.getString(R.string.msg_help),
context.getString(R.string.button_ok), null, true, null);
break;
}
return super.onOptionsItemSelected(item);
}
@ -143,29 +148,22 @@ public final class BarcodeReaderCaptureActivity extends Activity {
private final Handler messageHandler = new Handler() {
@Override
public void handleMessage(Message message) {
Date now = new Date();
long duration = now.getTime() - decodeStart.getTime();
switch (message.what) {
case R.id.decoding_succeeded_message:
case R.id.decode_succeeded:
int duration = message.arg1;
handleDecode((Result) message.obj, duration);
break;
case R.id.decoding_failed_message:
Context context = getApplication();
String title = context.getString(R.string.title_no_barcode_detected) +
" (" + duration + " ms)";
showAlert(title, context.getString(R.string.msg_no_barcode_detected),
context.getString(R.string.button_ok), null, true, null);
break;
}
}
};
public void restartPreview() {
workerThread.requestPreviewLoop();
Message restart = Message.obtain(cameraThread.handler, R.id.restart_preview);
restart.sendToTarget();
}
// TODO(dswitkin): These deprecated showAlert calls need to be updated.
private void handleDecode(Result rawResult, long duration) {
private void handleDecode(Result rawResult, int duration) {
ResultPoint[] points = rawResult.getResultPoints();
if (points != null && points.length > 0) {
surfaceView.drawResultPoints(points);

View file

@ -45,17 +45,21 @@ final class CameraManager {
private int stillMultiplier;
private Point screenResolution;
private Rect framingRect;
private final Bitmap bitmap;
private Bitmap bitmap;
private CameraDevice camera;
private final CameraDevice.CaptureParams params;
private boolean previewMode;
private boolean usePreviewForDecode;
CameraManager(Context context) {
this.context = context;
getScreenResolution();
calculateStillResolution();
calculatePreviewResolution();
bitmap = Bitmap.createBitmap(stillResolution.x, stillResolution.y, false);
usePreviewForDecode = true;
setUsePreviewForDecode(false);
camera = CameraDevice.open();
params = new CameraDevice.CaptureParams();
previewMode = false;
@ -84,12 +88,31 @@ final class CameraManager {
}
public Bitmap captureStill() {
setPreviewMode(false);
setPreviewMode(usePreviewForDecode);
Canvas canvas = new Canvas(bitmap);
camera.capture(canvas);
return bitmap;
}
/**
* This method exists to help us evaluate how to best set up and use the camera.
* @param usePreview Decode at preview resolution if true, else use still resolution.
*/
public void setUsePreviewForDecode(boolean usePreview) {
if (usePreviewForDecode != usePreview) {
usePreviewForDecode = usePreview;
if (usePreview) {
Log.v(TAG, "Creating bitmap at screen resolution: " + screenResolution.x + "," +
screenResolution.y);
bitmap = Bitmap.createBitmap(screenResolution.x, screenResolution.y, false);
} else {
Log.v(TAG, "Creating bitmap at still resolution: " + screenResolution.x + "," +
screenResolution.y);
bitmap = Bitmap.createBitmap(stillResolution.x, stillResolution.y, false);
}
}
}
/**
* Calculates the framing rect which the UI should draw to show the user where to place the
* barcode. The actual captured image should be a bit larger than indicated because they might
@ -104,6 +127,7 @@ final class CameraManager {
int leftOffset = (screenResolution.x - size) / 2;
int topOffset = (screenResolution.y - size) / 2;
framingRect = new Rect(leftOffset, topOffset, leftOffset + size, topOffset + size);
Log.v(TAG, "Calculated framing rect: " + framingRect);
}
return framingRect;
}
@ -122,8 +146,13 @@ final class CameraManager {
Point[] output = new Point[count];
for (int x = 0; x < count; x++) {
output[x] = new Point();
output[x].x = frame.left + (int) (points[x].getX() * frameSize / stillResolution.x + 0.5f);
output[x].y = frame.top + (int) (points[x].getY() * frameSize / stillResolution.y + 0.5f);
if (usePreviewForDecode) {
output[x].x = (int) (points[x].getX() + 0.5f);
output[x].y = (int) (points[x].getY() + 0.5f);
} else {
output[x].x = frame.left + (int) (points[x].getX() * frameSize / stillResolution.x + 0.5f);
output[x].y = frame.top + (int) (points[x].getY() * frameSize / stillResolution.y + 0.5f);
}
}
return output;
}

View file

@ -0,0 +1,144 @@
/*
* Copyright (C) 2008 Google Inc.
*
* 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 android.os.Handler;
import android.os.Looper;
import android.os.Message;
/**
* This thread continuously pulls preview frames from the camera and draws them to the screen. It
* also asks the DecodeThread to process as many images as it can keep up with, and coordinates with
* the main thread to display the results.
*
* @author dswitkin@google.com (Daniel Switkin)
*/
final class CameraThread extends Thread {
public Handler handler;
private final CameraSurfaceView surfaceView;
private final Handler activityHandler;
private final DecodeThread decodeThread;
private boolean requestDecode;
private boolean suspendPreview;
CameraThread(BarcodeReaderCaptureActivity activity, CameraSurfaceView surfaceView,
CameraManager cameraManager, Handler activityHandler) {
this.surfaceView = surfaceView;
this.activityHandler = activityHandler;
decodeThread = new DecodeThread(activity, cameraManager);
decodeThread.start();
requestDecode = true;
suspendPreview = false;
}
@Override
public void run() {
Looper.prepare();
handler = new Handler() {
public void handleMessage(Message message) {
switch (message.what) {
case R.id.preview:
if (!suspendPreview) {
surfaceView.capturePreviewAndDraw();
}
break;
case R.id.save:
suspendPreview = true;
Message save = Message.obtain(decodeThread.handler, R.id.save);
save.sendToTarget();
break;
case R.id.restart_preview:
restartPreviewAndDecode();
return;
case R.id.quit:
Message quit = Message.obtain(decodeThread.handler, R.id.quit);
quit.sendToTarget();
Looper.myLooper().quit();
break;
case R.id.decode_started:
// Since the decoder is done with the camera, continue fetching preview frames.
suspendPreview = false;
break;
case R.id.decode_succeeded:
// Message.copyFrom() did not work as expected, hence this workaround.
Message success = Message.obtain(activityHandler, R.id.decode_succeeded, message.obj);
success.arg1 = message.arg1;
success.sendToTarget();
suspendPreview = true;
break;
case R.id.decode_failed:
// We're decoding as fast as possible, so when one fails, start another.
requestDecode = true;
break;
case R.id.save_succeeded:
// TODO: Put up a non-blocking status message
restartPreviewAndDecode();
break;
case R.id.save_failed:
// TODO: Put up a blocking error message
restartPreviewAndDecode();
return;
}
if (requestDecode) {
requestDecode = false;
suspendPreview = true;
Message decode = Message.obtain(decodeThread.handler, R.id.decode);
decode.sendToTarget();
} else if (!suspendPreview) {
Message preview = Message.obtain(handler, R.id.preview);
preview.sendToTarget();
}
}
};
decodeThread.setCameraThreadHandler(handler);
// Start ourselves capturing previews
Message preview = Message.obtain(handler, R.id.preview);
preview.sendToTarget();
Looper.loop();
}
public void setDecodeAllMode() {
Message message = Message.obtain(decodeThread.handler, R.id.set_decode_all_mode);
message.sendToTarget();
}
public void setDecode1DMode() {
Message message = Message.obtain(decodeThread.handler, R.id.set_decode_1D_mode);
message.sendToTarget();
}
public void setDecodeQRMode() {
Message message = Message.obtain(decodeThread.handler, R.id.set_decode_QR_mode);
message.sendToTarget();
}
/**
* Take one preview to update the screen, then do a decode and continue previews.
*/
private void restartPreviewAndDecode() {
requestDecode = true;
suspendPreview = false;
Message preview = Message.obtain(handler, R.id.preview);
preview.sendToTarget();
}
}

View file

@ -0,0 +1,200 @@
/*
* Copyright (C) 2008 Google Inc.
*
* 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 android.app.Application;
import android.graphics.Bitmap;
import android.os.Handler;
import android.os.Looper;
import android.os.Message;
import com.google.zxing.BarcodeFormat;
import com.google.zxing.DecodeHintType;
import com.google.zxing.MonochromeBitmapSource;
import com.google.zxing.MultiFormatReader;
import com.google.zxing.ReaderException;
import com.google.zxing.Result;
import java.io.File;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.OutputStream;
import java.util.Date;
import java.util.Hashtable;
import java.util.Vector;
/**
* This thread does all the heavy lifting of decoding the images. It can also save images to flash
* for debugging purposes.
*
* @author dswitkin@google.com (Daniel Switkin)
*/
final class DecodeThread extends Thread {
public Handler handler;
private final BarcodeReaderCaptureActivity activity;
private final CameraManager cameraManager;
private Hashtable<DecodeHintType, Object> hints;
private Handler cameraThreadHandler;
DecodeThread(BarcodeReaderCaptureActivity activity, CameraManager cameraManager) {
this.activity = activity;
this.cameraManager = cameraManager;
}
@Override
public void run() {
Looper.prepare();
handler = new Handler() {
public void handleMessage(Message message) {
switch (message.what) {
case R.id.decode:
captureAndDecode();
break;
case R.id.save:
captureAndSave();
break;
case R.id.quit:
Looper.myLooper().quit();
break;
case R.id.set_decode_all_mode:
setDecodeAllMode();
break;
case R.id.set_decode_1D_mode:
setDecode1DMode();
break;
case R.id.set_decode_QR_mode:
setDecodeQRMode();
break;
}
}
};
Looper.loop();
}
public void setCameraThreadHandler(Handler cameraThreadHandler) {
this.cameraThreadHandler = cameraThreadHandler;
}
private void setDecodeAllMode() {
hints = null;
}
// TODO: This is fragile in case we add new formats. It would be better to have a new enum
// value which represented all 1D formats.
private void setDecode1DMode() {
hints = new Hashtable<DecodeHintType, Object>(3);
Vector<BarcodeFormat> vector = new Vector<BarcodeFormat>();
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);
hints.put(DecodeHintType.POSSIBLE_FORMATS, vector);
}
private void setDecodeQRMode() {
hints = new Hashtable<DecodeHintType, Object>(3);
Vector<BarcodeFormat> vector = new Vector<BarcodeFormat>();
vector.addElement(BarcodeFormat.QR_CODE);
hints.put(DecodeHintType.POSSIBLE_FORMATS, vector);
}
private void captureAndDecode() {
Date startDate = new Date();
Bitmap bitmap = cameraManager.captureStill();
// Let the CameraThread know it can resume previews while the decoding continues in parallel.
Message restart = Message.obtain(cameraThreadHandler, R.id.decode_started);
restart.sendToTarget();
Result rawResult;
try {
MonochromeBitmapSource source = new RGBMonochromeBitmapSource(bitmap);
rawResult = new MultiFormatReader().decode(source, hints);
} catch (ReaderException e) {
Message failure = Message.obtain(cameraThreadHandler, R.id.decode_failed);
failure.sendToTarget();
return;
}
Date endDate = new Date();
Message success = Message.obtain(cameraThreadHandler, R.id.decode_succeeded, rawResult);
success.arg1 = (int) (endDate.getTime() - startDate.getTime());
success.sendToTarget();
}
/**
* This is a debugging feature used to take photos and save them as JPEGs using the exact camera
* setup as in normal decoding. This is useful for building up a library of test images.
*/
private void captureAndSave() {
Bitmap bitmap = cameraManager.captureStill();
OutputStream outStream = getNewPhotoOutputStream();
if (outStream != null) {
bitmap.compress(Bitmap.CompressFormat.JPEG, 80, outStream);
try {
outStream.close();
} catch (IOException e) {
}
Message success = Message.obtain(cameraThreadHandler, R.id.save_succeeded);
success.sendToTarget();
} else {
Message failure = Message.obtain(cameraThreadHandler, R.id.save_failed);
failure.sendToTarget();
}
}
/**
* We prefer to write to the SD Card because it has more space, and is automatically mounted as a
* drive over USB. If it's not present, fall back to the package's private file area here:
*
* /data/data/com.google.zxing.client.android/files
*
* @return A stream which represents the new file where the photo will be saved.
*/
private OutputStream getNewPhotoOutputStream() {
File sdcard = new File("/sdcard");
if (sdcard.exists()) {
File barcodes = new File(sdcard, "barcodes");
if (!barcodes.exists()) {
if (!barcodes.mkdir()) {
return null;
}
}
String fileName = getNewPhotoName();
try {
return new FileOutputStream(new File(barcodes, fileName));
} catch (FileNotFoundException e) {
}
} else {
Application application = activity.getApplication();
String fileName = getNewPhotoName();
try {
return application.openFileOutput(fileName, 0);
} catch (FileNotFoundException e) {
}
}
return null;
}
private String getNewPhotoName() {
Date now = new Date();
return "capture" + now.getTime() + ".jpg";
}
}

View file

@ -24,10 +24,7 @@ import com.google.zxing.common.BitArray;
import com.google.zxing.common.BlackPointEstimator;
/**
* This object implements MonochromeBitmapSource around an Android Bitmap. Rather than capturing an
* RGB image and calculating the grey value at each pixel, we ask the camera driver for YUV data and
* strip out the luminance channel directly. This should be faster but provides fewer bits, i.e.
* fewer grey levels.
* This object implements MonochromeBitmapSource around an Android Bitmap.
*
* @author dswitkin@google.com (Daniel Switkin)
* @author srowen@google.com (Sean Owen)
@ -61,7 +58,7 @@ final class RGBMonochromeBitmapSource implements MonochromeBitmapSource {
row.clear();
}
int[] pixelRow = new int[getWidth];
image.getPixels(pixelRow, 0, getWidth, startX, y, getWidth, 1);
image.getPixels(pixelRow, 0, getWidth, startX, y, getWidth, 1);
for (int i = 0; i < getWidth; i++) {
if (computeRGBLuminance(pixelRow[i]) < blackPoint) {
row.set(i);
@ -124,6 +121,9 @@ final class RGBMonochromeBitmapSource implements MonochromeBitmapSource {
/**
* An optimized approximation of a more proper conversion from RGB to luminance which
* only uses shifts. See BufferedImageMonochromeBitmapSource for an original version.
*
* @param pixel An ARGB input pixel
* @return An eight bit luminance value
*/
private static int computeRGBLuminance(int pixel) {
// Instead of multiplying by 306, 601, 117, we multiply by 256, 512, 256, so that

View file

@ -1,238 +0,0 @@
/*
* Copyright (C) 2008 Google Inc.
*
* 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 android.app.Application;
import android.graphics.Bitmap;
import android.os.Handler;
import android.os.Message;
import com.google.zxing.BarcodeFormat;
import com.google.zxing.DecodeHintType;
import com.google.zxing.MonochromeBitmapSource;
import com.google.zxing.MultiFormatReader;
import com.google.zxing.ReaderException;
import com.google.zxing.Result;
import java.io.File;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.OutputStream;
import java.util.Hashtable;
import java.util.Vector;
/**
* This thread does all the heavy lifting, both during preview and for the final capture and
* decoding. That leaves the main thread free to handle UI tasks.
*
* @author dswitkin@google.com (Daniel Switkin)
*/
final class WorkerThread extends Thread {
private final BarcodeReaderCaptureActivity activity;
private final CameraSurfaceView surfaceView;
private final CameraManager cameraManager;
private final Handler handler;
private final Object idleLock;
private State state;
private enum State {
IDLE,
PREVIEW_LOOP,
STILL_AND_DECODE,
STILL_AND_DECODE_1D,
STILL_AND_DECODE_QR,
STILL_AND_SAVE,
DONE
}
WorkerThread(BarcodeReaderCaptureActivity activity, CameraSurfaceView surfaceView,
CameraManager cameraManager, Handler handler) {
this.activity = activity;
this.surfaceView = surfaceView;
this.cameraManager = cameraManager;
this.handler = handler;
this.idleLock = new Object();
state = State.IDLE;
}
@Override
public void run() {
while (true) {
switch (state) {
case IDLE:
idle();
break;
case PREVIEW_LOOP:
surfaceView.capturePreviewAndDraw();
break;
case STILL_AND_DECODE:
takeStillAndDecode(null);
break;
case STILL_AND_DECODE_1D: {
Hashtable<DecodeHintType, Object> hints = new Hashtable<DecodeHintType, Object>(3);
// TODO: This is fragile in case we add new formats. It would be better to have a new enum
// value which represented all 1D formats.
Vector vector = new Vector();
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);
hints.put(DecodeHintType.POSSIBLE_FORMATS, vector);
takeStillAndDecode(hints);
break;
}
case STILL_AND_DECODE_QR: {
Hashtable<DecodeHintType, Object> hints = new Hashtable<DecodeHintType, Object>(3);
Vector vector = new Vector();
vector.addElement(BarcodeFormat.QR_CODE);
hints.put(DecodeHintType.POSSIBLE_FORMATS, vector);
takeStillAndDecode(hints);
break;
}
case STILL_AND_SAVE:
takeStillAndSave();
break;
case DONE:
return;
}
}
}
public void requestPreviewLoop() {
state = State.PREVIEW_LOOP;
wakeFromIdle();
}
public void requestStillAndDecode() {
state = State.STILL_AND_DECODE;
wakeFromIdle();
}
public void requestStillAndDecode1D() {
state = State.STILL_AND_DECODE_1D;
wakeFromIdle();
}
public void requestStillAndDecodeQR() {
state = State.STILL_AND_DECODE_QR;
wakeFromIdle();
}
public void requestStillAndSave() {
state = State.STILL_AND_SAVE;
wakeFromIdle();
}
public void requestExitAndWait() {
state = State.DONE;
wakeFromIdle();
try {
join();
} catch (InterruptedException e) {
}
}
private void idle() {
try {
synchronized (idleLock) {
idleLock.wait();
}
} catch (InterruptedException e) {
// Continue
}
}
private void wakeFromIdle() {
synchronized (idleLock) {
idleLock.notifyAll();
}
}
private void takeStillAndDecode(Hashtable<DecodeHintType, Object> hints) {
Bitmap bitmap = cameraManager.captureStill();
Result rawResult;
try {
MonochromeBitmapSource source = new RGBMonochromeBitmapSource(bitmap);
rawResult = new MultiFormatReader().decode(source, hints);
} catch (ReaderException e) {
Message message = Message.obtain(handler, R.id.decoding_failed_message);
message.sendToTarget();
state = State.PREVIEW_LOOP;
return;
}
Message message = Message.obtain(handler, R.id.decoding_succeeded_message, rawResult);
message.sendToTarget();
state = State.IDLE;
}
/**
* This is a debugging feature used to take photos and save them as JPEGs using the exact camera
* setup as in normal decoding. This is useful for building up a library of test images.
*/
private void takeStillAndSave() {
Bitmap bitmap = cameraManager.captureStill();
OutputStream outStream = getNewPhotoOutputStream();
bitmap.compress(Bitmap.CompressFormat.JPEG, 80, outStream);
try {
outStream.close();
} catch (IOException e) {
}
state = State.PREVIEW_LOOP;
}
/**
* We prefer to write to the SD Card because it has more space, and is automatically mounted as a
* drive over USB. If it's not present, fall back to the package's private file area here:
*
* /data/data/com.google.zxing.client.android/files
*
* @return A stream which represents the new file where the photo will be saved.
*/
private OutputStream getNewPhotoOutputStream() {
File sdcard = new File("/sdcard");
if (sdcard.exists()) {
File barcodes = new File(sdcard, "barcodes");
if (!barcodes.exists()) {
if (!barcodes.mkdir()) {
return null;
}
}
String fileName = getNewPhotoName(barcodes.list());
try {
return new FileOutputStream(new File(barcodes, fileName));
} catch (FileNotFoundException e) {
}
} else {
Application application = activity.getApplication();
String fileName = getNewPhotoName(application.fileList());
try {
return application.openFileOutput(fileName, 0);
} catch (FileNotFoundException e) {
}
}
return null;
}
private String getNewPhotoName(String[] listOfFiles) {
int existingFileCount = (listOfFiles != null) ? listOfFiles.length : 0;
return "capture" + existingFileCount + ".jpg";
}
}

View file

@ -20,16 +20,17 @@
<string name="button_ok">OK</string>
<string name="button_yes">Yes</string>
<string name="menu_about">About...</string>
<string name="menu_help">Help...</string>
<string name="msg_about">ZXing Barcode Reader v@VERSION@\nhttp://code.google.com/p/zxing</string>
<string name="msg_no_barcode_detected">Sorry, no barcode was found.</string>
<string name="msg_help">A: Decode all barcodes\nC: Capture and save a JPEG\nP: Use the preview image for decoding\nQ: Decode only QR Codes\nS: Use a still image for decoding\nU: Decode only UPC/1D barcodes</string>
<string name="title_about">About</string>
<string name="title_barcode_detected">Barcode Detected</string>
<string name="title_no_barcode_detected">No Barcode Detected</string>
<string name="title_error">Error</string>
<string name="title_open_url">Open Web Page?</string>
<string name="title_add_contact">Add Contact?</string>
<string name="title_barcode_detected">Barcode Detected</string>
<string name="title_compose_email">Compose E-mail?</string>
<string name="title_lookup_barcode">Look Up Barcode Online?</string>
<string name="title_dial">Dial Number?</string>
<string name="title_error">Error</string>
<string name="title_help">Keyboard Shortcut Help</string>
<string name="title_lookup_barcode">Look Up Barcode Online?</string>
<string name="title_open_url">Open Web Page?</string>
<string name="title_view_maps">View In Google Maps?</string>
</resources>