0

I am working on a painting app, with undo/redo function and would like to add eraser function.

Code for MainActivity

case R.id.undoBtn:          
     doodleView.onClickUndo();          
     break;

case R.id.redoBtn:          
     doodleView.onClickRedo();          
     break;

case R.id.eraserBtn:            
     Constants.mPaint.setXfermode(new PorterDuffXfermode(PorterDuff.Mode.DST_OUT)); break; 

DrawView

// Drawing Part    
    private Bitmap mBitmap;   
    private Paint  mBitmapPaint;
    private Canvas mCanvas; 
    private Path   mPath; 
    private int selectedColor = Color.BLACK;
    private int selectedWidth = 5;

    private ArrayList<Path> paths = new ArrayList<Path>();
    private ArrayList<Path> undonePaths = new ArrayList<Path>();
    private Map<Path, Integer> colorsMap = new HashMap<Path, Integer>();
    private Map<Path, Integer> widthMap = new HashMap<Path, Integer>();

    private float mX, mY;
    private static final float TOUCH_TOLERANCE = 4; 
    Context context_new;

   public DoodleView(Context c, AttributeSet attrs) 
   {
       super(c, attrs);
       context_new = c;    
       setFocusable(true);
       setFocusableInTouchMode(true);
       setLayerType(View.LAYER_TYPE_SOFTWARE, null); // for solely removing the black eraser

       mPath = new Path(); 
       mCanvas = new Canvas();  
       mBitmapPaint = new Paint(Paint.DITHER_FLAG);   

       Constants.mPaint = new Paint();
       Constants.mPaint.setAntiAlias(true); // smooth edges of drawn line
       Constants.mPaint.setDither(true);
       Constants.mPaint.setColor(Color.BLACK); // default color is black
       Constants.mPaint.setStyle(Paint.Style.STROKE); // solid line
       Constants.mPaint.setStrokeJoin(Paint.Join.ROUND);
       Constants.mPaint.setStrokeWidth(20); // set the default line width
       Constants.mPaint.setStrokeCap(Paint.Cap.ROUND); // rounded line ends 
       Constants.mPaint.setXfermode(null);
       Constants.mPaint.setAlpha(0xFF);
   } 

   @Override
   public 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);

      Constants.DRAW_W = w;
      Constants.DRAW_H = h;
      Log.d("TAG", "onSizeChanged!!!" + Constants.DRAW_W + Constants.DRAW_H + Constants.SCREEN_W + Constants.SCREEN_H);   
      // bitmap.eraseColor(Color.WHITE); // erase the BitMap with white    
   }   

   @Override
   protected void onDraw(Canvas canvas) 
   {
       canvas.drawBitmap(mBitmap, 0, 0, null); // draw the background screen

       for (Path p : paths)
       {
           Constants.mPaint.setColor(colorsMap.get(p));
           Constants.mPaint.setStrokeWidth(widthMap.get(p));
           canvas.drawPath(p, Constants.mPaint);           
       }       
       Constants.mPaint.setColor(selectedColor);
       Constants.mPaint.setStrokeWidth(selectedWidth);
       canvas.drawPath(mPath, Constants.mPaint);                   
   } 

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

          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;
    }

   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;

           startdrawing = true;
       }
   }

   private void touch_up() 
   {
       mPath.lineTo(mX, mY);      
       mCanvas.drawPath(mPath, Constants.mPaint); 
       paths.add(mPath);
       colorsMap.put(mPath,selectedColor);
       widthMap.put(mPath,selectedWidth);
       mPath = new Path(); 
   }

   public void onClickUndo() 
   { 
       if (paths.size()>0) 
        { 
           undonePaths.add(paths.remove(paths.size()-1));
           invalidate();
        }      
       else Toast.makeText(getContext(), R.string.toast_nothing_to_undo, Toast.LENGTH_SHORT).show();  
    }

   public void onClickRedo()
   {
       if (undonePaths.size()>0) 
       { 
           paths.add(undonePaths.remove(undonePaths.size()-1)); 
           invalidate();
       } 
       else 
           Toast.makeText(getContext(), R.string.toast_nothing_to_redo, Toast.LENGTH_SHORT).show(); 
    }  

   public void setDrawingColor(int color)
   {       
       selectedColor = color;
       Constants.mPaint.setColor(color);
   } 

   public int getDrawingColor()
   {
      return Constants.mPaint.getColor();
   } 

