6

So I'm trying to speed up some drawing we're doing (drawing a portion of an arc with alpha transparency) and was attempting to cache the entire arc into a separate bitmap, and show it selectively with an alpha mask.

From the research I've done (the Xfermodes API demo for Android, this example, and this tool), if I have for example the following two graphics:

enter image description here

enter image description here

and draw using the following:

Xfermode DST_IN = new PorterDuffXfermode(PorterDuff.Mode.DST_IN);
Paint paint = new Paint(Paint.ANTI_ALIAS_FLAG);

canvas.drawBitmap(circle, 0, 0, paint);
paint.setXfermode(DST_IN);
canvas.drawBitmap(arc, 0, 0, paint);
paint.setXfermode(null);

I should get this result:

enter image description here

Where the destination image (the circle) is clipped to the area where the source image (the arc) is drawn. Instead, I get the full circle. If I just draw the arc, it appears in the correct location, and if I use DST_OUT instead, I get the inverse of the expected result (the other three quadrants of the circle).

I've also made sure to disable hardware rendering for this view, in case there was an issue with this Xfermode, but it doesn't make a difference.

I broke it out into a separate project at the simplest level trying to get it to work, and using the following code, I still have the same problem:

public class ClippedView extends View {
    private Xfermode DST_IN, DST_OUT;
    private Paint paint;

    public ClippedView(Context context) {
        super(context);
        init();
    }

    private void init() {
        setLayoutParams(new RelativeLayout.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.MATCH_PARENT));
        this.setLayerType(View.LAYER_TYPE_SOFTWARE, null);
        paint = new Paint(Paint.ANTI_ALIAS_FLAG);
        DST_IN = new PorterDuffXfermode(PorterDuff.Mode.DST_IN);
        DST_OUT = new PorterDuffXfermode(PorterDuff.Mode.DST_OUT);
    }

    @Override
    protected void onDraw(Canvas canvas) {
        super.onDraw(canvas);
        paint.setColor(Color.GREEN);
        canvas.drawRect(0, 0, getWidth() / 2, getHeight() / 2, paint);
        paint.setColor(Color.BLACK);
        paint.setXfermode(DST_IN);
        canvas.drawCircle(getWidth() / 2, getHeight() / 2, getWidth() / 2, paint);
        paint.setXfermode(null);
    }
}

Am I using it wrong? Am I just missing something? Have I found a bug? :)

Kevin Coppock
  • 133,643
  • 45
  • 263
  • 274
  • I faced the same problem: I am trying to crop the canvas to a rounded rectangle and DST_IN is not working. After long research, I found that PorterDuff.DST_IN only works if you call canvas.drawBitmap (). So canvas.drawCircle (), canvas.drawRoundedRect (), etc. won't work. You can draw your circle to a bitmap first and then draw a bitmap. – vadiole Apr 19 '21 at 19:30

1 Answers1

9

There's a much cheaper and easier way to achieve this: use clipping. Canvas.clipRect() is enough. Your solution is burning a lot of fillrate. You can get the effect you want by using SRC_IN instead of DST_IN. Be careful though: it will work only in a transparent Bitmap or in layer. When you draw directly on screen, the destination is already filled by the window background.

Romain Guy
  • 97,993
  • 18
  • 219
  • 200
  • Thanks! I didn't realize the onDraw canvas included the window background -- that makes what's happening make a lot more sense. I went with the bitmap route because there was no easy way to draw a closed arc as a path, and then use that in clipPath(). I'll find a route to draw that path myself unless you have another suggestion? – Kevin Coppock Dec 01 '12 at 00:55
  • I have a View I need to refresh every 1/25sec. Creating each time a new transparent `Bitmap` and then using `PotterDuff.Mode`s costs me a lot of FPS and memory, so this method fails. I tried drawing on screen (on provided `Canvas` by `onDraw()`) but the problem is each `PotterDuff.Mode.CLEAR` makes my `Canvas` black instead of transparent (no `Bitmap` binded to `Canvas`). So I understand the problem is "destination is already filled by the window background"? How then create a fast refreshing View with a transparent background and PotterDuff.Mode.SRC_IN? What means "it will work in layer"? – cadavre Aug 23 '13 at 17:34
  • Don't create a new bitmap every time, reuse it. – Romain Guy Sep 03 '13 at 18:13
  • @RomainGuy is there a tutorial or resource to learn about fillrate, and other properties? – Thracian Jan 10 '21 at 11:30