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 
18 package android.hardware.camera2.params;
19 
20 import android.annotation.IntRange;
21 import android.annotation.NonNull;
22 import android.annotation.Nullable;
23 import android.graphics.Point;
24 import android.graphics.Rect;
25 import android.hardware.camera2.CameraCharacteristics;
26 import android.hardware.camera2.CameraMetadata;
27 import android.hardware.camera2.CaptureResult;
28 
29 /**
30  * Describes a face detected in an image.
31  */
32 public final class Face {
33 
34     /**
35      * The ID is {@code -1} when the optional set of fields is unsupported.
36      *
37      * @see #getId()
38      */
39     public static final int ID_UNSUPPORTED = -1;
40 
41     /**
42      * The minimum possible value for the confidence level.
43      *
44      * @see #getScore()
45      */
46     public static final int SCORE_MIN = 1;
47 
48     /**
49      * The maximum possible value for the confidence level.
50      *
51      * @see #getScore()
52      */
53     public static final int SCORE_MAX = 100;
54 
55     private Rect mBounds;
56     private int mScore;
57     private int mId;
58     private Point mLeftEye;
59     private Point mRightEye;
60     private Point mMouth;
61 
62     /**
63      * Create a new face with all fields set.
64      *
65      * <p>The id, leftEyePosition, rightEyePosition, and mouthPosition are considered optional.
66      * They are only required when the {@link CaptureResult} reports that the value of key
67      * {@link CaptureResult#STATISTICS_FACE_DETECT_MODE} is
68      * {@link CameraMetadata#STATISTICS_FACE_DETECT_MODE_FULL}.
69      * If the id is {@value #ID_UNSUPPORTED} then the leftEyePosition, rightEyePosition, and
70      * mouthPositions are guaranteed to be {@code null}. Otherwise, each of leftEyePosition,
71      * rightEyePosition, and mouthPosition may be independently null or not-null.</p>
72      *
73      * @param bounds Bounds of the face.
74      * @param score Confidence level between {@value #SCORE_MIN}-{@value #SCORE_MAX}.
75      * @param id A unique ID per face visible to the tracker.
76      * @param leftEyePosition The position of the left eye.
77      * @param rightEyePosition The position of the right eye.
78      * @param mouthPosition The position of the mouth.
79      *
80      * @throws IllegalArgumentException
81      *             if bounds is {@code null},
82      *             or if the confidence is not in the range of
83      *             {@value #SCORE_MIN}-{@value #SCORE_MAX},
84      *             or if id is {@value #ID_UNSUPPORTED} and
85      *               leftEyePosition/rightEyePosition/mouthPosition aren't all null,
86      *             or else if id is negative.
87      *
88      * @hide
89      */
Face(@onNull Rect bounds, int score, int id, @NonNull Point leftEyePosition, @NonNull Point rightEyePosition, @NonNull Point mouthPosition)90     public Face(@NonNull Rect bounds, int score, int id,
91             @NonNull Point leftEyePosition, @NonNull Point rightEyePosition,
92             @NonNull Point mouthPosition) {
93         init(bounds, score, id, leftEyePosition, rightEyePosition, mouthPosition);
94     }
95 
96     /**
97      * Create a new face without the optional fields.
98      *
99      * <p>The id, leftEyePosition, rightEyePosition, and mouthPosition are considered optional.
100      * If the id is {@value #ID_UNSUPPORTED} then the leftEyePosition, rightEyePosition, and
101      * mouthPositions are guaranteed to be {@code null}. Otherwise, each of leftEyePosition,
102      * rightEyePosition, and mouthPosition may be independently null or not-null. When devices
103      * report the value of key {@link CaptureResult#STATISTICS_FACE_DETECT_MODE} as
104      * {@link CameraMetadata#STATISTICS_FACE_DETECT_MODE_SIMPLE} in {@link CaptureResult},
105      * the face id of each face is expected to be {@value #ID_UNSUPPORTED}, the leftEyePosition,
106      * rightEyePosition, and mouthPositions are expected to be {@code null} for each face.</p>
107      *
108      * @param bounds Bounds of the face.
109      * @param score Confidence level between {@value #SCORE_MIN}-{@value #SCORE_MAX}.
110      *
111      * @throws IllegalArgumentException
112      *             if bounds is {@code null},
113      *             or if the confidence is not in the range of
114      *             {@value #SCORE_MIN}-{@value #SCORE_MAX}.
115      *
116      * @hide
117      */
Face(@onNull Rect bounds, int score)118     public Face(@NonNull Rect bounds, int score) {
119         init(bounds, score, ID_UNSUPPORTED,
120                 /*leftEyePosition*/null, /*rightEyePosition*/null, /*mouthPosition*/null);
121     }
122 
123     /**
124      * Initialize the object (shared by constructors).
125      */
init(@onNull Rect bounds, int score, int id, @Nullable Point leftEyePosition, @Nullable Point rightEyePosition, @Nullable Point mouthPosition)126     private void init(@NonNull Rect bounds, int score, int id,
127             @Nullable Point leftEyePosition, @Nullable Point rightEyePosition,
128             @Nullable Point mouthPosition) {
129         checkNotNull("bounds", bounds);
130         checkScore(score);
131         checkId(id);
132         if (id == ID_UNSUPPORTED) {
133             checkNull("leftEyePosition", leftEyePosition);
134             checkNull("rightEyePosition", rightEyePosition);
135             checkNull("mouthPosition", mouthPosition);
136         }
137         checkFace(leftEyePosition, rightEyePosition, mouthPosition);
138 
139         mBounds = bounds;
140         mScore = score;
141         mId = id;
142         mLeftEye = leftEyePosition;
143         mRightEye = rightEyePosition;
144         mMouth = mouthPosition;
145     }
146 
147     /**
148      * Bounds of the face.
149      *
150      * <p>A rectangle relative to the sensor's
151      * {@link CameraCharacteristics#SENSOR_INFO_ACTIVE_ARRAY_SIZE}, with (0,0)
152      * representing the top-left corner of the active array rectangle.</p>
153      *
154      * <p>There is no constraints on the Rectangle value other than it
155      * is not-{@code null}.</p>
156      */
getBounds()157     public Rect getBounds() {
158         return mBounds;
159     }
160 
161     /**
162      * The confidence level for the detection of the face.
163      *
164      * <p>The range is {@value #SCORE_MIN} to {@value #SCORE_MAX}.
165      * {@value #SCORE_MAX} is the highest confidence.</p>
166      *
167      * <p>Depending on the device, even very low-confidence faces may be
168      * listed, so applications should filter out faces with low confidence,
169      * depending on the use case. For a typical point-and-shoot camera
170      * application that wishes to display rectangles around detected faces,
171      * filtering out faces with confidence less than half of {@value #SCORE_MAX}
172      * is recommended.</p>
173      *
174      * @see #SCORE_MAX
175      * @see #SCORE_MIN
176      */
177     @IntRange(from = SCORE_MIN, to = SCORE_MAX)
getScore()178     public int getScore() {
179         return mScore;
180     }
181 
182     /**
183      * An unique id per face while the face is visible to the tracker.
184      *
185      * <p>
186      * If the face leaves the field-of-view and comes back, it will get a new
187      * id.</p>
188      *
189      * <p>This is an optional field and may not be supported on all devices.
190      * If the id is {@value #ID_UNSUPPORTED} then the leftEyePosition, rightEyePosition, and
191      * mouthPositions are guaranteed to be {@code null}. Otherwise, each of leftEyePosition,
192      * rightEyePosition, and mouthPosition may be independently null or not-null. When devices
193      * report the value of key {@link CaptureResult#STATISTICS_FACE_DETECT_MODE} as
194      * {@link CameraMetadata#STATISTICS_FACE_DETECT_MODE_SIMPLE} in {@link CaptureResult},
195      * the face id of each face is expected to be {@value #ID_UNSUPPORTED}.</p>
196      *
197      * <p>This value will either be {@value #ID_UNSUPPORTED} or
198      * otherwise greater than {@code 0}.</p>
199      *
200      * @see #ID_UNSUPPORTED
201      */
getId()202     public int getId() {
203         return mId;
204     }
205 
206     /**
207      * The coordinates of the center of the left eye.
208      *
209      * <p>The coordinates are in
210      * the same space as the ones for {@link #getBounds}. This is an
211      * optional field and may not be supported on all devices. If not
212      * supported, the value will always be set to null.
213      * This value will  always be null only if {@link #getId()} returns
214      * {@value #ID_UNSUPPORTED}.</p>
215      *
216      * @return The left eye position, or {@code null} if unknown.
217      */
getLeftEyePosition()218     public Point getLeftEyePosition() {
219         return mLeftEye;
220     }
221 
222     /**
223      * The coordinates of the center of the right eye.
224      *
225      * <p>The coordinates are
226      * in the same space as the ones for {@link #getBounds}.This is an
227      * optional field and may not be supported on all devices. If not
228      * supported, the value will always be set to null.
229      * This value will  always be null only if {@link #getId()} returns
230      * {@value #ID_UNSUPPORTED}.</p>
231      *
232      * @return The right eye position, or {@code null} if unknown.
233      */
getRightEyePosition()234     public Point getRightEyePosition() {
235         return mRightEye;
236     }
237 
238     /**
239      * The coordinates of the center of the mouth.
240      *
241      * <p>The coordinates are in
242      * the same space as the ones for {@link #getBounds}. This is an optional
243      * field and may not be supported on all devices. If not
244      * supported, the value will always be set to null.
245      * This value will  always be null only if {@link #getId()} returns
246      * {@value #ID_UNSUPPORTED}.</p>
247      * </p>
248      *
249      * @return The mouth position, or {@code null} if unknown.
250      */
getMouthPosition()251     public Point getMouthPosition() {
252         return mMouth;
253     }
254 
255     /**
256      * Represent the Face as a string for debugging purposes.
257      */
258     @Override
toString()259     public String toString() {
260         return String.format("{ bounds: %s, score: %s, id: %d, " +
261                 "leftEyePosition: %s, rightEyePosition: %s, mouthPosition: %s }",
262                 mBounds, mScore, mId, mLeftEye, mRightEye, mMouth);
263     }
264 
checkNotNull(String name, Object obj)265     private static void checkNotNull(String name, Object obj) {
266         if (obj == null) {
267             throw new IllegalArgumentException(name + " was required, but it was null");
268         }
269     }
270 
checkNull(String name, Object obj)271     private static void checkNull(String name, Object obj) {
272         if (obj != null) {
273             throw new IllegalArgumentException(name + " was required to be null, but it wasn't");
274         }
275     }
276 
checkScore(int score)277     private static void checkScore(int score) {
278         if (score < SCORE_MIN || score > SCORE_MAX) {
279             throw new IllegalArgumentException("Confidence out of range");
280         }
281     }
282 
checkId(int id)283     private static void checkId(int id) {
284         if (id < 0 && id != ID_UNSUPPORTED) {
285             throw new IllegalArgumentException("Id out of range");
286         }
287     }
288 
checkFace(@ullable Point leftEyePosition, @Nullable Point rightEyePosition, @Nullable Point mouthPosition)289     private static void checkFace(@Nullable Point leftEyePosition,
290             @Nullable Point rightEyePosition, @Nullable Point mouthPosition) {
291         if (leftEyePosition != null || rightEyePosition != null || mouthPosition != null) {
292             if (leftEyePosition == null || rightEyePosition == null || mouthPosition == null) {
293                 throw new IllegalArgumentException("If any of leftEyePosition, rightEyePosition, "
294                         + "or mouthPosition are non-null, all three must be non-null.");
295             }
296         }
297     }
298 
299     /**
300      * Builds a Face object.
301      *
302      * <p>This builder is public to allow for easier application testing by
303      * creating custom object instances. It's not necessary to construct these
304      * objects during normal use of the camera API.</p>
305      */
306     public static final class Builder {
307         private long mBuilderFieldsSet = 0L;
308 
309         private static final long FIELD_BOUNDS = 1 << 1;
310         private static final long FIELD_SCORE = 1 << 2;
311         private static final long FIELD_ID = 1 << 3;
312         private static final long FIELD_LEFT_EYE = 1 << 4;
313         private static final long FIELD_RIGHT_EYE = 1 << 5;
314         private static final long FIELD_MOUTH = 1 << 6;
315         private static final long FIELD_BUILT = 1 << 0;
316 
317         private static final String FIELD_NAME_BOUNDS = "bounds";
318         private static final String FIELD_NAME_SCORE = "score";
319         private static final String FIELD_NAME_LEFT_EYE = "left eye";
320         private static final String FIELD_NAME_RIGHT_EYE = "right eye";
321         private static final String FIELD_NAME_MOUTH = "mouth";
322 
323         private Rect mBounds = null;
324         private int mScore = 0;
325         private int mId = ID_UNSUPPORTED;
326         private Point mLeftEye = null;
327         private Point mRightEye = null;
328         private Point mMouth = null;
329 
Builder()330         public Builder() {
331             // Empty
332         }
333 
Builder(@onNull Face current)334         public Builder(@NonNull Face current) {
335             mBounds = current.mBounds;
336             mScore = current.mScore;
337             mId = current.mId;
338             mLeftEye = current.mLeftEye;
339             mRightEye = current.mRightEye;
340             mMouth = current.mMouth;
341         }
342 
343         /**
344          * Bounds of the face.
345          *
346          * <p>A rectangle relative to the sensor's
347          * {@link CameraCharacteristics#SENSOR_INFO_ACTIVE_ARRAY_SIZE}, with (0,0)
348          * representing the top-left corner of the active array rectangle.</p>
349          *
350          * <p>There is no constraints on the Rectangle value other than it
351          * is not-{@code null}.</p>
352          *
353          * @param bounds Bounds of the face.
354          * @return This builder.
355          */
setBounds(@onNull Rect bounds)356         public @NonNull Builder setBounds(@NonNull Rect bounds) {
357             checkNotUsed();
358             mBuilderFieldsSet |= FIELD_BOUNDS;
359             mBounds = bounds;
360             return this;
361         }
362 
363         /**
364          * The confidence level for the detection of the face.
365          *
366          * <p>The range is {@value #SCORE_MIN} to {@value #SCORE_MAX}.
367          * {@value #SCORE_MAX} is the highest confidence.</p>
368          *
369          * <p>Depending on the device, even very low-confidence faces may be
370          * listed, so applications should filter out faces with low confidence,
371          * depending on the use case. For a typical point-and-shoot camera
372          * application that wishes to display rectangles around detected faces,
373          * filtering out faces with confidence less than half of {@value #SCORE_MAX}
374          * is recommended.</p>
375          *
376          * @see #SCORE_MAX
377          * @see #SCORE_MIN
378          *
379          * @param score Confidence level between {@value #SCORE_MIN}-{@value #SCORE_MAX}.
380          * @return This builder.
381          */
setScore(@ntRangefrom = SCORE_MIN, to = SCORE_MAX) int score)382         public @NonNull Builder setScore(@IntRange(from = SCORE_MIN, to = SCORE_MAX) int score) {
383             checkNotUsed();
384             checkScore(score);
385             mBuilderFieldsSet |= FIELD_SCORE;
386             mScore = score;
387             return this;
388         }
389 
390         /**
391          * An unique id per face while the face is visible to the tracker.
392          *
393          * <p>
394          * If the face leaves the field-of-view and comes back, it will get a new
395          * id.</p>
396          *
397          * <p>This is an optional field and may not be supported on all devices.
398          * If the id is {@value #ID_UNSUPPORTED} then the leftEyePosition, rightEyePosition, and
399          * mouthPositions should be {@code null}. Otherwise, each of leftEyePosition,
400          * rightEyePosition, and mouthPosition may be independently null or not-null. When devices
401          * report the value of key {@link CaptureResult#STATISTICS_FACE_DETECT_MODE} as
402          * {@link CameraMetadata#STATISTICS_FACE_DETECT_MODE_SIMPLE} in {@link CaptureResult},
403          * the face id of each face is expected to be {@value #ID_UNSUPPORTED}.</p>
404          *
405          * <p>This value should either be {@value #ID_UNSUPPORTED} or
406          * otherwise greater than {@code 0}.</p>
407          *
408          * @see #ID_UNSUPPORTED
409          *
410          * @param id A unique ID per face visible to the tracker.
411          * @return This builder.
412          */
setId(int id)413         public @NonNull Builder setId(int id) {
414             checkNotUsed();
415             checkId(id);
416             mBuilderFieldsSet |= FIELD_ID;
417             mId = id;
418             return this;
419         }
420 
421         /**
422          * The coordinates of the center of the left eye.
423          *
424          * <p>The coordinates should be
425          * in the same space as the ones for {@link #setBounds}. This is an
426          * optional field and may not be supported on all devices. If not
427          * supported, the value should always be unset or set to null.
428          * This value should always be null if {@link #setId} is called with
429          * {@value #ID_UNSUPPORTED}.</p>
430          *
431          * @param leftEyePosition The position of the left eye.
432          * @return This builder.
433          */
setLeftEyePosition(@onNull Point leftEyePosition)434         public @NonNull Builder setLeftEyePosition(@NonNull Point leftEyePosition) {
435             checkNotUsed();
436             mBuilderFieldsSet |= FIELD_LEFT_EYE;
437             mLeftEye = leftEyePosition;
438             return this;
439         }
440 
441         /**
442          * The coordinates of the center of the right eye.
443          *
444          * <p>The coordinates should be
445          * in the same space as the ones for {@link #setBounds}.This is an
446          * optional field and may not be supported on all devices. If not
447          * supported, the value should always be set to null.
448          * This value should always be null if {@link #setId} is called with
449          * {@value #ID_UNSUPPORTED}.</p>
450          *
451          * @param rightEyePosition The position of the right eye.
452          * @return This builder.
453          */
setRightEyePosition(@onNull Point rightEyePosition)454         public @NonNull Builder setRightEyePosition(@NonNull Point rightEyePosition) {
455             checkNotUsed();
456             mBuilderFieldsSet |= FIELD_RIGHT_EYE;
457             mRightEye = rightEyePosition;
458             return this;
459         }
460 
461         /**
462          * The coordinates of the center of the mouth.
463          *
464          * <p>The coordinates should be in
465          * the same space as the ones for {@link #setBounds}. This is an optional
466          * field and may not be supported on all devices. If not
467          * supported, the value should always be set to null.
468          * This value should always be null if {@link #setId} is called with
469          * {@value #ID_UNSUPPORTED}.</p>
470          * </p>
471          *
472          * @param mouthPosition The position of the mouth.
473          * @return This builder.
474          */
setMouthPosition(@onNull Point mouthPosition)475         public @NonNull Builder setMouthPosition(@NonNull Point mouthPosition) {
476             checkNotUsed();
477             mBuilderFieldsSet |= FIELD_MOUTH;
478             mMouth = mouthPosition;
479             return this;
480         }
481 
482         /**
483          * Returns an instance of <code>Face</code> created from the fields set
484          * on this builder.
485          *
486          * @return A Face.
487          */
build()488         public @NonNull Face build() {
489             checkNotUsed();
490             checkFieldSet(FIELD_BOUNDS, FIELD_NAME_BOUNDS);
491             checkFieldSet(FIELD_SCORE, FIELD_NAME_SCORE);
492             if (mId == ID_UNSUPPORTED) {
493                 checkIdUnsupportedThenNull(mLeftEye, FIELD_NAME_LEFT_EYE);
494                 checkIdUnsupportedThenNull(mRightEye, FIELD_NAME_RIGHT_EYE);
495                 checkIdUnsupportedThenNull(mMouth, FIELD_NAME_MOUTH);
496             }
497             checkFace(mLeftEye, mRightEye, mMouth);
498 
499             mBuilderFieldsSet |= FIELD_BUILT;
500 
501             return new Face(mBounds, mScore, mId, mLeftEye, mRightEye, mMouth);
502         }
503 
checkNotUsed()504         private void checkNotUsed() {
505             if ((mBuilderFieldsSet & FIELD_BUILT) != 0) {
506                 throw new IllegalStateException(
507                         "This Builder should not be reused. Use a new Builder instance instead");
508             }
509         }
510 
checkFieldSet(long field, String fieldName)511         private void checkFieldSet(long field, String fieldName) {
512             if ((mBuilderFieldsSet & field) == 0) {
513                 throw new IllegalStateException(
514                         "Field \"" + fieldName + "\" must be set before building.");
515             }
516         }
517 
checkIdUnsupportedThenNull(Object obj, String fieldName)518         private void checkIdUnsupportedThenNull(Object obj, String fieldName) {
519             if (obj != null) {
520                 throw new IllegalArgumentException("Field \"" + fieldName
521                         + "\" must be unset or null if id is ID_UNSUPPORTED.");
522             }
523         }
524     }
525 }
526