• After 15+ years, we've made a big change: Android Forums is now Early Bird Club. Learn more here.

Apps Faster SurfaceView Drawing

hacktics

Lurker
I'm developing multitouch drawing pad now, and having problem with latency.

Code:
package com.lge.touchpen.view;

import java.util.Enumeration;
import java.util.Hashtable;

import com.lge.touchpen.R;

import android.content.Context;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.graphics.Canvas;
import android.graphics.Paint;
import android.graphics.Path;
import android.graphics.drawable.BitmapDrawable;
import android.os.Handler;
import android.util.AttributeSet;
import android.util.Log;
import android.view.MotionEvent;
import android.view.SurfaceHolder;
import android.view.SurfaceHolder.Callback;
import android.view.SurfaceView;
import android.widget.ImageView;

public class DrawingView extends SurfaceView implements Callback{
	
	

	public static final float STROKEWIDTH = 3f;
	public static final int MOVETOLERANCE = 10;
	
	class FPSTimer {  
        private int mFPS;  
        private double mSecPerFrame;  
        private double mSecTiming;  
        private long mCur;  
        public FPSTimer(int fps) {  
            mFPS = fps;  
            reset();  
        }  
        public void reset() {  
            mSecPerFrame = 1.0 / mFPS;  
            mCur = System.currentTimeMillis();  
            mSecTiming = 0.0;  
        }  
        public boolean elapsed() {  
            long next = System.currentTimeMillis();  
            long passage_time = next - mCur;  
            mCur = next;  
            mSecTiming += (passage_time/1000.0);  
            mSecTiming -= mSecPerFrame;  
            if (mSecTiming > 0) {  
                if (mSecTiming > mSecPerFrame) {  
                    reset();  
                    return true; // force redraw  
                }  
                return false;  
            }  
            try {  
                Thread.sleep((long)(-mSecTiming * 1000.0));  
            } catch (InterruptedException e) {  
            }  
            return true;  
        }  
    }  
	
	class DrawingThread extends Thread {  

        private SurfaceHolder mSurfaceHolder;  
        private boolean mRun = false;  
        public DrawingThread(SurfaceHolder holder, Context context, Handler handler) {  
            mSurfaceHolder = holder;  
        }  
        public void setRunning(boolean b) {  
            mRun = b;  
        }  
        public void run() {  
            int fps = 0;  
            long cur = System.currentTimeMillis();  
            boolean isdraw = true;  
            FPSTimer timer = new FPSTimer(120);  
            while (mRun) {  
                Canvas c = null;  
                if (isdraw) {  
                    try {  
                        c = mSurfaceHolder.lockCanvas(null);  
                        synchronized (mSurfaceHolder) {  
                           drawMe(c);
                        }  
                        fps++;  
                    } finally {  
                        if (c != null)  
                            mSurfaceHolder.unlockCanvasAndPost(c);  
                    }  
                }  
                isdraw = timer.elapsed();  
                long now = System.currentTimeMillis();  
                if (now - cur > 1000) {  
                    Log.d("KZK", "FPS=" + (fps * 1000 / ((double)now - cur)));  
                    fps = 0;  
                    cur = now;  
                }  
            }  
        }  
    }  
	
	
	private Hashtable<Integer, Path> pathTable = new Hashtable<Integer, Path>();
	private Hashtable<Integer, Integer> pathUpdateTable = new Hashtable<Integer, Integer>();
	private int pathID=0;
	
	//Bulk Paint
	private Paint mPaint;
	private Paint aPaint;
	
	private Paint penPaint;
	private String msg1="";
	private String msg2="";
	private String msg3="";
	
	private int[] color = {0xFF484C47,0xFFB0C49E ,0xFFFFFEF1};
	
	private DrawingThread mThread = null;
	
	
	private long maxRefreshTime = 0;
	
	public DrawingView(Context context, AttributeSet attr)
	{
		super(context, attr);
		SurfaceHolder holder = getHolder();  
	    holder.addCallback(this);
	    mThread = new DrawingThread(holder, context, new Handler());
	    mThread.setPriority(10);
	    
	    setupView();
	}
	
