weknowhowyoufeel/app/src/main/java/com/affectiva/affdexme/DrawingView.java

726 lines
30 KiB
Java

package com.affectiva.affdexme;
import android.content.Context;
import android.content.res.TypedArray;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.Paint;
import android.graphics.PixelFormat;
import android.graphics.PointF;
import android.graphics.PorterDuff;
import android.graphics.Rect;
import android.graphics.Typeface;
import android.os.Process;
import android.support.annotation.NonNull;
import android.util.AttributeSet;
import android.util.Log;
import android.util.Pair;
import android.view.SurfaceHolder;
import android.view.SurfaceView;
import com.affectiva.android.affdex.sdk.detector.Face;
import java.io.FileNotFoundException;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Locale;
import java.util.Map;
/**
* 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 {
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<>();
}
}
//Inner Thread class
class DrawingThread extends Thread {
private final FacesSharer sharer;
private final SurfaceHolder mSurfaceHolder;
private Paint trackingPointsPaint;
private Paint boundingBoxPaint;
private Paint dominantEmotionScoreBarPaint;
private volatile boolean stopFlag = false; //boolean to indicate when thread has been told to stop
private DrawingViewConfig config;
public DrawingThread(SurfaceHolder surfaceHolder, DrawingViewConfig con) {
mSurfaceHolder = surfaceHolder;
//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);
config = con;
sharer = new FacesSharer();
setThickness(config.drawThickness);
}
void setValenceOfBoundingBox(float valence) {
//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.
if (valence > 0) {
float colorScore = ((100f - valence) / 100f) * 255;
boundingBoxPaint.setColor(Color.rgb((int) colorScore, 255, (int) colorScore));
} else {
float colorScore = ((100f + valence) / 100f) * 255;
boundingBoxPaint.setColor(Color.rgb(255, (int) colorScore, (int) colorScore));
}
}
public void stopThread() {
stopFlag = true;
}
public boolean isStopped() {
return stopFlag;
}
//Updates thread with latest faces returned by the onImageResults() event.
public void updatePoints(List<Face> faces, boolean isPointsMirrored) {
synchronized (sharer) {
sharer.facesToDraw.clear();
if (faces != null) {
sharer.facesToDraw.addAll(faces);
}
sharer.isPointsMirrored = isPointsMirrored;
}
}
void setThickness(int thickness) {
boundingBoxPaint.setStrokeWidth(thickness);
}
//Inform thread face detection has stopped, so pending faces are no longer valid.
public void invalidatePoints() {
synchronized (sharer) {
sharer.facesToDraw.clear();
}
}
@Override
public void run() {
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
draw(c);
}
}
} finally {
if (c != null) {
mSurfaceHolder.unlockCanvasAndPost(c);
}
}
}
config = null; //nullify object to avoid memory leak
}
void draw(Canvas c) {
Face nextFaceToDraw;
boolean mirrorPoints;
boolean multiFaceMode;
int index = 0;
synchronized (sharer) {
mirrorPoints = sharer.isPointsMirrored;
multiFaceMode = sharer.facesToDraw.size() > 1;
if (sharer.facesToDraw.isEmpty()) {
nextFaceToDraw = null;
} else {
nextFaceToDraw = sharer.facesToDraw.get(index);
index++;
}
}
while (nextFaceToDraw != null) {
drawFaceAttributes(c, nextFaceToDraw, mirrorPoints, multiFaceMode);
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()) {
//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;
if (mirrorPoints) {
x = (config.imageWidth - point.x) * config.screenToImageRatio;
} else {
x = (point.x) * config.screenToImageRatio;
}
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));
//Draw facial tracking dots.
if (config.isDrawPointsEnabled) {
c.drawCircle(x, y, config.drawThickness, trackingPointsPaint);
}
}
//Draw the bounding box.
if (config.isDrawPointsEnabled) {
drawBoundingBox(c, face, boundingRect);
}
float heightOffset = findNecessaryHeightOffset(boundingRect, face);
//Draw the Appearance markers (gender / glasses)
if (config.isDrawAppearanceMarkersEnabled) {
drawAppearanceMarkers(c, face, boundingRect, heightOffset);
}
//Draw the Emoji markers
if (config.isDrawEmojiMarkersEnabled) {
drawDominantEmoji(c, face, boundingRect, heightOffset);
}
//Only draw the dominant emotion bar in multiface mode
if (isMultiFaceMode) {
drawDominantEmotion(c, face, boundingRect);
}
}
private float findNecessaryHeightOffset(Rect boundingBox, Face face) {
Bitmap appearanceBitmap = getAppearanceBitmapForFace(face);
Bitmap emojiBitmap = getDominantEmojiBitmapForFace(face);
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;
float bitmapHeightOverflow = Math.max(totalHeightRequired - boundingBox.height(), 0);
return bitmapHeightOverflow / 2; // distribute the overflow evenly on both sides of the bounding box
}
private void drawBoundingBox(Canvas c, Face f, Rect boundingBox) {
setValenceOfBoundingBox(f.emotions.getValence());
c.drawRect(boundingBox.left,
boundingBox.top,
boundingBox.right,
boundingBox.bottom,
boundingBoxPaint);
}
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);
}
}
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());
}
return bitmap;
}
private void drawBitmapIfNotRecycled(Canvas c, Bitmap b, float posX, float posY) {
if (!b.isRecycled()) {
c.drawBitmap(b, posX, posY, null);
}
}
private void drawDominantEmoji(Canvas c, Face f, Rect boundingBox, float offset) {
drawEmojiFromCache(c, f.emojis.getDominantEmoji().name(), boundingBox.right + MARGIN, boundingBox.top - offset);
}
private void drawDominantEmotion(Canvas c, Face f, Rect boundingBox) {
Pair<String, Float> dominantMetric = findDominantEmotion(f);
if (dominantMetric == null || dominantMetric.first.isEmpty()) {
return;
}
String emotionText = dominantMetric.first;
String emotionValue = Math.round(dominantMetric.second) + "%";
Rect emotionTextBounds = new Rect();
config.dominantEmotionLabelPaint.getTextBounds(emotionText, 0, emotionText.length(), emotionTextBounds);
Rect emotionValueBounds = new Rect();
config.dominantEmotionValuePaint.getTextBounds(emotionValue, 0, emotionValue.length(), emotionValueBounds);
float drawAtX = boundingBox.exactCenterX();
float drawAtY = boundingBox.bottom + MARGIN + emotionTextBounds.height();
c.drawText(emotionText, drawAtX, drawAtY, config.dominantEmotionLabelPaint);
//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);
//draws the score
c.drawText(emotionValue, drawAtX, drawAtY, config.dominantEmotionValuePaint);
}
private Pair<String, Float> findDominantEmotion(Face f) {
String dominantMetricName = "";
Float dominantMetricValue = 50.0f; // no emotion is dominant unless at least greater than this value
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
if (dominantMetricName.isEmpty()) {
return null;
} else {
return new Pair<>(dominantMetricName, dominantMetricValue);
}
}
void drawEmojiFromCache(Canvas c, String emojiName, float markerPosX, float markerPosY) {
Bitmap emojiBitmap;
try {
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);
}
}
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;
}
}
Bitmap getEmojiBitmapByName(String emojiName) throws FileNotFoundException {
// No bitmap necessary if emoji is unknown
if (emojiName.equals(Face.EMOJI.UNKNOWN.name())) {
return null;
}
String emojiResourceName = emojiName.trim().replace(' ', '_').toLowerCase(Locale.US).concat("_emoji");
String emojiFileName = emojiResourceName + ".png";
//Try to get the emoji from the cache
Bitmap desiredEmojiBitmap = emojiMarkerBitmapToEmojiTypeMap.get(emojiFileName);
if (desiredEmojiBitmap != null) {
//emoji bitmap found in the cache
return desiredEmojiBitmap;
}
//Cache miss, try and load the bitmap from disk
desiredEmojiBitmap = ImageHelper.loadBitmapFromInternalStorage(getContext(), emojiFileName);
if (desiredEmojiBitmap != null) {
//emoji bitmap found in the app storage
//Bitmap loaded, add to cache for subsequent use.
emojiMarkerBitmapToEmojiTypeMap.put(emojiFileName, desiredEmojiBitmap);
return desiredEmojiBitmap;
}
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;
}
}
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;
public void setDominantEmotionLabelPaints(Paint labelPaint, Paint valuePaint) {
dominantEmotionLabelPaint = labelPaint;
dominantEmotionValuePaint = valuePaint;
}
public void setDominantEmotionMetricBarConfig(Paint metricBarPaint, int metricBarWidth) {
dominantEmotionMetricBarPaint = metricBarPaint;
this.metricBarWidth = metricBarWidth;
}
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;
}
}
}