1 /*
2  * Copyright (C) 2016 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 package com.android.devcamera;
17 
18 import android.content.Context;
19 import android.content.res.Resources;
20 import android.graphics.Canvas;
21 import android.graphics.Paint;
22 import android.graphics.PointF;
23 import android.graphics.RectF;
24 import android.hardware.camera2.CameraCharacteristics;
25 import android.util.AttributeSet;
26 import android.view.View;
27 
28 public class PreviewOverlay extends View {
29     private static final String TAG = "DevCamera_FACE";
30 
31     private boolean mShow3AInfo;
32     private boolean mShowGyroGrid;
33     private int mColor;
34     private int mColor2;
35     private Paint mPaint;
36     private Paint mPaint2;
37 
38     // Rendered data:
39     private NormalizedFace[] mFaces;
40     private float mExposure;
41     private float mLens;
42     private int mAfState;
43     private float mFovLargeDegrees;
44     private float mFovSmallDegrees;
45     private int mFacing = CameraCharacteristics.LENS_FACING_BACK;
46     private int mOrientation = 0;  // degrees
47 
48     float[] mAngles = new float[2];
49 
50 
PreviewOverlay(Context context, AttributeSet attrs)51     public PreviewOverlay(Context context, AttributeSet attrs) {
52         super(context, attrs);
53         Resources res = getResources();
54         mColor = res.getColor(R.color.face_color);
55         mPaint = new Paint();
56         mPaint.setColor(mColor);
57         mPaint.setAntiAlias(true);
58         mPaint.setStyle(Paint.Style.STROKE);
59         mPaint.setStrokeWidth(res.getDimension(R.dimen.face_circle_stroke));
60 
61         mColor2 = res.getColor(R.color.hud_color);
62         mPaint2 = new Paint();
63         mPaint2.setAntiAlias(true);
64         mPaint2.setStyle(Paint.Style.STROKE);
65         mPaint2.setStrokeWidth(res.getDimension(R.dimen.hud_stroke));
66     }
67 
setFrameData(NormalizedFace[] faces, float normExposure, float normLens, int afState)68     public void setFrameData(NormalizedFace[] faces, float normExposure, float normLens, int afState) {
69         mFaces = faces;
70         mExposure = normExposure;
71         mLens = normLens;
72         mAfState = afState;
73         this.setVisibility(VISIBLE);
74         invalidate();
75     }
76 
77     /**
78      * Set the facing of the current camera, for correct coordinate mapping.
79      * One of the CameraCharacteristics.LENS_INFO_FACING_* constants
80      */
setFacingAndOrientation(int facing, int orientation)81     public void setFacingAndOrientation(int facing, int orientation) {
82         mFacing = facing;
83         mOrientation = orientation;
84     }
85 
show3AInfo(boolean show)86     public void show3AInfo(boolean show) {
87         mShow3AInfo = show;
88         this.setVisibility(VISIBLE);
89         invalidate();
90     }
91 
setGyroAngles(float[] angles)92     public void setGyroAngles(float[] angles) {
93         boolean front = (mFacing == CameraCharacteristics.LENS_FACING_BACK);
94         // Rotate gyro coordinates to match camera orientation
95         // Gyro data is always presented in the device native coordinate system, which
96         // is either portrait or landscape depending on device.
97         // (http://developer.android.com/reference/android/hardware/SensorEvent.html)
98         // DevCamera locks itself to portrait, and the camera sensor long edge is always aligned
99         // with the long edge of the device.
100         // mOrientation is the relative orientation of the camera sensor and the device native
101         // orientation, so it can be used to decide if the gyro data is meant to be interpreted
102         // in landscape or portrait and flip coordinates/sign accordingly.
103         // Additionally, front-facing cameras are mirrored, so an additional sign flip is needed.
104         switch (mOrientation) {
105             case 0:
106                 mAngles[1] = -angles[0];
107                 mAngles[0] = angles[1];
108                 break;
109             case 90:
110                 mAngles[0] = angles[0];
111                 mAngles[1] = angles[1];
112                 break;
113             case 180:
114                 mAngles[1] = -angles[0];
115                 mAngles[0] = angles[1];
116                 break;
117             case 270:
118                 mAngles[0] = angles[0];
119                 mAngles[1] = angles[1];
120                 break;
121         }
122         if (mFacing != CameraCharacteristics.LENS_FACING_BACK) {
123             // Reverse sensor readout for front/external facing cameras
124             mAngles[0] = -mAngles[0];
125             mAngles[1] = -mAngles[1];
126         }
127     }
128 
setFieldOfView(float fovLargeDegrees, float fovSmallDegrees)129     public void setFieldOfView(float fovLargeDegrees, float fovSmallDegrees) {
130         mFovLargeDegrees = fovLargeDegrees;
131         mFovSmallDegrees = fovSmallDegrees;
132     }
133 
showGyroGrid(boolean show)134     public void showGyroGrid(boolean show) {
135         mShowGyroGrid = show;
136         this.setVisibility(VISIBLE);
137         invalidate();
138     }
139 
140     private static double SHORT_LOG_EXPOSURE = Math.log10(1000000000 / 10000); // 1/10000 second
141     private static double LONG_LOG_EXPOSURE = Math.log10(1000000000 / 10); // 1/10 second
142     float[] yGridValues = new float[] {
143             (float) ((Math.log10(1000000000 / 30) - SHORT_LOG_EXPOSURE) / (LONG_LOG_EXPOSURE - SHORT_LOG_EXPOSURE)),
144             (float) ((Math.log10(1000000000 / 100) - SHORT_LOG_EXPOSURE) / (LONG_LOG_EXPOSURE - SHORT_LOG_EXPOSURE)),
145             (float) ((Math.log10(1000000000 / 1000) - SHORT_LOG_EXPOSURE) / (LONG_LOG_EXPOSURE - SHORT_LOG_EXPOSURE))};
146 
147     /** Focus states
148      CONTROL_AF_STATE_INACTIVE 0
149      CONTROL_AF_STATE_PASSIVE_SCAN 1
150      CONTROL_AF_STATE_PASSIVE_FOCUSED 2
151      CONTROL_AF_STATE_ACTIVE_SCAN 3
152      CONTROL_AF_STATE_FOCUSED_LOCKED 4
153      CONTROL_AF_STATE_NOT_FOCUSED_LOCKED 5
154      CONTROL_AF_STATE_PASSIVE_UNFOCUSED 6
155      */
156 
157     @Override
onDraw(Canvas canvas)158     protected void onDraw(Canvas canvas) {
159         if (mFaces == null) {
160             return;
161         }
162         float previewW = this.getWidth();
163         float previewH = this.getHeight();
164 
165         // 3A visualizatoins
166         if (mShow3AInfo) {
167 
168             // Draw 3A ball on a rail
169             if (false) {
170                 mPaint2.setStyle(Paint.Style.FILL_AND_STROKE);
171                 mPaint2.setColor(0x33FFFFFF);
172                 canvas.drawRect(0.04f * previewW, 0.03f * previewH, 0.96f * previewW, 0.05f * previewH, mPaint2);
173 
174                 mPaint2.setStyle(Paint.Style.FILL_AND_STROKE);
175                 float x1 = (0.92f * mLens + 0.04f) * previewW;
176                 float y1 = (0.04f) * previewH;
177                 mPaint2.setColor(0xFF000000);
178                 canvas.drawCircle(x1, y1, 20, mPaint2);
179                 mPaint2.setColor(0xFFDDDDDD);
180                 canvas.drawCircle(x1, y1, 18, mPaint2);
181             }
182 
183             // Draw AF center thing
184             mPaint2.setStyle(Paint.Style.FILL_AND_STROKE);
185             float x2 = 0.5f * previewW;
186             float y2 = 0.5f * previewH;
187             mPaint2.setColor(0x990000FF);
188             String text = "NOT IN CAF";
189             if (mAfState == 1) { // passive scan RED
190                 mPaint2.setColor(0x99FF0000);
191                 text = "CAF SCAN";
192             }
193             if (mAfState == 2) { // passive good
194                 mPaint2.setColor(0x9999FF99);
195                 text = "CAF FOCUSED";
196             }
197             if (mAfState == 6) { // passive bad
198                 mPaint2.setColor(0x99FFFFFF);
199                 text = "CAF UNFOCUSED";
200             }
201             canvas.drawCircle(x2, y2, mLens * 0.25f * previewW, mPaint2);
202             mPaint.setColor(0xFFFFFFFF);
203             mPaint.setTextSize(36f);
204             canvas.drawText(text, x2, y2 - mLens * 0.25f * previewW - 7f, mPaint);
205         }
206 
207         // Draw Faces
208         for (NormalizedFace face : mFaces) {
209             RectF r1 = face.bounds;
210             float newY = r1.centerX() * previewH;
211             float newX = (1 - r1.centerY()) * previewW;
212             float dY = r1.width() * previewH;
213             float dX = r1.height() * previewW;
214             float dP = (dX + dY) * 0.045f;
215             RectF newR1 = new RectF(newX - dX * 0.5f, newY - dY * 0.5f, newX + dX * 0.5f, newY + dY * 0.5f);
216             canvas.drawRoundRect(newR1, dP, dP, mPaint);
217 
218             PointF[] p = new PointF[3];
219             p[0] = face.leftEye;
220             p[1] = face.rightEye;
221             p[2] = face.mouth;
222 
223             for (int j = 0; j < 3; j++) {
224                 if (p[j] == null) {
225                     continue;
226                 }
227                 newY = p[j].x * previewH;
228                 newX = (1 - p[j].y) * previewW;
229                 canvas.drawCircle(newX, newY, dP, mPaint);
230             }
231         }
232 
233         // Draw Gyro grid.
234         if (mShowGyroGrid) {
235             float x1, x2, y1, y2;
236 
237             //
238             //                    screen/sensor
239             //                          |
240             // screen/2 = FL tan(FOV/2) |
241             //                          |                             lens
242             //                          |<––––––––––––– FL –––––––––––>()–––––––––> scene @ infinity
243             //                          |
244             //                          |
245             //                          |
246             //
247 
248             float focalLengthH = 0.5f * previewH / (float) Math.tan(Math.toRadians(mFovLargeDegrees) * 0.5);
249             float focalLengthW = 0.5f * previewW / (float) Math.tan(Math.toRadians(mFovSmallDegrees) * 0.5);
250             final double ANGLE_STEP = (float) Math.toRadians(10f);
251             // Draw horizontal lines, with 10 degree spacing.
252             double phase1 = mAngles[0] % ANGLE_STEP;
253             for (double i = -5 * ANGLE_STEP + phase1; i < 5 * ANGLE_STEP; i += ANGLE_STEP) {
254                 x1 = 0;
255                 x2 = previewW;
256                 y1 = y2 = previewH / 2 + focalLengthH * (float) Math.tan(i);
257                 canvas.drawLine(x1, y1, x2, y2, mPaint);
258             }
259             // Draw vertical lines, with 10 degree spacing.
260             double phase2 = mAngles[1] % ANGLE_STEP;
261             for (double i = -5 * ANGLE_STEP + phase2; i < 5 * ANGLE_STEP; i += ANGLE_STEP) {
262                 x1 = x2 = previewW / 2 + focalLengthW * (float) Math.tan(i);
263                 y1 = 0;
264                 y2 = previewH;
265                 canvas.drawLine(x1, y1, x2, y2, mPaint);
266             }
267         }
268 
269         super.onDraw(canvas);
270     }
271 }
272