1 package org.opencv.android;
5 import org.opencv.BuildConfig;
7 import org.opencv.core.Mat;
8 import org.opencv.core.Size;
10 import android.app.Activity;
11 import android.app.AlertDialog;
12 import android.content.Context;
13 import android.content.DialogInterface;
14 import android.content.res.TypedArray;
15 import android.graphics.Bitmap;
16 import android.graphics.Canvas;
17 import android.graphics.Rect;
18 import android.util.AttributeSet;
19 import android.util.Log;
20 import android.view.SurfaceHolder;
21 import android.view.SurfaceView;
24 * This is a basic class, implementing the interaction with Camera and OpenCV library.
25 * The main responsibility of it - is to control when camera can be enabled, process the frame,
26 * call external listener to make any adjustments to the frame and then draw the resulting
27 * frame to the screen.
28 * The clients shall implement CvCameraViewListener.
30 public abstract class CameraBridgeViewBase extends SurfaceView implements SurfaceHolder.Callback {
32 private static final String TAG = "CameraBridge";
33 private static final int MAX_UNSPECIFIED = -1;
34 private static final int STOPPED = 0;
35 private static final int STARTED = 1;
37 private int mState = STOPPED;
38 private Bitmap mCacheBitmap;
39 private CvCameraViewListener2 mListener;
40 private boolean mSurfaceExist;
41 private final Object mSyncObject = new Object();
43 protected int mFrameWidth;
44 protected int mFrameHeight;
45 protected int mMaxHeight;
46 protected int mMaxWidth;
47 protected float mScale = 0;
48 protected int mPreviewFormat = RGBA;
49 protected int mCameraIndex = CAMERA_ID_ANY;
50 protected boolean mEnabled;
51 protected FpsMeter mFpsMeter = null;
53 public static final int CAMERA_ID_ANY = -1;
54 public static final int CAMERA_ID_BACK = 99;
55 public static final int CAMERA_ID_FRONT = 98;
56 public static final int RGBA = 1;
57 public static final int GRAY = 2;
59 public CameraBridgeViewBase(Context context, int cameraId) {
61 mCameraIndex = cameraId;
62 getHolder().addCallback(this);
63 mMaxWidth = MAX_UNSPECIFIED;
64 mMaxHeight = MAX_UNSPECIFIED;
67 public CameraBridgeViewBase(Context context, AttributeSet attrs) {
68 super(context, attrs);
70 int count = attrs.getAttributeCount();
71 Log.d(TAG, "Attr count: " + Integer.valueOf(count));
73 TypedArray styledAttrs = getContext().obtainStyledAttributes(attrs, R.styleable.CameraBridgeViewBase);
74 if (styledAttrs.getBoolean(R.styleable.CameraBridgeViewBase_show_fps, false))
77 mCameraIndex = styledAttrs.getInt(R.styleable.CameraBridgeViewBase_camera_id, -1);
79 getHolder().addCallback(this);
80 mMaxWidth = MAX_UNSPECIFIED;
81 mMaxHeight = MAX_UNSPECIFIED;
82 styledAttrs.recycle();
86 * Sets the camera index
87 * @param cameraIndex new camera index
89 public void setCameraIndex(int cameraIndex) {
90 this.mCameraIndex = cameraIndex;
93 public interface CvCameraViewListener {
95 * This method is invoked when camera preview has started. After this method is invoked
96 * the frames will start to be delivered to client via the onCameraFrame() callback.
97 * @param width - the width of the frames that will be delivered
98 * @param height - the height of the frames that will be delivered
100 public void onCameraViewStarted(int width, int height);
103 * This method is invoked when camera preview has been stopped for some reason.
104 * No frames will be delivered via onCameraFrame() callback after this method is called.
106 public void onCameraViewStopped();
109 * This method is invoked when delivery of the frame needs to be done.
110 * The returned values - is a modified frame which needs to be displayed on the screen.
111 * TODO: pass the parameters specifying the format of the frame (BPP, YUV or RGB and etc)
113 public Mat onCameraFrame(Mat inputFrame);
116 public interface CvCameraViewListener2 {
118 * This method is invoked when camera preview has started. After this method is invoked
119 * the frames will start to be delivered to client via the onCameraFrame() callback.
120 * @param width - the width of the frames that will be delivered
121 * @param height - the height of the frames that will be delivered
123 public void onCameraViewStarted(int width, int height);
126 * This method is invoked when camera preview has been stopped for some reason.
127 * No frames will be delivered via onCameraFrame() callback after this method is called.
129 public void onCameraViewStopped();
132 * This method is invoked when delivery of the frame needs to be done.
133 * The returned values - is a modified frame which needs to be displayed on the screen.
134 * TODO: pass the parameters specifying the format of the frame (BPP, YUV or RGB and etc)
136 public Mat onCameraFrame(CvCameraViewFrame inputFrame);
139 protected class CvCameraViewListenerAdapter implements CvCameraViewListener2 {
140 public CvCameraViewListenerAdapter(CvCameraViewListener oldStypeListener) {
141 mOldStyleListener = oldStypeListener;
144 public void onCameraViewStarted(int width, int height) {
145 mOldStyleListener.onCameraViewStarted(width, height);
148 public void onCameraViewStopped() {
149 mOldStyleListener.onCameraViewStopped();
152 public Mat onCameraFrame(CvCameraViewFrame inputFrame) {
154 switch (mPreviewFormat) {
156 result = mOldStyleListener.onCameraFrame(inputFrame.rgba());
159 result = mOldStyleListener.onCameraFrame(inputFrame.gray());
162 Log.e(TAG, "Invalid frame format! Only RGBA and Gray Scale are supported!");
168 public void setFrameFormat(int format) {
169 mPreviewFormat = format;
172 private int mPreviewFormat = RGBA;
173 private CvCameraViewListener mOldStyleListener;
177 * This class interface is abstract representation of single frame from camera for onCameraFrame callback
178 * Attention: Do not use objects, that represents this interface out of onCameraFrame callback!
180 public interface CvCameraViewFrame {
183 * This method returns RGBA Mat with frame
188 * This method returns single channel gray scale Mat with frame
193 public void surfaceChanged(SurfaceHolder arg0, int arg1, int arg2, int arg3) {
194 Log.d(TAG, "call surfaceChanged event");
195 synchronized(mSyncObject) {
196 if (!mSurfaceExist) {
197 mSurfaceExist = true;
200 /** Surface changed. We need to stop camera and restart with new parameters */
201 /* Pretend that old surface has been destroyed */
202 mSurfaceExist = false;
204 /* Now use new surface. Say we have it now */
205 mSurfaceExist = true;
211 public void surfaceCreated(SurfaceHolder holder) {
212 /* Do nothing. Wait until surfaceChanged delivered */
215 public void surfaceDestroyed(SurfaceHolder holder) {
216 synchronized(mSyncObject) {
217 mSurfaceExist = false;
223 * This method is provided for clients, so they can enable the camera connection.
224 * The actual onCameraViewStarted callback will be delivered only after both this method is called and surface is available
226 public void enableView() {
227 synchronized(mSyncObject) {
234 * This method is provided for clients, so they can disable camera connection and stop
235 * the delivery of frames even though the surface view itself is not destroyed and still stays on the scren
237 public void disableView() {
238 synchronized(mSyncObject) {
245 * This method enables label with fps value on the screen
247 public void enableFpsMeter() {
248 if (mFpsMeter == null) {
249 mFpsMeter = new FpsMeter();
250 mFpsMeter.setResolution(mFrameWidth, mFrameHeight);
254 public void disableFpsMeter() {
263 public void setCvCameraViewListener(CvCameraViewListener2 listener) {
264 mListener = listener;
267 public void setCvCameraViewListener(CvCameraViewListener listener) {
268 CvCameraViewListenerAdapter adapter = new CvCameraViewListenerAdapter(listener);
269 adapter.setFrameFormat(mPreviewFormat);
274 * This method sets the maximum size that camera frame is allowed to be. When selecting
275 * size - the biggest size which less or equal the size set will be selected.
276 * As an example - we set setMaxFrameSize(200,200) and we have 176x152 and 320x240 sizes. The
277 * preview frame will be selected with 176x152 size.
278 * This method is useful when need to restrict the size of preview frame for some reason (for example for video recording)
279 * @param maxWidth - the maximum width allowed for camera frame.
280 * @param maxHeight - the maximum height allowed for camera frame
282 public void setMaxFrameSize(int maxWidth, int maxHeight) {
283 mMaxWidth = maxWidth;
284 mMaxHeight = maxHeight;
287 public void SetCaptureFormat(int format)
289 mPreviewFormat = format;
290 if (mListener instanceof CvCameraViewListenerAdapter) {
291 CvCameraViewListenerAdapter adapter = (CvCameraViewListenerAdapter) mListener;
292 adapter.setFrameFormat(mPreviewFormat);
297 * Called when mSyncObject lock is held
299 private void checkCurrentState() {
300 Log.d(TAG, "call checkCurrentState");
303 if (mEnabled && mSurfaceExist && getVisibility() == VISIBLE) {
304 targetState = STARTED;
306 targetState = STOPPED;
309 if (targetState != mState) {
310 /* The state change detected. Need to exit the current state and enter target state */
311 processExitState(mState);
312 mState = targetState;
313 processEnterState(mState);
317 private void processEnterState(int state) {
318 Log.d(TAG, "call processEnterState: " + state);
321 onEnterStartedState();
322 if (mListener != null) {
323 mListener.onCameraViewStarted(mFrameWidth, mFrameHeight);
327 onEnterStoppedState();
328 if (mListener != null) {
329 mListener.onCameraViewStopped();
335 private void processExitState(int state) {
336 Log.d(TAG, "call processExitState: " + state);
339 onExitStartedState();
342 onExitStoppedState();
347 private void onEnterStoppedState() {
351 private void onExitStoppedState() {
355 // NOTE: The order of bitmap constructor and camera connection is important for android 4.1.x
356 // Bitmap must be constructed before surface
357 private void onEnterStartedState() {
358 Log.d(TAG, "call onEnterStartedState");
360 if (!connectCamera(getWidth(), getHeight())) {
361 AlertDialog ad = new AlertDialog.Builder(getContext()).create();
362 ad.setCancelable(false); // This blocks the 'BACK' button
363 ad.setMessage("It seems that you device does not support camera (or it is locked). Application will be closed.");
364 ad.setButton(DialogInterface.BUTTON_NEUTRAL, "OK", new DialogInterface.OnClickListener() {
365 public void onClick(DialogInterface dialog, int which) {
367 ((Activity) getContext()).finish();
375 private void onExitStartedState() {
377 if (mCacheBitmap != null) {
378 mCacheBitmap.recycle();
383 * This method shall be called by the subclasses when they have valid
384 * object and want it to be delivered to external client (via callback) and
385 * then displayed on the screen.
386 * @param frame - the current frame to be delivered
388 protected void deliverAndDrawFrame(CvCameraViewFrame frame) {
391 if (mListener != null) {
392 modified = mListener.onCameraFrame(frame);
394 modified = frame.rgba();
397 boolean bmpValid = true;
398 if (modified != null) {
400 Utils.matToBitmap(modified, mCacheBitmap);
401 } catch(Exception e) {
402 Log.e(TAG, "Mat type: " + modified);
403 Log.e(TAG, "Bitmap type: " + mCacheBitmap.getWidth() + "*" + mCacheBitmap.getHeight());
404 Log.e(TAG, "Utils.matToBitmap() throws an exception: " + e.getMessage());
409 if (bmpValid && mCacheBitmap != null) {
410 Canvas canvas = getHolder().lockCanvas();
411 if (canvas != null) {
412 canvas.drawColor(0, android.graphics.PorterDuff.Mode.CLEAR);
413 if (BuildConfig.DEBUG)
414 Log.d(TAG, "mStretch value: " + mScale);
417 canvas.drawBitmap(mCacheBitmap, new Rect(0,0,mCacheBitmap.getWidth(), mCacheBitmap.getHeight()),
418 new Rect((int)((canvas.getWidth() - mScale*mCacheBitmap.getWidth()) / 2),
419 (int)((canvas.getHeight() - mScale*mCacheBitmap.getHeight()) / 2),
420 (int)((canvas.getWidth() - mScale*mCacheBitmap.getWidth()) / 2 + mScale*mCacheBitmap.getWidth()),
421 (int)((canvas.getHeight() - mScale*mCacheBitmap.getHeight()) / 2 + mScale*mCacheBitmap.getHeight())), null);
423 canvas.drawBitmap(mCacheBitmap, new Rect(0,0,mCacheBitmap.getWidth(), mCacheBitmap.getHeight()),
424 new Rect((canvas.getWidth() - mCacheBitmap.getWidth()) / 2,
425 (canvas.getHeight() - mCacheBitmap.getHeight()) / 2,
426 (canvas.getWidth() - mCacheBitmap.getWidth()) / 2 + mCacheBitmap.getWidth(),
427 (canvas.getHeight() - mCacheBitmap.getHeight()) / 2 + mCacheBitmap.getHeight()), null);
430 if (mFpsMeter != null) {
432 mFpsMeter.draw(canvas, 20, 30);
434 getHolder().unlockCanvasAndPost(canvas);
440 * This method is invoked shall perform concrete operation to initialize the camera.
441 * CONTRACT: as a result of this method variables mFrameWidth and mFrameHeight MUST be
442 * initialized with the size of the Camera frames that will be delivered to external processor.
443 * @param width - the width of this SurfaceView
444 * @param height - the height of this SurfaceView
446 protected abstract boolean connectCamera(int width, int height);
449 * Disconnects and release the particular camera object being connected to this surface view.
450 * Called when syncObject lock is held
452 protected abstract void disconnectCamera();
454 // NOTE: On Android 4.1.x the function must be called before SurfaceTexture constructor!
455 protected void AllocateCache()
457 mCacheBitmap = Bitmap.createBitmap(mFrameWidth, mFrameHeight, Bitmap.Config.ARGB_8888);
460 public interface ListItemAccessor {
461 public int getWidth(Object obj);
462 public int getHeight(Object obj);
466 * This helper method can be called by subclasses to select camera preview size.
467 * It goes over the list of the supported preview sizes and selects the maximum one which
468 * fits both values set via setMaxFrameSize() and surface frame allocated for this view
469 * @param supportedSizes
470 * @param surfaceWidth
471 * @param surfaceHeight
472 * @return optimal frame size
474 protected Size calculateCameraFrameSize(List<?> supportedSizes, ListItemAccessor accessor, int surfaceWidth, int surfaceHeight) {
478 int maxAllowedWidth = (mMaxWidth != MAX_UNSPECIFIED && mMaxWidth < surfaceWidth)? mMaxWidth : surfaceWidth;
479 int maxAllowedHeight = (mMaxHeight != MAX_UNSPECIFIED && mMaxHeight < surfaceHeight)? mMaxHeight : surfaceHeight;
481 for (Object size : supportedSizes) {
482 int width = accessor.getWidth(size);
483 int height = accessor.getHeight(size);
485 if (width <= maxAllowedWidth && height <= maxAllowedHeight) {
486 if (width >= calcWidth && height >= calcHeight) {
487 calcWidth = (int) width;
488 calcHeight = (int) height;
493 return new Size(calcWidth, calcHeight);