4

I have this code in onDraw().

radius = drawGmpImage(this.gmpImage, canvas);
canvas.drawCircle(kHorizontalOffset, kScreenVerticalOffset, radius , maskPaint);

drawGmpImage creates a complex graphic which is a circle with many lines drawn on it. It's a library function which I cannot change. The lines are polygons and can extend beyond the circumference of the circle.

The need is to "blank out" everything drawn outside the circle.

This is a port from iOS and the original developers solution is to use a simple bitmap mask, stored as a resource, with a transparent circle which matches the size of the circle drawn. Simply drawing the bitmap over the drawn circle has the desired effect but is not an option on Android as I need to support all possible resolutions and ratios.

Therefore, the canvas.drawCircle() call is the beginning of my attempt to mask out everything outside the circle. It works fine in that a filled circle is drawn over my drawn circle so that the only thing left are the polygon lines outside the drawn circles circumference. Radius is the radius of the drawn circle.

How can I invert this so that I am left with the contents of the circle?

Cœur
  • 37,241
  • 25
  • 195
  • 267
Simon
  • 14,407
  • 8
  • 46
  • 61

2 Answers2

7

Why is it that you can spend hours working on something, give up, ask the question, then stumble upon the answer 20 minutes later? The joys of life.

Path path = new Path();
path.addCircle(kHorizontalOffset, kScreenVerticalOffset, radius, Path.Direction.CW);
canvas.clipPath(path);

I'd missed the clipPath method which will take any path and use it as a clipping region. Adding my masking circle to the path does exactly what I need.

[EDIT]

This works well, but there is a problem. It doesn't work if hardware acceleration is turned on. I could turn acceleration off, but then I lose a lot of performance in the rest of the draw which is complex.

Here's how I finally solved it:

In onSizeChanged(), create a bitmap mask. I draw a transparent circle in the right place on the bitmap using this paint. The key to is to use a PorterDuffXfermode.

 maskPaint = new Paint();
 maskPaint.setColor(Color.TRANSPARENT);
 maskPaint.setXfermode(new PorterDuffXfermode(PorterDuff.Mode.CLEAR));
 maskPaint.setStyle(Paint.Style.FILL);

then create the bitmap

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

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

    createMask(w,h,this.radius);

}

private void createMask(int w,int h, int radius){

    if (mask!=null){mask.recycle();}

    mask = Bitmap.createBitmap(w, h, Bitmap.Config.ARGB_8888);
    Canvas maskCanvas = new Canvas(mask);
    maskCanvas.drawCircle(w, h, radius, maskPaint);

}

Then in onDraw(), I simply draw the mask over the entire view:

@Override
protected void onDraw(Canvas canvas){

    // draw the image();
    setRadius(drawGmpImage(this.gmpImage, canvas));
    canvas.drawCircle(kHorizontalOffset, kScreenVerticalOffset, radius , maskPaint);

    // overlay the mask bitmap
    if (mask != null) {
        canvas.drawBitmap(mask, 0f, 0f, bitmapPaint);
    }

If the radius changes, the mask is recreated:

 private void setRadius(int radius){

     this.radius = radius;
     createMask(kHorizontalOffset, kScreenVerticalOffset, radius);

 }
Simon
  • 14,407
  • 8
  • 46
  • 61
1

I don't know how to achieve this using masks, hence another approach :

you could draw the radius in a specific colour, say black.

Than Floodfill from one of the corners. I ve created a Floodfill algorithm before its not very difficult. You start in the upperleft corner, set that pixel to your desired colo. Then you look at the neighbouring pixels. If they are black, you stop in that direction, if not, you change the colour and look at the neighbouring pixels again.

Good luck

Entreco
  • 12,738
  • 8
  • 75
  • 95
  • Ah ha. That's an idea. Thank you. I'll give it a try but I'm a little pessimistic because to get smooth zooming, I need to keep the frame rates high and I'm not sure a flood fill on something like a Galaxy S3 can be quick enough. I'm already down to about 20FPS with all the maths going on but I'll give it a go and report back. Thanks again... – Simon Jan 28 '13 at 22:24
  • Or http://stackoverflow.com/questions/8551035/draw-transparent-circle-filled-outside – Entreco Jan 28 '13 at 22:37
  • but that solution uses BufferedImage which is not available in Android – Simon Jan 28 '13 at 22:44
  • r, you can draw your circle with much larger radius, and a much larger thickness. You will need a tiny bit of math to make the thick circle cover the whole screen – Entreco Jan 28 '13 at 23:03
  • 1
    I was about to code up a fast flood fill when I stumbled across the easiest solution. I've added it as a answer. Cheers, and thanks again. – Simon Jan 28 '13 at 23:03