Question:

Normal painting and undo / redo can be performed perfectly. However, the eraser does not work.

  1. After pressing (touch_start) the eraser button and touch the screen, all the previous drawn line immediately turn black.

  2. When using the eraser upon touch_move , the eraser itself is drawing black lines, even though it was on CLEAR mode.

  3. Upon touch_up, All other drawn lines maintained at black color. The "erased" area was also in black color. However, when drawing a new path subsequently, the original line turned to their original color, the area "erased" by the eraser turn its color to the last color chosen and the paths retained in the View.

How could the code on eraser be written in a proper way? (keeping undo / redo)

Thanks a lot in advance!

pearmak
  • 4,979
  • 15
  • 64
  • 122

4 Answers4

0

It's hard to answer your question, but. I'd implement the erase functionality as follows:

The erase function will by simple white path. So the erase mode means that you are drawing white path which will be wider than the drawing path. This way you will be able to select even the width of the eraser and the undo/redo functionality will stay the same.

Lubo
  • 384
  • 1
  • 5
  • Thanks Lubo. However, in later development the background of the paint will noy simply a white board, instead there will be image loaded to the background. In this way white lines may not possibly act as eraser – pearmak Oct 30 '14 at 00:38
  • It is possible do it even in that case. You would have two images one as a background and the second will be transparent with lines. The erase will be then only color which will make all the pixels transparent. Then in the draw method you first draw the background and than the paths. – Lubo Oct 30 '14 at 17:32
  • i know what you mean. at the bottom will be an imageview, while on top will be the drawing view with transparent background. The eraser is then to draw transparent lines over the painted lines to overwrite the drawn line so as to pretend the erasing effect. But i have tried and it does not work. The "erasing" lines are drawn onto others instead of erasing and hence is solely drawing over the drawn lines with transparent color, and will not eliminate out the drawn one. I replaced the transparent color by white and code was performing – pearmak Oct 31 '14 at 14:54
  • So use this little trick: your white color for drawing will be RGB(254,254,254) and your erase color will be RGB(255,255,255). Then just befor drawing the pathes you will convert all RGB(255,255,255) pixel to transparent. If you want example google for: java android convert white color to transparent. I have found this one: http://www.javaworld.com/article/2074105/core-java/making-white-image-backgrounds-transparent-with-java-2d-groovy.html – Lubo Oct 31 '14 at 15:25
0

Try this type of application with samsung spen sdk, all this funtions are simplified there. SPen Sdk Tutorial

or

Make your erase paint color to your background color.

  • thanks for your reply! but will the samsung sdk useable on other brand's phone? eg LG, Sony? and since the background is to be loaded with an image, there is no background color for an eraser to be set. – pearmak Nov 19 '14 at 15:13
0

// Please use this is erasure concept . This is working and tested.

public void addErasure(){
        drawView.setErase(true);
        drawView.setBrushSize(20);
    }

    public void addPencil(){
        drawView.setErase(false);
        drawView.setBrushSize(20);
        drawView.setLastBrushSize(20);
    }
