erik
erik

Reputation: 4948

performance issue with canvas

I have a drawing class i have created that has some performance issues. I imagine it has to do with the way i am handling the drawing actions and undo/redo functions. Can anyone offer some advice as to how to improve the performance?

public class KNDrawingSurfaceView extends View {

    private static final float MINP = 0.25f;
    private static final float MAXP = 0.75f;
    public Bitmap mBitmap;
    public Canvas mCanvas;
    public Path mPath;
    public Paint mBitmapPaint;   
    float myWidth;
    float myHeight;
    public Paint mPaint;
    public MaskFilter mEmboss;
    public MaskFilter mBlur;
    public ArrayList<Path> paths = new ArrayList<Path>();
    public ArrayList<Paint>paints = new ArrayList<Paint>();
    public ArrayList<Path> undonePaths = new ArrayList<Path>(); 
    public ArrayList<Paint>undonePaints = new ArrayList<Paint>();

    private KNSketchBookActivity _parent;

    public KNDrawingSurfaceView(Context c, float width,float height, KNSketchBookActivity parent) {
        super(c);
        myWidth = width;
        myHeight = height;
        _parent =parent;
        mPaint = new Paint();
        mPaint.setAntiAlias(true);
        mPaint.setDither(true);
        mPaint.setColor(0xFFFF0000);
        mPaint.setStyle(Paint.Style.STROKE);
        mPaint.setStrokeJoin(Paint.Join.ROUND);
        mPaint.setStrokeCap(Paint.Cap.ROUND);
        mPaint.setStrokeWidth(12);

        mEmboss = new EmbossMaskFilter(new float[] { 1, 1, 1 }, 0.4f, 6, 3.5f);

        mBlur = new BlurMaskFilter(8, BlurMaskFilter.Blur.NORMAL);

        mPath = new Path();

        mBitmapPaint = new Paint(Paint.DITHER_FLAG);
    }

    @Override
    protected void onSizeChanged(int w, int h, int oldw, int oldh) {
        super.onSizeChanged(w, h, oldw, oldh);

    }

    @Override
    protected void onDraw(Canvas canvas) {
        mBitmap = Bitmap.createBitmap((int)myWidth, (int)myHeight, Bitmap.Config.ARGB_8888);
        mCanvas = new Canvas(mBitmap);
        Log.v("onDraw:", "curent paths size:"+paths.size());
        canvas.drawColor(Color.TRANSPARENT, PorterDuff.Mode.CLEAR);
        Paint tile = new Paint();

        Bitmap tileImage =  BitmapFactory.decodeResource(getResources(),R.drawable.checkerpattern);
        BitmapShader shader = new BitmapShader(tileImage, Shader.TileMode.REPEAT, Shader.TileMode.REPEAT);
        tile.setShader(shader);
        canvas.drawRect(0, 0, myWidth, myHeight, tile);

        canvas.drawBitmap(mBitmap, 0, 0, mBitmapPaint);

        for (Path p : paths){
            canvas.drawPath(p, mPaint);
        }
        canvas.drawPath(mPath,mPaint);
    }

    public void onClickUndo () { 

        if (paths.size()>0) 
        { 
           undonePaths.add(paths.remove(paths.size()-1));
           undonePaints.add(paints.remove(paints.size()-1));
           invalidate();
         }
        else
        {

        }
         _parent.checkButtonStates();
    }
    public void onClickRedo (){
        if (undonePaths.size()>0) 
        { 
            paths.add(undonePaths.remove(undonePaths.size()-1));
            paints.add(undonePaints.remove(undonePaints.size()-1));
            invalidate();
        } 
        else 
        {

        }
        _parent.checkButtonStates();
     }
    public void onClickClear (){
        paths.clear();
        undonePaths.clear();
        invalidate();
        _parent.checkButtonStates();
     }
    public void saveDrawing(){


        FileOutputStream outStream = null;
        String fileName = "tempTag";
        try {

            outStream = new FileOutputStream("/sdcard/" + fileName + ".png");

            mBitmap.compress(Bitmap.CompressFormat.PNG, 100, outStream);
            outStream.close();

        } catch (FileNotFoundException e) {
            e.printStackTrace();
        } catch (IOException e) {
            e.printStackTrace();
        } finally {
        }


    }
    private float mX, mY;
    private static final float TOUCH_TOLERANCE = 4;

    private void touch_start(float x, float y) {
        undonePaths.clear();
        mPath.reset();
        mPath.moveTo(x, y);
        mX = x;
        mY = y;
    }

    private void touch_move(float x, float y) {
        float dx = Math.abs(x - mX);
        float dy = Math.abs(y - mY);
        if (dx >= TOUCH_TOLERANCE || dy >= TOUCH_TOLERANCE) {
            mPath.quadTo(mX, mY, (x + mX) / 2, (y + mY) / 2);
            mX = x;
            mY = y;
        }
    }

