12

I am making a drawing app, and would like to implement an undo function to remove the immediate previous drawn path.

Coding:

private HashMap<Integer, Path> pathMap; // current Paths being drawn
private HashMap<Integer, Point> previousPointMap; // current Points
private Bitmap bitmap; // drawing area for display or saving
private Canvas bitmapCanvas; // used to draw on bitmap
private Paint paintScreen; // use to draw bitmap onto screen
private Paint paintLine; // used to draw lines onto bitmap

public DrawView(Context context, AttributeSet attrs) 
{
     super(context, attrs); // pass context to View's constructor
     this.context_new=context;

      paintScreen = new Paint(); // used to display bitmap onto screen

      // set the initial display settings for the painted line
      paintLine = new Paint();
      paintLine.setAntiAlias(true); // smooth edges of drawn line
      paintLine.setColor(Color.BLACK); // default color is black
      paintLine.setStyle(Paint.Style.STROKE); // solid line
      paintLine.setStrokeWidth(5); // set the default line width
      paintLine.setStrokeCap(Paint.Cap.ROUND); // rounded line ends
      pathMap = new HashMap<Integer, Path>();
      previousPointMap = new HashMap<Integer, Point>();
} // end DrawView constructor

@Override
protected void onDraw(Canvas canvas)  
{
    canvas.drawBitmap(bitmap, 0, 0, paintScreen); 
    for (Integer key : pathMap.keySet()) 
    canvas.drawPath(pathMap.get(key), paintLine);
} 

// called when the user finishes a touch    
   private void touchEnded(int lineID)   
   {
      Path path = pathMap.get(lineID); // get the corresponding Path
      bitmapCanvas.drawPath(path, paintLine); // draw to bitmapCanvas
      path.reset(); // reset the Path
      rememberLineId = lineID;
   } // end method touch_ended

//undo       
   private void undo()
   {
      Path path = pathMap.get(rememberLineId); // get the corresponding Path
      pathMap.remove(rememberLineId);
      bitmapCanvas.clearPath(path, paintLine); 
      path.reset(); // reset the Path
   } 

Question:

However, it seems there is no bitmapCanvas.clearPath this method? If then how could it be modified?

Codes Amended:

Declarations:

private Bitmap bitmap; // drawing area for display or saving
private Canvas bitmapCanvas; // used to draw on bitmap
private Paint paintScreen; // use to draw bitmap onto screen
private Paint paintLine; // used to draw lines onto bitmap
private HashMap<Integer, Path> pathMap; // current Paths being drawn
private HashMap<Integer, Point> previousPointMap; // current Points

private Bitmap bitmapBackup; 

OnSizeChanged

@Override
public void onSizeChanged(int w, int h, int oldW, int oldH)
{ 
   super.onSizeChanged(w, h, oldW, oldH);
   DoodlzViewWidth = w;    
   DoodlzViewHeight = h;

   bitmapBackup = Bitmap.createBitmap(getWidth(), DoodlzViewHeight, Bitmap.Config.ARGB_8888);     
   bitmap =       Bitmap.createBitmap(getWidth(), DoodlzViewHeight, Bitmap.Config.ARGB_8888);

   bitmapCanvas = new Canvas(bitmap);
   bitmap      .eraseColor(Color.WHITE); // erase the BitMap with white 
   bitmapBackup.eraseColor(Color.WHITE); 
} 

FirsttoBackup method, will invoke when the below TouchedStart performs

public void firsttobackup()
{ 
   bitmapBackup=bitmap;
       Toast message = Toast.makeText(getContext(), "backuped 123", Toast.LENGTH_SHORT);
   message.show(); //THIS TOAST CAN BE SUCESSFULLY PRESENTED when touching screen starting to draw
} 

OnDraw

@Override
protected void onDraw(Canvas canvas) 
{
canvas.drawBitmap(bitmap, 0, 0, paintScreen); 
    for (Integer key : pathMap.keySet()) 
     canvas.drawPath(pathMap.get(key), paintLine); 

}

OnTouchEvent

