Undo and Redo Functionality in android drawing app using Canvas - java

i'm working on a project where i create mirror of drawing. my main logic is working fine.Only thing causing Problem is Redo and undo functionality.
i have searched a lot. implement may methods but couldn't get success. Following is my Drawing Class.
DrawingView.java
private ArrayList<Path> paths = new ArrayList<Path>();
private ArrayList<Path> undonePaths = new ArrayList<Path>();
public DrawingView(Context context, AttributeSet attrs){
super(context, attrs);
this.context=context;
setupDrawing();
}
//setup drawing
private void setupDrawing(){
//prepare for drawing and setup paint stroke properties
brushSize = getResources().getInteger(R.integer.small_size);
lastBrushSize = brushSize;
drawPath = new Path();
drawPath1 = new Path();
drawPaint = new Paint();
drawPaint.setColor(paintColor);
drawPaint.setAntiAlias(true);
drawPaint.setStrokeWidth(brushSize);
drawPaint.setStyle(Paint.Style.STROKE);
drawPaint.setStrokeJoin(Paint.Join.ROUND);
drawPaint.setStrokeCap(Paint.Cap.ROUND);
canvasPaint = new Paint(Paint.DITHER_FLAG);
}
//size assigned to view
#Override
protected void onSizeChanged(int w, int h, int oldw, int oldh) {
super.onSizeChanged(w, h, oldw, oldh);
canvasBitmap = Bitmap.createBitmap(w, h, Bitmap.Config.ARGB_8888);
width=w;
height=h;
Log.d("width,height", w + " , " + h);
drawCanvas = new Canvas(canvasBitmap);
}
//draw the view - will be called after touch event
#Override
protected void onDraw(Canvas canvas) {
for (Path p : paths){canvas.drawPath(p, drawPaint);}
canvas.drawPath(drawPath, drawPaint);
Log.i("OnDRAWING", "REACH ON DRAW");
/*canvas.drawBitmap(canvasBitmap, 0, 0, canvasPaint);
canvas.drawPath(drawPath, drawPaint);
canvas.drawPath(drawPath1, drawPaint);*/
}
//register user touches as drawing action
#Override
public boolean onTouchEvent(MotionEvent event) {
float touchX = event.getX();
float touchY = event.getY();
//respond to down, move and up events
switch (event.getAction()) {
case MotionEvent.ACTION_DOWN:
drawPath.reset();
undonePaths.clear();
drawPath.moveTo(touchX, touchY);
undonePaths.clear();
break;
case MotionEvent.ACTION_MOVE:
drawPath.lineTo(touchX, touchY);
break;
case MotionEvent.ACTION_UP:
drawPath.lineTo(touchX, touchY);
drawCanvas.drawPath(drawPath, drawPaint);// commit the path to our offscreen
paths.add(drawPath);
drawCanvas.drawPath(drawPath, drawPaint);
drawPath.reset();
drawCanvas.drawPath(drawPath1, drawPaint);
drawPath1.reset();
break;
default:
return false;
}
//redraw
invalidate();
return true;
}
What i am missing here?
Any suggestions/ideas/examples which is the best way to implement this kind of functionality on my project?

Canvas drawing is sort of hierarchal in that drawing happens in the order that you do them.
So all drawing should be done in onDraw and only here.
Your draw events should be pushed on a draw stack. You do not store a canvas. You just store the operations that are supposed to happen when the drawing eventually occurs (coordinates, width, and color of a path for example).
An "undo" operation can be done by popping from the drawing stack and pushing the event to a "redo" stack. The "redo" event can be done by popping from the "redo" stack and pushing back on to the "drawing" stack.
In onDraw method of your View, just scan through the drawing events and draw on the canvas.
EDIT:
The onDraw() method would just iterate through your drawing operations. So first you could have an interface called DrawEvent like so:
public interface DrawEvent {
void draw(Canvas c);
}
Then in your View class have your collection of DrawEvents and iterate through them in onDraw(Canvas c).
public class MyView extends View {
Deque<DrawEvent> drawEvents = new LinkedList<DrawEvent>();
#Override
public void onDraw(Canvas c) {
for (DrawEvent e : drawEvents) {
e.draw(c);
}
}
}

Related

