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