@Override
public boolean onTouchEvent(MotionEvent event) 
{        
  int action = event.getActionMasked(); // event type 
  int actionIndex = event.getActionIndex(); // pointer (i.e., finger)

  if (action == MotionEvent.ACTION_DOWN || action == MotionEvent.ACTION_POINTER_DOWN) 
  {
      firsttobackup(); //TOAST CAN SHOW "BACKUP 123"

      touchStarted(event.getX(actionIndex), event.getY(actionIndex), 
        event.getPointerId(actionIndex));
  }

Undo: user pressing the undo button will invoke this

public void undo()
{
  bitmap = bitmapBackup.copy(Bitmap.Config.ARGB_8888, true);
  bitmapCanvas = new Canvas(bitmap);        
}  

Question revised:

A method firsttobackup() is used now such that bitmapBackup would set = bitmap when executing OnTouchEvent touchStarted. I have put a toast in it and it is successfully to be presented "backup 123" when user press the screen and started to draw.

When user clicks undo button, it will invoke the undo method, but now pressing the undo button, no action can be seen...why?

Ajeett
  • 814
  • 2
  • 7
  • 18
pearmak
  • 4,979
  • 15
  • 64
  • 122

4 Answers4

7

I think the easiest way for doing that is having 2 bitmaps (1 additional backup bitmap for restoring previous state).

You need to save previous state of a bitmap before you start new drawings.

Here is how I would modify your code:

  private HashMap<Integer, Path> pathMap; // current Paths being drawn
  private HashMap<Integer, Point> previousPointMap; // current Points
  private Bitmap bitmap; // drawing area for display or saving
  private Bitmap bitmapBackup; 
  private Canvas bitmapCanvas; // used to draw on bitmap
  private Canvas bitmapBackupCanvas; 


  // remember last bitmap before new drawings...    
     private void touchStarted()   
     {
        bitmapBackupCanvas.drawBitmap(bitmap, 0, 0, null);
     } 
  // called when the user finishes a touch    
     private void touchEnded(int lineID)   
     {
        Path path = pathMap.get(lineID); // get the corresponding Path
        bitmapCanvas.drawPath(path, paintLine); // draw to bitmapCanvas
        path.reset(); // reset the Path
        rememberLineId = lineID;
     } // end method touch_ended

  //undo       
     private void undo()
     {
        Path path = pathMap.get(rememberLineId); // get the corresponding Path
        pathMap.remove(rememberLineId);
        bitmapCanvas.drawBitmap(bitmapBackup, 0, 0, null); // restore from backup
        path.reset(); // reset the Path
     } 
Pavel Dudka
  • 20,754
  • 7
  • 70
  • 83
  • 1
    yes i have also thought about this way. This no doubt is the simpliest & most clear: saving one by one. yet just curious, i really dont know whether if i allow user to undo more times, say, 5 actions (and save 5 previous bitmap), would it popup outofmemory error. it is because i have tested on S3 for importing some not too big photo to draw on it but it would crash upon direct import (yet wont outofmemory for S2!!)...all in all, let me first try running on different actual devices and revert to you =) thanks for your brillant idea and i would considered this is the last resort for the issue – pearmak Feb 13 '13 at 17:56
  • i tested using above code but crashes. logcat is at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:747) at dalvik.system.NativeStart.main(Native Method) Shutting down VM threadid=1: thread exiting with uncaught exception FATAL EXCEPTION: main java.lang.NullPointerException at com.pear.draw.DrawView.touchStarted(DrawView.java:584) at com.pear.draw.DrawView.onTouchEvent(DrawView.java:560) at android.view.View.dispatchTouchEvent(View.java:5717) at android.view.ViewGroup.dispatchTransformedTouchEvent(ViewGroup.java:1962) at android.view.ViewGroup.dispatchTouchEvent(ViewGroup.java:1689) – pearmak Feb 13 '13 at 18:19
  • yeap, you've got `NullPointerException`. Have you initialized everything correctly? My code is just a pseudocode w/o proper initialization routines. I assume you know what to do with it – Pavel Dudka Feb 13 '13 at 18:31
  • now there is no more nullpointerexception but no action can be seen when the user presses the undo button. Do you know why? Coding revised as above~ – pearmak Feb 16 '13 at 18:15
5

It is an old post, but I was looking for an answer for that issue as well. I was not satisfied with the chosen answer for that post and I found one myself after that. I actually think that having a full bitmap as a backup is not great memory-wise and it will limit the number of undo steps we can have.

I believe a better solution would be :

To have a stack of paths in your class

private Stack<Path> m_pathHistory = new Stack<Path>();

next to your canvas and your paintbrush (initialization skipped) :

private Canvas m_drawingCanvas;
private Paint m_paint;

Then every time a stroke is finished (on the touch up event), add a "clone" of the path to the undo history:

m_pathHistory.Push(new Path(currentPath));

And here is the undo function :

public void Undo()
{
    if(m_pathHistory.Count > 0)
    {
        m_pathHistory.Pop(); // Remove the last path from the history

        m_drawingCanvas.DrawColor(Color.Transparent, PorterDuff.Mode.Clear); // Clear the canvas with a transparent color

        // Draw the paths which are still in the history
        foreach (Path p in m_pathHistory)
        {
            m_drawingCanvas.DrawPath(p, m_paint);
        }
     }
}

The paths are much smaller to store in the memory than the full bitmaps, so we can have a much bigger history.

Matheus
  • 51
  • 1
  • 4
1
use path.reset()in the MotionEvent.ACTION_DOWN event of OnTouchEvent() method.

@Override
public boolean onTouchEvent(MotionEvent event) {
    switch (event.getAction()) {
        case MotionEvent.ACTION_DOWN:
            mPath.reset();
            invalidate();
            break;
    }
    return true;

}
vishnuc156
  • 940
  • 12
  • 10
0

if you use PorterDuffXfermode, save the view to your bitmapBackup, instead of previous bitmap

public void undo (){
    bitmap.eraseColor(getDrawingCacheBackgroundColor());
    mCanvas.drawBitmap(bitmapBackup, 0, 0, null);
    invalidate();
    mPath.reset();
    undofresh=true;
}


private void touch_start(float x, float y) {
    View v1 = this;
    v1.setDrawingCacheEnabled(true);
    this.bitmapBackup = Bitmap.createBitmap(v1.getDrawingCache());
    v1.setDrawingCacheEnabled(false);
}
J. Chomel
  • 8,193
  • 15
  • 41
  • 69
meri sg
  • 1
  • 2