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