Trying to create an Eraser Paint for canvas

erik picture erik · Jun 19, 2013 · Viewed 11.2k times · Source

I am creating a drawing app that utilizes the DrawingSurfaceView class below. In that class i have a Paint Called eraserPaint that the user can toggle on and off.. When on that paint is suppose to eraser what ever is in its path. but instead its just drawing a black line..

When i save out the canvas as a transparent png the eraser is correct but on the screen it shows black..

Screenshot from phone of EraserPaint used to write "Erik" on blob

enter image description here

Saved out PNG from canvas enter image description here

eraserPaint looks like this:

eraserPaint = new Paint();
        eraserPaint.setAlpha(0);
        eraserPaint.setColor(Color.TRANSPARENT);
        eraserPaint.setStrokeWidth(60);
        eraserPaint.setStyle(Style.STROKE);
        eraserPaint.setMaskFilter(null);
        eraserPaint.setXfermode(new PorterDuffXfermode(PorterDuff.Mode.CLEAR));
        eraserPaint.setAntiAlias(true);

the WHOLE class

     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;


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


        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();
        eraserPaint = new Paint();
        eraserPaint.setAlpha(0x00);
        eraserPaint.setColor(Color.TRANSPARENT);
        eraserPaint.setStrokeWidth(60);
        eraserPaint.setStyle(Style.STROKE);
        //eraserPaint.setMaskFilter(null);
        eraserPaint.setXfermode(new PorterDuffXfermode(PorterDuff.Mode.CLEAR));
        eraserPaint.setAntiAlias(true);

        mBitmapPaint = new Paint(Paint.DITHER_FLAG);



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

        mCanvas.drawBitmap(mBitmap, 0, 0, mBitmapPaint);
        mCanvas.drawColor(Color.TRANSPARENT, PorterDuff.Mode.CLEAR);
    }

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

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


    }

    @Override
    protected void onDraw(Canvas canvas) {

        if (!_parent.isDrawerOpen()&&mPaint!=null) {
            Log.v("onDraw:", "curent paths size:" + paths.size());

            //mCanvas.drawColor(Color.TRANSPARENT, PorterDuff.Mode.CLEAR);
            //canvas.drawPath(mPath, mPaint);
            for (int i=0;i< paths.size();i++) {
                tempPaint =  paints.get(i);
                eraserPaint.setStrokeWidth(tempPaint.getStrokeWidth());
                if(fills.get(i)){
                    tempPaint.setStyle(Style.FILL_AND_STROKE);
                    eraserPaint.setStyle(Style.FILL_AND_STROKE);
                }else{
                    tempPaint.setStyle(Style.STROKE);
                    eraserPaint.setStyle(Style.STROKE);
                }
                if(erasers.get(i)){
                    //tempPaint.setXfermode(new PorterDuffXfermode(PorterDuff.Mode.CLEAR));
                    canvas.drawPath(paths.get(i), eraserPaint);
                }else{
                    //tempPaint.setXfermode(null);
                    canvas.drawPath(paths.get(i), tempPaint);
                }
                //canvas.drawPath(paths.get(i), tempPaint);
            }
            if(_parent.toggleFill.isChecked()){
               mPaint.setStyle(Style.FILL_AND_STROKE); 
               eraserPaint.setStyle(Style.FILL_AND_STROKE);

            }else{
               mPaint.setStyle(Style.STROKE);
               eraserPaint.setStyle(Style.STROKE);
            }
            if(_parent.toggleErase.isChecked()){
               //mPaint.setXfermode(new PorterDuffXfermode(PorterDuff.Mode.CLEAR));
                 canvas.drawPath(mPath, eraserPaint);
            }else{
                //mPaint.setXfermode(null);
                canvas.drawPath(mPath, 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));
            undoneFills.add(fills.remove(fills.size() - 1));
            undoneErasers.add(erasers.remove(erasers.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));
            fills.add(undoneFills.remove(undoneFills.size() - 1));
            erasers.add(undoneErasers.remove(undoneErasers.size() - 1));
            clearCanvasCache();
            invalidate();
        } else {

        }
        _parent.checkButtonStates();
    }

    public void onClickClear() {

        paths.clear();
        paints.clear();
        fills.clear();
        erasers.clear();
        undoneFills.clear();
        undonePaths.clear();
        undonePaints.clear();
        undoneErasers.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();
        undonePaints.clear();
        undoneFills.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);
        // commit the path to our offscreen
        if(_parent.toggleErase.isChecked()){
            mCanvas.drawPath(mPath, eraserPaint);
            erasers.add(true);
            paints.add(eraserPaint);
        }else{
            mCanvas.drawPath(mPath, mPaint);
            erasers.add(false);
            paints.add(mPaint);
        }

        // kill this so we don't double draw

        paths.add(mPath);


        if(_parent.toggleFill.isChecked()){
            fills.add(true);
        }else{
            fills.add(false);
        }
        if(_parent.toggleErase.isChecked()){
            erasers.add(true);
        }else{
            erasers.add(false);
        }


        _parent.checkButtonStates();
        mPath = new Path();
    }

    @Override
    public boolean onTouchEvent(MotionEvent event) {
        if(mPaint==null &&!_parent._showingAlert){
            _parent.showNoPaintAlert();
        }

        if (!_parent.isDrawerOpen()&&mPaint!=null) {
            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 true;
        }
    }

    public void clearCanvasCache() {

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

I should add that i am adding this Custom View to a relative layout that has that checkered pattern as the background image..

PLEASE PLEASE PLEASE help.. i need that preview image to NOT show black after an eraser paint was used.. i need it to show the checkered pattern behind.. I know the eraser is working as those black eraser marks save out as transparent.

NEW NOTE

I was playing around and discovered something else thats curious. Experimenting, i tried switching from drawing to the canvas as passed to the onDraw method and directly to the canvas i set up in the contructor called mCanvas and noticed it did not draw as far as i could see.. so I added a log to the onDraw like so:

 protected void onDraw(Canvas canvas) {
       Log.v("DRAWING SURFACE", "canvas:"+canvas+" mCanvas:"+mCanvas);

which spits out

06-21 11:10:43.994: V/DRAWING SURFACE(4532): canvas:android.view.Surface$CompatibleCanvas@42a8c030 mCanvas:android.graphics.Canvas@431df180

Answer

clavio picture clavio · Jun 21, 2013

I had this same problem with my app. I even tried the "finger paint" example code and still had the same problem. I was never able to have the eraser work as a path, but I was able to find a workaround. Rather than drawing a path when I erase, I draw a circle (It could be any shape) when the user puts his finger down or there is a "move" event:

case MotionEvent.ACTION_DOWN:
mPaint.setStrokeWidth(25);
            mPaint.setXfermode(new PorterDuffXfermode(
                    PorterDuff.Mode.CLEAR));
            mCanvas.drawCircle(x, y, 10, mPaint);
            isErase = true;
            invalidate();
        }
        touch_start(x, y);
        invalidate();
        break;
case MotionEvent.ACTION_MOVE:   
        if(isErase)
        {
            mCanvas.drawCircle(x, y, 20, mPaint);
        }
        else{
            touch_move(x, y);
        }invalidate();
        break;

It will take some time to incorporate this into your code, but I guarantee you it will take less time than the amount of time you have already spent trying to fix this problem. I can send you more of my PaintView if you think it would be helpful.