0

I'm trying to write a semi-transparent eraser brush into an app I'm developing but have found two problems crop up depending on how it is implemented. Hopefully there is a workaround I can get pointed at.

Code samples are in mono droid / xamarin

So, pretty typically I have a setup which draws paths on to a drawing canvas. the OnDraw method of my view draws its basic bitmap and then my path on top of that.

    // Canvas is set up elsewhere
    canvasBitmap = Bitmap.CreateBitmap(w, h, Bitmap.Config.Argb8888);
    DrawCanvas.SetBitmap(canvasBitmap);


    protected override void OnDraw(Canvas canvas)
    {
        base.OnDraw(canvas);

        // Draw the saved canvas bitmap
        canvas.DrawBitmap(canvasBitmap, 0, 0, canvasPaint);

        // On top of that draw the current path
        canvas.DrawPath(DrawPath, DrawPaint);
    }



    public bool OnTouch(View view, MotionEvent ev)
    {
        float touchX = ev.GetX();
        float touchY = ev.GetY();


        switch (ev.Action)
        {
            case MotionEventActions.Down:

                if (Editable)
                {
                    // I've removed a bunch of code here but it basically
                    // moves the path start to the touch point and sets
                    // up the brush to start erasing. It does set the
                    // porterduff mode to dstOur
                    DrawPaint.SetMaskFilter(null);
                    DrawPaint.SetXfermode( new PorterDuffXfermode(PorterDuff.Mode.DstOut));

                    DrawPath.MoveTo(touchX, touchY);

                } else { return false;  }
                break;

            case MotionEventActions.Move:

                // Just draws a line to the new touch coordinates
                DrawPath.LineTo(touchX, touchY);

                break;
            case MotionEventActions.Up:
            case MotionEventActions.Cancel:

                // saves some data about the path and draws to the drawing canvas.
                DrawCanvas.DrawPath(DrawPath, DrawPaint);

                // Recycle it
                DrawPath.Reset();

                break;

        }
        return true;
    }

So there are two ways to draw the eraser line while it is being drawn. The first is to draw on to the drawing canvas (which writes to the canvasBitmap) and the second is to draw directly on the canvas provided by OnDraw.

My problems are as follows:

If I draw on to the DrawCanvas I get a stacking effect where the erased line gets progressively more and more transparent. This is kind of understandable because during each OnDraw loop the eraser path is being baked down into the canvasBitmap.

If I draw directly on to the canvas provided by OnDraw I get the "black line" problem which also makes some sense as it is erasing on to nothing. When the path is drawn on to the canvasBitmap in the Up event then everything looks right and there's a nice semi-transparent eraser line.

Ideally there is a different solution of which I am not aware for the "black line" problem but I've not been able to find anything. I've also tried thinking of ways to cache the canvasBitmap and while a draw event is in progress use that rather than the active copy being drawn to by the Move events.

Does anyone have something I could take a look at which might suggest a solution? Java is fine as it's easy to port to c#.

Many thanks in advance! Nigel

Community
  • 1
  • 1
Nigel DH
  • 207
  • 3
  • 11

1 Answers1

0

In the absence of someone with more experience/smarts than me having a suggestion, I got around it in the follow horrible way. It might still be helpful for someone else while I continue to find a smarter way to do it.

When the Down event happens I grab a clean copy of the bitmap into a temporary variable. On each OnDraw event I readraw that clean bitmap and then draw the erase path on top of that.

The horrible bit is that each OnDraw event has a bitmap copy method in it to ensure that a doubling of the erase path doesn't happen.

OnDraw( Canvas canvas )
{
            tempBitmap.Recycle();
            tempBitmap = _drawingBitmap.Copy(_drawingBitmap.GetConfig(), true);

            DrawCanvas.DrawColor(Color.Transparent, PorterDuff.Mode.Clear);
            DrawCanvas.DrawBitmap(tempBitmap, 0, 0, DrawPaint));
            DrawCanvas.DrawPath(DrawPath, DrawPaint);
}

The bitmap recycling is pretty critical obviously. On the .Up event in the touch listener it recycles again and then nulls the variable.

Not pretty, but it works.

Any better ideas much appreciated!

Nigel DH
  • 207
  • 3
  • 11