package com.rubenvandeven.emotionhero; import android.content.Context; import android.graphics.Canvas; import android.graphics.Color; import android.graphics.Matrix; import android.graphics.Paint; import android.graphics.Path; import android.graphics.PixelFormat; import android.graphics.Point; import android.graphics.Rect; import android.graphics.Typeface; import android.util.Log; 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; // bottom at 60%; public static float BAR_POSITION = 0.7f; /** * 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 Map emoNamePaths = new HashMap<>(); private Paint mainPaint = new Paint(); private Paint attrScorePaint = new Paint(); private Paint attrScoreLinePaint = new Paint(); private Paint linePaint = new Paint(); private Paint missedPaint = new Paint(); private Paint scorePaint = new Paint(); private Paint bonusScorePaint = new Paint(); private Paint bonusBarPaint = new Paint(); private Point mLeftTop; private Point mRightTop; private Point mLeftBot; private Point mRightBot; private Matrix matrix; public boolean drawOverlay = false; public boolean noFace = false; public ScenarioView(Context context, Scenario s) { super(context); // setLayerType(LAYER_TYPE_HARDWARE, null); // doesn't seem to make a difference? getHolder().addCallback(this); _scenario = s; Typeface font = Typeface.createFromAsset(context.getAssets(), "unifont-9.0.02.ttf"); scorePaint.setColor(Color.YELLOW); scorePaint.setTextSize(getResources().getDimensionPixelSize(R.dimen.scenario_scorevalue)); scorePaint.setTypeface(Typeface.DEFAULT_BOLD); bonusScorePaint.setColor(Color.rgb(132, 0, 255)); bonusScorePaint.setTextSize(20); bonusScorePaint.setTypeface(Typeface.DEFAULT_BOLD); bonusBarPaint.setColor(Color.rgb(132, 0, 255)); //setup paints for drawing mainPaint.setColor(Color.GRAY); linePaint.setColor(Color.GRAY); linePaint.setStrokeWidth(5); attrScorePaint.setColor(Color.rgb(190,190,190)); missedPaint.setColor(Color.DKGRAY); attrScoreLinePaint.setColor(Color.DKGRAY); attrScoreLinePaint.setStrokeWidth(2); for(Emotion emotion: Emotion.values()) { Paint emoPaint = new Paint(); emoPaint.setTextSize(getResources().getDimensionPixelSize(R.dimen.scenario_emolabel)); // emoPaint.setTypeface(font);//gets ugly when transformed emoPaint.setColor(emotion.getColor()); // emoPaint.setShadowLayer(10,0,0,Color.BLACK); emoPaints.put(emotion, emoPaint); Paint emoPaintOutline = new Paint(); emoPaintOutline.setColor(emotion.getColor()); emoPaintOutline.setStyle(Paint.Style.STROKE); emoPaintOutline.setStrokeWidth(5); // emoPaintOutline.setShadowLayer(10,0,0,Color.BLACK); 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(getResources().getDimensionPixelSize(R.dimen.scenario_emolabel)); emoScoredPaint.setStrokeWidth(2); emoScoredPaint.setStyle(Paint.Style.FILL_AND_STROKE); // emoScoredPaint.setShadowLayer(10,0,0,Color.BLACK); emoScoredPaints.put(emotion, emoScoredPaint); emoNamePaths.put(emotion, new Path()); // was probably here for anti-aliasing? can't quite remember (I should make more notes!) // setLayerType(LAYER_TYPE_SOFTWARE, emoPaint); // setLayerType(LAYER_TYPE_SOFTWARE, emoPaintOutline); // setLayerType(LAYER_TYPE_SOFTWARE, emoScoredPaint); } } @Override public void onDraw(Canvas canvas) { if(drawOverlay) { canvas.drawColor(0x770000FF); } //do drawing stuff here. if (noFace) return; float height = canvas.getHeight(); float width = canvas.getWidth(); Log.i("ScenarioView", "onDraw "+ width+" x " +height); if(_scenario != null) { float hitWidth = width*(_scenario.getHitPercentage()/100); float misWidth = width*(_scenario.getMissedPercentage()/100); float bonusWidth = width*(_scenario.getBonusPercentage()/100); float bonus =_scenario.getBonusTotalValue(); String scoreText = String.format("%1$.0f", _scenario.getHitTotalValue() + bonus); Rect scoreTextBounds = new Rect(); scorePaint.getTextBounds(scoreText, 0, scoreText.length(), scoreTextBounds); canvas.drawText(scoreText, hitWidth, height*0.95f-scoreTextBounds.height(), scorePaint); /*if(bonus > 0) { String bonusText = String.format("+ %1$.0f", bonus); Rect bonusTextBounds = new Rect(); bonusScorePaint.getTextBounds(bonusText, 0, bonusText.length(), bonusTextBounds); canvas.drawText(bonusText, hitWidth+scoreTextBounds.width()+3, height*0.95f-bonusTextBounds.height()-10, bonusScorePaint); }*/ canvas.drawRect(0, height*0.95f, width, height, mainPaint); float stepSize = width / _scenario.targets.size(); for(int i = _scenario.targets.size(); i > 0; i--) { canvas.drawLine(stepSize*i, height*0.95f, stepSize*i, height, attrScoreLinePaint); } canvas.drawRect(0, height*0.95f, hitWidth, height, emoPaints.get(Emotion.JOY)); // paint: yellow canvas.drawRect(hitWidth, height*0.95f, hitWidth+misWidth, height, missedPaint); // canvas.drawRect(0, height*0.95f, bonusWidth, height*0.975f, bonusBarPaint); } if(matrix != null) { canvas.concat(matrix); } // bottom at 60%; float bottomline_height = height * BAR_POSITION; // draw current hits: float used_width = (float) 0.9; // 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 = emoNamePaths.get(emotion); emoNamePath.moveTo(cx - max_ball_radius * 1.25f, cy + max_ball_radius * 1.60f); // 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 if(_scenario != null) { float diff_y = sec_height * _scenario.getTime(); for(Scenario.Target target: _scenario.getTargets()) { // don't draw hit targets if(target.isHit){ continue; } float cy = bottomline_height - (target.timestamp * sec_height) + diff_y; if(cy < (-1 * canvas.getHeight()) ) { continue; } 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)); } } // canvas.restore(); // triggers error that there are less saves then restores? } } // TODO: create AttributeScoreCollection class, with this method. public void setCurrentAttributeScoresForFace(Face face) { for(Emotion emotion: Emotion.values()) { currentAttributeScores.put(emotion, emotion.getValueFromFace(face)); } } /** * Called when surface is created or size is changed (eg. when switching landscape/portrait) * @param holder * @param format * @param width * @param height */ @Override public void surfaceChanged(SurfaceHolder holder, int format, int width, int height) { // define matrix that skews, providing a sense of depth for the targets to come down mLeftTop = new Point((int) (width*0.2), height/2); mRightTop = new Point((int) (width*0.8), height/2); mLeftBot = new Point(0, height); mRightBot = new Point(width, height); matrix = new Matrix(); matrix.setPolyToPoly(new float[]{0, 0, width - 1, 0, 0, height - 1, width - 1, height - 1}, 0, new float[]{mLeftTop.x, mLeftTop.y, mRightTop.x, mRightTop.y, mLeftBot.x, mLeftBot.y, mRightBot.x, mRightBot.y } , 0, 4); } @Override public SurfaceHolder getHolder() { return super.getHolder(); } @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); _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) {} } }