Path is not being drawn on canvas despite being stored in arraylist

I am making a drawing app and it has layers. Each time I draw a line it disappears. The process is like this :
Draw a line with path and add it to path ArrayList once TOUCH_UP happens (I lift my finger)
invalidate is called and then canvas draws all paths stored in the arraylist
Instead of such behavior my path disappears the moment it is drawn and canvas is blank. Methods :
(path,layer1,layer2,layer3 are all ArrayList of Path type)
// constructor where path arraylist is pointing to currently used layer arraylist (all contain path types)
public ArtView(Context context, #Nullable AttributeSet attrs) {
super(context, attrs);
this.context = context;
setupDrawing();
layer = 1;
layer1 = new ArrayList<>();
layer2 = new ArrayList<>();
layer3 = new ArrayList<>();
path = layer1;
undo = new ArrayList<>();
}
// onDraw is called each time to update canvas with each change
#Override
protected void onDraw(Canvas canvas) {
canvas.save();
// load a picture from galery if under is true, make a new bitmap if false
if (loadedBitmap != null) {
canvas.drawBitmap(loadedBitmap, 0, 0, paintLine);
} else {
canvas.drawBitmap(bitmap, 0, 0, canvasPaint);
}
// for each layer arraylist draw all their paths on canvas
for (Path path : layer1) {
canvas.drawPath(path, paintLine);
}
for (Path path : layer2) {
canvas.drawPath(path, paintLine);
}
for (Path path : layer3) {
canvas.drawPath(path, paintLine);
}
canvas.restore();
}
// touch method that handles drawing
#Override
public boolean onTouchEvent(MotionEvent event) {
float touchX = event.getX();
float touchY = event.getY();
switch (event.getAction()) {
case MotionEvent.ACTION_DOWN:
mPath.moveTo(touchX, touchY);
break;
case MotionEvent.ACTION_MOVE:
mPath.lineTo(touchX, touchY);
break;
case MotionEvent.ACTION_UP:
path.add(mPath); // add currently created path to arraylist to be painted in onDraw
mPath.reset();
break;
default:
return false;
}
invalidate(); // call onDraw and update changes
return true;
}
// set up method called when starting this activity (from MainActivity class)
public void setupDrawing() {
mPath = new Path();
paintLine = new Paint();
paintLine.setAntiAlias(true);
paintLine.setDither(true);
paintLine.setStrokeJoin(Paint.Join.ROUND);
paintLine.setStyle(Paint.Style.STROKE);
paintLine.setColor(paintColor);
paintLine.setStrokeWidth(1);
paintLine.setStrokeCap(Paint.Cap.ROUND);
paintLine.setXfermode(null);
paintLine.setAlpha(255);
canvasPaint = new Paint(Paint.DITHER_FLAG);
lastBrushSize = paintLine.getStrokeWidth();
}
One more problem is that whenever I draw the first line when starting the app, it never shows the line that is being traced. Only once I finish drawing it is shows up then disappears. The next one I make is visible nicely as I trace it , it also disappears once its done.
Any tips on how to make all the lines remain on canvas and how to make my first line being visible while it is being drawn ?

How to make a canvas white after drawing?

