1 package org.opencv.android;
6 import org.opencv.android.Utils;
7 import org.opencv.core.Mat;
8 import org.opencv.core.Size;
9 import org.opencv.highgui.Highgui;
11 import android.app.Activity;
12 import android.app.AlertDialog;
13 import android.content.Context;
14 import android.content.DialogInterface;
15 import android.content.res.TypedArray;
16 import android.graphics.Bitmap;
17 import android.graphics.Canvas;
18 import android.graphics.Rect;
19 import android.util.AttributeSet;
20 import android.util.Log;
21 import android.view.SurfaceHolder;
22 import android.view.SurfaceView;
25 * This is a basic class, implementing the interaction with Camera and OpenCV library.
26 * The main responsibility of it - is to control when camera can be enabled, process the frame,
27 * call external listener to make any adjustments to the frame and then draw the resulting
28 * frame to the screen.
29 * The clients shall implement CvCameraViewListener.
31 public abstract class CameraBridgeViewBase extends SurfaceView implements SurfaceHolder.Callback {
33 private static final String TAG = "CameraBridge";
34 private static final int MAX_UNSPECIFIED = -1;
35 private static final int STOPPED = 0;
36 private static final int STARTED = 1;
38 private int mState = STOPPED;
39 private Bitmap mCacheBitmap;
40 private CvCameraViewListener2 mListener;
41 private boolean mSurfaceExist;
42 private Object mSyncObject = new Object();
44 protected int mFrameWidth;
45 protected int mFrameHeight;
46 protected int mMaxHeight;
47 protected int mMaxWidth;
48 protected float mScale = 0;
49 protected int mPreviewFormat = Highgui.CV_CAP_ANDROID_COLOR_FRAME_RGBA;
50 protected int mCameraIndex = CAMERA_ID_ANY;
51 protected boolean mEnabled;
52 protected FpsMeter mFpsMeter = null;
54 public static final int CAMERA_ID_ANY = -1;
55 public static final int CAMERA_ID_BACK = 99;
56 public static final int CAMERA_ID_FRONT = 98;
58 public CameraBridgeViewBase(Context context, int cameraId) {
60 mCameraIndex = cameraId;
61 getHolder().addCallback(this);
62 mMaxWidth = MAX_UNSPECIFIED;
63 mMaxHeight = MAX_UNSPECIFIED;
66 public CameraBridgeViewBase(Context context, AttributeSet attrs) {
67 super(context, attrs);
69 int count = attrs.getAttributeCount();
70 Log.d(TAG, "Attr count: " + Integer.valueOf(count));
72 TypedArray styledAttrs = getContext().obtainStyledAttributes(attrs, R.styleable.CameraBridgeViewBase);
73 if (styledAttrs.getBoolean(R.styleable.CameraBridgeViewBase_show_fps, false))
76 mCameraIndex = styledAttrs.getInt(R.styleable.CameraBridgeViewBase_camera_id, -1);
78 getHolder().addCallback(this);
79 mMaxWidth = MAX_UNSPECIFIED;
80 mMaxHeight = MAX_UNSPECIFIED;
81 styledAttrs.recycle();
84 public interface CvCameraViewListener {
86 * This method is invoked when camera preview has started. After this method is invoked
87 * the frames will start to be delivered to client via the onCameraFrame() callback.
88 * @param width - the width of the frames that will be delivered
89 * @param height - the height of the frames that will be delivered
91 public void onCameraViewStarted(int width, int height);
94 * This method is invoked when camera preview has been stopped for some reason.
95 * No frames will be delivered via onCameraFrame() callback after this method is called.
97 public void onCameraViewStopped();
100 * This method is invoked when delivery of the frame needs to be done.
101 * The returned values - is a modified frame which needs to be displayed on the screen.
102 * TODO: pass the parameters specifying the format of the frame (BPP, YUV or RGB and etc)
104 public Mat onCameraFrame(Mat inputFrame);
107 public interface CvCameraViewListener2 {
109 * This method is invoked when camera preview has started. After this method is invoked
110 * the frames will start to be delivered to client via the onCameraFrame() callback.
111 * @param width - the width of the frames that will be delivered
112 * @param height - the height of the frames that will be delivered
114 public void onCameraViewStarted(int width, int height);
117 * This method is invoked when camera preview has been stopped for some reason.
118 * No frames will be delivered via onCameraFrame() callback after this method is called.
120 public void onCameraViewStopped();
123 * This method is invoked when delivery of the frame needs to be done.
124 * The returned values - is a modified frame which needs to be displayed on the screen.
125 * TODO: pass the parameters specifying the format of the frame (BPP, YUV or RGB and etc)
127 public Mat onCameraFrame(CvCameraViewFrame inputFrame);
130 protected class CvCameraViewListenerAdapter implements CvCameraViewListener2 {
131 public CvCameraViewListenerAdapter(CvCameraViewListener oldStypeListener) {
132 mOldStyleListener = oldStypeListener;
135 public void onCameraViewStarted(int width, int height) {
136 mOldStyleListener.onCameraViewStarted(width, height);
139 public void onCameraViewStopped() {
140 mOldStyleListener.onCameraViewStopped();
143 public Mat onCameraFrame(CvCameraViewFrame inputFrame) {
145 switch (mPreviewFormat) {
146 case Highgui.CV_CAP_ANDROID_COLOR_FRAME_RGBA:
147 result = mOldStyleListener.onCameraFrame(inputFrame.rgba());
149 case Highgui.CV_CAP_ANDROID_GREY_FRAME:
150 result = mOldStyleListener.onCameraFrame(inputFrame.gray());
153 Log.e(TAG, "Invalid frame format! Only RGBA and Gray Scale are supported!");
159 public void setFrameFormat(int format) {
160 mPreviewFormat = format;
163 private int mPreviewFormat = Highgui.CV_CAP_ANDROID_COLOR_FRAME_RGBA;
164 private CvCameraViewListener mOldStyleListener;
168 * This class interface is abstract representation of single frame from camera for onCameraFrame callback
169 * Attention: Do not use objects, that represents this interface out of onCameraFrame callback!
171 public interface CvCameraViewFrame {
174 * This method returns RGBA Mat with frame
179 * This method returns single channel gray scale Mat with frame
184 public void surfaceChanged(SurfaceHolder arg0, int arg1, int arg2, int arg3) {
185 Log.d(TAG, "call surfaceChanged event");
186 synchronized(mSyncObject) {
187 if (!mSurfaceExist) {
188 mSurfaceExist = true;
191 /** Surface changed. We need to stop camera and restart with new parameters */
192 /* Pretend that old surface has been destroyed */
193 mSurfaceExist = false;
195 /* Now use new surface. Say we have it now */
196 mSurfaceExist = true;
202 public void surfaceCreated(SurfaceHolder holder) {
203 /* Do nothing. Wait until surfaceChanged delivered */
206 public void surfaceDestroyed(SurfaceHolder holder) {
207 synchronized(mSyncObject) {
208 mSurfaceExist = false;
214 * This method is provided for clients, so they can enable the camera connection.
215 * The actual onCameraViewStarted callback will be delivered only after both this method is called and surface is available
217 public void enableView() {
218 synchronized(mSyncObject) {
225 * This method is provided for clients, so they can disable camera connection and stop
226 * the delivery of frames even though the surface view itself is not destroyed and still stays on the scren
228 public void disableView() {
229 synchronized(mSyncObject) {
236 * This method enables label with fps value on the screen
238 public void enableFpsMeter() {
239 if (mFpsMeter == null) {
240 mFpsMeter = new FpsMeter();
241 mFpsMeter.setResolution(mFrameWidth, mFrameHeight);
245 public void disableFpsMeter() {
254 public void setCvCameraViewListener(CvCameraViewListener2 listener) {
255 mListener = listener;
258 public void setCvCameraViewListener(CvCameraViewListener listener) {
259 CvCameraViewListenerAdapter adapter = new CvCameraViewListenerAdapter(listener);
260 adapter.setFrameFormat(mPreviewFormat);
265 * This method sets the maximum size that camera frame is allowed to be. When selecting
266 * size - the biggest size which less or equal the size set will be selected.
267 * As an example - we set setMaxFrameSize(200,200) and we have 176x152 and 320x240 sizes. The
268 * preview frame will be selected with 176x152 size.
269 * This method is useful when need to restrict the size of preview frame for some reason (for example for video recording)
270 * @param maxWidth - the maximum width allowed for camera frame.
271 * @param maxHeight - the maximum height allowed for camera frame
273 public void setMaxFrameSize(int maxWidth, int maxHeight) {
274 mMaxWidth = maxWidth;
275 mMaxHeight = maxHeight;
278 public void SetCaptureFormat(int format)
280 mPreviewFormat = format;
281 if (mListener instanceof CvCameraViewListenerAdapter) {
282 CvCameraViewListenerAdapter adapter = (CvCameraViewListenerAdapter) mListener;
283 adapter.setFrameFormat(mPreviewFormat);
288 * Called when mSyncObject lock is held
290 private void checkCurrentState() {
293 if (mEnabled && mSurfaceExist && getVisibility() == VISIBLE) {
294 targetState = STARTED;
296 targetState = STOPPED;
299 if (targetState != mState) {
300 /* The state change detected. Need to exit the current state and enter target state */
301 processExitState(mState);
302 mState = targetState;
303 processEnterState(mState);
307 private void processEnterState(int state) {
310 onEnterStartedState();
311 if (mListener != null) {
312 mListener.onCameraViewStarted(mFrameWidth, mFrameHeight);
316 onEnterStoppedState();
317 if (mListener != null) {
318 mListener.onCameraViewStopped();
324 private void processExitState(int state) {
327 onExitStartedState();
330 onExitStoppedState();
335 private void onEnterStoppedState() {
339 private void onExitStoppedState() {
343 // NOTE: The order of bitmap constructor and camera connection is important for android 4.1.x
344 // Bitmap must be constructed before surface
345 private void onEnterStartedState() {
347 if (!connectCamera(getWidth(), getHeight())) {
348 AlertDialog ad = new AlertDialog.Builder(getContext()).create();
349 ad.setCancelable(false); // This blocks the 'BACK' button
350 ad.setMessage("It seems that you device does not support camera (or it is locked). Application will be closed.");
351 ad.setButton(DialogInterface.BUTTON_NEUTRAL, "OK", new DialogInterface.OnClickListener() {
352 public void onClick(DialogInterface dialog, int which) {
354 ((Activity) getContext()).finish();
362 private void onExitStartedState() {
364 if (mCacheBitmap != null) {
365 mCacheBitmap.recycle();
370 * This method shall be called by the subclasses when they have valid
371 * object and want it to be delivered to external client (via callback) and
372 * then displayed on the screen.
373 * @param frame - the current frame to be delivered
375 protected void deliverAndDrawFrame(CvCameraViewFrame frame) {
378 if (mListener != null) {
379 modified = mListener.onCameraFrame(frame);
381 modified = frame.rgba();
384 boolean bmpValid = true;
385 if (modified != null) {
387 Utils.matToBitmap(modified, mCacheBitmap);
388 } catch(Exception e) {
389 Log.e(TAG, "Mat type: " + modified);
390 Log.e(TAG, "Bitmap type: " + mCacheBitmap.getWidth() + "*" + mCacheBitmap.getHeight());
391 Log.e(TAG, "Utils.matToBitmap() throws an exception: " + e.getMessage());
396 if (bmpValid && mCacheBitmap != null) {
397 Canvas canvas = getHolder().lockCanvas();
398 if (canvas != null) {
399 canvas.drawColor(0, android.graphics.PorterDuff.Mode.CLEAR);
400 Log.d(TAG, "mStretch value: " + mScale);
403 canvas.drawBitmap(mCacheBitmap, new Rect(0,0,mCacheBitmap.getWidth(), mCacheBitmap.getHeight()),
404 new Rect((int)((canvas.getWidth() - mScale*mCacheBitmap.getWidth()) / 2),
405 (int)((canvas.getHeight() - mScale*mCacheBitmap.getHeight()) / 2),
406 (int)((canvas.getWidth() - mScale*mCacheBitmap.getWidth()) / 2 + mScale*mCacheBitmap.getWidth()),
407 (int)((canvas.getHeight() - mScale*mCacheBitmap.getHeight()) / 2 + mScale*mCacheBitmap.getHeight())), null);
409 canvas.drawBitmap(mCacheBitmap, new Rect(0,0,mCacheBitmap.getWidth(), mCacheBitmap.getHeight()),
410 new Rect((canvas.getWidth() - mCacheBitmap.getWidth()) / 2,
411 (canvas.getHeight() - mCacheBitmap.getHeight()) / 2,
412 (canvas.getWidth() - mCacheBitmap.getWidth()) / 2 + mCacheBitmap.getWidth(),
413 (canvas.getHeight() - mCacheBitmap.getHeight()) / 2 + mCacheBitmap.getHeight()), null);
416 if (mFpsMeter != null) {
418 mFpsMeter.draw(canvas, 20, 30);
420 getHolder().unlockCanvasAndPost(canvas);
426 * This method is invoked shall perform concrete operation to initialize the camera.
427 * CONTRACT: as a result of this method variables mFrameWidth and mFrameHeight MUST be
428 * initialized with the size of the Camera frames that will be delivered to external processor.
429 * @param width - the width of this SurfaceView
430 * @param height - the height of this SurfaceView
432 protected abstract boolean connectCamera(int width, int height);
435 * Disconnects and release the particular camera object being connected to this surface view.
436 * Called when syncObject lock is held
438 protected abstract void disconnectCamera();
440 // NOTE: On Android 4.1.x the function must be called before SurfaceTextre constructor!
441 protected void AllocateCache()
443 mCacheBitmap = Bitmap.createBitmap(mFrameWidth, mFrameHeight, Bitmap.Config.ARGB_8888);
446 public interface ListItemAccessor {
447 public int getWidth(Object obj);
448 public int getHeight(Object obj);
452 * This helper method can be called by subclasses to select camera preview size.
453 * It goes over the list of the supported preview sizes and selects the maximum one which
454 * fits both values set via setMaxFrameSize() and surface frame allocated for this view
455 * @param supportedSizes
456 * @param surfaceWidth
457 * @param surfaceHeight
458 * @return optimal frame size
460 protected Size calculateCameraFrameSize(List<?> supportedSizes, ListItemAccessor accessor, int surfaceWidth, int surfaceHeight) {
464 int maxAllowedWidth = (mMaxWidth != MAX_UNSPECIFIED && mMaxWidth < surfaceWidth)? mMaxWidth : surfaceWidth;
465 int maxAllowedHeight = (mMaxHeight != MAX_UNSPECIFIED && mMaxHeight < surfaceHeight)? mMaxHeight : surfaceHeight;
467 for (Object size : supportedSizes) {
468 int width = accessor.getWidth(size);
469 int height = accessor.getHeight(size);
471 if (width <= maxAllowedWidth && height <= maxAllowedHeight) {
472 if (width >= calcWidth && height >= calcHeight) {
473 calcWidth = (int) width;
474 calcHeight = (int) height;
479 return new Size(calcWidth, calcHeight);