	public DrawingView(Context context) {
		super(context);
        //Setting Pen
		SurfaceHolder holder = getHolder();  
	    holder.addCallback(this);
	    mThread = new DrawingThread(holder, context, new Handler());
	    mThread.setPriority(10);
		
//		mBitmap = Bitmap.createBitmap(320, 480, Bitmap.Config.ARGB_8888);
//        mCanvas = new Canvas(mBitmap);
		
		
		setupView();
	}
	
	
	
	public void setupView()
	{
		//Load Image to canvas
		
//		ImageView imageview = new ImageView(this.getContext()); 
		
		penPaint = new Paint();
		penPaint.setAntiAlias(true);
			// &#44536;&#47532;&#45716; &#49353;&#44628; &#52488;&#44592; &#44050;
		penPaint.setColor(0xFFFFFFFF);
		penPaint.setStyle(Paint.Style.STROKE);
		penPaint.setStrokeJoin(Paint.Join.ROUND);
		penPaint.setStrokeCap(Paint.Cap.ROUND);
		penPaint.setStrokeWidth(1);
        
        mPaint = new Paint();
		mPaint.setAntiAlias(true);
		mPaint.setDither(true);
		// &#44536;&#47532;&#45716; &#49353;&#44628; &#52488;&#44592; &#44050;
		mPaint.setColor(color[1]);
		mPaint.setStyle(Paint.Style.STROKE);
		mPaint.setStrokeJoin(Paint.Join.ROUND);
		mPaint.setStrokeCap(Paint.Cap.ROUND);
		mPaint.setStrokeWidth(STROKEWIDTH);
		mPaint.setTextSize(15f);
		
		aPaint = new Paint();
		aPaint.setAntiAlias(true);
		aPaint.setDither(true);
		// &#44536;&#47532;&#45716; &#49353;&#44628; &#52488;&#44592; &#44050;
		aPaint.setColor(color[2]);
		aPaint.setStyle(Paint.Style.STROKE);
		aPaint.setStrokeJoin(Paint.Join.ROUND);
		aPaint.setStrokeCap(Paint.Cap.ROUND);
		aPaint.setStrokeWidth(20);
		
	}
	
	private Bitmap background = BitmapFactory.decodeResource(this.getResources(), R.drawable.background);
	
	public void drawMe(Canvas canvas) {
//       canvas.drawColor(0xFF999999);
		canvas.drawBitmap(background, 0,0,null);
//       canvas.setBitmap(new BitmapDrawable(this.getContext().getResources().get)));
       
		
//		if( System.currentTimeMillis()-timegap_draw <100)
//		{
			msg1 = "Refresh Rate : " + (System.currentTimeMillis()-timegap_draw) + "ms    max refresh rate : "+maxRefreshTime;
			
			if( (maxRefreshTime < System.currentTimeMillis()-timegap_draw) && (System.currentTimeMillis()-timegap_draw < 100))
				maxRefreshTime = System.currentTimeMillis()-timegap_draw;
			timegap_draw = System.currentTimeMillis();
	        canvas.drawText(msg1, 10, 10, penPaint);
//		}
		
        //if(	isUpdate )
		if(true)
        {
        	long drawing_start = System.currentTimeMillis();
        	Enumeration<Integer> e = pathTable.keys();
            
            while(e.hasMoreElements())
            {
            	int k = e.nextElement();
            	
            	mPaint.setColor(color[k]);
            	
            	canvas.drawPath(pathTable.get(k), mPaint);
//            	canvas.drawCircle(xHolder, yHolder, 5, mPaint);
            }
        	
        	long drawing_end = System.currentTimeMillis();
        	
        	msg3 = "Drawing Time : " + (drawing_end - drawing_start) + "ms";
        	
        	
            canvas.drawText(msg2, 10, 25, penPaint);
            canvas.drawText(msg3, 10, 40, penPaint);
            
        }
		
//		Log.d("Message", msg1);
//		Log.d("Message", msg2);
//		Log.d("Message", msg3);
    }
	
	long timegap_draw=0;	
	
	
	long timegap_touch = 0;
	