    private void touch_up() {
        mPath.lineTo(mX, mY);

        mCanvas.drawPath(mPath, mPaint);

        paths.add(mPath);
        paints.add(mPaint);
        _parent.checkButtonStates();
        mPath = new Path(); 
    }

    @Override
    public boolean onTouchEvent(MotionEvent event) {
        float x = event.getX();
        float y = event.getY();
        if(x>myWidth){
            x=myWidth;

        }
        if(y>myHeight){
            y=myHeight;

        }
        switch (event.getAction()) {
        case MotionEvent.ACTION_DOWN:
            touch_start(x, y);
            invalidate();
            break;
        case MotionEvent.ACTION_MOVE:
            touch_move(x, y);
            invalidate();
            break;
        case MotionEvent.ACTION_UP:
            touch_up();
            invalidate();
            break;
        }
        return true;
    }
}

please share with me any experience or links you may have dealing with drawing/canvas optimization

Upvotes: 1

Views: 2341

Answers (4)

erik
erik

Reputation: 4948

As you all suggested i pulled all my var initiations out of onDraw and put them in the constructor.

This particular piece is needed to clear the canvas when the user undoes or redoes something they have drawn:

mBitmap = Bitmap.createBitmap((int) myWidth, (int) myHeight, Bitmap.Config.ARGB_8888);
    mCanvas = new Canvas(mBitmap);

so i created a new method that i just call on undo/redo:

public void clearCanvasCache() {

        mBitmap = Bitmap.createBitmap((int) myWidth, (int) myHeight, Bitmap.Config.ARGB_8888);
        mCanvas = new Canvas(mBitmap);
    }

Works great now..

The Whole class:

public class KNDrawingSurfaceView extends View {

    private static final float MINP = 0.25f;

    private static final float MAXP = 0.75f;

    public Bitmap mBitmap;

    public Canvas mCanvas;

    public Path mPath;

    public Paint mBitmapPaint;

    float myWidth;

    float myHeight;

    public Paint mPaint;

    public MaskFilter mEmboss;

    public MaskFilter mBlur;

    public ArrayList<Path> paths = new ArrayList<Path>();

    public ArrayList<Paint> paints = new ArrayList<Paint>();

    public ArrayList<Path> undonePaths = new ArrayList<Path>();

    public ArrayList<Paint> undonePaints = new ArrayList<Paint>();

    private KNSketchBookActivity _parent;

    Paint tile;

    Bitmap tileImage;

    BitmapShader shader;

    public KNDrawingSurfaceView(Context c, float width, float height, KNSketchBookActivity parent) {

        super(c);

        myWidth = width;
        myHeight = height;

        mBitmap = Bitmap.createBitmap((int) myWidth, (int) myHeight, Bitmap.Config.ARGB_8888);
        mCanvas = new Canvas(mBitmap);

        _parent = parent;
        mPaint = new Paint();
        mPaint.setAntiAlias(true);
        mPaint.setDither(true);
        mPaint.setColor(0xFFFF0000);
        mPaint.setStyle(Paint.Style.STROKE);
        mPaint.setStrokeJoin(Paint.Join.ROUND);
        mPaint.setStrokeCap(Paint.Cap.ROUND);
        mPaint.setStrokeWidth(12);

        mEmboss = new EmbossMaskFilter(new float[] { 1, 1, 1 }, 0.4f, 6, 3.5f);

        mBlur = new BlurMaskFilter(8, BlurMaskFilter.Blur.NORMAL);
        tile = new Paint();

        tileImage = BitmapFactory.decodeResource(getResources(), R.drawable.checkerpattern);
        shader = new BitmapShader(tileImage, Shader.TileMode.REPEAT, Shader.TileMode.REPEAT);
        tile.setShader(shader);

        mPath = new Path();

        mBitmapPaint = new Paint(Paint.DITHER_FLAG);
    }

    @Override
    protected void onSizeChanged(int w, int h, int oldw, int oldh) {

        super.onSizeChanged(w, h, oldw, oldh);

    }

    @Override
    protected void onDraw(Canvas canvas) {

        Log.v("onDraw:", "curent paths size:" + paths.size());
        canvas.drawColor(Color.TRANSPARENT, PorterDuff.Mode.CLEAR);

        canvas.drawRect(0, 0, myWidth, myHeight, tile);

        canvas.drawBitmap(mBitmap, 0, 0, mBitmapPaint);

        for (Path p : paths) {
            canvas.drawPath(p, mPaint);
        }
        canvas.drawPath(mPath, mPaint);
    }

