Merge pull request #800 from asmorkalov:android_cam_idx_semantic
[profile/ivi/opencv.git] / modules / java / generator / src / java / android+CameraBridgeViewBase.java
1 package org.opencv.android;
2
3 import java.util.List;
4
5 import org.opencv.R;
6 import org.opencv.android.Utils;
7 import org.opencv.core.Mat;
8 import org.opencv.core.Size;
9 import org.opencv.highgui.Highgui;
10
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;
23
24 /**
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.
30  */
31 public abstract class CameraBridgeViewBase extends SurfaceView implements SurfaceHolder.Callback {
32
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;
37
38     private int mState = STOPPED;
39     private Bitmap mCacheBitmap;
40     private CvCameraViewListener2 mListener;
41     private boolean mSurfaceExist;
42     private Object mSyncObject = new Object();
43
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;
53
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;
57
58     public CameraBridgeViewBase(Context context, int cameraId) {
59         super(context);
60         mCameraIndex = cameraId;
61         getHolder().addCallback(this);
62         mMaxWidth = MAX_UNSPECIFIED;
63         mMaxHeight = MAX_UNSPECIFIED;
64     }
65
66     public CameraBridgeViewBase(Context context, AttributeSet attrs) {
67         super(context, attrs);
68
69         int count = attrs.getAttributeCount();
70         Log.d(TAG, "Attr count: " + Integer.valueOf(count));
71
72         TypedArray styledAttrs = getContext().obtainStyledAttributes(attrs, R.styleable.CameraBridgeViewBase);
73         if (styledAttrs.getBoolean(R.styleable.CameraBridgeViewBase_show_fps, false))
74             enableFpsMeter();
75
76         mCameraIndex = styledAttrs.getInt(R.styleable.CameraBridgeViewBase_camera_id, -1);
77
78         getHolder().addCallback(this);
79         mMaxWidth = MAX_UNSPECIFIED;
80         mMaxHeight = MAX_UNSPECIFIED;
81         styledAttrs.recycle();
82     }
83
84     public interface CvCameraViewListener {
85         /**
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
90          */
91         public void onCameraViewStarted(int width, int height);
92
93         /**
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.
96          */
97         public void onCameraViewStopped();
98
99         /**
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)
103          */
104         public Mat onCameraFrame(Mat inputFrame);
105     }
106
107     public interface CvCameraViewListener2 {
108         /**
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
113          */
114         public void onCameraViewStarted(int width, int height);
115
116         /**
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.
119          */
120         public void onCameraViewStopped();
121
122         /**
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)
126          */
127         public Mat onCameraFrame(CvCameraViewFrame inputFrame);
128     };
129
130     protected class CvCameraViewListenerAdapter implements CvCameraViewListener2  {
131         public CvCameraViewListenerAdapter(CvCameraViewListener oldStypeListener) {
132             mOldStyleListener = oldStypeListener;
133         }
134
135         public void onCameraViewStarted(int width, int height) {
136             mOldStyleListener.onCameraViewStarted(width, height);
137         }
138
139         public void onCameraViewStopped() {
140             mOldStyleListener.onCameraViewStopped();
141         }
142
143         public Mat onCameraFrame(CvCameraViewFrame inputFrame) {
144              Mat result = null;
145              switch (mPreviewFormat) {
146                 case Highgui.CV_CAP_ANDROID_COLOR_FRAME_RGBA:
147                     result = mOldStyleListener.onCameraFrame(inputFrame.rgba());
148                     break;
149                 case Highgui.CV_CAP_ANDROID_GREY_FRAME:
150                     result = mOldStyleListener.onCameraFrame(inputFrame.gray());
151                     break;
152                 default:
153                     Log.e(TAG, "Invalid frame format! Only RGBA and Gray Scale are supported!");
154             };
155
156             return result;
157         }
158
159         public void setFrameFormat(int format) {
160             mPreviewFormat = format;
161         }
162
163         private int mPreviewFormat = Highgui.CV_CAP_ANDROID_COLOR_FRAME_RGBA;
164         private CvCameraViewListener mOldStyleListener;
165     };
166
167     /**
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!
170      */
171     public interface CvCameraViewFrame {
172
173         /**
174          * This method returns RGBA Mat with frame
175          */
176         public Mat rgba();
177
178         /**
179          * This method returns single channel gray scale Mat with frame
180          */
181         public Mat gray();
182     };
183
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;
189                 checkCurrentState();
190             } else {
191                 /** Surface changed. We need to stop camera and restart with new parameters */
192                 /* Pretend that old surface has been destroyed */
193                 mSurfaceExist = false;
194                 checkCurrentState();
195                 /* Now use new surface. Say we have it now */
196                 mSurfaceExist = true;
197                 checkCurrentState();
198             }
199         }
200     }
201
202     public void surfaceCreated(SurfaceHolder holder) {
203         /* Do nothing. Wait until surfaceChanged delivered */
204     }
205
206     public void surfaceDestroyed(SurfaceHolder holder) {
207         synchronized(mSyncObject) {
208             mSurfaceExist = false;
209             checkCurrentState();
210         }
211     }
212
213     /**
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
216      */
217     public void enableView() {
218         synchronized(mSyncObject) {
219             mEnabled = true;
220             checkCurrentState();
221         }
222     }
223
224     /**
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
227      */
228     public void disableView() {
229         synchronized(mSyncObject) {
230             mEnabled = false;
231             checkCurrentState();
232         }
233     }
234
235     /**
236      * This method enables label with fps value on the screen
237      */
238     public void enableFpsMeter() {
239         if (mFpsMeter == null) {
240             mFpsMeter = new FpsMeter();
241             mFpsMeter.setResolution(mFrameWidth, mFrameHeight);
242         }
243     }
244
245     public void disableFpsMeter() {
246             mFpsMeter = null;
247     }
248
249     /**
250      *
251      * @param listener
252      */
253
254     public void setCvCameraViewListener(CvCameraViewListener2 listener) {
255         mListener = listener;
256     }
257
258     public void setCvCameraViewListener(CvCameraViewListener listener) {
259         CvCameraViewListenerAdapter adapter = new CvCameraViewListenerAdapter(listener);
260         adapter.setFrameFormat(mPreviewFormat);
261         mListener = adapter;
262     }
263
264     /**
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
272      */
273     public void setMaxFrameSize(int maxWidth, int maxHeight) {
274         mMaxWidth = maxWidth;
275         mMaxHeight = maxHeight;
276     }
277
278     public void SetCaptureFormat(int format)
279     {
280         mPreviewFormat = format;
281         if (mListener instanceof CvCameraViewListenerAdapter) {
282             CvCameraViewListenerAdapter adapter = (CvCameraViewListenerAdapter) mListener;
283             adapter.setFrameFormat(mPreviewFormat);
284         }
285     }
286
287     /**
288      * Called when mSyncObject lock is held
289      */
290     private void checkCurrentState() {
291         int targetState;
292
293         if (mEnabled && mSurfaceExist && getVisibility() == VISIBLE) {
294             targetState = STARTED;
295         } else {
296             targetState = STOPPED;
297         }
298
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);
304         }
305     }
306
307     private void processEnterState(int state) {
308         switch(state) {
309         case STARTED:
310             onEnterStartedState();
311             if (mListener != null) {
312                 mListener.onCameraViewStarted(mFrameWidth, mFrameHeight);
313             }
314             break;
315         case STOPPED:
316             onEnterStoppedState();
317             if (mListener != null) {
318                 mListener.onCameraViewStopped();
319             }
320             break;
321         };
322     }
323
324     private void processExitState(int state) {
325         switch(state) {
326         case STARTED:
327             onExitStartedState();
328             break;
329         case STOPPED:
330             onExitStoppedState();
331             break;
332         };
333     }
334
335     private void onEnterStoppedState() {
336         /* nothing to do */
337     }
338
339     private void onExitStoppedState() {
340         /* nothing to do */
341     }
342
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() {
346         /* Connect camera */
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) {
353                     dialog.dismiss();
354                     ((Activity) getContext()).finish();
355                 }
356             });
357             ad.show();
358
359         }
360     }
361
362     private void onExitStartedState() {
363         disconnectCamera();
364         if (mCacheBitmap != null) {
365             mCacheBitmap.recycle();
366         }
367     }
368
369     /**
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
374      */
375     protected void deliverAndDrawFrame(CvCameraViewFrame frame) {
376         Mat modified;
377
378         if (mListener != null) {
379             modified = mListener.onCameraFrame(frame);
380         } else {
381             modified = frame.rgba();
382         }
383
384         boolean bmpValid = true;
385         if (modified != null) {
386             try {
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());
392                 bmpValid = false;
393             }
394         }
395
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);
401
402                 if (mScale != 0) {
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);
408                 } else {
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);
414                 }
415
416                 if (mFpsMeter != null) {
417                     mFpsMeter.measure();
418                     mFpsMeter.draw(canvas, 20, 30);
419                 }
420                 getHolder().unlockCanvasAndPost(canvas);
421             }
422         }
423     }
424
425     /**
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
431      */
432     protected abstract boolean connectCamera(int width, int height);
433
434     /**
435      * Disconnects and release the particular camera object being connected to this surface view.
436      * Called when syncObject lock is held
437      */
438     protected abstract void disconnectCamera();
439
440     // NOTE: On Android 4.1.x the function must be called before SurfaceTextre constructor!
441     protected void AllocateCache()
442     {
443         mCacheBitmap = Bitmap.createBitmap(mFrameWidth, mFrameHeight, Bitmap.Config.ARGB_8888);
444     }
445
446     public interface ListItemAccessor {
447         public int getWidth(Object obj);
448         public int getHeight(Object obj);
449     };
450
451     /**
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
459      */
460     protected Size calculateCameraFrameSize(List<?> supportedSizes, ListItemAccessor accessor, int surfaceWidth, int surfaceHeight) {
461         int calcWidth = 0;
462         int calcHeight = 0;
463
464         int maxAllowedWidth = (mMaxWidth != MAX_UNSPECIFIED && mMaxWidth < surfaceWidth)? mMaxWidth : surfaceWidth;
465         int maxAllowedHeight = (mMaxHeight != MAX_UNSPECIFIED && mMaxHeight < surfaceHeight)? mMaxHeight : surfaceHeight;
466
467         for (Object size : supportedSizes) {
468             int width = accessor.getWidth(size);
469             int height = accessor.getHeight(size);
470
471             if (width <= maxAllowedWidth && height <= maxAllowedHeight) {
472                 if (width >= calcWidth && height >= calcHeight) {
473                     calcWidth = (int) width;
474                     calcHeight = (int) height;
475                 }
476             }
477         }
478
479         return new Size(calcWidth, calcHeight);
480     }
481 }