15

I try to erase parts of a bitmap in my Android application by using Porter-Duff Xfermodes.

I have a green background which is overlayed by a blue bitmap. When I touch the screen a "hole" in the overlaying bitmap is supposed to be created making the green background visible. Instead of a hole my current code produces a black dot.

Below is my code. Any ideas, what I am doing wrong here?

/** Called when the activity is first created. */
@Override
public void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    getWindow().setFlags(WindowManager.LayoutParams.FLAG_FULLSCREEN,
            WindowManager.LayoutParams.FLAG_FULLSCREEN);
    setContentView(new DrawView(this));
}

public class DrawView extends View implements OnTouchListener {

    private int x = 0;
    private int y = 0;

    Bitmap bitmap;
    Canvas bitmapCanvas;

    private final Paint paint = new Paint();
    private final Paint eraserPaint = new Paint();

    public DrawView(Context context) {
        super(context);
        setFocusable(true);
        setFocusableInTouchMode(true);

        this.setOnTouchListener(this);

        // Set background
        this.setBackgroundColor(Color.GREEN);

        // Set bitmap
        bitmap = Bitmap.createBitmap(320, 480, Bitmap.Config.RGB_565);
        bitmapCanvas = new Canvas();
        bitmapCanvas.setBitmap(bitmap);
        bitmapCanvas.drawColor(Color.BLUE);

        // Set eraser paint properties
        eraserPaint.setAlpha(0);
        eraserPaint.setXfermode(new PorterDuffXfermode(PorterDuff.Mode.DST_IN));
        eraserPaint.setAntiAlias(true);
    }

    @Override
    public void onDraw(Canvas canvas) {
        bitmapCanvas.drawColor(Color.BLUE);
        bitmapCanvas.drawCircle(x, y, 10, eraserPaint);

        canvas.drawBitmap(bitmap, 0, 0, paint);
    }

    public boolean onTouch(View view, MotionEvent event) {
        x = (int) event.getX();
        y = (int) event.getY();

        invalidate();
        return true;
    }

}
Philipp
  • 788
  • 1
  • 11
  • 23

2 Answers2

10

Here is working code... may help somebody

public class ImageDemo extends Activity {

    @Override
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);

        setContentView(new Panel(this));   
    }

    class Panel extends View {

        private Bitmap  mBitmap;
        private Canvas  mCanvas;
        private Path    mPath;
        private Paint   mPaint;

        Bitmap bitmap;
        Canvas pcanvas;

        int x = 0;
        int y =0;
        int r =0;

        public Panel(Context context) {
            super(context);

            Log.v("Panel", ">>>>>>");

            setFocusable(true);
            setBackgroundColor(Color.GREEN);

            // setting paint 
            mPaint = new Paint();
            mPaint.setAlpha(0);
            mPaint.setXfermode(new PorterDuffXfermode(PorterDuff.Mode.DST_IN));
            mPaint.setAntiAlias(true);

            // getting image from resources
            Resources r = this.getContext().getResources();

            Bitmap bm = BitmapFactory.decodeResource(getResources(),  R.drawable.mickey);

            // converting image bitmap into mutable bitmap  
            bitmap =  bm.createBitmap(295, 260, Config.ARGB_8888);
            pcanvas = new Canvas();
            pcanvas.setBitmap(bitmap);    // drawXY will result on that Bitmap
            pcanvas.drawBitmap(bm, 0, 0, null);           
        }

        @Override
        protected void onDraw(Canvas canvas) {  
            // draw a circle that is  erasing bitmap            
            pcanvas.drawCircle(x, y, r, mPaint);

            canvas.drawBitmap(bitmap, 0, 0,null);

            super.onDraw(canvas);
        }       

        @Override
        public boolean onTouchEvent(MotionEvent event) {        
             // set parameter to draw circle on touch event
             x = (int) event.getX();
             y = (int) event.getY();

             r =20;
             // At last invalidate canvas
             invalidate();
             return true;
        }
    }
}
Wryday
  • 161
  • 7
pawan
  • 101
  • 1
  • 2
  • 1
    thanks I found this helpful, you can convert the bitmap like this though with our hard coding the size. `bitmap = bm.copy(Config.ARGB_8888, true);` – Nathan Schwermann Aug 05 '11 at 21:13
  • Thanks I also found this really helpful. I was searching for this solution earlier last year. – Sourabh Feb 18 '14 at 12:39
  • Note that if you want to just draw a hole in the image, you should just set the background color to Color.TRANSPARENT instead of Color.GREEN. I was having a lot of time piecing together working code from all of the half-examples out there, thanks so much for posting a full example :) – adavea Sep 08 '14 at 19:34
6

First thought, I'm not sure if setting alpha to 0 on your erase paint object is a good idea. That might make the whole thing ineffective.

Also, you should always use Bitmap.Config.ARGB_8888 if you're dealing with alphas.

If you're having trouble with the PorterDuff stuff, though, I would suggest simplifying your approach to ONLY do that (temporarily). That will help you narrow down the part which isn't working. Comment out everything to do with touch and view updates.

Then you can single out what part of the drawing isn't working right. Set up your constructor like this:

DrawView()
{
    /* Create the background green bitmap */
    ...

    /* Create foreground transparent bitmap */
    ...

    /* Draw a blue circle on the foreground bitmap */
    ...

    /* Apply the foreground to the background bitmap
       using a PorterDuff method */
    ...
}

onDraw()
{
    /* Simply draw the background bitmap */
    ...
}

If you set things up like that, you should be able to tell how your PD method is affecting the green bitmap, and change things accordingly.

Josh
  • 10,618
  • 2
  • 32
  • 36
  • Thanks a lot Josh! Creating the Bitmap using Bitmap.Config.ARGB_8888 instead of Bitmap.Config.RGB_565 solved the problem. Regarding effectives: I plan to apply the alpha of a paint path to my foreground bitmap as next step. What approach would you suggest instead of using Porter-Duff regarding drawing performance? – Philipp Aug 13 '10 at 09:18
  • 1. As you get touch events in onTouch, use drawPath once in a while to draw a black path to a transparent image. This is your eraser bitmap. If you're not already doing it, the FingerPaint app in API demos has some nice example code. 2. In onDraw, apply your eraser bitmap to the ORIGINAL blue bitmap with the proper PorterDuff method. This will cut a whole in the blue bitmap the shape of what you've erased (the black path) so far. 3. Still in onDraw, put the green bitmap on screen, followed by the newly cut blue bitmap. – Josh Aug 13 '10 at 14:57
  • Note that the above could be costly every draw cycle - look into clipping regions to limit the area you're updating to only what the user has changed in the eraser bitmap. – Josh Aug 13 '10 at 14:58
  • Thanks again, Josh! Regarding limited screen updates and smooth paths, I found a pretty good tutorial here: http://corner.squareup.com/2010/07/smooth-signatures.html – Philipp Aug 22 '10 at 15:49
  • Haha, yeah, that section on "Surgically Invalidate" is exactly what I was talking about. That's a nice tutorial, too! – Josh Aug 23 '10 at 15:59