• Home
  • History
  • Annotate
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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