I am drawing on the canvas properly and saving it into a bitmap.
However, I want to reset the canvas to white by clicking a button.
Here is my code:
public class Canvas extends View {
Paint paint;
Path path;
boolean cc = false;
public Canvas(Context context, #Nullable AttributeSet attrs) {
super(context, attrs);
paint = new Paint();
path = new Path();
paint.setAntiAlias(true);
paint.setColor(Color.RED);
paint.setStrokeJoin(Paint.Join.ROUND);
paint.setStyle(Paint.Style.STROKE);
paint.setStrokeWidth(5f);
}
#Override
protected void onDraw(android.graphics.Canvas canvas) {
super.onDraw(canvas);
if (!cc) {
canvas.drawPath(path, paint);
}
else {
canvas.drawColor(Color.WHITE);
cc = false;
}
}
#Override
public boolean onTouchEvent(MotionEvent event) {
float xPos = event.getX();
float yPos = event.getY();
switch (event.getAction()) {
case MotionEvent.ACTION_DOWN:
path.moveTo(xPos, yPos);
return true;
case MotionEvent.ACTION_MOVE:
path.lineTo(xPos, yPos);
break;
case MotionEvent.ACTION_UP:
break;
default:
return false;
}
invalidate();
return true;
}
public void clear() {
cc = true;
invalidate();
}
my clear() function set cc to "true" then invalidate() calls the onDraw() function. But it seems that "cc" is not recognized inside the onDraw() or it has always the same value inside.
I tried the path.reset() with no result.
calling clear() does not return any error.
Seems you'd want the path to get cleared too when your clear() method is called, so do that, then use the fact that the path is empty to clear the canvas.
public void clear() {
path.reset();
invalidate();
}
#Override
protected void onDraw(android.graphics.Canvas canvas) {
super.onDraw(canvas);
if (path.isEmpty()) {
canvas.drawColor(Color.WHITE);
} else {
canvas.drawPath(path, paint);
}
}
That entirely eliminates the cc field.
To clear all your canvas use this:
Paint transparent = new Paint();
transparent.setAlpha(0);
Update:
Add this line in your button onclick():
canvas.drawColor(Color.WHITE);
And remove it from the draw function.

How do I show old bitmap into DrawingView while painting

I want to edit my drawing painted image. I am getting a bitmap image and setting it into a canvas bitmap, but the old image is not appearing. I have tried using the drawingView.refresh() and recycle() methods, but couldn't make it work.
private DrawingView mDrawingView;
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_drawing);
if(datadtlimgsItem !=null && datadtlimgsItem.getImageStr()
!=null) {
decodedString = Base64.decode(datadtlimgsItem.getImageStr(),
Base64.DEFAULT);
decodedByte = BitmapFactory.decodeByteArray(decodedString, 0,
decodedString.length);
decodedByte = Util.resize(decodedByte, 400, 100);
mDrawingView.canvasBitmap = decodedByte;
}
}
public class DrawingView extends View{
// To hold the path that will be drawn.
private Path drawPath;
// Paint object to draw drawPath and drawCanvas.
private Paint drawPaint, canvasPaint;
// initial color
private int paintColor = 0xff000000;
private int previousColor = paintColor;
// canvas on which drawing takes place.
private Canvas drawCanvas;
// canvas bitmap
public Bitmap canvasBitmap;
// Brush stroke width
private float brushSize, lastBrushSize;
// To enable and disable erasing mode.
private boolean erase = false;
public DrawingView(Context context, AttributeSet attrs){
super(context, attrs);
setUpDrawing();
}
/**
* Initialize all objects required for drawing here.
* One time initialization reduces resource consumption.
*/
private void setUpDrawing(){
drawPath = new Path();
drawPaint = new Paint();
drawPaint.setColor(paintColor);
// Making drawing smooth.
drawPaint.setAntiAlias(true);
drawPaint.setStyle(Paint.Style.STROKE);
drawPaint.setStrokeJoin(Paint.Join.ROUND);
drawPaint.setStrokeCap(Paint.Cap.ROUND);
canvasPaint = new Paint(Paint.DITHER_FLAG);
// Initial brush size is medium.
brushSize = getResources().getInteger(R.integer.medium_size);
lastBrushSize = brushSize;
drawPaint.setStrokeWidth(brushSize);
}
#Override
protected void onSizeChanged(int w, int h, int oldw, int oldh) {
super.onSizeChanged(w, h, oldw, oldh);
canvasBitmap = Bitmap.createBitmap(w, h,
Bitmap.Config.ARGB_8888);
drawCanvas = new Canvas(canvasBitmap);
}
#Override
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
canvas.drawBitmap(canvasBitmap, 0, 0, canvasPaint);
canvas.drawPath(drawPath, drawPaint);
}
#Override
public boolean onTouchEvent(MotionEvent event) {
// X and Y position of user touch.
float touchX = event.getX();
float touchY = event.getY();
// Draw the path according to the touch event taking place.
switch (event.getAction()) {
case MotionEvent.ACTION_DOWN:
drawPath.moveTo(touchX, touchY);
break;
case MotionEvent.ACTION_MOVE:
drawPath.lineTo(touchX, touchY);
break;
case MotionEvent.ACTION_UP:
if (erase){
drawPaint.setXfermode(new
PorterDuffXfermode(PorterDuff.Mode.CLEAR));
}
drawCanvas.drawPath(drawPath, drawPaint);
drawPath.reset();
drawPaint.setXfermode(null);
break;
default:
return false;
}
// invalidate the view so that canvas is redrawn.
invalidate();
return true;
}
public void setColor(String newColor){
// invalidate the view
invalidate();
paintColor = Color.parseColor(newColor);
drawPaint.setColor(paintColor);
previousColor = paintColor;
}
public void setBrushSize(float newSize){
float pixelAmount =
TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP,
newSize, getResources().getDisplayMetrics());
brushSize=pixelAmount;
drawPaint.setStrokeWidth(brushSize);
}
public void setLastBrushSize(float lastSize){
lastBrushSize=lastSize;
}
public float getLastBrushSize(){
return lastBrushSize;
}
public void setErase(boolean isErase){
//set erase true or false
erase = isErase;
if(erase) {
drawPaint.setColor(Color.WHITE);
//drawPaint.setXfermode(new
PorterDuffXfermode(PorterDuff.Mode.CLEAR));
}
else {
drawPaint.setColor(previousColor);
drawPaint.setXfermode(null);
}
}
public void startNew(){
drawCanvas.drawColor(0, PorterDuff.Mode.CLEAR);
invalidate();
}
}
I have a view like Paint I want to show old image getting from activity in current view and then edit my image.
image is blank but i need to show previous image here
here is saved image want to edit this image
I have a view like Paint I want to show old image getting from activity in current view and then edit my image. I have a view like Paint I want to show old image getting from activity in current view and then edit my image. I have a view like Paint I want to show old image getting from activity in current view and then edit my image. I have a view like Paint I want to show old image getting from activity in current view and then edit my image. I have a view like Paint I want to show old image getting from activity in current view and then edit my image.
#Override
protected void onSizeChanged(int w, int h, int oldw, int oldh) {
super.onSizeChanged(w, h, oldw, oldh);
if (canvasBitmap == null){
canvasBitmap = Bitmap.createBitmap(w, h, Bitmap.Config.ARGB_8888);
}
drawCanvas = new Canvas(canvasBitmap);
}

