2015-06-23 22:44:24 +02:00
package com.affectiva.affdexme ;
import android.content.Context ;
2015-07-20 21:53:09 +02:00
import android.content.res.TypedArray ;
2015-12-23 07:10:49 +01:00
import android.graphics.Bitmap ;
import android.graphics.BitmapFactory ;
2015-06-23 22:44:24 +02: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-12-23 07:10:49 +01:00
import android.graphics.Rect ;
2015-07-20 21:53:09 +02:00
import android.graphics.Typeface ;
2015-06-23 22:44:24 +02:00
import android.os.Process ;
2015-12-23 07:10:49 +01:00
import android.support.annotation.NonNull ;
2015-06-23 22:44:24 +02:00
import android.util.AttributeSet ;
import android.util.Log ;
2015-12-23 07:10:49 +01:00
import android.util.Pair ;
2015-06-23 22:44:24 +02:00
import android.view.SurfaceHolder ;
import android.view.SurfaceView ;
2015-07-20 21:53:09 +02:00
import com.affectiva.android.affdex.sdk.detector.Face ;
2015-12-23 07:10:49 +01:00
import java.io.FileNotFoundException ;
import java.util.ArrayList ;
import java.util.HashMap ;
import java.util.List ;
import java.util.Locale ;
import java.util.Map ;
2015-06-23 22:44:24 +02: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-12-23 07:10:49 +01:00
private final static String LOG_TAG = " AffdexMe " ;
private final float MARGIN = 4 ;
private Bitmap appearanceMarkerBitmap_genderMale_glassesOn ;
private Bitmap appearanceMarkerBitmap_genderFemale_glassesOn ;
private Bitmap appearanceMarkerBitmap_genderUnknown_glassesOn ;
private Bitmap appearanceMarkerBitmap_genderUnknown_glassesOff ;
private Bitmap appearanceMarkerBitmap_genderMale_glassesOff ;
private Bitmap appearanceMarkerBitmap_genderFemale_glassesOff ;
private Map < String , Bitmap > emojiMarkerBitmapToEmojiTypeMap ;
private SurfaceHolder surfaceHolder ;
private DrawingThread drawingThread ; //DrawingThread object
private DrawingViewConfig drawingViewConfig ;
//three constructors required of any custom view
public DrawingView ( Context context ) {
super ( context ) ;
initView ( ) ;
}
public DrawingView ( Context context , AttributeSet attrs ) {
super ( context , attrs ) ;
initView ( ) ;
}
public DrawingView ( Context context , AttributeSet attrs , int defStyle ) {
super ( context , attrs , defStyle ) ;
initView ( ) ;
}
private static int getDrawable ( @NonNull Context context , @NonNull String name ) {
return context . getResources ( ) . getIdentifier ( name , " drawable " , context . getPackageName ( ) ) ;
}
void initView ( ) {
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 generates
drawingViewConfig = new DrawingViewConfig ( ) ;
//Default values
Paint emotionLabelPaint = new Paint ( ) ;
emotionLabelPaint . setColor ( Color . parseColor ( " #ff8000 " ) ) ; //Orange
emotionLabelPaint . setStyle ( Paint . Style . FILL ) ;
emotionLabelPaint . setTextAlign ( Paint . Align . CENTER ) ;
emotionLabelPaint . setTextSize ( 48 ) ;
Paint emotionValuePaint = new Paint ( ) ;
emotionValuePaint . setColor ( Color . parseColor ( " #514a40 " ) ) ; //Grey
emotionValuePaint . setStyle ( Paint . Style . FILL ) ;
emotionValuePaint . setTextAlign ( Paint . Align . CENTER ) ;
emotionValuePaint . setTextSize ( 48 ) ;
Paint metricBarPaint = new Paint ( ) ;
metricBarPaint . setColor ( Color . GREEN ) ;
metricBarPaint . setStyle ( Paint . Style . FILL ) ;
int metricBarWidth = 150 ;
//load and parse XML attributes
int [ ] emotionLabelAttrs = {
android . R . attr . textStyle , // 0
android . R . attr . textColor , // 1
android . R . attr . shadowColor , // 2
android . R . attr . shadowDy , // 3
android . R . attr . shadowRadius , // 4
android . R . attr . layout_weight , // 5
android . R . attr . textSize } ; // 6
TypedArray a = getContext ( ) . obtainStyledAttributes ( R . style . metricName , emotionLabelAttrs ) ;
if ( a ! = null ) {
emotionLabelPaint . setColor ( a . getColor ( 1 , emotionLabelPaint . getColor ( ) ) ) ;
emotionLabelPaint . setShadowLayer (
a . getFloat ( 4 , 1 . 0f ) ,
a . getFloat ( 3 , 2 . 0f ) , a . getFloat ( 3 , 2 . 0f ) ,
a . getColor ( 2 , Color . BLACK ) ) ;
emotionLabelPaint . setTextSize ( a . getDimensionPixelSize ( 6 , 48 ) ) ;
emotionLabelPaint . setFakeBoldText ( " bold " . equalsIgnoreCase ( a . getString ( 0 ) ) ) ;
a . recycle ( ) ;
}
int [ ] emotionValueAttrs = {
android . R . attr . textColor , // 0
android . R . attr . textSize , // 1
R . styleable . custom_attributes_metricBarLength } ; // 2
a = getContext ( ) . obtainStyledAttributes ( R . style . metricPct , emotionValueAttrs ) ;
if ( a ! = null ) {
emotionValuePaint . setColor ( a . getColor ( 0 , emotionValuePaint . getColor ( ) ) ) ;
emotionValuePaint . setTextSize ( a . getDimensionPixelSize ( 1 , 36 ) ) ;
metricBarWidth = a . getDimensionPixelSize ( 2 , 150 ) ;
a . recycle ( ) ;
}
drawingViewConfig . setDominantEmotionLabelPaints ( emotionLabelPaint , emotionValuePaint ) ;
drawingViewConfig . setDominantEmotionMetricBarConfig ( metricBarPaint , metricBarWidth ) ;
drawingThread = new DrawingThread ( surfaceHolder , drawingViewConfig ) ;
//statically load the emoji bitmaps on-demand and cache
emojiMarkerBitmapToEmojiTypeMap = new HashMap < > ( ) ;
}
public void setTypeface ( Typeface face ) {
drawingViewConfig . dominantEmotionLabelPaint . setTypeface ( face ) ;
drawingViewConfig . dominantEmotionValuePaint . setTypeface ( face ) ;
}
@Override
public void surfaceCreated ( SurfaceHolder holder ) {
if ( drawingThread . isStopped ( ) ) {
drawingThread = new DrawingThread ( surfaceHolder , drawingViewConfig ) ;
}
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 ( ) ) ;
}
}
cleanup ( ) ;
}
public boolean isDimensionsNeeded ( ) {
return drawingViewConfig . isDimensionsNeeded ;
}
public void invalidateDimensions ( ) {
drawingViewConfig . isDimensionsNeeded = true ;
}
public void updateViewDimensions ( int surfaceViewWidth , int surfaceViewHeight , int imageWidth , int imageHeight ) {
try {
drawingViewConfig . updateViewDimensions ( surfaceViewWidth , surfaceViewHeight , imageWidth , imageHeight ) ;
} catch ( IllegalArgumentException e ) {
Log . e ( LOG_TAG , " Attempted to set a dimension with a negative value " , e ) ;
}
}
public void setThickness ( int t ) {
try {
drawingViewConfig . setDrawThickness ( t ) ;
drawingThread . setThickness ( t ) ;
} catch ( IllegalArgumentException e ) {
Log . e ( LOG_TAG , " Attempted to set a thickness with a negative value " , e ) ;
}
}
public boolean getDrawPointsEnabled ( ) {
return drawingViewConfig . isDrawPointsEnabled ;
}
public void setDrawPointsEnabled ( boolean b ) {
drawingViewConfig . isDrawPointsEnabled = b ;
}
public boolean getDrawAppearanceMarkersEnabled ( ) {
return drawingViewConfig . isDrawAppearanceMarkersEnabled ;
}
public void setDrawAppearanceMarkersEnabled ( boolean b ) {
drawingViewConfig . isDrawAppearanceMarkersEnabled = b ;
}
public boolean getDrawEmojiMarkersEnabled ( ) {
return drawingViewConfig . isDrawEmojiMarkersEnabled ;
}
public void setDrawEmojiMarkersEnabled ( boolean b ) {
drawingViewConfig . isDrawEmojiMarkersEnabled = b ;
}
public void updatePoints ( List < Face > faces , boolean isPointsMirrored ) {
drawingThread . updatePoints ( faces , isPointsMirrored ) ;
}
public void invalidatePoints ( ) {
drawingThread . invalidatePoints ( ) ;
}
/ * *
* To be called when this view element is potentially being destroyed
* I . E . when the Activity ' s onPause ( ) gets called .
* /
public void cleanup ( ) {
if ( emojiMarkerBitmapToEmojiTypeMap ! = null ) {
for ( Bitmap bitmap : emojiMarkerBitmapToEmojiTypeMap . values ( ) ) {
bitmap . recycle ( ) ;
}
emojiMarkerBitmapToEmojiTypeMap . clear ( ) ;
}
if ( appearanceMarkerBitmap_genderMale_glassesOn ! = null ) {
appearanceMarkerBitmap_genderMale_glassesOn . recycle ( ) ;
}
if ( appearanceMarkerBitmap_genderFemale_glassesOn ! = null ) {
appearanceMarkerBitmap_genderFemale_glassesOn . recycle ( ) ;
}
if ( appearanceMarkerBitmap_genderUnknown_glassesOn ! = null ) {
appearanceMarkerBitmap_genderUnknown_glassesOn . recycle ( ) ;
}
if ( appearanceMarkerBitmap_genderUnknown_glassesOff ! = null ) {
appearanceMarkerBitmap_genderUnknown_glassesOff . recycle ( ) ;
}
if ( appearanceMarkerBitmap_genderMale_glassesOff ! = null ) {
appearanceMarkerBitmap_genderMale_glassesOff . recycle ( ) ;
}
if ( appearanceMarkerBitmap_genderFemale_glassesOff ! = null ) {
appearanceMarkerBitmap_genderFemale_glassesOff . recycle ( ) ;
}
}
class FacesSharer {
boolean isPointsMirrored ;
List < Face > facesToDraw ;
public FacesSharer ( ) {
isPointsMirrored = false ;
facesToDraw = new ArrayList < > ( ) ;
}
2015-08-27 18:07:37 +02:00
}
2015-06-23 22:44:24 +02:00
//Inner Thread class
2015-12-23 07:10:49 +01:00
class DrawingThread extends Thread {
private final FacesSharer sharer ;
private final SurfaceHolder mSurfaceHolder ;
private Paint trackingPointsPaint ;
private Paint boundingBoxPaint ;
private Paint dominantEmotionScoreBarPaint ;
2015-08-27 18:07:37 +02:00
private volatile boolean stopFlag = false ; //boolean to indicate when thread has been told to stop
2015-07-13 20:17:57 +02:00
private DrawingViewConfig config ;
2015-07-20 21:53:09 +02:00
2015-07-13 20:17:57 +02:00
public DrawingThread ( SurfaceHolder surfaceHolder , DrawingViewConfig con ) {
2015-06-23 22:44:24 +02:00
mSurfaceHolder = surfaceHolder ;
2015-12-23 07:10:49 +01:00
//statically load the Appearance marker bitmaps so they only have to load once
appearanceMarkerBitmap_genderMale_glassesOn = ImageHelper . loadBitmapFromInternalStorage ( getContext ( ) , " male_glasses.png " ) ;
appearanceMarkerBitmap_genderMale_glassesOff = ImageHelper . loadBitmapFromInternalStorage ( getContext ( ) , " male_noglasses.png " ) ;
appearanceMarkerBitmap_genderFemale_glassesOn = ImageHelper . loadBitmapFromInternalStorage ( getContext ( ) , " female_glasses.png " ) ;
appearanceMarkerBitmap_genderFemale_glassesOff = ImageHelper . loadBitmapFromInternalStorage ( getContext ( ) , " female_noglasses.png " ) ;
appearanceMarkerBitmap_genderUnknown_glassesOn = ImageHelper . loadBitmapFromInternalStorage ( getContext ( ) , " unknown_glasses.png " ) ;
appearanceMarkerBitmap_genderUnknown_glassesOff = ImageHelper . loadBitmapFromInternalStorage ( getContext ( ) , " unknown_noglasses.png " ) ;
trackingPointsPaint = new Paint ( ) ;
trackingPointsPaint . setColor ( Color . WHITE ) ;
boundingBoxPaint = new Paint ( ) ;
boundingBoxPaint . setColor ( Color . WHITE ) ;
boundingBoxPaint . setStyle ( Paint . Style . STROKE ) ;
dominantEmotionScoreBarPaint = new Paint ( ) ;
dominantEmotionScoreBarPaint . setColor ( Color . GREEN ) ;
dominantEmotionScoreBarPaint . setStyle ( Paint . Style . STROKE ) ;
2015-06-23 22:44:24 +02:00
2015-07-13 20:17:57 +02:00
config = con ;
2015-12-23 07:10:49 +01:00
sharer = new FacesSharer ( ) ;
2015-07-13 20:17:57 +02:00
setThickness ( config . drawThickness ) ;
2015-06-23 22:44:24 +02:00
}
2015-12-23 07:10:49 +01:00
void setValenceOfBoundingBox ( float valence ) {
2015-08-20 17:24:17 +02: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 21:53:09 +02:00
if ( valence > 0 ) {
2015-12-23 07:10:49 +01:00
float colorScore = ( ( 100f - valence ) / 100f ) * 255 ;
boundingBoxPaint . setColor ( Color . rgb ( ( int ) colorScore , 255 , ( int ) colorScore ) ) ;
2015-06-23 22:44:24 +02:00
} else {
2015-12-23 07:10:49 +01:00
float colorScore = ( ( 100f + valence ) / 100f ) * 255 ;
boundingBoxPaint . setColor ( Color . rgb ( 255 , ( int ) colorScore , ( int ) colorScore ) ) ;
2015-06-23 22:44:24 +02:00
}
}
public void stopThread ( ) {
stopFlag = true ;
}
public boolean isStopped ( ) {
return stopFlag ;
}
2015-12-23 07:10:49 +01:00
//Updates thread with latest faces returned by the onImageResults() event.
public void updatePoints ( List < Face > faces , boolean isPointsMirrored ) {
2015-08-27 18:07:37 +02:00
synchronized ( sharer ) {
2015-12-23 07:10:49 +01:00
sharer . facesToDraw . clear ( ) ;
if ( faces ! = null ) {
sharer . facesToDraw . addAll ( faces ) ;
}
2015-08-28 21:10:43 +02:00
sharer . isPointsMirrored = isPointsMirrored ;
2015-08-27 18:07:37 +02:00
}
2015-06-23 22:44:24 +02:00
}
2015-07-13 20:17:57 +02:00
void setThickness ( int thickness ) {
2015-12-23 07:10:49 +01:00
boundingBoxPaint . setStrokeWidth ( thickness ) ;
2015-06-23 22:44:24 +02:00
}
2015-12-23 07:10:49 +01:00
//Inform thread face detection has stopped, so pending faces are no longer valid.
2015-06-23 22:44:24 +02:00
public void invalidatePoints ( ) {
2015-08-27 18:07:37 +02:00
synchronized ( sharer ) {
2015-12-23 07:10:49 +01:00
sharer . facesToDraw . clear ( ) ;
2015-08-27 18:07:37 +02:00
}
2015-06-23 22:44:24 +02:00
}
@Override
public void run ( ) {
2015-12-23 07:10:49 +01:00
Process . setThreadPriority ( Process . THREAD_PRIORITY_BACKGROUND ) ;
2015-06-23 22:44:24 +02:00
2015-12-23 07:10:49 +01:00
while ( ! stopFlag ) {
2015-06-23 22:44:24 +02:00
/ * *
* 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 ( ) ;
2015-12-23 07:10:49 +01:00
if ( c ! = null ) {
2015-06-23 22:44:24 +02:00
synchronized ( mSurfaceHolder ) {
c . drawColor ( Color . TRANSPARENT , PorterDuff . Mode . CLEAR ) ; //clear previous dots
2015-08-27 18:07:37 +02:00
draw ( c ) ;
2015-06-23 22:44:24 +02:00
}
}
2015-12-23 07:10:49 +01:00
} finally {
if ( c ! = null ) {
2015-06-23 22:44:24 +02:00
mSurfaceHolder . unlockCanvasAndPost ( c ) ;
}
}
}
2015-07-13 20:17:57 +02:00
config = null ; //nullify object to avoid memory leak
2015-06-23 22:44:24 +02:00
}
void draw ( Canvas c ) {
2015-12-23 07:10:49 +01:00
Face nextFaceToDraw ;
2015-08-28 21:10:43 +02:00
boolean mirrorPoints ;
2015-12-23 07:10:49 +01:00
boolean multiFaceMode ;
int index = 0 ;
2015-08-27 18:07:37 +02:00
synchronized ( sharer ) {
2015-08-28 21:10:43 +02:00
mirrorPoints = sharer . isPointsMirrored ;
2015-12-23 07:10:49 +01:00
multiFaceMode = sharer . facesToDraw . size ( ) > 1 ;
if ( sharer . facesToDraw . isEmpty ( ) ) {
nextFaceToDraw = null ;
} else {
nextFaceToDraw = sharer . facesToDraw . get ( index ) ;
index + + ;
}
2015-08-27 18:07:37 +02:00
}
2015-06-23 22:44:24 +02:00
2015-12-23 07:10:49 +01:00
while ( nextFaceToDraw ! = null ) {
2015-06-23 22:44:24 +02:00
2015-12-23 07:10:49 +01:00
drawFaceAttributes ( c , nextFaceToDraw , mirrorPoints , multiFaceMode ) ;
2015-07-20 21:53:09 +02:00
2015-12-23 07:10:49 +01:00
synchronized ( sharer ) {
mirrorPoints = sharer . isPointsMirrored ;
if ( index < sharer . facesToDraw . size ( ) ) {
nextFaceToDraw = sharer . facesToDraw . get ( index ) ;
index + + ;
} else {
nextFaceToDraw = null ;
}
}
}
}
private void drawFaceAttributes ( Canvas c , Face face , boolean mirrorPoints , boolean isMultiFaceMode ) {
//Coordinates around which to draw bounding box.
//Default to an 'inverted' box, where the absolute max and min values of the surface view are inside-out
Rect boundingRect = new Rect ( config . surfaceViewWidth , config . surfaceViewHeight , 0 , 0 ) ;
for ( PointF point : face . getFacePoints ( ) ) {
2015-08-20 17:24:17 +02: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.
2015-08-28 21:10:43 +02:00
float x ;
if ( mirrorPoints ) {
2015-12-23 07:10:49 +01:00
x = ( config . imageWidth - point . x ) * config . screenToImageRatio ;
2015-08-28 21:10:43 +02:00
} else {
2015-12-23 07:10:49 +01:00
x = ( point . x ) * config . screenToImageRatio ;
2015-08-28 21:10:43 +02:00
}
2015-12-23 07:10:49 +01:00
float y = ( point . y ) * config . screenToImageRatio ;
//For some reason I needed to add each point twice to make sure that all the
//points get properly registered in the bounding box.
boundingRect . union ( Math . round ( x ) , Math . round ( y ) ) ;
boundingRect . union ( Math . round ( x ) , Math . round ( y ) ) ;
2015-06-23 22:44:24 +02:00
//Draw facial tracking dots.
2015-07-20 21:53:09 +02:00
if ( config . isDrawPointsEnabled ) {
2015-12-23 07:10:49 +01:00
c . drawCircle ( x , y , config . drawThickness , trackingPointsPaint ) ;
2015-07-20 21:53:09 +02:00
}
2015-06-23 22:44:24 +02:00
}
//Draw the bounding box.
2015-07-20 21:53:09 +02:00
if ( config . isDrawPointsEnabled ) {
2015-12-23 07:10:49 +01:00
drawBoundingBox ( c , face , boundingRect ) ;
2015-07-20 21:53:09 +02:00
}
2015-12-23 07:10:49 +01:00
float heightOffset = findNecessaryHeightOffset ( boundingRect , face ) ;
2015-07-20 21:53:09 +02:00
2015-12-23 07:10:49 +01:00
//Draw the Appearance markers (gender / glasses)
if ( config . isDrawAppearanceMarkersEnabled ) {
drawAppearanceMarkers ( c , face , boundingRect , heightOffset ) ;
}
2015-07-20 21:53:09 +02:00
2015-12-23 07:10:49 +01:00
//Draw the Emoji markers
if ( config . isDrawEmojiMarkersEnabled ) {
drawDominantEmoji ( c , face , boundingRect , heightOffset ) ;
}
2015-07-20 21:53:09 +02:00
2015-12-23 07:10:49 +01:00
//Only draw the dominant emotion bar in multiface mode
if ( isMultiFaceMode ) {
drawDominantEmotion ( c , face , boundingRect ) ;
2015-07-20 21:53:09 +02:00
}
2015-12-23 07:10:49 +01:00
}
2015-07-20 21:53:09 +02:00
2015-12-23 07:10:49 +01:00
private float findNecessaryHeightOffset ( Rect boundingBox , Face face ) {
Bitmap appearanceBitmap = getAppearanceBitmapForFace ( face ) ;
Bitmap emojiBitmap = getDominantEmojiBitmapForFace ( face ) ;
2015-07-20 21:53:09 +02:00
2015-12-23 07:10:49 +01:00
float appearanceBitmapHeight = ( appearanceBitmap ! = null ) ? appearanceBitmap . getHeight ( ) : 0 ;
float emojiBitmapHeight = ( emojiBitmap ! = null ) ? emojiBitmap . getHeight ( ) : 0 ;
float spacingBetween = ( appearanceBitmapHeight > 0 & & emojiBitmapHeight > 0 ) ? MARGIN : 0 ;
float totalHeightRequired = appearanceBitmapHeight + emojiBitmapHeight + spacingBetween ;
2015-07-13 20:17:57 +02:00
2015-12-23 07:10:49 +01:00
float bitmapHeightOverflow = Math . max ( totalHeightRequired - boundingBox . height ( ) , 0 ) ;
2015-07-20 21:53:09 +02:00
2015-12-23 07:10:49 +01:00
return bitmapHeightOverflow / 2 ; // distribute the overflow evenly on both sides of the bounding box
}
2015-07-20 21:53:09 +02:00
2015-12-23 07:10:49 +01:00
private void drawBoundingBox ( Canvas c , Face f , Rect boundingBox ) {
setValenceOfBoundingBox ( f . emotions . getValence ( ) ) ;
c . drawRect ( boundingBox . left ,
boundingBox . top ,
boundingBox . right ,
boundingBox . bottom ,
boundingBoxPaint ) ;
2015-07-20 21:53:09 +02:00
}
2015-07-13 20:17:57 +02:00
2015-12-23 07:10:49 +01:00
private void drawAppearanceMarkers ( Canvas c , Face f , Rect boundingBox , float offset ) {
Bitmap bitmap = getAppearanceBitmapForFace ( f ) ;
if ( bitmap ! = null ) {
drawBitmapIfNotRecycled ( c , bitmap , boundingBox . right + MARGIN , boundingBox . bottom - bitmap . getHeight ( ) + offset ) ;
2015-07-13 20:17:57 +02:00
}
}
2015-12-23 07:10:49 +01:00
private Bitmap getAppearanceBitmapForFace ( Face f ) {
Bitmap bitmap = null ;
switch ( f . appearance . getGender ( ) ) {
case MALE :
if ( Face . GLASSES . YES . equals ( f . appearance . getGlasses ( ) ) ) {
bitmap = appearanceMarkerBitmap_genderMale_glassesOn ;
} else {
bitmap = appearanceMarkerBitmap_genderMale_glassesOff ;
}
break ;
case FEMALE :
if ( Face . GLASSES . YES . equals ( f . appearance . getGlasses ( ) ) ) {
bitmap = appearanceMarkerBitmap_genderFemale_glassesOn ;
} else {
bitmap = appearanceMarkerBitmap_genderFemale_glassesOff ;
}
break ;
case UNKNOWN :
if ( Face . GLASSES . YES . equals ( f . appearance . getGlasses ( ) ) ) {
bitmap = appearanceMarkerBitmap_genderUnknown_glassesOn ;
} else {
bitmap = appearanceMarkerBitmap_genderUnknown_glassesOff ;
}
break ;
default :
Log . e ( LOG_TAG , " Unknown gender: " + f . appearance . getGender ( ) ) ;
2015-07-13 20:17:57 +02:00
}
2015-12-23 07:10:49 +01:00
return bitmap ;
}
2015-07-13 20:17:57 +02:00
2015-12-23 07:10:49 +01:00
private void drawBitmapIfNotRecycled ( Canvas c , Bitmap b , float posX , float posY ) {
if ( ! b . isRecycled ( ) ) {
c . drawBitmap ( b , posX , posY , null ) ;
}
2015-06-23 22:44:24 +02:00
}
2015-12-23 07:10:49 +01:00
private void drawDominantEmoji ( Canvas c , Face f , Rect boundingBox , float offset ) {
drawEmojiFromCache ( c , f . emojis . getDominantEmoji ( ) . name ( ) , boundingBox . right + MARGIN , boundingBox . top - offset ) ;
}
2015-06-23 22:44:24 +02:00
2015-12-23 07:10:49 +01:00
private void drawDominantEmotion ( Canvas c , Face f , Rect boundingBox ) {
Pair < String , Float > dominantMetric = findDominantEmotion ( f ) ;
2015-07-13 20:17:57 +02:00
2015-12-23 07:10:49 +01:00
if ( dominantMetric = = null | | dominantMetric . first . isEmpty ( ) ) {
return ;
}
2015-06-23 22:44:24 +02:00
2015-12-23 07:10:49 +01:00
String emotionText = dominantMetric . first ;
String emotionValue = Math . round ( dominantMetric . second ) + " % " ;
2015-07-20 21:53:09 +02:00
2015-12-23 07:10:49 +01:00
Rect emotionTextBounds = new Rect ( ) ;
config . dominantEmotionLabelPaint . getTextBounds ( emotionText , 0 , emotionText . length ( ) , emotionTextBounds ) ;
2015-07-20 21:53:09 +02:00
2015-12-23 07:10:49 +01:00
Rect emotionValueBounds = new Rect ( ) ;
config . dominantEmotionValuePaint . getTextBounds ( emotionValue , 0 , emotionValue . length ( ) , emotionValueBounds ) ;
2015-07-20 21:53:09 +02:00
2015-12-23 07:10:49 +01:00
float drawAtX = boundingBox . exactCenterX ( ) ;
float drawAtY = boundingBox . bottom + MARGIN + emotionTextBounds . height ( ) ;
c . drawText ( emotionText , drawAtX , drawAtY , config . dominantEmotionLabelPaint ) ;
2015-07-20 21:53:09 +02:00
2015-12-23 07:10:49 +01:00
//draws the colored bar that appears behind our score
drawAtY + = MARGIN + emotionValueBounds . height ( ) ;
int halfWidth = Math . round ( config . metricBarWidth / 200 . 0f * dominantMetric . second ) ;
c . drawRect ( drawAtX - halfWidth , drawAtY - emotionValueBounds . height ( ) , drawAtX + halfWidth , drawAtY , config . dominantEmotionMetricBarPaint ) ;
2015-07-20 21:53:09 +02:00
2015-12-23 07:10:49 +01:00
//draws the score
c . drawText ( emotionValue , drawAtX , drawAtY , config . dominantEmotionValuePaint ) ;
2015-07-20 21:53:09 +02:00
}
2015-12-23 07:10:49 +01:00
private Pair < String , Float > findDominantEmotion ( Face f ) {
String dominantMetricName = " " ;
Float dominantMetricValue = 50 . 0f ; // no emotion is dominant unless at least greater than this value
2015-09-11 21:17:50 +02:00
2015-12-23 07:10:49 +01:00
if ( f . emotions . getAnger ( ) > dominantMetricValue ) {
dominantMetricName = MetricsManager . getCapitalizedName ( MetricsManager . Emotions . ANGER ) ;
dominantMetricValue = f . emotions . getAnger ( ) ;
}
if ( f . emotions . getContempt ( ) > dominantMetricValue ) {
dominantMetricName = MetricsManager . getCapitalizedName ( MetricsManager . Emotions . CONTEMPT ) ;
dominantMetricValue = f . emotions . getContempt ( ) ;
}
if ( f . emotions . getDisgust ( ) > dominantMetricValue ) {
dominantMetricName = MetricsManager . getCapitalizedName ( MetricsManager . Emotions . DISGUST ) ;
dominantMetricValue = f . emotions . getDisgust ( ) ;
}
if ( f . emotions . getFear ( ) > dominantMetricValue ) {
dominantMetricName = MetricsManager . getCapitalizedName ( MetricsManager . Emotions . FEAR ) ;
dominantMetricValue = f . emotions . getFear ( ) ;
}
if ( f . emotions . getJoy ( ) > dominantMetricValue ) {
dominantMetricName = MetricsManager . getCapitalizedName ( MetricsManager . Emotions . JOY ) ;
dominantMetricValue = f . emotions . getJoy ( ) ;
}
if ( f . emotions . getSadness ( ) > dominantMetricValue ) {
dominantMetricName = MetricsManager . getCapitalizedName ( MetricsManager . Emotions . SADNESS ) ;
dominantMetricValue = f . emotions . getSadness ( ) ;
}
if ( f . emotions . getSurprise ( ) > dominantMetricValue ) {
dominantMetricName = MetricsManager . getCapitalizedName ( MetricsManager . Emotions . SURPRISE ) ;
dominantMetricValue = f . emotions . getSurprise ( ) ;
}
// Ignore VALENCE and ENGAGEMENT
2015-06-23 22:44:24 +02:00
2015-12-23 07:10:49 +01:00
if ( dominantMetricName . isEmpty ( ) ) {
return null ;
} else {
return new Pair < > ( dominantMetricName , dominantMetricValue ) ;
}
2015-06-23 22:44:24 +02:00
}
2015-12-23 07:10:49 +01:00
void drawEmojiFromCache ( Canvas c , String emojiName , float markerPosX , float markerPosY ) {
Bitmap emojiBitmap ;
2015-06-23 22:44:24 +02:00
try {
2015-12-23 07:10:49 +01:00
emojiBitmap = getEmojiBitmapByName ( emojiName ) ;
} catch ( FileNotFoundException e ) {
Log . e ( LOG_TAG , " Error, file not found! " , e ) ;
return ;
}
if ( emojiBitmap ! = null ) {
c . drawBitmap ( emojiBitmap , markerPosX , markerPosY , null ) ;
2015-06-23 22:44:24 +02:00
}
}
2015-12-23 07:10:49 +01:00
private Bitmap getDominantEmojiBitmapForFace ( Face f ) {
try {
return getEmojiBitmapByName ( f . emojis . getDominantEmoji ( ) . name ( ) ) ;
} catch ( FileNotFoundException e ) {
Log . e ( LOG_TAG , " Dominant emoji bitmap not available " , e ) ;
return null ;
}
}
2015-06-23 22:44:24 +02:00
2015-12-23 07:10:49 +01:00
Bitmap getEmojiBitmapByName ( String emojiName ) throws FileNotFoundException {
// No bitmap necessary if emoji is unknown
if ( emojiName . equals ( Face . EMOJI . UNKNOWN . name ( ) ) ) {
return null ;
}
2015-07-13 20:17:57 +02:00
2015-12-23 07:10:49 +01:00
String emojiResourceName = emojiName . trim ( ) . replace ( ' ' , '_' ) . toLowerCase ( Locale . US ) . concat ( " _emoji " ) ;
String emojiFileName = emojiResourceName + " .png " ;
2015-07-13 20:17:57 +02:00
2015-12-23 07:10:49 +01:00
//Try to get the emoji from the cache
Bitmap desiredEmojiBitmap = emojiMarkerBitmapToEmojiTypeMap . get ( emojiFileName ) ;
2015-07-13 20:17:57 +02:00
2015-12-23 07:10:49 +01:00
if ( desiredEmojiBitmap ! = null ) {
//emoji bitmap found in the cache
return desiredEmojiBitmap ;
}
2015-06-23 22:44:24 +02:00
2015-12-23 07:10:49 +01:00
//Cache miss, try and load the bitmap from disk
desiredEmojiBitmap = ImageHelper . loadBitmapFromInternalStorage ( getContext ( ) , emojiFileName ) ;
2015-06-23 22:44:24 +02:00
2015-12-23 07:10:49 +01:00
if ( desiredEmojiBitmap ! = null ) {
//emoji bitmap found in the app storage
2015-07-20 21:53:09 +02:00
2015-12-23 07:10:49 +01:00
//Bitmap loaded, add to cache for subsequent use.
emojiMarkerBitmapToEmojiTypeMap . put ( emojiFileName , desiredEmojiBitmap ) ;
2015-06-23 22:44:24 +02:00
2015-12-23 07:10:49 +01:00
return desiredEmojiBitmap ;
}
2015-06-23 22:44:24 +02:00
2015-12-23 07:10:49 +01:00
Log . d ( LOG_TAG , " Emoji not found on disk: " + emojiFileName ) ;
//Still unable to find the file, try to locate the emoji resource
final int resourceId = getDrawable ( getContext ( ) , emojiFileName ) ;
if ( resourceId = = 0 ) {
//unrecognised emoji file name
throw new FileNotFoundException ( " Resource not found for file named: " + emojiFileName ) ;
}
desiredEmojiBitmap = BitmapFactory . decodeResource ( getResources ( ) , resourceId ) ;
if ( desiredEmojiBitmap = = null ) {
//still unable to load the resource from the file
throw new FileNotFoundException ( " Resource id [ " + resourceId + " ] but could not load bitmap: " + emojiFileName ) ;
}
//Bitmap loaded, add to cache for subsequent use.
emojiMarkerBitmapToEmojiTypeMap . put ( emojiFileName , desiredEmojiBitmap ) ;
return desiredEmojiBitmap ;
}
2015-06-23 22:44:24 +02:00
}
2015-12-23 07:10:49 +01:00
class DrawingViewConfig {
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
private boolean isDimensionsNeeded = true ;
private boolean isDrawAppearanceMarkersEnabled = true ; //by default, draw the appearance markers
private boolean isDrawEmojiMarkersEnabled = true ; //by default, draw the dominant emoji markers
private Paint dominantEmotionLabelPaint ;
private Paint dominantEmotionMetricBarPaint ;
private Paint dominantEmotionValuePaint ;
private int metricBarWidth ;
2015-06-23 22:44:24 +02:00
2015-12-23 07:10:49 +01:00
public void setDominantEmotionLabelPaints ( Paint labelPaint , Paint valuePaint ) {
dominantEmotionLabelPaint = labelPaint ;
dominantEmotionValuePaint = valuePaint ;
}
2015-06-23 22:44:24 +02:00
2015-12-23 07:10:49 +01:00
public void setDominantEmotionMetricBarConfig ( Paint metricBarPaint , int metricBarWidth ) {
dominantEmotionMetricBarPaint = metricBarPaint ;
this . metricBarWidth = metricBarWidth ;
}
2015-06-23 22:44:24 +02:00
2015-12-23 07:10:49 +01: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 " ) ;
}
this . imageWidth = imageWidth ;
this . surfaceViewWidth = surfaceViewWidth ;
this . surfaceViewHeight = surfaceViewHeight ;
screenToImageRatio = ( float ) surfaceViewWidth / imageWidth ;
isDimensionsNeeded = false ;
}
public void setDrawThickness ( int t ) {
if ( t < = 0 ) {
throw new IllegalArgumentException ( " Thickness must be positive. " ) ;
}
drawThickness = t ;
}
}
2015-06-23 22:44:24 +02:00
}