	@Override
	public boolean onTouchEvent(MotionEvent event) {
		//Acquire ID of pen and its action
//		final int fullaction=event.getAction())
		final int action=event.getActionMasked();
		final int actionindex = event.getActionIndex();
		final int id = event.getPointerId(actionindex);
		
		if(System.currentTimeMillis()-timegap_touch< 100)
		{
			msg2 = "Touch Refresh Rate : " + (System.currentTimeMillis()-timegap_touch) + "ms";
		}
		
		timegap_touch = System.currentTimeMillis();
		
		//Log.d("action", ""+action+" action index : " + actionindex);
		
		//Distribute events

				
		float x = event.getX(actionindex);
		float y = event.getY(actionindex);
		
		switch(action)
		{
		case MotionEvent.ACTION_POINTER_DOWN:
		case MotionEvent.ACTION_DOWN:
			penDown(id , x, y);
			break;
		case MotionEvent.ACTION_POINTER_UP:
		case MotionEvent.ACTION_UP:
			penUp(id , x, y);
			break;
			
		case MotionEvent.ACTION_MOVE:
			for(int i = 0 ; i < event.getPointerCount() ; i ++)
			{
				int idMove = event.getPointerId(i);
				x = event.getX(i);
				y = event.getY(i);
				
				penMove(idMove , x , y);
			}
			break;
		}
		invalidate();
		return true;
	}
	
	
	private void penDown(int id, float x , float y)
	{		
		pathUpdateTable.put(id, pathID);
		
		//if(!pathTable.containsKey(id))
			pathTable.put(id, new Path());
		pathTable.get(id).moveTo(x,y);
		
		
		
		Log.d("PEN","PEN"+id+" DOWN at x: "+x+" y: "+y);
	}
	
	private void penUp(int id, float x , float y)
	{	
		pathTable.get(id).rewind();	
//		Log.d("PEN","PEN"+id+" UP at x: "+x+" y: "+y);
	}

	
	
	
	private void penMove(int id, float x , float y)
	{
		if(!pathTable.containsKey(id))
			pathTable.put(id, new Path());
	
		
		pathTable.get(id).lineTo(x,y);

//		Log.d("PEN","PEN"+id+" MOVE at x: "+x+" y: "+y);
	}

	@Override
	public void surfaceChanged(SurfaceHolder holder, int format, int width,
			int height) {
		// TODO Auto-generated method stub
		
	}

	@Override
	public void surfaceCreated(SurfaceHolder holder) {
		mThread.setRunning(true);
		mThread.start();
	}

	@Override
	public void surfaceDestroyed(SurfaceHolder holder) {
		boolean retry = true;  
        mThread.setRunning(false);  
        while (retry) {  
            try {  
                mThread.join();  
                retry = false;  
            } catch (InterruptedException e) {  
            }  
        } 
		
	}
}

Currently, I'm re-drawing all the stored paths for each frame. So when the canvas gets more complex, it starts lag pretty bad.

Is there anyway I can get around this?

Thanks in advance.
 
Code:
public void drawMe(Canvas realcanvas) {
		msg1 = "Refresh Rate : " + 1000/(System.currentTimeMillis()-timegap_draw) + "Hz" ;
		timegap_draw = System.currentTimeMillis();
		long drawing_start = System.currentTimeMillis();
		if(bmOverlay == null)
		{
			bmOverlay = Bitmap.createBitmap(realcanvas.getWidth(),realcanvas.getHeight(),Config.ARGB_8888);
		}
		if(bufferCanvas ==null)
		{
			bufferCanvas = new Canvas(bmOverlay);
			bufferCanvas.drawBitmap(background, 0,0,null);
		}

		bufferCanvas.drawBitmap(background, statusZoneRect, statusZoneRect, null);
		
//		if( (maxRefreshTime < System.currentTimeMillis()-timegap_draw) && (System.currentTimeMillis()-timegap_draw < 100))
//			maxRefreshTime = System.currentTimeMillis()-timegap_draw;
		
		bufferCanvas.drawText(msg1, 10, 10, penPaint);
		//		}



		Enumeration<Integer> e = pathTable.keys();

		while(e.hasMoreElements())
		{
			int k = e.nextElement();
			mPaint.setColor(color[k]);
			bufferCanvas.drawPath(pathTable.get(k), mPaint);
		}

		bufferCanvas.drawText(msg2, 10, 25, penPaint);
		bufferCanvas.drawText(msg3, 10, 40, penPaint);
		realcanvas.drawBitmap(bmOverlay, new Matrix(), null);
		long drawing_end = System.currentTimeMillis();
		msg3 = "Drawing Time : " + (drawing_end - drawing_start) + "ms";


	}
Converting to RGB made a little faster
 
Back
Top Bottom