Undo Button In Android Drawing App

So I would like to implement an undo button for a drawing app I am working on for android. My current idea is to put the current draw path into a list of paths on MotionEvent.ACTION_UP. In onDraw(), simply draw everything that is in the list of paths. When the user presses undo, you remove the last drawn path from the list of paths and call invalidate(), forcing onDraw() to be called, which will draw everything in the list of paths. Since we removed the previous path from the list, that path should not be drawn and therefore should be "undone".
The problem I am having is the path never seems to actually be undone. It seems like the logic in my head is correct, but this implementation does not work correctly. Any help will be greatly appreciated.
Here is my code:
DrawingView.java:
Instance variables (to clarify following methods):
private Context context;
private Path drawPath;
private Paint drawPaint;
private Paint canvasPaint;
private Canvas drawCanvas;
private Bitmap canvasBitmap;
private int previousPaintColor;
private int paintColor;
private float brushSize;
private float eraserSize;
private float lastBrushSize;
private boolean isErasing = false;
private List<Path> moveList = null;
private List<Path> undoList = null;
private List<Path> currentMoveList = null;
Called from constructor:
private void setupDrawing() {
drawPath = new Path();
drawPaint = new Paint();
brushSize = getResources().getInteger(R.integer.default_brush_size);
lastBrushSize = brushSize;
drawPaint.setColor(paintColor);
drawPaint.setAntiAlias(true);
drawPaint.setStrokeWidth(brushSize);
drawPaint.setStyle(Paint.Style.STROKE);
drawPaint.setStrokeJoin(Paint.Join.ROUND);
drawPaint.setStrokeCap(Paint.Cap.ROUND);
canvasPaint = new Paint(Paint.DITHER_FLAG);
}
onDraw:
#Override
protected void onDraw(Canvas canvas) {
canvas.drawBitmap(canvasBitmap, 0, 0, canvasPaint);
for (Path path : currentMoveList) {
canvas.drawPath(path, drawPaint);
}
for (Path path : moveList) {
canvas.drawPath(path, drawPaint);
}
}
onTouchEvent:
#Override
public boolean onTouchEvent(MotionEvent event) {
float touchX = event.getX();
float touchY = event.getY();
switch (event.getAction()) {
case MotionEvent.ACTION_DOWN:
drawPath.moveTo(touchX, touchY);
break;
case MotionEvent.ACTION_MOVE:
drawPath.lineTo(touchX, touchY);
currentMoveList.add(drawPath);
break;
case MotionEvent.ACTION_UP:
drawPath.lineTo(touchX, touchY);
drawCanvas.drawPath(drawPath, drawPaint);
moveList.add(drawPath);
drawPath = new Path();
currentMoveList.clear();
break;
default:
return false;
}
invalidate();
return true;
}
undo() :
public void undo() {
if (moveList.size() > 0) {
undoList.add(moveList.remove(moveList.size() - 1));
invalidate();
}
}
Regards,
Kyle.
After some trial and error, here is the fix:
Remove this line of code:
canvas.drawBitmap(canvasBitmap, 0, 0, canvasPaint);
from onDraw() method and everything works perfectly :)
**Undo your onDraw override method ((TESTED)) **
#Override
protected void onDraw(Canvas canvas) {
canvas.drawBitmap(canvasBitmap, 0, 0, canvasPaint);
canvas.drawPath(drawPath, drawPaint);
for (Path path : currentMoveList) {
canvas.drawPath(path, drawPaint);
}
for (Path path : moveList) {
canvas.drawPath(path, drawPaint);
}
super.onDraw(canvas);
}

