FaceView.java revision f43dee9f40bb4f21df23ae3876213a5b2f9afcfc
1/*
2 * Copyright (C) 2011 The Android Open Source Project
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
17package com.android.camera.ui;
18
19import android.annotation.TargetApi;
20import android.content.Context;
21import android.content.res.Resources;
22import android.graphics.Canvas;
23import android.graphics.Matrix;
24import android.graphics.Paint;
25import android.graphics.Paint.Style;
26import android.graphics.RectF;
27import android.hardware.Camera.Face;
28import android.os.Handler;
29import android.os.Message;
30import android.util.AttributeSet;
31import android.util.Log;
32import android.view.View;
33
34import com.android.camera.CameraActivity;
35import com.android.camera.CameraScreenNail;
36import com.android.camera.Util;
37import com.android.gallery3d.R;
38import com.android.gallery3d.common.ApiHelper;
39
40@TargetApi(ApiHelper.VERSION_CODES.ICE_CREAM_SANDWICH)
41public class FaceView extends View
42    implements FocusIndicator, Rotatable {
43    private static final String TAG = "CAM FaceView";
44    private final boolean LOGV = false;
45    // The value for android.hardware.Camera.setDisplayOrientation.
46    private int mDisplayOrientation;
47    // The orientation compensation for the face indicator to make it look
48    // correctly in all device orientations. Ex: if the value is 90, the
49    // indicator should be rotated 90 degrees counter-clockwise.
50    private int mOrientation;
51    private boolean mMirror;
52    private boolean mPause;
53    private Matrix mMatrix = new Matrix();
54    private RectF mRect = new RectF();
55    // As face detection can be flaky, we add a layer of filtering on top of it
56    // to avoid rapid changes in state (eg, flickering between has faces and
57    // not having faces)
58    private Face[] mFaces;
59    private Face[] mPendingFaces;
60    private int mColor;
61    private final int mFocusingColor;
62    private final int mFocusedColor;
63    private final int mFailColor;
64    private Paint mPaint;
65    private volatile boolean mBlocked;
66
67    private int mUncroppedWidth;
68    private int mUncroppedHeight;
69    private static final int MSG_SWITCH_FACES = 1;
70    private static final int SWITCH_DELAY = 70;
71    private boolean mStateSwitchPending = false;
72    private Handler mHandler = new Handler() {
73        @Override
74        public void handleMessage(Message msg) {
75            switch (msg.what) {
76            case MSG_SWITCH_FACES:
77                mStateSwitchPending = false;
78                mFaces = mPendingFaces;
79                invalidate();
80                break;
81            }
82        }
83    };
84
85    public FaceView(Context context, AttributeSet attrs) {
86        super(context, attrs);
87        Resources res = getResources();
88        mFocusingColor = res.getColor(R.color.face_detect_start);
89        mFocusedColor = res.getColor(R.color.face_detect_success);
90        mFailColor = res.getColor(R.color.face_detect_fail);
91        mColor = mFocusingColor;
92        mPaint = new Paint();
93        mPaint.setAntiAlias(true);
94        mPaint.setStyle(Style.STROKE);
95        mPaint.setStrokeWidth(res.getDimension(R.dimen.face_circle_stroke));
96    }
97
98    public void setFaces(Face[] faces) {
99        if (LOGV) Log.v(TAG, "Num of faces=" + faces.length);
100        if (mPause) return;
101        if (mFaces != null) {
102            if ((faces.length > 0 && mFaces.length == 0)
103                    || (faces.length == 0 && mFaces.length > 0)) {
104                mPendingFaces = faces;
105                if (!mStateSwitchPending) {
106                    mStateSwitchPending = true;
107                    mHandler.sendEmptyMessageDelayed(MSG_SWITCH_FACES, SWITCH_DELAY);
108                }
109                return;
110            }
111        }
112        if (mStateSwitchPending) {
113            mStateSwitchPending = false;
114            mHandler.removeMessages(MSG_SWITCH_FACES);
115        }
116        mFaces = faces;
117        invalidate();
118    }
119
120    public void setDisplayOrientation(int orientation) {
121        mDisplayOrientation = orientation;
122        if (LOGV) Log.v(TAG, "mDisplayOrientation=" + orientation);
123    }
124
125    @Override
126    public void setOrientation(int orientation, boolean animation) {
127        mOrientation = orientation;
128        invalidate();
129    }
130
131    public void setMirror(boolean mirror) {
132        mMirror = mirror;
133        if (LOGV) Log.v(TAG, "mMirror=" + mirror);
134    }
135
136    public boolean faceExists() {
137        return (mFaces != null && mFaces.length > 0);
138    }
139
140    @Override
141    public void showStart() {
142        mColor = mFocusingColor;
143        invalidate();
144    }
145
146    // Ignore the parameter. No autofocus animation for face detection.
147    @Override
148    public void showSuccess(boolean timeout) {
149        mColor = mFocusedColor;
150        invalidate();
151    }
152
153    // Ignore the parameter. No autofocus animation for face detection.
154    @Override
155    public void showFail(boolean timeout) {
156        mColor = mFailColor;
157        invalidate();
158    }
159
160    @Override
161    public void clear() {
162        // Face indicator is displayed during preview. Do not clear the
163        // drawable.
164        mColor = mFocusingColor;
165        mFaces = null;
166        invalidate();
167    }
168
169    public void pause() {
170        mPause = true;
171    }
172
173    public void resume() {
174        mPause = false;
175    }
176
177    public void setBlockDraw(boolean block) {
178        mBlocked = block;
179    }
180
181    @Override
182    protected void onDraw(Canvas canvas) {
183        if (!mBlocked && (mFaces != null) && (mFaces.length > 0)) {
184            int rw, rh;
185            if (mUncroppedWidth == 0) {
186                // TODO: This check is temporary. It needs to be removed after the
187                // refactoring is fully functioning.
188                final CameraScreenNail sn = ((CameraActivity) getContext()).getCameraScreenNail();
189                rw = sn.getUncroppedRenderWidth();
190                rh = sn.getUncroppedRenderHeight();
191            } else {
192                rw = mUncroppedWidth;
193                rh = mUncroppedHeight;
194            }
195            // Prepare the matrix.
196            if (((rh > rw) && ((mDisplayOrientation == 0) || (mDisplayOrientation == 180)))
197                    || ((rw > rh) && ((mDisplayOrientation == 90) || (mDisplayOrientation == 270)))) {
198                int temp = rw;
199                rw = rh;
200                rh = temp;
201            }
202            Util.prepareMatrix(mMatrix, mMirror, mDisplayOrientation, rw, rh);
203            int dx = (getWidth() - rw) / 2;
204            int dy = (getHeight() - rh) / 2;
205
206            // Focus indicator is directional. Rotate the matrix and the canvas
207            // so it looks correctly in all orientations.
208            canvas.save();
209            mMatrix.postRotate(mOrientation); // postRotate is clockwise
210            canvas.rotate(-mOrientation); // rotate is counter-clockwise (for canvas)
211            for (int i = 0; i < mFaces.length; i++) {
212                // Filter out false positives.
213                if (mFaces[i].score < 50) continue;
214
215                // Transform the coordinates.
216                mRect.set(mFaces[i].rect);
217                if (LOGV) Util.dumpRect(mRect, "Original rect");
218                mMatrix.mapRect(mRect);
219                if (LOGV) Util.dumpRect(mRect, "Transformed rect");
220                mPaint.setColor(mColor);
221                mRect.offset(dx, dy);
222                canvas.drawOval(mRect, mPaint);
223            }
224            canvas.restore();
225        }
226        super.onDraw(canvas);
227    }
228}
229