    public void onClickUndo() {

        if (paths.size() > 0) {
            undonePaths.add(paths.remove(paths.size() - 1));
            undonePaints.add(paints.remove(paints.size() - 1));
            clearCanvasCache();
            invalidate();
        } else {

        }
        _parent.checkButtonStates();
    }

    public void onClickRedo() {

        if (undonePaths.size() > 0) {
            paths.add(undonePaths.remove(undonePaths.size() - 1));
            paints.add(undonePaints.remove(undonePaints.size() - 1));
            clearCanvasCache();
            invalidate();
        } else {

        }
        _parent.checkButtonStates();
    }

    public void onClickClear() {

        paths.clear();
        undonePaths.clear();
        clearCanvasCache();
        invalidate();
        _parent.checkButtonStates();
    }

    public void saveDrawing() {

        FileOutputStream outStream = null;
        String fileName = "tempTag";
        try {

            outStream = new FileOutputStream("/sdcard/" + fileName + ".png");

            mBitmap.compress(Bitmap.CompressFormat.PNG, 100, outStream);
            outStream.close();

        } catch (FileNotFoundException e) {
            e.printStackTrace();
        } catch (IOException e) {
            e.printStackTrace();
        } finally {
        }

    }

    private float mX, mY;

    private static final float TOUCH_TOLERANCE = 4;

    private void touch_start(float x, float y) {

        undonePaths.clear();
        mPath.reset();
        mPath.moveTo(x, y);
        mX = x;
        mY = y;
    }

    private void touch_move(float x, float y) {

        float dx = Math.abs(x - mX);
        float dy = Math.abs(y - mY);
        if (dx >= TOUCH_TOLERANCE || dy >= TOUCH_TOLERANCE) {
            mPath.quadTo(mX, mY, (x + mX) / 2, (y + mY) / 2);
            mX = x;
            mY = y;
        }
    }

    private void touch_up() {

        mPath.lineTo(mX, mY);

        mCanvas.drawPath(mPath, mPaint);


        paths.add(mPath);
        paints.add(mPaint);
        _parent.checkButtonStates();
        mPath = new Path();
    }

    @Override
    public boolean onTouchEvent(MotionEvent event) {

        if (!_parent.isDrawerOpen()) {
            float x = event.getX();
            float y = event.getY();
            if (x > myWidth) {
                x = myWidth;

            }
            if (y > myHeight) {
                y = myHeight;

            }
            switch (event.getAction()) {
            case MotionEvent.ACTION_DOWN:
                touch_start(x, y);
                invalidate();
                break;
            case MotionEvent.ACTION_MOVE:
                touch_move(x, y);
                invalidate();
                break;
            case MotionEvent.ACTION_UP:
                touch_up();
                invalidate();
                break;
            }
            return true;
        } else {
            return false;
        }
    }

    public void clearCanvasCache() {

        mBitmap = Bitmap.createBitmap((int) myWidth, (int) myHeight, Bitmap.Config.ARGB_8888);
        mCanvas = new Canvas(mBitmap);
    }
}

Upvotes: 1

thiagolr
thiagolr

Reputation: 7027

Do not create or instantiate any variable on your "loop", do that before and outside onDraw and reuse the same variables. This will surely increase performance!

Upvotes: 1

Raghunandan
Raghunandan

Reputation: 133560

Use the below for reference and modify the below according to your requirements.

You have the below in onDraw()

 canvas.drawColor(Color.TRANSPARENT, PorterDuff.Mode.CLEAR);
 // will clear the draw

Everytime you can invalidate() will call onDraw(canvas). Your draw will be refreshed.

I don't know what you are trying to do but i removed the above

Moved the inside onSizeChanged

   mBitmap = Bitmap.createBitmap((int)myWidth, (int)myHeight, Bitmap.Config.ARGB_8888); 
   mCanvas = new Canvas(mBitmap); 

Your code works fine. Tested it on emulator.

 public class MainActivity extends Activity {

DrawingView dv ;


@Override
protected void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    dv = new DrawingView(this);
    setContentView(dv); 
}

public class DrawingView extends View {

    private static final float MINP = 0.25f;
    private static final float MAXP = 0.75f;
    public Bitmap mBitmap,tileImage;
    public Canvas mCanvas;
    public Path mPath;
    public Paint mBitmapPaint;   
    float myWidth;
    float myHeight;
    Paint tile ;
    public Paint mPaint;
    public MaskFilter mEmboss;
    public MaskFilter mBlur;
    public ArrayList<Path> paths = new ArrayList<Path>();
    public ArrayList<Paint>paints = new ArrayList<Paint>();
    public ArrayList<Path> undonePaths = new ArrayList<Path>(); 
    public ArrayList<Paint>undonePaints = new ArrayList<Paint>();
    BitmapShader shader;

