androidandroid-canvaseraser

Canvas Eraser is drawing a black line


I have googled this problem and had various solutions proposed.
However, none worked for me.

I have a Drawing Canvas in an app.
The background of the canvas is set to a png Image in the Activity which uses the custom view (drawView);

Bundle extras = intent.getExtras();
    if (extras != null) {
        if (extras.containsKey("background")) {

            //set the background to the resource in the extras
            int imageResource = intent.getIntExtra("background",-1);
            Drawable image = getResources().getDrawable(imageResource);
            drawView.setBackground(image);
        }
    }

In the DrawingView class (drawview is the instance), I store the paths drawn in a collection of PathPaints, which has 3 properties (the path, the paint used and if it was an eraser);

private ArrayList<PathPaint> paths = new ArrayList<PathPaint>();

I then attempt to loop through these paths in OnDraw and redraw them each time with the paints that they were drawn with (mutiple colours);

protected void onDraw(Canvas canvas) {

    //if the drawing is new - dont draw any paths
    if (isNew != true) {

        //go through every previous path and draw them
        for (PathPaint p : paths) {

            if (!p.isErase)
            {
                canvas.drawPath(p.myPath, p.myPaint);
            }
            else
            {
                //Paint eraserPaint = setDefaultPaint();
                //eraserPaint.setAlpha(0xFF);
                //eraserPaint.setXfermode(new PorterDuffXfermode(PorterDuff.Mode.CLEAR));
                //eraserPaint.setColor(Color.TRANSPARENT);
                //canvas.drawBitmap(canvasBitmap, 0, 0, canvasPaint);
                canvas.drawPath(p.myPath, p.myPaint);
            }
            canvas.drawBitmap(canvasBitmap, 0, 0, null);

        }
    }

I have tried lots of the proposed options online, but to no avail.

I have tried setting the paint on the drawpath to have all the various commented out properties set.

I have tried drawing on a bitmap and then loading that bitmap to the canvas (canvas.drawBitmap(canvasBitmap, 0, 0, null))

I have turned off hardware acceleration in this class' constructor

setLayerType(View.LAYER_TYPE_SOFTWARE, null);

but either the line is not drawn or when the collection is redrawing the paths, the eraser draws a black line;

enter image description here

What is interesting is that if I perform the erasing using the bitmap without the loop aspect - the eraser works as expected;

//If we are making a new drawing we don't want to go through all the paths
    if (isNew != true && erase ==false) {

        //go through every previous path and draw them


        for (PathPaint p : paths) {

            if (!p.isErase)
            {
                canvas.drawPath(p.myPath, p.myPaint);
            }
            //this section now takes place in the elseIF
            else
            {
                Paint eraserPaint = setDefaultPaint();
                eraserPaint.setAlpha(0xFF);
                eraserPaint.setXfermode(new PorterDuffXfermode(PorterDuff.Mode.CLEAR));
                eraserPaint.setColor(Color.TRANSPARENT);


                canvas.drawBitmap(canvasBitmap, 0, 0, canvasPaint);
                canvas.drawPath(p.myPath, p.myPaint);
            }
        }
    }
    else if (isNew != true && erase ==true)
    {   
        //This works correctly for Erasing but I dont have the ability to Undo/Redo with this approach! 
        canvas.drawBitmap(canvasBitmap, 0, 0, canvasPaint);
        canvas.drawPath(drawPath, drawPaint);

    }

This, however, is a problem since I want to be able to Undo/Redo erasing (thus the point of the collection)

Can anyone please help me?


Solution

  • It looks like you only use one view (layer) where you first put a background image and then draw the paths, which replace the background. If so, when you erase, you are removing from that one and only view/layer, which includes the paths and the background. If you use two layers (two views inside a Framelayout), one in the back where you would load the background, and one in the front where you put all the paths, then erasing on the top layer only removes the paths and the background would come through.

    There are different ways of doing the layering. As an example, this FrameLayout replaces the view that currently holds the background and the drawn paths (refer to as XXXView in the code.)

    <FrameLayout
       android:layout_width= ...copy from the existing XXXView ...
       android:layout_height= ...copy from the existing XXXView ... >
    
       <ImageView 
          android:id = "@+id/background"
          android:layout_width="fill_parent"
          android:layout_height="fill_parent"
          ...
          ... the background is loaded here />
    
    
       <XXXView (this the existing view where the paths are drawn onto)
          android:layout_width="fill_parent"
          android:layout_height="fill_parent"
          ...
          ... no background here />
    
    </FrameLayout>