package com.rubenvandeven.emotionhero; import android.content.Context; import android.graphics.Canvas; import android.graphics.Color; import android.graphics.Paint; import android.graphics.Path; import android.graphics.PixelFormat; import android.graphics.Rect; import android.graphics.Typeface; import android.view.SurfaceHolder; import android.view.SurfaceView; import com.affectiva.android.affdex.sdk.detector.Face; import java.util.HashMap; import java.util.Map; /** * Created by ruben on 16/08/16. */ public class ScenarioView extends SurfaceView implements SurfaceHolder.Callback { private PanelThread _thread; private Scenario _scenario; /** * The scorres in this moment, as to draw them on the screen. * Indexes are Emotion ordinals */ private Map currentAttributeScores = new HashMap<>(); //avoid object instantiation on each onDraw private Map emoPaints = new HashMap<>(); private Map emoOutlinePaints = new HashMap<>(); private Map emoScoredPaints = new HashMap<>(); private Paint mainPaint = new Paint(); private Paint attrScorePaint = new Paint(); private Paint linePaint = new Paint(); private Paint scorePaint = new Paint(); private Paint scoreFinishedPaint = new Paint(); // see: http://blog.danielnadeau.io/2012/01/android-canvas-beginners-tutorial.html class PanelThread extends Thread { private SurfaceHolder _surfaceHolder; private ScenarioView _panel; private boolean _run = false; public PanelThread(SurfaceHolder surfaceHolder, ScenarioView panel) { _surfaceHolder = surfaceHolder; _panel = panel; } public void setRunning(boolean run) { //Allow us to stop the thread _run = run; } @Override public void run() { Canvas c; while (_run) { //When setRunning(false) occurs, _run is c = null; //set to false and loop ends, stopping thread try { c = _surfaceHolder.lockCanvas(null); synchronized (_surfaceHolder) { //Insert methods to modify positions of items in onDraw() postInvalidate(); } } finally { if (c != null) { _surfaceHolder.unlockCanvasAndPost(c); } } } } } public ScenarioView(Context context, Scenario s) { super(context); getHolder().addCallback(this); _scenario = s; scorePaint.setColor(Color.YELLOW); scorePaint.setTextSize(50); scorePaint.setTypeface(Typeface.DEFAULT_BOLD); scoreFinishedPaint.setColor(Color.YELLOW); scoreFinishedPaint.setTextSize(100); scoreFinishedPaint.setTypeface(Typeface.DEFAULT_BOLD); //setup paints for drawing mainPaint.setColor(Color.GRAY); linePaint.setColor(Color.GRAY); linePaint.setStrokeWidth(5); attrScorePaint.setColor(Color.DKGRAY); for(Emotion emotion: Emotion.values()) { Paint emoPaint = new Paint(); emoPaint.setTextSize(22); emoPaint.setColor(emotion.getColor()); emoPaints.put(emotion, emoPaint); Paint emoPaintOutline = new Paint(); emoPaintOutline.setColor(emotion.getColor()); emoPaintOutline.setStyle(Paint.Style.STROKE); emoPaintOutline.setStrokeWidth(3); emoOutlinePaints.put(emotion, emoPaintOutline); Paint emoScoredPaint = new Paint(); float darkenFactor = 0.4f; int red = (int) ((Color.red(emotion.getColor()) * darkenFactor)); int green = (int) ((Color.green(emotion.getColor()) * darkenFactor)); int blue = (int) ((Color.blue(emotion.getColor()) * darkenFactor)); int emoLightenedColor = Color.rgb(red, green, blue); emoScoredPaint.setColor(emoLightenedColor); emoScoredPaint.setTextSize(20); emoScoredPaint.setStrokeWidth(2); emoScoredPaint.setStyle(Paint.Style.FILL_AND_STROKE); emoScoredPaints.put(emotion, emoScoredPaint); } } @Override public void onDraw(Canvas canvas) { //do drawing stuff here. float height = canvas.getHeight(); float width = canvas.getWidth(); // bottom at 80%; float bottomline_height = height * (float) 0.8; // draw current hits: float used_width = (float) 0.8; // 0.7: only use center float padding_left = canvas.getWidth() * (1-used_width) / 2; float step_y = (canvas.getWidth() * used_width) / Emotion.values().length; float max_ball_radius = step_y * (float) 0.8 / 2; // canvas.drawLine(0, bottomline_height, width, bottomline_height, linePaint); for(Emotion emotion: Emotion.values()) { float value = 0; if(currentAttributeScores.containsKey(emotion)) { value = currentAttributeScores.get(emotion); } if(value < 5) { value = 5; } float cx = padding_left + (step_y * emotion.ordinal() + step_y / 2); float cy = bottomline_height; canvas.drawCircle(cx, cy, max_ball_radius, mainPaint); canvas.drawCircle(cx, cy, max_ball_radius + 5, emoOutlinePaints.get(emotion)); // canvas.drawCircle(cx, cy, max_ball_radius * value/100, emoPaints.get(emotion.ordinal())); canvas.drawCircle(cx, cy, max_ball_radius * value/100, attrScorePaint); Path emoNamePath = new Path(); emoNamePath.moveTo(cx, cy + max_ball_radius * 1.55f); // more curly line to draw on: // emoNamePath.rCubicTo(width*0.1f,0, width*0.1f, height*0.2f,width*0.2f,height*0.2f); emoNamePath.rLineTo(1000,1000); // canvas.drawText(emotion.toString(), cx, cy + max_ball_radius * (float) 1.3, emoPaint); canvas.drawTextOnPath(emotion.toString(), emoNamePath, 0, 0, emoPaints.get(emotion)); } // Draw targets: float sec_height = height * (float) 0.2; // each second is 20% of canvas height // current moved position float diff_y = sec_height * _scenario.getTime(); for(Scenario.Target target: _scenario.getTargets()) { // Paint targetPaint = (target.hit == null) ? emoPaints.get(target.emotion) : emoScoredPaints.get(target.emotion); float cy = bottomline_height - (target.timestamp * sec_height) + diff_y; float cx = padding_left + (step_y * target.emotion.ordinal() + step_y / 2); canvas.drawCircle(cx, cy, max_ball_radius * target.value/100, emoPaints.get(target.emotion)); if(target.isHit) { Hit hit = _scenario.getHitForTarget(target); canvas.drawText(Integer.toString(Math.round(hit.score)), cx, cy, emoPaints.get(target.emotion)); canvas.drawCircle(cx, cy, max_ball_radius * hit.score/100, emoScoredPaints.get(target.emotion)); // TODO: Use target.hit.face to set Rect src. // if(target.hit.image != null) { // Rect rect = new Rect((int) cx-10, (int) cy-10, (int) cx+10, (int) cy+10); // canvas.drawBitmap(target.hit.image, null, rect, null); // } } // String target_text = target.emotion.toString() + " " + target.value + "%"; // canvas.drawText(target_text, cx, y_pos + diff_y , emoPaint); } // draw the hit middle bottom; Paint usedScorePaint = _scenario.isFinished() ? scoreFinishedPaint : scorePaint; String scoreText = String.format("Total: %1$.0f", _scenario.getHitTotalValue()); Rect scoreTextBounds = new Rect(); usedScorePaint.getTextBounds(scoreText, 0, scoreText.length(), scoreTextBounds); canvas.drawText(scoreText, (width - scoreTextBounds.width()) / 2, height - 10, usedScorePaint); } // TODO: create AttributeScoreCollection class, with this method. public void setCurrentAttributeScoresForFace(Face face) { for(Emotion emotion: Emotion.values()) { currentAttributeScores.put(emotion, emotion.getValueFromFace(face)); } } @Override public void surfaceChanged(SurfaceHolder holder, int format, int width, int height) { } @Override public void surfaceCreated(SurfaceHolder holder) { setWillNotDraw(false); //Allows us to use invalidate() to call onDraw() // this.setZOrderOnTop(true); // performance impact // holder.setFormat(PixelFormat.TRANSPARENT); // holder.setFormat(PixelFormat.TRANSLUCENT); // Log.e("TEST2", "Jaa2!"); _thread = new PanelThread(getHolder(), this); //Start the thread that _thread.setRunning(true); //will make calls to _thread.start(); //onDraw() } @Override public void surfaceDestroyed(SurfaceHolder holder) { try { if(_thread != null) { _thread.setRunning(false); //Tells thread to stop _thread.join(); //Removes thread from mem. } } catch (InterruptedException e) {} } }