    public DrawingView(Context c) {
        super(c);

        mPaint = new Paint();
        mPaint.setAntiAlias(true);
        mPaint.setDither(true);
        mPaint.setColor(Color.RED);
        tile = new Paint();
        mPaint.setStyle(Paint.Style.STROKE);
        mPaint.setStrokeJoin(Paint.Join.ROUND);
        mPaint.setStrokeCap(Paint.Cap.ROUND);
        mPaint.setStrokeWidth(12);
        mEmboss = new EmbossMaskFilter(new float[] { 1, 1, 1 }, 0.4f, 6, 3.5f);
        mBlur = new BlurMaskFilter(8, BlurMaskFilter.Blur.NORMAL);
        mPath = new Path();
        mBitmapPaint = new Paint(Paint.DITHER_FLAG);
        tileImage =  BitmapFactory.decodeResource(getResources(),R.drawable.ic_launcher);
        shader = new BitmapShader(tileImage, Shader.TileMode.REPEAT, Shader.TileMode.REPEAT); 
        tile.setShader(shader);
    }

    @Override
    protected void onSizeChanged(int w, int h, int oldw, int oldh) {
        super.onSizeChanged(w, h, oldw, oldh);
        mBitmap = Bitmap.createBitmap(w, h, Bitmap.Config.ARGB_8888);
        mCanvas = new Canvas(mBitmap);    
        myWidth =w;
        myHeight = h;

    }

    @Override
    protected void onDraw(Canvas canvas) {
        canvas.drawBitmap(mBitmap, 0, 0, mBitmapPaint);
        Log.v("onDraw:", "curent paths size:"+paths.size());
        for (Path p : paths){
            canvas.drawPath(p, mPaint);
        }
        canvas.drawPath(mPath,mPaint);
    }

    public void onClickUndo () { 

        if (paths.size()>0) 
        { 
           undonePaths.add(paths.remove(paths.size()-1));
           undonePaints.add(paints.remove(paints.size()-1));
           invalidate();
         }
        else
        {

        }

    }
    public void onClickRedo (){
        if (undonePaths.size()>0) 
        { 
            paths.add(undonePaths.remove(undonePaths.size()-1));
            paints.add(undonePaints.remove(undonePaints.size()-1));
            invalidate();
        } 
        else 
        {

        }

     }
    public void onClickClear (){
        paths.clear();
        undonePaths.clear();
        invalidate();

     }
    public void saveDrawing(){


        FileOutputStream outStream = null;
        String fileName = "tempTag";
        try {

            outStream = new FileOutputStream("/sdcard/" + fileName + ".png");

            mBitmap.compress(Bitmap.CompressFormat.PNG, 100, outStream);
            outStream.close();

        } catch (FileNotFoundException e) {
            e.printStackTrace();
        } catch (IOException e) {
            e.printStackTrace();
        } finally {
        }


    }
    private float mX, mY;
    private static final float TOUCH_TOLERANCE = 4;

    private void touch_start(float x, float y) {
        undonePaths.clear();
        mPath.reset();
        mPath.moveTo(x, y);
        mX = x;
        mY = y;
    }

    private void touch_move(float x, float y) {
        float dx = Math.abs(x - mX);
        float dy = Math.abs(y - mY);
        if (dx >= TOUCH_TOLERANCE || dy >= TOUCH_TOLERANCE) {
            mPath.quadTo(mX, mY, (x + mX) / 2, (y + mY) / 2);
            mX = x;
            mY = y;
        }
    }

    private void touch_up() {
        mPath.lineTo(mX, mY);

        mCanvas.drawPath(mPath, mPaint);

        paths.add(mPath);
        paints.add(mPaint);

        mPath = new Path(); 
    }

    @Override
    public boolean onTouchEvent(MotionEvent event) {
        float x = event.getX();
        float y = event.getY();
        if(x>myWidth){
            x=myWidth;

        }
        if(y>myHeight){
            y=myHeight;

        }
        switch (event.getAction()) {
        case MotionEvent.ACTION_DOWN:
            touch_start(x, y);
            invalidate();
            break;
        case MotionEvent.ACTION_MOVE:
            touch_move(x, y);
            invalidate();
            break;
        case MotionEvent.ACTION_UP:
            touch_up();
            invalidate();
            break;
        }
        return true;
    }
}

enter image description here

Upvotes: 2

zoroz
zoroz

Reputation: 76

You are recreating 2 bitmaps, paint and BitmapShader in onDraw() method. This is causing your performance issues. try this: - move object creations to constructor. - i think that you can remove this part completely:

mBitmap = Bitmap.createBitmap((int)myWidth, (int)myHeight, Bitmap.Config.ARGB_8888);
        mCanvas = new Canvas(mBitmap);

(if you need to get bitmap from your canvas, create separate method for that and call it when needed)

Upvotes: 1

Related Questions