1 /* 2 * Copyright (C) 2008 ZXing authors 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.camera; 18 19 import com.google.zxing.client.android.PlanarYUVLuminanceSource; 20 21 import android.content.Context; 22 import android.content.SharedPreferences; 23 import android.graphics.Point; 24 import android.graphics.Rect; 25 import android.hardware.Camera; 26 import android.os.Handler; 27 import android.preference.PreferenceManager; 28 import android.util.Log; 29 import android.view.SurfaceHolder; 30 31 import java.io.IOException; 32 33 /** 34 * This object wraps the Camera service object and expects to be the only one talking to it. The 35 * implementation encapsulates the steps needed to take preview-sized images, which are used for 36 * both preview and decoding. 37 * 38 * @author dswitkin (at) google.com (Daniel Switkin) 39 */ 40 public final class CameraManager { 41 42 private static final String TAG = CameraManager.class.getSimpleName(); 43 44 private static final int MIN_FRAME_WIDTH = 240; 45 private static final int MIN_FRAME_HEIGHT = 240; 46 private static final int MAX_FRAME_WIDTH = 600; 47 private static final int MAX_FRAME_HEIGHT = 400; 48 49 private final Context context; 50 private final CameraConfigurationManager configManager; 51 private Camera camera; 52 private Rect framingRect; 53 private Rect framingRectInPreview; 54 private boolean initialized; 55 private boolean previewing; 56 private boolean reverseImage; 57 private int requestedFramingRectWidth; 58 private int requestedFramingRectHeight; 59 /** 60 * Preview frames are delivered here, which we pass on to the registered handler. Make sure to 61 * clear the handler so it will only receive one message. 62 */ 63 private final PreviewCallback previewCallback; 64 /** Autofocus callbacks arrive here, and are dispatched to the Handler which requested them. */ 65 private final AutoFocusCallback autoFocusCallback; 66 67 public CameraManager(Context context) { 68 this.context = context; 69 this.configManager = new CameraConfigurationManager(context); 70 previewCallback = new PreviewCallback(configManager); 71 autoFocusCallback = new AutoFocusCallback(); 72 } 73 74 /** 75 * Opens the camera driver and initializes the hardware parameters. 76 * 77 * @param holder The surface object which the camera will draw preview frames into. 78 * @throws IOException Indicates the camera driver failed to open. 79 */ 80 public void openDriver(SurfaceHolder holder) throws IOException { 81 Camera theCamera = camera; 82 if (theCamera == null) { 83 theCamera = Camera.open(); 84 if (theCamera == null) { 85 throw new IOException(); 86 } 87 camera = theCamera; 88 } 89 theCamera.setPreviewDisplay(holder); 90 91 if (!initialized) { 92 initialized = true; 93 configManager.initFromCameraParameters(theCamera); 94 if (requestedFramingRectWidth > 0 && requestedFramingRectHeight > 0) { 95 setManualFramingRect(requestedFramingRectWidth, requestedFramingRectHeight); 96 requestedFramingRectWidth = 0; 97 requestedFramingRectHeight = 0; 98 } 99 } 100 configManager.setDesiredCameraParameters(theCamera); 101 102 SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(context); 103 reverseImage = false; 104 } 105 106 /** 107 * Closes the camera driver if still in use. 108 */ 109 public void closeDriver() { 110 if (camera != null) { 111 camera.release(); 112 camera = null; 113 // Make sure to clear these each time we close the camera, so that any scanning rect 114 // requested by intent is forgotten. 115 framingRect = null; 116 framingRectInPreview = null; 117 } 118 } 119 120 /** 121 * Asks the camera hardware to begin drawing preview frames to the screen. 122 */ 123 public void startPreview() { 124 Camera theCamera = camera; 125 if (theCamera != null && !previewing) { 126 theCamera.startPreview(); 127 previewing = true; 128 } 129 } 130 131 /** 132 * Tells the camera to stop drawing preview frames. 133 */ 134 public void stopPreview() { 135 if (camera != null && previewing) { 136 camera.stopPreview(); 137 previewCallback.setHandler(null, 0); 138 autoFocusCallback.setHandler(null, 0); 139 previewing = false; 140 } 141 } 142 143 /** 144 * A single preview frame will be returned to the handler supplied. The data will arrive as byte[] 145 * in the message.obj field, with width and height encoded as message.arg1 and message.arg2, 146 * respectively. 147 * 148 * @param handler The handler to send the message to. 149 * @param message The what field of the message to be sent. 150 */ 151 public void requestPreviewFrame(Handler handler, int message) { 152 Camera theCamera = camera; 153 if (theCamera != null && previewing) { 154 previewCallback.setHandler(handler, message); 155 theCamera.setOneShotPreviewCallback(previewCallback); 156 } 157 } 158 159 /** 160 * Asks the camera hardware to perform an autofocus. 161 * 162 * @param handler The Handler to notify when the autofocus completes. 163 * @param message The message to deliver. 164 */ 165 public void requestAutoFocus(Handler handler, int message) { 166 if (camera != null && previewing) { 167 autoFocusCallback.setHandler(handler, message); 168 camera.autoFocus(autoFocusCallback); 169 } 170 } 171 172 /** 173 * Calculates the framing rect which the UI should draw to show the user where to place the 174 * barcode. This target helps with alignment as well as forces the user to hold the device 175 * far enough away to ensure the image will be in focus. 176 * 177 * @return The rectangle to draw on screen in window coordinates. 178 */ 179 public Rect getFramingRect() { 180 if (framingRect == null) { 181 if (camera == null) { 182 return null; 183 } 184 Point screenResolution = configManager.getScreenResolution(); 185 int width = screenResolution.x * 3 / 4; 186 if (width < MIN_FRAME_WIDTH) { 187 width = MIN_FRAME_WIDTH; 188 } else if (width > MAX_FRAME_WIDTH) { 189 width = MAX_FRAME_WIDTH; 190 } 191 int height = screenResolution.y * 3 / 4; 192 if (height < MIN_FRAME_HEIGHT) { 193 height = MIN_FRAME_HEIGHT; 194 } else if (height > MAX_FRAME_HEIGHT) { 195 height = MAX_FRAME_HEIGHT; 196 } 197 int leftOffset = (screenResolution.x - width) / 2; 198 int topOffset = (screenResolution.y - height) / 2; 199 framingRect = new Rect(leftOffset, topOffset, leftOffset + width, topOffset + height); 200 Log.d(TAG, "Calculated framing rect: " + framingRect); 201 } 202 return framingRect; 203 } 204 205 /** 206 * Like {@link #getFramingRect} but coordinates are in terms of the preview frame, 207 * not UI / screen. 208 */ 209 public Rect getFramingRectInPreview() { 210 if (framingRectInPreview == null) { 211 Rect framingRect = getFramingRect(); 212 if (framingRect == null) { 213 return null; 214 } 215 Rect rect = new Rect(framingRect); 216 Point cameraResolution = configManager.getCameraResolution(); 217 if (cameraResolution == null) { 218 return framingRect; 219 } 220 Point screenResolution = configManager.getScreenResolution(); 221 rect.left = rect.left * cameraResolution.x / screenResolution.x; 222 rect.right = rect.right * cameraResolution.x / screenResolution.x; 223 rect.top = rect.top * cameraResolution.y / screenResolution.y; 224 rect.bottom = rect.bottom * cameraResolution.y / screenResolution.y; 225 framingRectInPreview = rect; 226 } 227 return framingRectInPreview; 228 } 229 230 /** 231 * Allows third party apps to specify the scanning rectangle dimensions, rather than determine 232 * them automatically based on screen resolution. 233 * 234 * @param width The width in pixels to scan. 235 * @param height The height in pixels to scan. 236 */ 237 public void setManualFramingRect(int width, int height) { 238 if (initialized) { 239 Point screenResolution = configManager.getScreenResolution(); 240 if (width > screenResolution.x) { 241 width = screenResolution.x; 242 } 243 if (height > screenResolution.y) { 244 height = screenResolution.y; 245 } 246 int leftOffset = (screenResolution.x - width) / 2; 247 int topOffset = (screenResolution.y - height) / 2; 248 framingRect = new Rect(leftOffset, topOffset, leftOffset + width, topOffset + height); 249 Log.d(TAG, "Calculated manual framing rect: " + framingRect); 250 framingRectInPreview = null; 251 } else { 252 requestedFramingRectWidth = width; 253 requestedFramingRectHeight = height; 254 } 255 } 256 257 /** 258 * A factory method to build the appropriate LuminanceSource object based on the format 259 * of the preview buffers, as described by Camera.Parameters. 260 * 261 * @param data A preview frame. 262 * @param width The width of the image. 263 * @param height The height of the image. 264 * @return A PlanarYUVLuminanceSource instance. 265 */ 266 public PlanarYUVLuminanceSource buildLuminanceSource(byte[] data, int width, int height) { 267 Rect rect = getFramingRectInPreview(); 268 if (rect == null) { 269 return null; 270 } 271 // Go ahead and assume it's YUV rather than die. 272 return new PlanarYUVLuminanceSource(data, width, height, rect.left, rect.top, 273 rect.width(), rect.height(), reverseImage); 274 } 275 276 } 277