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