2015-06-23 20:44:24 +00:00
package com.affectiva.affdexme ;
import android.content.Context ;
2015-07-20 19:53:09 +00:00
import android.content.res.TypedArray ;
2015-06-23 20:44:24 +00:00
import android.graphics.Canvas ;
import android.graphics.Color ;
import android.graphics.Paint ;
import android.graphics.PixelFormat ;
import android.graphics.PointF ;
import android.graphics.PorterDuff ;
2015-07-20 19:53:09 +00:00
import android.graphics.Typeface ;
2015-06-23 20:44:24 +00:00
import android.os.Process ;
import android.os.SystemClock ;
import android.util.AttributeSet ;
import android.util.Log ;
import android.view.SurfaceHolder ;
import android.view.SurfaceView ;
2015-07-20 19:53:09 +00:00
import com.affectiva.android.affdex.sdk.detector.Face ;
2015-06-23 20:44:24 +00:00
/ * *
* This class contains a SurfaceView and its own thread that draws to it .
* It is used to display the facial tracking dots over a user ' s face .
* /
public class DrawingView extends SurfaceView implements SurfaceHolder . Callback {
2015-08-27 16:07:37 +00:00
class PointFArraySharer {
PointF [ ] nextPointsToDraw = null ;
}
2015-06-23 20:44:24 +00:00
//Inner Thread class
class DrawingThread extends Thread {
private SurfaceHolder mSurfaceHolder ;
private Paint circlePaint ;
private Paint boxPaint ;
2015-08-27 16:07:37 +00:00
private volatile boolean stopFlag = false ; //boolean to indicate when thread has been told to stop
private final PointFArraySharer sharer ;
2015-07-13 18:17:57 +00:00
private DrawingViewConfig config ;
2015-06-23 20:44:24 +00:00
private final long drawPeriod = 33 ; //draw at 30 fps
2015-07-20 19:53:09 +00:00
private final int TEXT_RAISE = 10 ;
String roll = " " ;
String yaw = " " ;
String pitch = " " ;
String interOcDis = " " ;
2015-07-13 18:17:57 +00:00
public DrawingThread ( SurfaceHolder surfaceHolder , DrawingViewConfig con ) {
2015-06-23 20:44:24 +00:00
mSurfaceHolder = surfaceHolder ;
circlePaint = new Paint ( ) ;
circlePaint . setColor ( Color . WHITE ) ;
boxPaint = new Paint ( ) ;
boxPaint . setColor ( Color . WHITE ) ;
boxPaint . setStyle ( Paint . Style . STROKE ) ;
2015-07-13 18:17:57 +00:00
config = con ;
2015-08-27 16:07:37 +00:00
sharer = new PointFArraySharer ( ) ;
2015-07-13 18:17:57 +00:00
setThickness ( config . drawThickness ) ;
2015-06-23 20:44:24 +00:00
}
2015-08-20 15:24:17 +00:00
void setMetrics ( float roll , float yaw , float pitch , float interOcDis , float valence ) {
//format string for our DrawingView to use when ready
2015-07-20 19:53:09 +00:00
this . roll = String . format ( " %.2f " , roll ) ;
this . yaw = String . format ( " %.2f " , yaw ) ;
this . pitch = String . format ( " %.2f " , pitch ) ;
this . interOcDis = String . format ( " %.2f " , interOcDis ) ;
2015-08-20 15:24:17 +00:00
//prepare the color of the bounding box using the valence score. Red for -100, White for 0, and Green for +100, with linear interpolation in between.
2015-07-20 19:53:09 +00:00
if ( valence > 0 ) {
float colorScore = ( ( 100f - valence ) / 100f ) * 255 ;
2015-06-23 20:44:24 +00:00
boxPaint . setColor ( Color . rgb ( ( int ) colorScore , 255 , ( int ) colorScore ) ) ;
} else {
2015-07-20 19:53:09 +00:00
float colorScore = ( ( 100f + valence ) / 100f ) * 255 ;
2015-07-13 18:17:57 +00:00
boxPaint . setColor ( Color . rgb ( 255 , ( int ) colorScore , ( int ) colorScore ) ) ;
2015-06-23 20:44:24 +00:00
}
2015-07-20 19:53:09 +00:00
2015-06-23 20:44:24 +00:00
}
public void stopThread ( ) {
stopFlag = true ;
}
public boolean isStopped ( ) {
return stopFlag ;
}
//Updates thread with latest points returned by the onImageResults() event.
public void updatePoints ( PointF [ ] pointList ) {
2015-08-27 16:07:37 +00:00
synchronized ( sharer ) {
sharer . nextPointsToDraw = pointList ;
}
2015-06-23 20:44:24 +00:00
}
2015-07-13 18:17:57 +00:00
void setThickness ( int thickness ) {
2015-06-23 20:44:24 +00:00
boxPaint . setStrokeWidth ( thickness ) ;
}
//Inform thread face detection has stopped, so array of points is no longer valid.
public void invalidatePoints ( ) {
2015-08-27 16:07:37 +00:00
synchronized ( sharer ) {
sharer . nextPointsToDraw = null ;
}
2015-06-23 20:44:24 +00:00
}
@Override
public void run ( ) {
android . os . Process . setThreadPriority ( Process . THREAD_PRIORITY_BACKGROUND ) ;
while ( ! stopFlag ) {
/ * *
* We use SurfaceHolder . lockCanvas ( ) to get the canvas that draws to the SurfaceView .
* After we are done drawing , we let go of the canvas using SurfaceHolder . unlockCanvasAndPost ( )
* * * /
Canvas c = null ;
try {
c = mSurfaceHolder . lockCanvas ( ) ;
if ( c ! = null ) {
synchronized ( mSurfaceHolder ) {
c . drawColor ( Color . TRANSPARENT , PorterDuff . Mode . CLEAR ) ; //clear previous dots
2015-08-27 16:07:37 +00:00
draw ( c ) ;
2015-06-23 20:44:24 +00:00
}
}
}
finally {
if ( c ! = null ) {
mSurfaceHolder . unlockCanvasAndPost ( c ) ;
}
}
}
2015-07-13 18:17:57 +00:00
config = null ; //nullify object to avoid memory leak
2015-06-23 20:44:24 +00:00
}
void draw ( Canvas c ) {
2015-08-27 16:07:37 +00:00
PointF [ ] points ;
synchronized ( sharer ) {
if ( sharer . nextPointsToDraw = = null )
return ;
points = sharer . nextPointsToDraw ;
}
2015-06-23 20:44:24 +00:00
//Coordinates around which to draw bounding box.
2015-07-20 19:53:09 +00:00
float leftBx = config . surfaceViewWidth ;
2015-06-23 20:44:24 +00:00
float rightBx = 0 ;
2015-07-20 19:53:09 +00:00
float topBx = config . surfaceViewHeight ;
2015-06-23 20:44:24 +00:00
float botBx = 0 ;
2015-08-20 15:24:17 +00:00
for ( int i = 0 ; i < points . length ; i + + ) {
2015-07-20 19:53:09 +00:00
2015-08-20 15:24:17 +00:00
//transform from the camera coordinates to our screen coordinates
//The camera preview is displayed as a mirror, so X pts have to be mirrored back.
float x = ( config . imageWidth - points [ i ] . x - 1 ) * config . screenToImageRatio ;
float y = ( points [ i ] . y ) * config . screenToImageRatio ;
2015-06-23 20:44:24 +00:00
//We determine the left-most, top-most, right-most, and bottom-most points to draw the bounding box around.
2015-07-20 19:53:09 +00:00
if ( x < leftBx )
leftBx = x ;
if ( x > rightBx )
rightBx = x ;
if ( y < topBx )
topBx = y ;
if ( y > botBx )
botBx = y ;
2015-06-23 20:44:24 +00:00
//Draw facial tracking dots.
2015-07-20 19:53:09 +00:00
if ( config . isDrawPointsEnabled ) {
c . drawCircle ( x , y , config . drawThickness , circlePaint ) ;
}
2015-06-23 20:44:24 +00:00
}
//Draw the bounding box.
2015-07-20 19:53:09 +00:00
if ( config . isDrawPointsEnabled ) {
c . drawRect ( leftBx , topBx , rightBx , botBx , boxPaint ) ;
}
2015-08-20 15:24:17 +00:00
//Draw the measurement metrics, with a dark border around the words to make them visible for users of all skin colors.
2015-07-20 19:53:09 +00:00
if ( config . isDrawMeasurementsEnabled ) {
float centerBx = ( leftBx + rightBx ) / 2 ;
float upperText = topBx - TEXT_RAISE ;
2015-08-20 15:24:17 +00:00
c . drawText ( " PITCH " , centerBx , upperText - config . textSize , config . textBorderPaint ) ;
2015-07-20 19:53:09 +00:00
c . drawText ( " PITCH " , centerBx , upperText - config . textSize , config . textPaint ) ;
2015-08-20 15:24:17 +00:00
c . drawText ( pitch , centerBx , upperText , config . textBorderPaint ) ;
2015-07-20 19:53:09 +00:00
c . drawText ( pitch , centerBx , upperText , config . textPaint ) ;
float upperLeft = centerBx - config . upperTextSpacing ;
2015-08-20 15:24:17 +00:00
c . drawText ( " YAW " , upperLeft , upperText - config . textSize , config . textBorderPaint ) ;
2015-07-20 19:53:09 +00:00
c . drawText ( " YAW " , upperLeft , upperText - config . textSize , config . textPaint ) ;
2015-08-20 15:24:17 +00:00
c . drawText ( yaw , upperLeft , upperText , config . textBorderPaint ) ;
2015-07-20 19:53:09 +00:00
c . drawText ( yaw , upperLeft , upperText , config . textPaint ) ;
float upperRight = centerBx + config . upperTextSpacing ;
2015-08-20 15:24:17 +00:00
c . drawText ( " ROLL " , upperRight , upperText - config . textSize , config . textBorderPaint ) ;
2015-07-20 19:53:09 +00:00
c . drawText ( " ROLL " , upperRight , upperText - config . textSize , config . textPaint ) ;
2015-08-20 15:24:17 +00:00
c . drawText ( roll , upperRight , upperText , config . textBorderPaint ) ;
2015-07-20 19:53:09 +00:00
c . drawText ( roll , upperRight , upperText , config . textPaint ) ;
2015-08-20 15:24:17 +00:00
c . drawText ( " INTEROCULAR DISTANCE " , centerBx , botBx + config . textSize , config . textBorderPaint ) ;
c . drawText ( " INTEROCULAR DISTANCE " , centerBx , botBx + config . textSize , config . textPaint ) ;
c . drawText ( interOcDis , centerBx , botBx + config . textSize * 2 , config . textBorderPaint ) ;
c . drawText ( interOcDis , centerBx , botBx + config . textSize * 2 , config . textPaint ) ;
2015-07-20 19:53:09 +00:00
}
2015-07-13 18:17:57 +00:00
}
}
class DrawingViewConfig {
private int imageHeight = 1 ;
private int imageWidth = 1 ;
private int surfaceViewWidth = 0 ;
private int surfaceViewHeight = 0 ;
private float screenToImageRatio = 0 ;
private int drawThickness = 0 ;
private boolean isDrawPointsEnabled = true ; //by default, have the drawing thread draw tracking dots
2015-07-20 19:53:09 +00:00
private boolean isDrawMeasurementsEnabled = false ;
2015-08-27 16:07:37 +00:00
private boolean isDimensionsNeeded = true ;
2015-07-20 19:53:09 +00:00
private Paint textPaint ;
private int textSize ;
2015-08-20 15:24:17 +00:00
private Paint textBorderPaint ;
2015-07-20 19:53:09 +00:00
private int upperTextSpacing ;
2015-08-20 15:24:17 +00:00
public void setMeasurementMetricConfigs ( Paint textPaint , Paint dropShadowPaint , int textSize , int upperTextSpacing ) {
2015-07-20 19:53:09 +00:00
this . textPaint = textPaint ;
this . textSize = textSize ;
2015-08-20 15:24:17 +00:00
this . textBorderPaint = dropShadowPaint ;
2015-07-20 19:53:09 +00:00
this . upperTextSpacing = upperTextSpacing ;
}
2015-07-13 18:17:57 +00:00
2015-08-27 16:07:37 +00:00
public void updateViewDimensions ( int surfaceViewWidth , int surfaceViewHeight , int imageWidth , int imageHeight ) {
if ( surfaceViewWidth < = 0 | | surfaceViewHeight < = 0 | | imageWidth < = 0 | | imageHeight < = 0 ) {
throw new IllegalArgumentException ( " All dimensions submitted to updateViewDimensions() must be positive " ) ;
2015-07-13 18:17:57 +00:00
}
2015-08-27 16:07:37 +00:00
this . imageWidth = imageWidth ;
this . imageHeight = imageHeight ;
this . surfaceViewWidth = surfaceViewWidth ;
this . surfaceViewHeight = surfaceViewHeight ;
screenToImageRatio = ( float ) surfaceViewWidth / imageWidth ;
isDimensionsNeeded = false ;
2015-07-13 18:17:57 +00:00
}
public void setDrawThickness ( int t ) {
if ( t < = 0 ) {
throw new IllegalArgumentException ( " Thickness must be positive. " ) ;
}
drawThickness = t ;
2015-06-23 20:44:24 +00:00
}
}
//Class variables of DrawingView class
private SurfaceHolder surfaceHolder ;
private DrawingThread drawingThread ; //DrawingThread object
2015-07-20 19:53:09 +00:00
private Typeface typeface ;
2015-07-13 18:17:57 +00:00
private DrawingViewConfig drawingViewConfig ;
2015-06-23 20:44:24 +00:00
private static String LOG_TAG = " AffdexMe " ;
2015-07-13 18:17:57 +00:00
2015-06-23 20:44:24 +00:00
//three constructors required of any custom view
public DrawingView ( Context context ) {
super ( context ) ;
2015-07-20 19:53:09 +00:00
initView ( context , null ) ;
2015-06-23 20:44:24 +00:00
}
public DrawingView ( Context context , AttributeSet attrs ) {
super ( context , attrs ) ;
2015-07-20 19:53:09 +00:00
initView ( context , attrs ) ;
2015-06-23 20:44:24 +00:00
}
public DrawingView ( Context context , AttributeSet attrs , int defStyle ) {
super ( context , attrs , defStyle ) ;
2015-07-20 19:53:09 +00:00
initView ( context , attrs ) ;
2015-06-23 20:44:24 +00:00
}
2015-07-20 19:53:09 +00:00
void initView ( Context context , AttributeSet attrs ) {
2015-06-23 20:44:24 +00:00
surfaceHolder = getHolder ( ) ; //The SurfaceHolder object will be used by the thread to request canvas to draw on SurfaceView
surfaceHolder . setFormat ( PixelFormat . TRANSPARENT ) ; //set to Transparent so this surfaceView does not obscure the one it is overlaying (the one displaying the camera).
surfaceHolder . addCallback ( this ) ; //become a Listener to the three events below that SurfaceView throws
2015-07-20 19:53:09 +00:00
2015-07-13 18:17:57 +00:00
drawingViewConfig = new DrawingViewConfig ( ) ;
2015-07-20 19:53:09 +00:00
//default values
int upperTextSpacing = 15 ;
int textSize = 15 ;
Paint measurementTextPaint = new Paint ( ) ;
measurementTextPaint . setStyle ( Paint . Style . FILL ) ;
measurementTextPaint . setTextAlign ( Paint . Align . CENTER ) ;
Paint dropShadow = new Paint ( ) ;
dropShadow . setColor ( Color . BLACK ) ;
dropShadow . setStyle ( Paint . Style . STROKE ) ;
dropShadow . setTextAlign ( Paint . Align . CENTER ) ;
//load and parse XML attributes
if ( attrs ! = null ) {
TypedArray a = getContext ( ) . obtainStyledAttributes ( attrs , R . styleable . drawing_view_attributes , 0 , 0 ) ;
upperTextSpacing = a . getDimensionPixelSize ( R . styleable . drawing_view_attributes_measurements_upper_spacing , upperTextSpacing ) ;
measurementTextPaint . setColor ( a . getColor ( R . styleable . drawing_view_attributes_measurements_color , Color . WHITE ) ) ;
dropShadow . setColor ( a . getColor ( R . styleable . drawing_view_attributes_measurements_text_border_color , Color . BLACK ) ) ;
dropShadow . setStrokeWidth ( a . getInteger ( R . styleable . drawing_view_attributes_measurements_text_border_thickness , 5 ) ) ;
textSize = a . getDimensionPixelSize ( R . styleable . drawing_view_attributes_measurements_text_size , textSize ) ;
measurementTextPaint . setTextSize ( textSize ) ;
dropShadow . setTextSize ( textSize ) ;
a . recycle ( ) ;
}
2015-08-20 15:24:17 +00:00
drawingViewConfig . setMeasurementMetricConfigs ( measurementTextPaint , dropShadow , textSize , upperTextSpacing ) ;
2015-07-20 19:53:09 +00:00
2015-07-13 18:17:57 +00:00
drawingThread = new DrawingThread ( surfaceHolder , drawingViewConfig ) ;
2015-06-23 20:44:24 +00:00
}
2015-07-20 19:53:09 +00:00
public void setTypeface ( Typeface face ) {
drawingViewConfig . textPaint . setTypeface ( face ) ;
2015-08-20 15:24:17 +00:00
drawingViewConfig . textBorderPaint . setTypeface ( face ) ;
2015-07-20 19:53:09 +00:00
}
2015-06-23 20:44:24 +00:00
@Override
public void surfaceCreated ( SurfaceHolder holder ) {
if ( drawingThread . isStopped ( ) ) {
2015-07-13 18:17:57 +00:00
drawingThread = new DrawingThread ( surfaceHolder , drawingViewConfig ) ;
2015-06-23 20:44:24 +00:00
}
drawingThread . start ( ) ;
}
@Override
public void surfaceChanged ( SurfaceHolder holder , int format , int width , int height ) {
}
@Override
public void surfaceDestroyed ( SurfaceHolder holder ) {
//command thread to stop, and wait until it stops
boolean retry = true ;
drawingThread . stopThread ( ) ;
while ( retry ) {
try {
drawingThread . join ( ) ;
retry = false ;
} catch ( InterruptedException e ) {
Log . e ( LOG_TAG , e . getMessage ( ) ) ;
}
}
}
2015-08-27 16:07:37 +00:00
public boolean isDimensionsNeeded ( ) {
return drawingViewConfig . isDimensionsNeeded ;
2015-06-23 20:44:24 +00:00
}
2015-08-27 16:07:37 +00:00
public void invalidateDimensions ( ) {
drawingViewConfig . isDimensionsNeeded = true ;
2015-07-13 18:17:57 +00:00
}
2015-08-27 16:07:37 +00:00
public void updateViewDimensions ( int surfaceViewWidth , int surfaceViewHeight , int imageWidth , int imageHeight ) {
2015-07-13 18:17:57 +00:00
try {
2015-08-27 16:07:37 +00:00
drawingViewConfig . updateViewDimensions ( surfaceViewWidth , surfaceViewHeight , imageWidth , imageHeight ) ;
2015-07-13 18:17:57 +00:00
} catch ( Exception e ) {
Log . e ( LOG_TAG , e . getMessage ( ) ) ;
}
}
public void setThickness ( int t ) {
drawingViewConfig . setDrawThickness ( t ) ;
try {
drawingThread . setThickness ( t ) ;
} catch ( Exception e ) {
Log . e ( LOG_TAG , e . getMessage ( ) ) ;
}
}
public void setDrawPointsEnabled ( boolean b ) {
drawingViewConfig . isDrawPointsEnabled = b ;
2015-06-23 20:44:24 +00:00
}
public boolean getDrawPointsEnabled ( ) {
2015-07-13 18:17:57 +00:00
return drawingViewConfig . isDrawPointsEnabled ;
2015-06-23 20:44:24 +00:00
}
2015-07-20 19:53:09 +00:00
public void setDrawMeasurementsEnabled ( boolean b ) {
drawingViewConfig . isDrawMeasurementsEnabled = b ;
}
public boolean getDrawMeasurementsEnabled ( ) {
return drawingViewConfig . isDrawMeasurementsEnabled ;
}
2015-08-20 15:24:17 +00:00
public void setMetrics ( float roll , float yaw , float pitch , float interOcDis , float valence ) {
drawingThread . setMetrics ( roll , yaw , pitch , interOcDis , valence ) ;
2015-06-23 20:44:24 +00:00
}
public void updatePoints ( PointF [ ] points ) {
drawingThread . updatePoints ( points ) ;
}
public void invalidatePoints ( ) {
drawingThread . invalidatePoints ( ) ;
}
}