1/*
2 * Copyright (C) 2013 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
17
18package android.hardware.camera2.params;
19
20import android.graphics.Point;
21import android.graphics.Rect;
22import android.hardware.camera2.CameraCharacteristics;
23import android.hardware.camera2.CameraMetadata;
24import android.hardware.camera2.CaptureResult;
25
26/**
27 * Describes a face detected in an image.
28 */
29public final class Face {
30
31    /**
32     * The ID is {@code -1} when the optional set of fields is unsupported.
33     *
34     * @see Face#Face(Rect, int)
35     * @see #getId()
36     */
37    public static final int ID_UNSUPPORTED = -1;
38
39    /**
40     * The minimum possible value for the confidence level.
41     *
42     * @see #getScore()
43     */
44    public static final int SCORE_MIN = 1;
45
46    /**
47     * The maximum possible value for the confidence level.
48     *
49     * @see #getScore()
50     */
51    public static final int SCORE_MAX = 100;
52
53    private final Rect mBounds;
54    private final int mScore;
55    private final int mId;
56    private final Point mLeftEye;
57    private final Point mRightEye;
58    private final Point mMouth;
59
60    /**
61     * Create a new face with all fields set.
62     *
63     * <p>The id, leftEyePosition, rightEyePosition, and mouthPosition are considered optional.
64     * They are only required when the {@link CaptureResult} reports that the value of key
65     * {@link CaptureResult#STATISTICS_FACE_DETECT_MODE} is
66     * {@link CameraMetadata#STATISTICS_FACE_DETECT_MODE_FULL}.
67     * If the id is {@value #ID_UNSUPPORTED} then the leftEyePosition, rightEyePosition, and
68     * mouthPositions are guaranteed to be {@code null}. Otherwise, each of leftEyePosition,
69     * rightEyePosition, and mouthPosition may be independently null or not-null.</p>
70     *
71     * @param bounds Bounds of the face.
72     * @param score Confidence level between {@value #SCORE_MIN}-{@value #SCORE_MAX}.
73     * @param id A unique ID per face visible to the tracker.
74     * @param leftEyePosition The position of the left eye.
75     * @param rightEyePosition The position of the right eye.
76     * @param mouthPosition The position of the mouth.
77     *
78     * @throws IllegalArgumentException
79     *             if bounds is {@code null},
80     *             or if the confidence is not in the range of
81     *             {@value #SCORE_MIN}-{@value #SCORE_MAX},
82     *             or if id is {@value #ID_UNSUPPORTED} and
83     *               leftEyePosition/rightEyePosition/mouthPosition aren't all null,
84     *             or else if id is negative.
85     *
86     * @hide
87     */
88    public Face(Rect bounds, int score, int id,
89            Point leftEyePosition, Point rightEyePosition, Point mouthPosition) {
90        checkNotNull("bounds", bounds);
91        if (score < SCORE_MIN || score > SCORE_MAX) {
92            throw new IllegalArgumentException("Confidence out of range");
93        } else if (id < 0 && id != ID_UNSUPPORTED) {
94            throw new IllegalArgumentException("Id out of range");
95        }
96        if (id == ID_UNSUPPORTED) {
97            checkNull("leftEyePosition", leftEyePosition);
98            checkNull("rightEyePosition", rightEyePosition);
99            checkNull("mouthPosition", mouthPosition);
100        }
101
102        mBounds = bounds;
103        mScore = score;
104        mId = id;
105        mLeftEye = leftEyePosition;
106        mRightEye = rightEyePosition;
107        mMouth = mouthPosition;
108    }
109
110    /**
111     * Create a new face without the optional fields.
112     *
113     * <p>The id, leftEyePosition, rightEyePosition, and mouthPosition are considered optional.
114     * If the id is {@value #ID_UNSUPPORTED} then the leftEyePosition, rightEyePosition, and
115     * mouthPositions are guaranteed to be {@code null}. Otherwise, each of leftEyePosition,
116     * rightEyePosition, and mouthPosition may be independently null or not-null. When devices
117     * report the value of key {@link CaptureResult#STATISTICS_FACE_DETECT_MODE} as
118     * {@link CameraMetadata#STATISTICS_FACE_DETECT_MODE_SIMPLE} in {@link CaptureResult},
119     * the face id of each face is expected to be {@value #ID_UNSUPPORTED}, the leftEyePosition,
120     * rightEyePosition, and mouthPositions are expected to be {@code null} for each face.</p>
121     *
122     * @param bounds Bounds of the face.
123     * @param score Confidence level between {@value #SCORE_MIN}-{@value #SCORE_MAX}.
124     *
125     * @throws IllegalArgumentException
126     *             if bounds is {@code null},
127     *             or if the confidence is not in the range of
128     *             {@value #SCORE_MIN}-{@value #SCORE_MAX}.
129     *
130     * @hide
131     */
132    public Face(Rect bounds, int score) {
133        this(bounds, score, ID_UNSUPPORTED,
134                /*leftEyePosition*/null, /*rightEyePosition*/null, /*mouthPosition*/null);
135    }
136
137    /**
138     * Bounds of the face.
139     *
140     * <p>A rectangle relative to the sensor's
141     * {@link CameraCharacteristics#SENSOR_INFO_ACTIVE_ARRAY_SIZE}, with (0,0)
142     * representing the top-left corner of the active array rectangle.</p>
143     *
144     * <p>There is no constraints on the the Rectangle value other than it
145     * is not-{@code null}.</p>
146     */
147    public Rect getBounds() {
148        return mBounds;
149    }
150
151    /**
152     * The confidence level for the detection of the face.
153     *
154     * <p>The range is {@value #SCORE_MIN} to {@value #SCORE_MAX}.
155     * {@value #SCORE_MAX} is the highest confidence.</p>
156     *
157     * <p>Depending on the device, even very low-confidence faces may be
158     * listed, so applications should filter out faces with low confidence,
159     * depending on the use case. For a typical point-and-shoot camera
160     * application that wishes to display rectangles around detected faces,
161     * filtering out faces with confidence less than half of {@value #SCORE_MAX}
162     * is recommended.</p>
163     *
164     * @see #SCORE_MAX
165     * @see #SCORE_MIN
166     */
167    public int getScore() {
168        return mScore;
169    }
170
171    /**
172     * An unique id per face while the face is visible to the tracker.
173     *
174     * <p>
175     * If the face leaves the field-of-view and comes back, it will get a new
176     * id.</p>
177     *
178     * <p>This is an optional field, may not be supported on all devices.
179     * If the id is {@value #ID_UNSUPPORTED} then the leftEyePosition, rightEyePosition, and
180     * mouthPositions are guaranteed to be {@code null}. Otherwise, each of leftEyePosition,
181     * rightEyePosition, and mouthPosition may be independently null or not-null. When devices
182     * report the value of key {@link CaptureResult#STATISTICS_FACE_DETECT_MODE} as
183     * {@link CameraMetadata#STATISTICS_FACE_DETECT_MODE_SIMPLE} in {@link CaptureResult},
184     * the face id of each face is expected to be {@value #ID_UNSUPPORTED}.</p>
185     *
186     * <p>This value will either be {@value #ID_UNSUPPORTED} or
187     * otherwise greater than {@code 0}.</p>
188     *
189     * @see #ID_UNSUPPORTED
190     */
191    public int getId() {
192        return mId;
193    }
194
195    /**
196     * The coordinates of the center of the left eye.
197     *
198     * <p>The coordinates are in
199     * the same space as the ones for {@link #getBounds}. This is an
200     * optional field, may not be supported on all devices. If not
201     * supported, the value will always be set to null.
202     * This value will  always be null only if {@link #getId()} returns
203     * {@value #ID_UNSUPPORTED}.</p>
204     *
205     * @return The left eye position, or {@code null} if unknown.
206     */
207    public Point getLeftEyePosition() {
208        return mLeftEye;
209    }
210
211    /**
212     * The coordinates of the center of the right eye.
213     *
214     * <p>The coordinates are
215     * in the same space as the ones for {@link #getBounds}.This is an
216     * optional field, may not be supported on all devices. If not
217     * supported, the value will always be set to null.
218     * This value will  always be null only if {@link #getId()} returns
219     * {@value #ID_UNSUPPORTED}.</p>
220     *
221     * @return The right eye position, or {@code null} if unknown.
222     */
223    public Point getRightEyePosition() {
224        return mRightEye;
225    }
226
227    /**
228     * The coordinates of the center of the mouth.
229     *
230     * <p>The coordinates are in
231     * the same space as the ones for {@link #getBounds}. This is an optional
232     * field, may not be supported on all devices. If not
233     * supported, the value will always be set to null.
234     * This value will  always be null only if {@link #getId()} returns
235     * {@value #ID_UNSUPPORTED}.</p>
236     * </p>
237     *
238     * @return The mouth position, or {@code null} if unknown.
239     */
240    public Point getMouthPosition() {
241        return mMouth;
242    }
243
244    /**
245     * Represent the Face as a string for debugging purposes.
246     */
247    @Override
248    public String toString() {
249        return String.format("{ bounds: %s, score: %s, id: %d, " +
250                "leftEyePosition: %s, rightEyePosition: %s, mouthPosition: %s }",
251                mBounds, mScore, mId, mLeftEye, mRightEye, mMouth);
252    }
253
254    private static void checkNotNull(String name, Object obj) {
255        if (obj == null) {
256            throw new IllegalArgumentException(name + " was required, but it was null");
257        }
258    }
259
260    private static void checkNull(String name, Object obj) {
261        if (obj != null) {
262            throw new IllegalArgumentException(name + " was required to be null, but it wasn't");
263        }
264    }
265}
266