Merged revisions 321,327,330,332,334,342-343,352-353,355-358,361-363,365,372 via...
[zxing.git] / android / src / com / google / zxing / client / android / CameraManager.java
1 /*
2  * Copyright (C) 2008 Google Inc.
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  *      http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */
16
17 package com.google.zxing.client.android;
18
19 import android.content.Context;
20 import android.graphics.Bitmap;
21 import android.graphics.BitmapFactory;
22 import android.graphics.Canvas;
23 import android.graphics.Point;
24 import android.graphics.Rect;
25 import android.hardware.CameraDevice;
26 import android.util.Log;
27 import android.view.Display;
28 import android.view.WindowManager;
29 import com.google.zxing.ResultPoint;
30 import com.tomgibara.android.camera.BitmapCamera;
31 import com.tomgibara.android.camera.CameraSource;
32
33 /**
34  * This object wraps the CameraDevice and expects to be the only one talking to it. The
35  * implementation encapsulates the steps needed to take preview-sized images and well as high
36  * resolution stills.
37  *
38  * @author dswitkin@google.com (Daniel Switkin)
39  */
40 final class CameraManager {
41
42   private static final String TAG = "CameraManager";
43
44   private final Context context;
45   private Point cameraResolution;
46   private Point stillResolution;
47   private Point previewResolution;
48   private int stillMultiplier;
49   private Point screenResolution;
50   private Rect framingRect;
51   private Bitmap bitmap;
52   // TODO switch back to CameraDevice later
53   // private CameraDevice camera;
54   private CameraSource cameraSource;
55   // end TODO
56   private final CameraDevice.CaptureParams params;
57   private boolean previewMode;
58   private boolean usePreviewForDecode;
59
60   CameraManager(Context context) {
61     this.context = context;
62     getScreenResolution();
63     calculateStillResolution();
64     calculatePreviewResolution();
65
66     usePreviewForDecode = true;
67     setUsePreviewForDecode(false);
68
69     // TODO switch back to CameraDevice later
70     // camera = null;
71     Bitmap fakeBitmap = BitmapFactory.decodeFile("/tmp/barcode.jpg");
72     if (fakeBitmap == null) {
73       throw new RuntimeException("/tmp/barcode.jpg was not found");
74     }
75     cameraSource = new BitmapCamera(fakeBitmap, stillResolution.x, stillResolution.y);
76     // end TODO
77
78     params = new CameraDevice.CaptureParams();
79   }
80
81   public void openDriver() {
82 //    TODO switch back to CameraDevice later
83 //    if (camera == null) {
84 //      camera = CameraDevice.open();
85 //      // If we're reopening the camera, we need to reset the capture params.
86 //      previewMode = false;
87 //      setPreviewMode(true);
88 //    }
89 //    end TODO
90   }
91
92   public void closeDriver() {
93     // TODO switch back to CameraDevice later
94     // if (camera != null) {
95     //   camera.close();
96     //   camera = null;
97     // }
98     // end TODO
99   }
100
101   public void capturePreview(Canvas canvas) {
102     setPreviewMode(true);
103     // TODO switch back to CameraDevice later
104     // camera.capture(canvas);
105     cameraSource.capture(canvas);
106     // end TODO
107   }
108
109   public Bitmap captureStill() {
110     setPreviewMode(usePreviewForDecode);
111     Canvas canvas = new Canvas(bitmap);
112     // TODO switch back to CameraDevice later
113     // camera.capture(canvas);
114     cameraSource.capture(canvas);
115     // end TODO
116     return bitmap;
117   }
118
119   /**
120    * This method exists to help us evaluate how to best set up and use the camera.
121    * @param usePreview Decode at preview resolution if true, else use still resolution.
122    */
123   public void setUsePreviewForDecode(boolean usePreview) {
124     if (usePreviewForDecode != usePreview) {
125       usePreviewForDecode = usePreview;
126       if (usePreview) {
127         Log.v(TAG, "Creating bitmap at screen resolution: " + screenResolution.x + "," +
128             screenResolution.y);
129         bitmap = Bitmap.createBitmap(screenResolution.x, screenResolution.y, false);
130       } else {
131         Log.v(TAG, "Creating bitmap at still resolution: " + stillResolution.x + "," +
132             stillResolution.y);
133         bitmap = Bitmap.createBitmap(stillResolution.x, stillResolution.y, false);
134       }
135     }
136   }
137
138   /**
139    * Calculates the framing rect which the UI should draw to show the user where to place the
140    * barcode. The actual captured image should be a bit larger than indicated because they might
141    * frame the shot too tightly. This target helps with alignment as well as forces the user to hold
142    * the device far enough away to ensure the image will be in focus.
143    *
144    * @return The rectangle to draw on screen in window coordinates.
145    */
146   public Rect getFramingRect() {
147     if (framingRect == null) {
148       int size = stillResolution.x * screenResolution.x / previewResolution.x;
149       int leftOffset = (screenResolution.x - size) / 2;
150       int topOffset = (screenResolution.y - size) / 2;
151       framingRect = new Rect(leftOffset, topOffset, leftOffset + size, topOffset + size);
152       Log.v(TAG, "Calculated framing rect: " + framingRect);
153     }
154     return framingRect;
155   }
156
157   /**
158    * Converts the result points from still resolution coordinates to screen coordinates.
159    *
160    * @param points The points returned by the Reader subclass through Result.getResultPoints().
161    * @return An array of Points scaled to the size of the framing rect and offset appropriately
162    *         so they can be drawn in screen coordinates.
163    */
164   public Point[] convertResultPoints(ResultPoint[] points) {
165     Rect frame = getFramingRect();
166     int frameSize = frame.width();
167     int count = points.length;
168     Point[] output = new Point[count];
169     for (int x = 0; x < count; x++) {
170       output[x] = new Point();
171       if (usePreviewForDecode) {
172         output[x].x = (int) (points[x].getX() + 0.5f);
173         output[x].y = (int) (points[x].getY() + 0.5f);
174       } else {
175         output[x].x = frame.left + (int) (points[x].getX() * frameSize / stillResolution.x + 0.5f);
176         output[x].y = frame.top + (int) (points[x].getY() * frameSize / stillResolution.y + 0.5f);
177       }
178     }
179     return output;
180   }
181
182   /**
183    * Images for the live preview are taken at low resolution in RGB. Other code depends
184    * on the ability to call this method for free if the correct mode is already set.
185    *
186    * @param on Setting on true will engage preview mode, setting it false will request still mode.
187    */
188   private void setPreviewMode(boolean on) {
189     if (on != previewMode) {
190       if (on) {
191         params.type = 1; // preview
192         params.srcWidth = previewResolution.x;
193         params.srcHeight = previewResolution.y;
194         params.leftPixel = (cameraResolution.x - params.srcWidth) / 2;
195         params.topPixel = (cameraResolution.y - params.srcHeight) / 2;
196         params.outputWidth = screenResolution.x;
197         params.outputHeight = screenResolution.y;
198         params.dataFormat = 2; // RGB565
199       } else {
200         params.type = 0; // still
201         params.srcWidth = stillResolution.x * stillMultiplier;
202         params.srcHeight = stillResolution.y * stillMultiplier;
203         params.leftPixel = (cameraResolution.x - params.srcWidth) / 2;
204         params.topPixel = (cameraResolution.y - params.srcHeight) / 2;
205         params.outputWidth = stillResolution.x;
206         params.outputHeight = stillResolution.y;
207         params.dataFormat = 2; // RGB565
208       }
209       String captureType = on ? "preview" : "still";
210       Log.v(TAG, "Setting params for " + captureType + ": srcWidth " + params.srcWidth +
211           " srcHeight " + params.srcHeight + " leftPixel " + params.leftPixel + " topPixel " +
212           params.topPixel + " outputWidth " + params.outputWidth + " outputHeight " +
213           params.outputHeight);
214       // TODO switch back to CameraDevice later
215       // camera.setCaptureParams(params);
216       // end TODO
217       previewMode = on;
218     }
219   }
220
221   /**
222    * This method determines how to take the highest quality image (i.e. the one which has the best
223    * chance of being decoded) given the capabilities of the camera. It is a balancing act between
224    * having enough resolution to read UPCs and having few enough pixels to keep the QR Code
225    * processing fast. The result is the dimensions of the rectangle to capture from the center of
226    * the sensor, plus a stillMultiplier which indicates whether we'll ask the driver to downsample
227    * for us. This has the added benefit of keeping the memory footprint of the bitmap as small as
228    * possible.
229    */
230   private void calculateStillResolution() {
231     cameraResolution = getMaximumCameraResolution();
232     int minDimension = (cameraResolution.x < cameraResolution.y) ? cameraResolution.x :
233         cameraResolution.y;
234     int diagonalResolution = (int) Math.sqrt(cameraResolution.x * cameraResolution.x +
235         cameraResolution.y * cameraResolution.y);
236     float diagonalFov = getFieldOfView();
237
238     // Determine the field of view in the smaller dimension, then calculate how large an object
239     // would be at the minimum focus distance.
240     float fov = diagonalFov * minDimension / diagonalResolution;
241     double objectSize = Math.tan(Math.toRadians(fov / 2.0)) * getMinimumFocusDistance() * 2;
242
243     // Let's assume the largest barcode we might photograph at this distance is 3 inches across. By
244     // cropping to this size, we can avoid processing surrounding pixels, which helps with speed and
245     // accuracy. 
246     // TODO(dswitkin): Handle a device with a great macro mode where objectSize < 4 inches.
247     double crop = 3.0 / objectSize;
248     int nativeResolution = (int) (minDimension * crop);
249
250     // The camera driver can only capture images which are a multiple of eight, so it's necessary to
251     // round up.
252     nativeResolution = ((nativeResolution + 7) >> 3) << 3;
253     if (nativeResolution > minDimension) {
254       nativeResolution = minDimension;
255     }
256
257     // There's no point in capturing too much detail, so ask the driver to downsample. I haven't
258     // tried a non-integer multiple, but it seems unlikely to work.
259     double dpi = nativeResolution / objectSize;
260     stillMultiplier = 1;
261     if (dpi > 200) {
262       stillMultiplier = (int) (dpi / 200 + 1);
263     }
264     stillResolution = new Point(nativeResolution, nativeResolution);
265     Log.v(TAG, "FOV " + fov + " objectSize " + objectSize + " crop " + crop + " dpi " + dpi +
266         " nativeResolution " + nativeResolution + " stillMultiplier " + stillMultiplier);
267   }
268
269   /**
270    * The goal of the preview resolution is to show a little context around the framing rectangle
271    * which is the actual captured area in still mode.
272    */
273   private void calculatePreviewResolution() {
274     if (previewResolution == null) {
275       int previewHeight = (int) (stillResolution.x * stillMultiplier * 1.8f);
276       int previewWidth = previewHeight * screenResolution.x / screenResolution.y;
277       previewWidth = ((previewWidth + 7) >> 3) << 3;
278       if (previewWidth > cameraResolution.x) previewWidth = cameraResolution.x;
279       previewHeight = previewWidth * screenResolution.y / screenResolution.x;
280       previewResolution = new Point(previewWidth, previewHeight);
281       Log.v(TAG, "previewWidth " + previewWidth + " previewHeight " + previewHeight);
282     }
283   }
284
285   // FIXME(dswitkin): These three methods have temporary constants until the new Camera API can
286   // provide the real values for the current device.
287   // Temporary: the camera's maximum resolution in pixels.
288   private static Point getMaximumCameraResolution() {
289     return new Point(1280, 1024);
290   }
291
292   // Temporary: the diagonal field of view in degrees.
293   private static float getFieldOfView() {
294     return 60.0f;
295   }
296
297   // Temporary: the minimum focus distance in inches.
298   private static float getMinimumFocusDistance() {
299     return 6.0f;
300   }
301
302   private Point getScreenResolution() {
303     if (screenResolution == null) {
304       WindowManager manager = (WindowManager) context.getSystemService(Context.WINDOW_SERVICE);
305       Display display = manager.getDefaultDisplay();
306       screenResolution = new Point(display.getWidth(), display.getHeight());
307     }
308     return screenResolution;
309   }
310
311 }