Home | History | Annotate | Download | only in camera
      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