How to draw on Canvas in another thread?

I have been developing the application, and I need that drawingg is executed in another thread. Now my code is:
public class PainterView extends View implements DrawingListener {
//private GestureDetector detector;
private Context context;
private Painter painter;
private Bitmap background;
private Bitmap bitmap;
private Paint bitmapPaint;
private Path path;
private Paint paint;
private float x;
private float y;
private boolean isErasing=false;
private boolean isTextDrawing=false;
private ExecutorService pool;
public PainterView(Context context, Painter painter) {
super(context);
this.context = context;
this.painter = painter;
pool=Executors.newFixedThreadPool(3);
//detector = new GestureDetector(context, new GestureListener());
}
#Override
protected void onSizeChanged(int w, int h, int oldw, int oldh) {
super.onSizeChanged(w, h, oldw, oldh);
}
#Override
protected void onDraw(final Canvas canvas) {
if (bitmap != null) {
pool.submit(new Runnable() {
#Override
public void run() {
// TODO Auto-generated method stub
synchronized (PainterView.this) {
canvas.drawBitmap(background, 0, 0, bitmapPaint);
canvas.drawBitmap(bitmap, 0, 0, bitmapPaint);
canvas.drawPath(path, paint);
}
}
});
}
}
#Override
public boolean onTouchEvent(MotionEvent event) {
//detector.onTouchEvent(event);
x = event.getX();
y = event.getY();
switch (event.getAction()) {
case MotionEvent.ACTION_DOWN:
painter.touchStart(x, y);
break;
case MotionEvent.ACTION_MOVE:
painter.touchMove(x, y);
break;
case MotionEvent.ACTION_UP:
painter.touchUp();
break;
}
return true;
}
#Override
public void onPictureUpdate(Bitmap background, Bitmap bitmap, Paint bitmapPaint, Path path, Paint paint) {
this.background=background;
this.bitmap = bitmap;
this.bitmapPaint = bitmapPaint;
this.path = path;
this.paint = paint;
invalidate();
}
public void setPainter(Painter painter) {
this.painter = painter;
}
}
I thought that if I uses ExecutorService then the application can draw in another thread, but it doesn't work - when I draw the screen of device blinkes. So, please, tell me, how can I use multithreading for drawing using SurfaceHolder and other elements? I need to make as few as possible changes in my code.
You can only draw in the main UI thread. You should use SurfaceView, since it was made specifically to support drawing from secondary threads.
One of the purposes of this class is to provide a surface in which a
secondary thread can render into the screen. If you are going to use
it this way, you need to be aware of some threading semantics.
source
See also this video: Learn Android Tutorial 1.28- Introduction to the SurfaceView

Categories

Resources