From 4716484508d92cb10f98103dce3953fa909db66d Mon Sep 17 00:00:00 2001 From: Byunghun Jeon Date: Wed, 8 Jun 2016 14:41:47 -0700 Subject: SnapdragonCamera: Add face detection to camera2 Add face detection to camera2. Receive face data from camera2 and use faceView UI to draw it Change-Id: Id9c49ab6dd73de316398c57981cc0b9df0400b45 CRs-Fixed: 1025797 --- res/layout/capture_module.xml | 11 ++ res/values/camera2arrays.xml | 10 ++ res/xml/capture_preferences.xml | 8 ++ src/com/android/camera/CaptureModule.java | 172 +++++++++++++++++-------- src/com/android/camera/CaptureUI.java | 59 ++++++++- src/com/android/camera/SettingsManager.java | 22 ++++ src/com/android/camera/ui/Camera2FaceView.java | 147 +++++++++++++++++++++ src/com/android/camera/ui/FaceView.java | 34 ++--- 8 files changed, 388 insertions(+), 75 deletions(-) create mode 100644 src/com/android/camera/ui/Camera2FaceView.java diff --git a/res/layout/capture_module.xml b/res/layout/capture_module.xml index 3b4f025bf..07594cef3 100644 --- a/res/layout/capture_module.xml +++ b/res/layout/capture_module.xml @@ -51,6 +51,17 @@ android:layout_height="match_parent" android:background="@android:color/black" /> + + + + + @string/pref_video_time_lapse_frame_interval_54000000 @string/pref_video_time_lapse_frame_interval_86400000 + + + @string/pref_camera_facedetection_entry_off + @string/pref_camera_facedetection_entry_on + + + + off + on + diff --git a/res/xml/capture_preferences.xml b/res/xml/capture_preferences.xml index 697770b04..06f816c7f 100644 --- a/res/xml/capture_preferences.xml +++ b/res/xml/capture_preferences.xml @@ -258,4 +258,12 @@ camera:entryValues="@array/pref_camera2_video_time_lapse_frame_interval_entryvalues" camera:key="pref_camera2_video_time_lapse_frame_interval_key" camera:title="@string/pref_video_time_lapse_frame_interval_title"/> + + diff --git a/src/com/android/camera/CaptureModule.java b/src/com/android/camera/CaptureModule.java index 336e7b410..9fa478924 100644 --- a/src/com/android/camera/CaptureModule.java +++ b/src/com/android/camera/CaptureModule.java @@ -388,7 +388,7 @@ public class CaptureModule implements CameraModule, PhotoController, private CameraCaptureSession.CaptureCallback mCaptureCallback = new CameraCaptureSession.CaptureCallback() { - private void updateState(CaptureResult result) { + private void processCaptureResult(CaptureResult result) { int id = (int) result.getRequest().getTag(); if (!mFirstPreviewLoaded) { @@ -408,58 +408,7 @@ public class CaptureModule implements CameraModule, PhotoController, } mPreviewCaptureResult = result; - switch (mState[id]) { - case STATE_PREVIEW: { - break; - } - case STATE_WAITING_LOCK: { - Integer afState = result.get(CaptureResult.CONTROL_AF_STATE); - Integer aeState = result.get(CaptureResult.CONTROL_AE_STATE); - Log.d(TAG, "STATE_WAITING_LOCK id: " + id + " afState:" + afState + " aeState:" + aeState); - // AF_PASSIVE is added for continous auto focus mode - if (CaptureResult.CONTROL_AF_STATE_FOCUSED_LOCKED == afState || - CaptureResult.CONTROL_AF_STATE_NOT_FOCUSED_LOCKED == afState || - CaptureResult.CONTROL_AF_STATE_PASSIVE_FOCUSED == afState || - CaptureResult.CONTROL_AF_STATE_PASSIVE_UNFOCUSED == afState || - (mLockRequestHashCode[id] == result.getRequest().hashCode() && - afState == CaptureResult.CONTROL_AF_STATE_INACTIVE)) { - // CONTROL_AE_STATE can be null on some devices - if (aeState == null || (aeState == CaptureResult - .CONTROL_AE_STATE_CONVERGED) && isFlashOff(id)) { - mState[id] = STATE_PICTURE_TAKEN; - captureStillPicture(id); - } else { - runPrecaptureSequence(id); - } - } - break; - } - case STATE_WAITING_PRECAPTURE: { - // CONTROL_AE_STATE can be null on some devices - Integer aeState = result.get(CaptureResult.CONTROL_AE_STATE); - Log.d(TAG, "STATE_WAITING_PRECAPTURE id: " + id + " aeState:" + aeState); - if (aeState == null || - aeState == CaptureResult.CONTROL_AE_STATE_PRECAPTURE || - aeState == CaptureResult.CONTROL_AE_STATE_FLASH_REQUIRED || - aeState == CaptureResult.CONTROL_AE_STATE_CONVERGED) { - if (mPrecaptureRequestHashCode[id] == result.getRequest().hashCode()) - mState[id] = STATE_WAITING_NON_PRECAPTURE; - } - break; - } - case STATE_WAITING_NON_PRECAPTURE: { - // CONTROL_AE_STATE can be null on some devices - Integer aeState = result.get(CaptureResult.CONTROL_AE_STATE); - Log.d(TAG, "STATE_WAITING_NON_PRECAPTURE id: " + id + " aeState:" + aeState); - if (aeState == null || aeState != CaptureResult.CONTROL_AE_STATE_PRECAPTURE) { - mState[id] = STATE_PICTURE_TAKEN; - captureStillPicture(id); - } - break; - } - case STATE_WAITING_TOUCH_FOCUS: - break; - } + updateCaptureStateMachine(id, result); } @Override @@ -468,6 +417,8 @@ public class CaptureModule implements CameraModule, PhotoController, CaptureResult partialResult) { int id = (int) partialResult.getRequest().getTag(); if (id == getMainCameraId()) updateFocusStateChange(partialResult); + Face[] faces = partialResult.get(CaptureResult.STATISTICS_FACES); + updateFaceView(faces); } @Override @@ -476,7 +427,9 @@ public class CaptureModule implements CameraModule, PhotoController, TotalCaptureResult result) { int id = (int) result.getRequest().getTag(); if (id == getMainCameraId()) updateFocusStateChange(result); - updateState(result); + Face[] faces = result.get(CaptureResult.STATISTICS_FACES); + updateFaceView(faces); + processCaptureResult(result); } }; @@ -540,6 +493,67 @@ public class CaptureModule implements CameraModule, PhotoController, }; + private void updateCaptureStateMachine(int id, CaptureResult result) { + switch (mState[id]) { + case STATE_PREVIEW: { + break; + } + case STATE_WAITING_LOCK: { + Integer afState = result.get(CaptureResult.CONTROL_AF_STATE); + Integer aeState = result.get(CaptureResult.CONTROL_AE_STATE); + Log.d(TAG, "STATE_WAITING_LOCK id: " + id + " afState:" + afState + " aeState:" + aeState); + // AF_PASSIVE is added for continous auto focus mode + if (CaptureResult.CONTROL_AF_STATE_FOCUSED_LOCKED == afState || + CaptureResult.CONTROL_AF_STATE_NOT_FOCUSED_LOCKED == afState || + CaptureResult.CONTROL_AF_STATE_PASSIVE_FOCUSED == afState || + CaptureResult.CONTROL_AF_STATE_PASSIVE_UNFOCUSED == afState || + (mLockRequestHashCode[id] == result.getRequest().hashCode() && + afState == CaptureResult.CONTROL_AF_STATE_INACTIVE)) { + // CONTROL_AE_STATE can be null on some devices + if (aeState == null || (aeState == CaptureResult + .CONTROL_AE_STATE_CONVERGED) && isFlashOff(id)) { + mState[id] = STATE_PICTURE_TAKEN; + captureStillPicture(id); + } else { + runPrecaptureSequence(id); + } + } + break; + } + case STATE_WAITING_PRECAPTURE: { + // CONTROL_AE_STATE can be null on some devices + Integer aeState = result.get(CaptureResult.CONTROL_AE_STATE); + Log.d(TAG, "STATE_WAITING_PRECAPTURE id: " + id + " aeState:" + aeState); + if (aeState == null || + aeState == CaptureResult.CONTROL_AE_STATE_PRECAPTURE || + aeState == CaptureResult.CONTROL_AE_STATE_FLASH_REQUIRED || + aeState == CaptureResult.CONTROL_AE_STATE_CONVERGED) { + if (mPrecaptureRequestHashCode[id] == result.getRequest().hashCode()) + mState[id] = STATE_WAITING_NON_PRECAPTURE; + } + break; + } + case STATE_WAITING_NON_PRECAPTURE: { + // CONTROL_AE_STATE can be null on some devices + Integer aeState = result.get(CaptureResult.CONTROL_AE_STATE); + Log.d(TAG, "STATE_WAITING_NON_PRECAPTURE id: " + id + " aeState:" + aeState); + if (aeState == null || aeState != CaptureResult.CONTROL_AE_STATE_PRECAPTURE) { + mState[id] = STATE_PICTURE_TAKEN; + captureStillPicture(id); + } + break; + } + case STATE_WAITING_TOUCH_FOCUS: + break; + } + } + + public void startFaceDetection() { + mUI.onStartFaceDetection(mDisplayOrientation, + mSettingsManager.isFacingFront(getMainCameraId()), + mSettingsManager.getSensorActiveArraySize(getMainCameraId())); + } + private boolean isMonoPreviewOn() { String value = mSettingsManager.getValue(SettingsManager.KEY_MONO_PREVIEW); if (value == null) return false; @@ -672,6 +686,8 @@ public class CaptureModule implements CameraModule, PhotoController, mCaptureSession[id] = cameraCaptureSession; mPreviewSession = cameraCaptureSession; initializePreviewConfiguration(id); + setDisplayOrientation(); + updateFaceDetection(); try { if (isBackCamera() && getCameraMode() == DUAL_MODE) { linkBayerMono(id); @@ -1324,6 +1340,7 @@ public class CaptureModule implements CameraModule, PhotoController, private void applyCommonSettings(CaptureRequest.Builder builder, int id) { builder.set(CaptureRequest.CONTROL_MODE, CaptureRequest.CONTROL_MODE_AUTO); builder.set(CaptureRequest.CONTROL_AF_MODE, mControlAFMode); + applyFaceDetection(builder); applyFlash(builder, id); applyWhiteBalance(builder); applyExposure(builder); @@ -1409,6 +1426,7 @@ public class CaptureModule implements CameraModule, PhotoController, @Override public void onPauseBeforeSuper() { mPaused = true; + mUI.onPause(); if (mIsRecordingVideo) { stopRecordingVideo(getMainCameraId()); } @@ -1426,7 +1444,6 @@ public class CaptureModule implements CameraModule, PhotoController, mUI.hideSurfaceView(); mFirstPreviewLoaded = false; stopBackgroundThread(); - mUI.onPause(); } @Override @@ -1530,6 +1547,7 @@ public class CaptureModule implements CameraModule, PhotoController, @Override public void onConfigurationChanged(Configuration config) { + Log.v(TAG, "onConfigurationChanged"); setDisplayOrientation(); } @@ -1653,7 +1671,11 @@ public class CaptureModule implements CameraModule, PhotoController, return; } Log.d(TAG, "onSingleTapUp " + x + " " + y); + int[] newXY = {x, y}; + if (!mUI.isOverSurfaceView(newXY)) return; mUI.setFocusPosition(x, y); + x = newXY[0]; + y = newXY[1]; mUI.onFocusStarted(); if (isBackCamera()) { switch (getCameraMode()) { @@ -1709,6 +1731,17 @@ public class CaptureModule implements CameraModule, PhotoController, return false; } + private void updateFaceView(final Face[] faces) { + if (faces != null) { + mHandler.post(new Runnable() { + @Override + public void run() { + mUI.onFaceDetection(faces); + } + }); + } + } + @Override public void onCountDownFinished() { mUI.showUIAfterCountDown(); @@ -2549,6 +2582,14 @@ public class CaptureModule implements CameraModule, PhotoController, } } + private void applyFaceDetection(CaptureRequest.Builder request) { + String value = mSettingsManager.getValue(SettingsManager.KEY_FACE_DETECTION); + if (value != null) { + request.set(CaptureRequest.STATISTICS_FACE_DETECT_MODE, + CaptureRequest.STATISTICS_FACE_DETECT_MODE_SIMPLE); + } + } + private void applyFlash(CaptureRequest.Builder request, int id) { if (mSettingsManager.isFlashSupported(id)) { String value = mSettingsManager.getValue(SettingsManager.KEY_FLASH_MODE); @@ -2701,6 +2742,9 @@ public class CaptureModule implements CameraModule, PhotoController, updatePictureSize(); if (count == 0) restart(); return; + case SettingsManager.KEY_FACE_DETECTION: + updateFaceDetection(); + break; case SettingsManager.KEY_CAMERA_ID: case SettingsManager.KEY_MONO_ONLY: case SettingsManager.KEY_CLEARSIGHT: @@ -2762,6 +2806,21 @@ public class CaptureModule implements CameraModule, PhotoController, } } + private void updateFaceDetection() { + final String value = mSettingsManager.getValue(SettingsManager.KEY_FACE_DETECTION); + mActivity.runOnUiThread(new Runnable() { + @Override + public void run() { + if (value == null || value.equals("off")) mUI.onStopFaceDetection(); + else { + mUI.onStartFaceDetection(mDisplayOrientation, + mSettingsManager.isFacingFront(getMainCameraId()), + mSettingsManager.getSensorActiveArraySize(getMainCameraId())); + } + } + }); + } + private int mCurrentMode; private boolean checkNeedToRestart(String value) { @@ -2794,6 +2853,9 @@ public class CaptureModule implements CameraModule, PhotoController, private Size getOptimalPreviewSize(Size pictureSize, Size[] prevSizes, int screenW, int screenH) { + if (pictureSize.getWidth() <= screenH && pictureSize.getHeight() <= screenW) { + return pictureSize; + } Size optimal = prevSizes[0]; float ratio = (float) pictureSize.getWidth() / pictureSize.getHeight(); for (Size prevSize: prevSizes) { diff --git a/src/com/android/camera/CaptureUI.java b/src/com/android/camera/CaptureUI.java index f10be5703..c6576d7f4 100644 --- a/src/com/android/camera/CaptureUI.java +++ b/src/com/android/camera/CaptureUI.java @@ -23,6 +23,7 @@ import android.animation.Animator; import android.content.Context; import android.content.res.Resources; import android.graphics.Point; +import android.graphics.Rect; import android.graphics.drawable.AnimationDrawable; import android.hardware.Camera.Face; import android.text.TextUtils; @@ -42,6 +43,7 @@ import android.widget.LinearLayout; import android.widget.TextView; import com.android.camera.ui.AutoFitSurfaceView; +import com.android.camera.ui.Camera2FaceView; import com.android.camera.ui.CameraControls; import com.android.camera.ui.CountDownView; import com.android.camera.ui.FocusIndicator; @@ -95,6 +97,7 @@ public class CaptureUI implements FocusOverlayManager.FocusUI, SettingsManager.KEY_WHITE_BALANCE, SettingsManager.KEY_CAMERA2, SettingsManager.KEY_MAKEUP, + SettingsManager.KEY_FACE_DETECTION, SettingsManager.KEY_VIDEO_FLASH_MODE, SettingsManager.KEY_VIDEO_DURATION, SettingsManager.KEY_VIDEO_QUALITY @@ -133,6 +136,7 @@ public class CaptureUI implements FocusOverlayManager.FocusUI, private SettingsManager mSettingsManager; private ImageView mThumbnail; + private Camera2FaceView mFaceView; private SurfaceHolder.Callback callback = new SurfaceHolder.Callback() { @@ -239,6 +243,18 @@ public class CaptureUI implements FocusOverlayManager.FocusUI, mSurfaceView2.setZOrderMediaOverlay(true); mSurfaceHolder = mSurfaceView.getHolder(); mSurfaceHolder.addCallback(callback); + mSurfaceView.addOnLayoutChangeListener(new View.OnLayoutChangeListener() { + @Override + public void onLayoutChange(View v, int left, int top, int right, + int bottom, int oldLeft, int oldTop, int oldRight, + int oldBottom) { + int width = right - left; + int height = bottom - top; + if (mFaceView != null) { + mFaceView.onSurfaceTextureSizeChanged(width, height); + } + } + }); mSurfaceHolder2 = mSurfaceView2.getHolder(); mSurfaceHolder2.addCallback(callback2); @@ -297,6 +313,7 @@ public class CaptureUI implements FocusOverlayManager.FocusUI, muteButton.setVisibility(View.GONE); mCameraControls = (CameraControls) mRootView.findViewById(R.id.camera_controls); + mFaceView = (Camera2FaceView) mRootView.findViewById(R.id.face_view); Point size = new Point(); mActivity.getWindowManager().getDefaultDisplay().getSize(size); @@ -1302,6 +1319,7 @@ public class CaptureUI implements FocusOverlayManager.FocusUI, public void onPause() { cancelCountDown(); collapseCameraControls(); + if (mFaceView != null) mFaceView.clear(); } public boolean collapseCameraControls() { @@ -1314,15 +1332,16 @@ public class CaptureUI implements FocusOverlayManager.FocusUI, } private FocusIndicator getFocusIndicator() { - return mPieRenderer; + return (mFaceView != null && mFaceView.faceExists()) ? mFaceView : mPieRenderer; } @Override public boolean hasFaces() { - return false; + return (mFaceView != null && mFaceView.faceExists()); } public void clearFaces() { + if (mFaceView != null) mFaceView.clear(); } @Override @@ -1362,16 +1381,31 @@ public class CaptureUI implements FocusOverlayManager.FocusUI, public void resumeFaceDetection() { } - public void onStartFaceDetection(int orientation, boolean mirror) { + public void onStartFaceDetection(int orientation, boolean mirror, Rect cameraBound) { + mFaceView.setBlockDraw(false); + mFaceView.clear(); + mFaceView.setVisibility(View.VISIBLE); + mFaceView.setDisplayOrientation(orientation); + mFaceView.setMirror(mirror); + mFaceView.setCameraBound(cameraBound); + mFaceView.resume(); } public void onStopFaceDetection() { + if (mFaceView != null) { + mFaceView.setBlockDraw(true); + mFaceView.clear(); + } } @Override public void onFaceDetection(Face[] faces, CameraManager.CameraProxy camera) { } + public void onFaceDetection(android.hardware.camera2.params.Face[] faces) { + mFaceView.setFaces(faces); + } + public Point getSurfaceViewSize() { Point point = new Point(); if (mSurfaceView != null) point.set(mSurfaceView.getWidth(), mSurfaceView.getHeight()); @@ -1415,6 +1449,9 @@ public class CaptureUI implements FocusOverlayManager.FocusUI, mRecordingTimeRect.setOrientation(orientation, false); } } + if (mFaceView != null) { + mFaceView.setDisplayRotation(orientation); + } if (mCountDownView != null) mCountDownView.setOrientation(orientation); RotateTextToast.setOrientation(orientation); @@ -1442,12 +1479,28 @@ public class CaptureUI implements FocusOverlayManager.FocusUI, mModule.onSingleTapUp(view, x, y); } + public boolean isOverSurfaceView(int[] xy) { + int x = xy[0]; + int y = xy[1]; + int[] surfaceViewLocation = new int[2]; + mSurfaceView.getLocationInWindow(surfaceViewLocation); + int surfaceViewX = surfaceViewLocation[0]; + int surfaceViewY = surfaceViewLocation[1]; + xy[0] = x - surfaceViewX; + xy[1] = y - surfaceViewY; + return (x > surfaceViewX) && (x < surfaceViewX + mSurfaceView.getWidth()) + && (y > surfaceViewY) && (y < surfaceViewY + mSurfaceView.getHeight()); + } + public void onPreviewFocusChanged(boolean previewFocused) { if (previewFocused) { showUI(); } else { hideUI(); } + if (mFaceView != null) { + mFaceView.setBlockDraw(!previewFocused); + } if (mGestures != null) { mGestures.setEnabled(previewFocused); } diff --git a/src/com/android/camera/SettingsManager.java b/src/com/android/camera/SettingsManager.java index 7909541ae..57dc62479 100644 --- a/src/com/android/camera/SettingsManager.java +++ b/src/com/android/camera/SettingsManager.java @@ -103,6 +103,7 @@ public class SettingsManager implements ListMenu.SettingsListener { public static final String KEY_VIDEO_ROTATION = "pref_camera2_video_rotation_key"; public static final String KEY_VIDEO_TIME_LAPSE_FRAME_INTERVAL = "pref_camera2_video_time_lapse_frame_interval_key"; + public static final String KEY_FACE_DETECTION = "pref_camera2_facedetection_key"; private static final String TAG = "SnapCam_SettingsManager"; private static SettingsManager sInstance; @@ -464,6 +465,7 @@ public class SettingsManager implements ListMenu.SettingsListener { ListPreference audioEncoder = mPreferenceGroup.findPreference(KEY_AUDIO_ENCODER); ListPreference noiseReduction = mPreferenceGroup.findPreference(KEY_NOISE_REDUCTION); ListPreference videoFlash = mPreferenceGroup.findPreference(KEY_VIDEO_FLASH_MODE); + ListPreference faceDetection = mPreferenceGroup.findPreference(KEY_FACE_DETECTION); if (whiteBalance != null) { CameraSettings.filterUnsupportedOptions(mPreferenceGroup, @@ -534,6 +536,11 @@ public class SettingsManager implements ListMenu.SettingsListener { if (!isFlashAvailable(cameraId)) removePreference(mPreferenceGroup, KEY_VIDEO_FLASH_MODE); } + + if (faceDetection != null) { + if (!isFaceDetectionSupported(cameraId)) + removePreference(mPreferenceGroup, KEY_FACE_DETECTION); + } } private void buildExposureCompensation(int cameraId) { @@ -682,6 +689,21 @@ public class SettingsManager implements ListMenu.SettingsListener { } } + public boolean isFaceDetectionSupported(int id) { + int[] faceDetection = mCharacteristics.get(id).get + (CameraCharacteristics.STATISTICS_INFO_AVAILABLE_FACE_DETECT_MODES); + for (int value: faceDetection) { + if (value == CameraMetadata.STATISTICS_FACE_DETECT_MODE_SIMPLE) + return true; + } + return false; + } + + public boolean isFacingFront(int id) { + int facing = mCharacteristics.get(id).get(CameraCharacteristics.LENS_FACING); + return facing == CameraCharacteristics.LENS_FACING_FRONT; + } + public boolean isFlashSupported(int id) { return mCharacteristics.get(id).get(CameraCharacteristics.FLASH_INFO_AVAILABLE) && mValuesMap.get(KEY_FLASH_MODE) != null; diff --git a/src/com/android/camera/ui/Camera2FaceView.java b/src/com/android/camera/ui/Camera2FaceView.java new file mode 100644 index 000000000..40a3469aa --- /dev/null +++ b/src/com/android/camera/ui/Camera2FaceView.java @@ -0,0 +1,147 @@ +/* + * Copyright (c) 2016, The Linux Foundation. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above + * copyright notice, this list of conditions and the following + * disclaimer in the documentation and/or other materials provided + * with the distribution. + * * Neither the name of The Linux Foundation nor the names of its + * contributors may be used to endorse or promote products derived + * from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED "AS IS" AND ANY EXPRESS OR IMPLIED + * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS + * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR + * BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, + * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE + * OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN + * IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +package com.android.camera.ui; + +import android.content.Context; +import android.graphics.Canvas; +import android.graphics.Matrix; +import android.graphics.Rect; +import android.hardware.camera2.params.Face; +import android.os.Handler; +import android.os.Message; +import android.util.AttributeSet; +import android.util.Log; + +import com.android.camera.util.CameraUtil; + +public class Camera2FaceView extends FaceView { + + private Face[] mFaces; + private Face[] mPendingFaces; + private Rect mCameraBound; + private Handler mHandler = new Handler() { + @Override + public void handleMessage(Message msg) { + switch (msg.what) { + case MSG_SWITCH_FACES: + mStateSwitchPending = false; + mFaces = mPendingFaces; + invalidate(); + break; + } + } + }; + + public Camera2FaceView(Context context, AttributeSet attrs) { + super(context, attrs); + } + + public void setCameraBound(Rect cameraBound) { + mCameraBound = cameraBound; + } + + public void setFaces(Face[] faces) { + if (LOGV) Log.v(TAG, "Num of faces=" + faces.length); + if (mPause) return; + if (mFaces != null) { + if ((faces.length > 0 && mFaces.length == 0) + || (faces.length == 0 && mFaces.length > 0)) { + mPendingFaces = faces; + if (!mStateSwitchPending) { + mStateSwitchPending = true; + mHandler.sendEmptyMessageDelayed(MSG_SWITCH_FACES, SWITCH_DELAY); + } + return; + } + } + if (mStateSwitchPending) { + mStateSwitchPending = false; + mHandler.removeMessages(MSG_SWITCH_FACES); + } + mFaces = faces; + if (!mBlocked && (mFaces != null) && (mFaces.length > 0) && mCameraBound != null) { + invalidate(); + } + } + + @Override + protected void onDraw(Canvas canvas) { + if (!mBlocked && (mFaces != null) && (mFaces.length > 0) && mCameraBound != null) { + int rw, rh; + rw = mUncroppedWidth; + rh = mUncroppedHeight; + if (((rh > rw) && ((mDisplayOrientation == 0) || (mDisplayOrientation == 180))) + || ((rw > rh) && ((mDisplayOrientation == 90) || (mDisplayOrientation == 270)))) { + int temp = rw; + rw = rh; + rh = temp; + } + CameraUtil.prepareMatrix(mMatrix, mMirror, mDisplayOrientation, rw, rh); + + // mMatrix assumes that the face coordinates are from -1000 to 1000. + // so translate the face coordination to match the assumption. + Matrix translateMatrix = new Matrix(); + translateMatrix.preTranslate(-mCameraBound.width() / 2f, -mCameraBound.height() / 2f); + translateMatrix.postScale(2000f / mCameraBound.width(), 2000f / mCameraBound.height()); + + int dx = (getWidth() - rw) / 2; + ; + int dy = (getHeight() - rh) / 2; + + // Focus indicator is directional. Rotate the matrix and the canvas + // so it looks correctly in all orientations. + canvas.save(); + mMatrix.postRotate(mOrientation); // postRotate is clockwise + canvas.rotate(-mOrientation); // rotate is counter-clockwise (for canvas) + for (int i = 0; i < mFaces.length; i++) { + if (mFaces[i].getScore() < 50) continue; + Rect faceBound = mFaces[i].getBounds(); + faceBound.offset(-mCameraBound.left, -mCameraBound.top); + mRect.set(faceBound); + translateMatrix.mapRect(mRect); + mMatrix.mapRect(mRect); + mPaint.setColor(mColor); + mRect.offset(dx, dy); + + canvas.drawOval(mRect, mPaint); + } + canvas.restore(); + } + super.onDraw(canvas); + } + + @Override + public void clear() { + // Face indicator is displayed during preview. Do not clear the + // drawable. + mColor = mFocusingColor; + mFaces = null; + invalidate(); + } +} diff --git a/src/com/android/camera/ui/FaceView.java b/src/com/android/camera/ui/FaceView.java index 0bdf0de15..dfa84ec5c 100644 --- a/src/com/android/camera/ui/FaceView.java +++ b/src/com/android/camera/ui/FaceView.java @@ -39,41 +39,41 @@ import org.codeaurora.camera.ExtendedFace; public class FaceView extends View implements FocusIndicator, Rotatable, PhotoUI.SurfaceTextureSizeChangedListener { - private static final String TAG = "CAM FaceView"; - private final boolean LOGV = false; + protected static final String TAG = "CAM FaceView"; + protected final boolean LOGV = false; // The value for android.hardware.Camera.setDisplayOrientation. - private int mDisplayOrientation; + protected int mDisplayOrientation; // The orientation compensation for the face indicator to make it look // correctly in all device orientations. Ex: if the value is 90, the // indicator should be rotated 90 degrees counter-clockwise. - private int mOrientation; - private boolean mMirror; - private boolean mPause; - private Matrix mMatrix = new Matrix(); - private RectF mRect = new RectF(); + protected int mOrientation; + protected boolean mMirror; + protected boolean mPause; + protected Matrix mMatrix = new Matrix(); + protected RectF mRect = new RectF(); // As face detection can be flaky, we add a layer of filtering on top of it // to avoid rapid changes in state (eg, flickering between has faces and // not having faces) private Face[] mFaces; private Face[] mPendingFaces; - private int mColor; - private final int mFocusingColor; + protected int mColor; + protected final int mFocusingColor; private final int mFocusedColor; private final int mFailColor; - private Paint mPaint; - private volatile boolean mBlocked; + protected Paint mPaint; + protected volatile boolean mBlocked; - private int mUncroppedWidth; - private int mUncroppedHeight; + protected int mUncroppedWidth; + protected int mUncroppedHeight; private final int smile_threashold_no_smile = 30; private final int smile_threashold_small_smile = 60; private final int blink_threshold = 60; - private static final int MSG_SWITCH_FACES = 1; - private static final int SWITCH_DELAY = 70; + protected static final int MSG_SWITCH_FACES = 1; + protected static final int SWITCH_DELAY = 70; private int mDisplayRotation = 0; - private boolean mStateSwitchPending = false; + protected boolean mStateSwitchPending = false; private Handler mHandler = new Handler() { @Override public void handleMessage(Message msg) { -- cgit v1.2.3