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 ;
import org.w3c.dom.Attr ;
import java.lang.reflect.Type ;
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 {
//Inner Thread class
class DrawingThread extends Thread {
private SurfaceHolder mSurfaceHolder ;
private Paint circlePaint ;
private Paint boxPaint ;
private boolean stopFlag = false ; //boolean to indicate when thread has been told to stop
private PointF [ ] nextPointsToDraw = null ; //holds a reference to the most recent set of points returned by CameraDetector, passed in by main thread
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 = " " ;
String gender = " " ;
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 ;
setThickness ( config . drawThickness ) ;
2015-06-23 20:44:24 +00:00
}
2015-07-20 19:53:09 +00:00
void setMetrics ( float roll , float yaw , float pitch , float interOcDis , float valence , Face . Gender gender ) {
this . roll = String . format ( " %.2f " , roll ) ;
this . yaw = String . format ( " %.2f " , yaw ) ;
this . pitch = String . format ( " %.2f " , pitch ) ;
this . interOcDis = String . format ( " %.2f " , interOcDis ) ;
switch ( gender ) {
case Male :
this . gender = " MALE " ;
break ;
case Female :
this . gender = " FEMALE " ;
break ;
default :
this . gender = " UNKNOWN " ;
}
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 ) {
nextPointsToDraw = pointList ;
}
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 ( ) {
nextPointsToDraw = null ;
}
@Override
public void run ( ) {
android . os . Process . setThreadPriority ( Process . THREAD_PRIORITY_BACKGROUND ) ;
while ( ! stopFlag ) {
long startTime = SystemClock . elapsedRealtime ( ) ; //get time at the start of thread loop
/ * *
* 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-07-20 19:53:09 +00:00
if ( ( nextPointsToDraw ! = null ) ) {
2015-06-23 20:44:24 +00:00
draw ( c ) ;
}
}
}
}
finally {
if ( c ! = null ) {
mSurfaceHolder . unlockCanvasAndPost ( c ) ;
}
}
//send thread to sleep so we don't draw faster than the requested 'drawPeriod'.
long sleepTime = drawPeriod - ( SystemClock . elapsedRealtime ( ) - startTime ) ;
try {
if ( sleepTime > 0 ) {
this . sleep ( sleepTime ) ;
}
} catch ( InterruptedException ex ) {
Log . e ( LOG_TAG , ex . getMessage ( ) ) ;
}
}
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 ) {
//Save our own reference to the list of points, in case the previous reference is overwritten by the main thread.
2015-07-20 19:53:09 +00:00
PointF [ ] rawPoints = 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-07-20 19:53:09 +00:00
for ( int i = 0 ; i < rawPoints . length ; i + + ) {
float x = ( config . imageWidth - rawPoints [ i ] . x - 1 ) * config . screenToImageRatio ;
float y = ( rawPoints [ 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.
//The camera preview is displayed as a mirror, so X pts have to be reversed
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 ) ;
}
if ( config . isDrawMeasurementsEnabled ) {
float centerBx = ( leftBx + rightBx ) / 2 ;
float upperText = topBx - TEXT_RAISE ;
c . drawText ( " PITCH " , centerBx , upperText - config . textSize , config . dropShadowPaint ) ;
c . drawText ( " PITCH " , centerBx , upperText - config . textSize , config . textPaint ) ;
c . drawText ( pitch , centerBx , upperText , config . dropShadowPaint ) ;
c . drawText ( pitch , centerBx , upperText , config . textPaint ) ;
float upperLeft = centerBx - config . upperTextSpacing ;
c . drawText ( " YAW " , upperLeft , upperText - config . textSize , config . dropShadowPaint ) ;
c . drawText ( " YAW " , upperLeft , upperText - config . textSize , config . textPaint ) ;
c . drawText ( yaw , upperLeft , upperText , config . dropShadowPaint ) ;
c . drawText ( yaw , upperLeft , upperText , config . textPaint ) ;
float upperRight = centerBx + config . upperTextSpacing ;
c . drawText ( " ROLL " , upperRight , upperText - config . textSize , config . dropShadowPaint ) ;
c . drawText ( " ROLL " , upperRight , upperText - config . textSize , config . textPaint ) ;
c . drawText ( roll , upperRight , upperText , config . dropShadowPaint ) ;
c . drawText ( roll , upperRight , upperText , config . textPaint ) ;
float lowerLeft = centerBx - config . lowerTextSpacing ;
c . drawText ( " GENDER " , lowerLeft , botBx + config . textSize , config . dropShadowPaint ) ;
c . drawText ( " GENDER " , lowerLeft , botBx + config . textSize , config . textPaint ) ;
c . drawText ( gender , lowerLeft , botBx + config . textSize * 2 , config . dropShadowPaint ) ;
c . drawText ( gender , lowerLeft , botBx + config . textSize * 2 , config . textPaint ) ;
float lowerRight = centerBx + config . lowerTextSpacing ;
c . drawText ( " INTEROCULAR DISTANCE " , lowerRight , botBx + config . textSize , config . dropShadowPaint ) ;
c . drawText ( " INTEROCULAR DISTANCE " , lowerRight , botBx + config . textSize , config . textPaint ) ;
c . drawText ( interOcDis , lowerRight , botBx + config . textSize * 2 , config . dropShadowPaint ) ;
c . drawText ( interOcDis , lowerRight , botBx + config . textSize * 2 , config . textPaint ) ;
}
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 isImageDimensionsNeeded = true ;
private boolean isSurfaceViewDimensionsNeeded = true ;
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 ;
private Paint textPaint ;
private int textSize ;
private Paint dropShadowPaint ;
private int upperTextSpacing ;
private int lowerTextSpacing ;
public void setMeasurementMetricConfigs ( Paint textPaint , Paint dropShadowPaint , int textSize , int upperTextSpacing , int lowerTextSpacing ) {
this . textPaint = textPaint ;
this . textSize = textSize ;
this . dropShadowPaint = dropShadowPaint ;
this . upperTextSpacing = upperTextSpacing ;
this . lowerTextSpacing = lowerTextSpacing ;
}
2015-07-13 18:17:57 +00:00
public void updateImageDimensions ( int w , int h ) {
if ( ( w < = 0 ) | | ( h < = 0 ) ) {
throw new IllegalArgumentException ( " Image Dimensions must be positive. " ) ;
}
imageWidth = w ;
imageHeight = h ;
if ( ! isSurfaceViewDimensionsNeeded ) {
screenToImageRatio = ( float ) surfaceViewWidth / ( float ) imageWidth ;
}
isImageDimensionsNeeded = false ;
}
public void updateSurfaceViewDimensions ( int w , int h ) {
if ( ( w < = 0 ) | | ( h < = 0 ) ) {
throw new IllegalArgumentException ( " SurfaceView Dimensions must be positive. " ) ;
}
surfaceViewWidth = w ;
surfaceViewHeight = h ;
if ( ! isImageDimensionsNeeded ) {
screenToImageRatio = ( float ) surfaceViewWidth / ( float ) imageWidth ;
}
isSurfaceViewDimensionsNeeded = false ;
}
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 lowerTextSpacing = 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 ) ;
lowerTextSpacing = a . getDimensionPixelSize ( R . styleable . drawing_view_attributes_measurements_lower_spacing , lowerTextSpacing ) ;
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 ( ) ;
}
drawingViewConfig . setMeasurementMetricConfigs ( measurementTextPaint , dropShadow , textSize , upperTextSpacing , lowerTextSpacing ) ;
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 ) ;
drawingViewConfig . dropShadowPaint . setTypeface ( face ) ;
}
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-07-13 18:17:57 +00:00
public boolean isImageDimensionsNeeded ( ) {
return drawingViewConfig . isImageDimensionsNeeded ;
2015-06-23 20:44:24 +00:00
}
2015-07-13 18:17:57 +00:00
public boolean isSurfaceDimensionsNeeded ( ) {
return drawingViewConfig . isSurfaceViewDimensionsNeeded ;
2015-06-23 20:44:24 +00:00
}
2015-07-13 18:17:57 +00:00
public void invalidateImageDimensions ( ) {
drawingViewConfig . isImageDimensionsNeeded = true ;
2015-06-23 20:44:24 +00:00
}
2015-07-13 18:17:57 +00:00
public void updateImageDimensions ( int w , int h ) {
try {
drawingViewConfig . updateImageDimensions ( w , h ) ;
} catch ( Exception e ) {
Log . e ( LOG_TAG , e . getMessage ( ) ) ;
}
}
public void updateSurfaceViewDimensions ( int w , int h ) {
try {
drawingViewConfig . updateSurfaceViewDimensions ( w , h ) ;
} 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 ;
}
public void setMetrics ( float roll , float yaw , float pitch , float interOcDis , float valence , Face . Gender gender ) {
drawingThread . setMetrics ( roll , yaw , pitch , interOcDis , valence , gender ) ;
2015-06-23 20:44:24 +00:00
}
public void updatePoints ( PointF [ ] points ) {
drawingThread . updatePoints ( points ) ;
}
public void invalidatePoints ( ) {
drawingThread . invalidatePoints ( ) ;
}
}