FaceView.java revision 62b4c3c0da3abd397b3790820359927784c50bc2
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.content.Context;
20import android.content.res.Resources;
21import android.graphics.Canvas;
22import android.graphics.Matrix;
23import android.graphics.Paint;
24import android.graphics.Paint.Style;
25import android.graphics.RectF;
26import android.hardware.Camera.Face;
27import android.os.Handler;
28import android.os.Message;
29import android.util.AttributeSet;
30import android.view.View;
31
32import com.android.camera.debug.Log;
33import com.android.camera.util.CameraUtil;
34import com.android.camera2.R;
35
36public class FaceView extends View
37    implements FocusIndicator, Rotatable, PreviewStatusListener.PreviewAreaChangedListener {
38    private static final Log.Tag TAG = new Log.Tag("FaceView");
39    private final boolean LOGV = false;
40    // The value for android.hardware.Camera.setDisplayOrientation.
41    private int mDisplayOrientation;
42    // The orientation compensation for the face indicator to make it look
43    // correctly in all device orientations. Ex: if the value is 90, the
44    // indicator should be rotated 90 degrees counter-clockwise.
45    private int mOrientation;
46    private boolean mMirror;
47    private boolean mPause;
48    private Matrix mMatrix = new Matrix();
49    private RectF mRect = new RectF();
50    // As face detection can be flaky, we add a layer of filtering on top of it
51    // to avoid rapid changes in state (eg, flickering between has faces and
52    // not having faces)
53    private Face[] mFaces;
54    private Face[] mPendingFaces;
55    private int mColor;
56    private final int mFocusingColor;
57    private final int mFocusedColor;
58    private final int mFailColor;
59    private Paint mPaint;
60    private volatile boolean mBlocked;
61
62    private static final int MSG_SWITCH_FACES = 1;
63    private static final int SWITCH_DELAY = 70;
64    private boolean mStateSwitchPending = false;
65    private Handler mHandler = new Handler() {
66        @Override
67        public void handleMessage(Message msg) {
68            switch (msg.what) {
69            case MSG_SWITCH_FACES:
70                mStateSwitchPending = false;
71                mFaces = mPendingFaces;
72                invalidate();
73                break;
74            }
75        }
76    };
77    private final RectF mPreviewArea = new RectF();
78
79    public FaceView(Context context, AttributeSet attrs) {
80        super(context, attrs);
81        Resources res = getResources();
82        mFocusingColor = res.getColor(R.color.face_detect_start);
83        mFocusedColor = res.getColor(R.color.face_detect_success);
84        mFailColor = res.getColor(R.color.face_detect_fail);
85        mColor = mFocusingColor;
86        mPaint = new Paint();
87        mPaint.setAntiAlias(true);
88        mPaint.setStyle(Style.STROKE);
89        mPaint.setStrokeWidth(res.getDimension(R.dimen.face_circle_stroke));
90    }
91
92    public void setFaces(Face[] faces) {
93        if (LOGV) {
94            Log.v(TAG, "Num of faces=" + faces.length);
95        }
96        if (mPause) return;
97        if (mFaces != null) {
98            if ((faces.length > 0 && mFaces.length == 0)
99                    || (faces.length == 0 && mFaces.length > 0)) {
100                mPendingFaces = faces;
101                if (!mStateSwitchPending) {
102                    mStateSwitchPending = true;
103                    mHandler.sendEmptyMessageDelayed(MSG_SWITCH_FACES, SWITCH_DELAY);
104                }
105                return;
106            }
107        }
108        if (mStateSwitchPending) {
109            mStateSwitchPending = false;
110            mHandler.removeMessages(MSG_SWITCH_FACES);
111        }
112        mFaces = faces;
113        invalidate();
114    }
115
116    public void setDisplayOrientation(int orientation) {
117        mDisplayOrientation = orientation;
118        if (LOGV) {
119            Log.v(TAG, "mDisplayOrientation=" + orientation);
120        }
121    }
122
123    @Override
124    public void setOrientation(int orientation, boolean animation) {
125        mOrientation = orientation;
126        invalidate();
127    }
128
129    public void setMirror(boolean mirror) {
130        mMirror = mirror;
131        if (LOGV) {
132            Log.v(TAG, "mMirror=" + mirror);
133        }
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            rw = (int) mPreviewArea.width();
186            rh = (int) mPreviewArea.height();
187            // Prepare the matrix.
188            if (((rh > rw) && ((mDisplayOrientation == 0) || (mDisplayOrientation == 180)))
189                    || ((rw > rh) && ((mDisplayOrientation == 90) || (mDisplayOrientation == 270)))) {
190                int temp = rw;
191                rw = rh;
192                rh = temp;
193            }
194            CameraUtil.prepareMatrix(mMatrix, mMirror, mDisplayOrientation, rw, rh);
195            // Focus indicator is directional. Rotate the matrix and the canvas
196            // so it looks correctly in all orientations.
197            canvas.save();
198            mMatrix.postRotate(mOrientation); // postRotate is clockwise
199            canvas.rotate(-mOrientation); // rotate is counter-clockwise (for canvas)
200            for (int i = 0; i < mFaces.length; i++) {
201                // Filter out false positives.
202                if (mFaces[i].score < 50) continue;
203
204                // Transform the coordinates.
205                mRect.set(mFaces[i].rect);
206                if (LOGV) {
207                    CameraUtil.dumpRect(mRect, "Original rect");
208                }
209                mMatrix.mapRect(mRect);
210                if (LOGV) {
211                    CameraUtil.dumpRect(mRect, "Transformed rect");
212                }
213                mPaint.setColor(mColor);
214                mRect.offset(mPreviewArea.left, mPreviewArea.top);
215                canvas.drawRect(mRect, mPaint);
216            }
217            canvas.restore();
218        }
219        super.onDraw(canvas);
220    }
221
222    @Override
223    public void onPreviewAreaChanged(RectF previewArea) {
224        mPreviewArea.set(previewArea);
225    }
226}
227