Home | History | Annotate | Download | only in devcamera
      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 
     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 
     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      */
     81     public void setFacingAndOrientation(int facing, int orientation) {
     82         mFacing = facing;
     83         mOrientation = orientation;
     84     }
     85 
     86     public void show3AInfo(boolean show) {
     87         mShow3AInfo = show;
     88         this.setVisibility(VISIBLE);
     89         invalidate();
     90     }
     91 
     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 
    129     public void setFieldOfView(float fovLargeDegrees, float fovSmallDegrees) {
    130         mFovLargeDegrees = fovLargeDegrees;
    131         mFovSmallDegrees = fovSmallDegrees;
    132     }
    133 
    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
    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