/// this my drawing view. you can add this view into your main layout.

    public class DrawingView extends View {

        //drawing path
        private Path drawPath;
        //drawing and canvas paint
        private Paint drawPaint, canvasPaint;
        //initial color
        private int paintColor = 0xFF660000;
        //canvas
        private Canvas drawCanvas;
        //canvas bitmap
        private Bitmap canvasBitmap;
        //brush sizes
        private float brushSize, lastBrushSize;
        //erase flag
        private boolean erase=false;

        private boolean isFirstTime = false;

        public DrawingView(Context context, AttributeSet attrs){
            super(context, attrs);
            //setBackgroundColor(Color.CYAN);
            setupDrawing();
        }

        //setup drawing
        private void setupDrawing(){

            //prepare for drawing and setup paint stroke properties
            brushSize = getResources().getInteger(R.integer.medium_size);
            lastBrushSize = brushSize;
            drawPath = 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);
            //canvasPaint.setColor(Color.GREEN);
        }

        //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);
            Bitmap canvasBackGroundBitmap = Bitmap.createBitmap(w, h, Bitmap.Config.ARGB_8888);
            canvasBackGroundBitmap = getResizedBitmap(BitmapFactory.decodeResource(getResources(), R.drawable.pop_up_big_bg),h,w);
    //      drawCanvas = new Canvas(canvasBackGroundBitmap);
    //      drawCanvas.drawColor(Color.GREEN);
            setBackgroundDrawable(new BitmapDrawable(canvasBackGroundBitmap));

            drawCanvas = new Canvas(canvasBitmap);
        }

        public static Bitmap getResizedBitmap(Bitmap bm, int newHeight, int newWidth) {
            int width = bm.getWidth();
            int height = bm.getHeight();
            float scaleWidth = ((float) newWidth) / width;
            float scaleHeight = ((float) newHeight) / height;
            // CREATE A MATRIX FOR THE MANIPULATION
            Matrix matrix = new Matrix();
            // RESIZE THE BIT MAP
            matrix.postScale(scaleWidth, scaleHeight);

            // "RECREATE" THE NEW BITMAP
            Bitmap resizedBitmap = Bitmap.createBitmap(bm, 0, 0, width, height, matrix, false);
            return resizedBitmap;
        }

        //draw the view - will be called after touch event
        @Override
        protected void onDraw(Canvas canvas) {
            canvas.drawBitmap(canvasBitmap, 0, 0, canvasPaint);
            canvas.drawPath(drawPath, 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.moveTo(touchX, touchY);
                break;
            case MotionEvent.ACTION_MOVE:
                drawPath.lineTo(touchX, touchY);
                drawCanvas.drawPath(drawPath, drawPaint);
                drawPath.reset();
                drawPath.moveTo(touchX, touchY);
                break;
            case MotionEvent.ACTION_UP:
                //drawPath.lineTo(touchX, touchY);
                //drawCanvas.drawPath(drawPath, drawPaint);
                drawPath.reset();
                break;
            default:
                return false;
            }
            //redraw
            invalidate();
            return true;

        }





        //update color
        public void setColor(String newColor){
            invalidate();
            paintColor = Color.parseColor(newColor);
            drawPaint.setColor(paintColor);
        }

        //set brush size
        public void setBrushSize(float newSize){
            float pixelAmount = TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, 
                    newSize, getResources().getDisplayMetrics());
            brushSize=pixelAmount;
            drawPaint.setStrokeWidth(brushSize);
        }

        //get and set last brush size
        public void setLastBrushSize(float lastSize){
            lastBrushSize=lastSize;
        }
        public float getLastBrushSize(){
            return lastBrushSize;
        }

        //set erase true or false
        public void setErase(boolean isErase){
            erase=isErase;
            if(erase) {
                drawPaint.setXfermode(new PorterDuffXfermode(PorterDuff.Mode.CLEAR));
            }else {
                drawPaint.setXfermode(null);
            }
        }

        //start new drawing
        public void startNew(){
            drawCanvas.drawColor(0, PorterDuff.Mode.CLEAR);
            invalidate();
        }
    }
Dhruba Bose
  • 446
  • 2
  • 7
-1

Try this solution - works great with undo/redo as well:

   private float blur = 0F;

    case R.id.eraserBtn:            
         Constants.mPaint.setColor(Color.WHITE); // or whatever color to match canvas color
         Constants.mPaint.setShadowLayer(this.blur, 0F, 0F, Color.WHITE);

With this solution, no need to set:

 Constants.mPaint.setXfermode(null);
Wildroid
  • 864
  • 7
  • 9
  • hi, I have tried and is not working as an eraser, instead it is having a white shading below the drawing lines (so as the previous drawn lines once eraser button is pressed) – pearmak Nov 05 '14 at 12:34
  • i just cannot find any sample codes for such a basic painting app, with drawing lines, eraser and undo/redo function. – pearmak Nov 05 '14 at 12:36
  • @pearmak In that case, I suggest having a separate eraser Path() and assign its paint the color of the canvas. You can implement undo/redo to the eraser Path() same way. Note: This solution works on canvas and not on image background. – Wildroid Nov 05 '14 at 19:33