android canvas remove previous path being drawn
Solution 1
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
}
Solution 2
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.
Solution 3
At first glance I see the following problems:
By adding your empty
Path
topaths
as soon as you make it, you're going to have a problem as soon as you undo: you're popping that emptyPath
first, making the first undo not seem to work. Then if you draw into thatPath
, it's not added topaths
. The solution is to add the completedPath
to paths intouch_up()
before creating a new one.
That is, remove
paths.add(mPath);
from the constructor, and in touch_up()
, change
mPath = new Path();
paths.add(mPath);
to
paths.add(mPath);
mPath = new Path();
You'll also want to add
canvas.drawPath(mPath, mPaint);
after your for
loop in onDraw()
in order to draw the in-progress Path
.
You're not emptying undonePaths
when the user starts drawing again.
Solution 4
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;
}
pearmak
Updated on February 04, 2020Comments
-
pearmak over 4 years
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 executingOnTouchEvent
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? -
pearmak over 11 yearsThanks NagarjunaReddy.P for your ref! I have read through both links but inside both blog the way to clear paint/path is totally erase all the paint but not the immediate previous path(s). Your way is public void onClick(View v) {mBitmap.eraseColor(Color.TRANSPARENT);mPath.reset();mView.invalidate();} --> It would be more perfect if it allows users to click undo one by one to clear previous actions. BTW, i am interested in how do you think for my another Q for placing imported bitmap in middle of extended view @ stackoverflow.com/questions/14675008/…
-
pearmak over 11 yearsyes 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 over 11 yearsi 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)
-
Pavel Dudka over 11 yearsyeap, 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 -
pearmak over 11 yearsnow 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~
-
lenooh over 5 years
path.reset()
is what I was looking for.