Welcome to OGeek Q&A Community for programmer and developer-Open, Learning and Share
Welcome To Ask or Share your Answers For Others

Categories

0 votes
200 views
in Technique[技术] by (71.8m points)

java - PorterduffXfermode: Clear a section of a bitmap

The goal is to draw a bitmap and over the top of something, and draw shapes that ERASE the underlying area of the bitmap.

I have a proof of concept to try and understand how I should go about this. I have found numerous hints about using:

android.graphics.PorterDuff.Mode.CLEAR

The code below creates a screen with a blue background and adds a custom view. This view draws on its canvas from bottom to top: a pink background, a bitmap with a slight inset to show the pink background, and a yellow circle for each PorterDuffXfermode.

import android.app.Activity;
import android.content.Context;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.Paint;
import android.graphics.PorterDuffXfermode;
import android.graphics.Paint.Style;
import android.graphics.PorterDuff.Mode;
import android.graphics.RectF;
import android.os.Bundle;
import android.view.View;
import android.view.Window;
import android.widget.RelativeLayout;

public class Test extends Activity {
    Drawing d = null;
    
    @Override
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        requestWindowFeature(Window.FEATURE_NO_TITLE);
        
        RelativeLayout.LayoutParams rlp = null;
        
        // Create the view for the xfermode test
        d = new Drawing(this);
        rlp = new RelativeLayout.LayoutParams(600, 900);
        rlp.addRule(RelativeLayout.CENTER_IN_PARENT);
        d.setLayoutParams(rlp);
        
        RelativeLayout rl = new RelativeLayout(this);
        rl.setBackgroundColor(Color.rgb(0, 0, 255));
        rl.addView(d);
        
        // Show the layout with the test view
        setContentView(rl);
    }
    
    public class Drawing extends View {
        Paint[] pDraw = null;
        Bitmap bm = null;
        
        public Drawing(Context ct) {
            super(ct);
            
            // Generate bitmap used for background
            bm = BitmapFactory.decodeFile("mnt/sdcard/Pictures/test.jpg");
            
            // Generate array of paints
            pDraw = new Paint[16];
            
            for (int i = 0; i<pDraw.length; i++) {
                pDraw[i] = new Paint();
                pDraw[i].setARGB(255, 255, 255, 0);
                pDraw[i].setStrokeWidth(20);
                pDraw[i].setStyle(Style.FILL);
            }
            
            // Set all transfer modes
            pDraw[0].setXfermode(new PorterDuffXfermode(Mode.CLEAR));
            pDraw[1].setXfermode(new PorterDuffXfermode(Mode.DARKEN));
            pDraw[2].setXfermode(new PorterDuffXfermode(Mode.DST));
            pDraw[3].setXfermode(new PorterDuffXfermode(Mode.DST_ATOP));
            pDraw[4].setXfermode(new PorterDuffXfermode(Mode.DST_IN));
            pDraw[5].setXfermode(new PorterDuffXfermode(Mode.DST_OUT));
            pDraw[6].setXfermode(new PorterDuffXfermode(Mode.DST_OVER));
            pDraw[7].setXfermode(new PorterDuffXfermode(Mode.LIGHTEN));
            pDraw[8].setXfermode(new PorterDuffXfermode(Mode.MULTIPLY));
            pDraw[9].setXfermode(new PorterDuffXfermode(Mode.SCREEN));
            pDraw[10].setXfermode(new PorterDuffXfermode(Mode.SRC));
            pDraw[11].setXfermode(new PorterDuffXfermode(Mode.SRC_ATOP));
            pDraw[12].setXfermode(new PorterDuffXfermode(Mode.SRC_IN));
            pDraw[13].setXfermode(new PorterDuffXfermode(Mode.SRC_OUT));
            pDraw[14].setXfermode(new PorterDuffXfermode(Mode.SRC_OVER));
            pDraw[15].setXfermode(new PorterDuffXfermode(Mode.XOR));
        }
        
        @Override
        public void onDraw(Canvas canv) {
            // Background colour for canvas
            canv.drawColor(Color.argb(255, 255, 0, 255));
            
            // Draw the bitmap leaving small outside border to see background
            canv.drawBitmap(bm, null, new RectF(15, 15, getMeasuredWidth() - 15, getMeasuredHeight() - 15), null);

            float w, h;
            w = getMeasuredWidth();
            h = getMeasuredHeight();

            // Loop, draws 4 circles per row, in 4 rows on top of bitmap
            // Drawn in the order of pDraw (alphabetical)
            for(int i = 0; i<4; i++) {
                for(int ii = 0; ii<4; ii++) {
                    canv.drawCircle((w / 8) * (ii * 2 + 1), (h / 8) * (i * 2 + 1), w / 8 * 0.8f, pDraw[i*4 + ii]);
                }
            }
        }
    }

}

This is the result of the test:

enter image description here

The CLEAR mode is the top left, which shows as black.

In another example where I was trying to use this I had a DialogFragment where CLEAR mode erased the entire DialogFragment so that the activity beneath could be seen. Hence the reason I used many different background colours in this test.

Could this possibly be clearing the pixels of the entire activity like that other example led me to believe? I would've thought only the pixels of canvas related to the view could be erased, but in my other example the pixels of the custom view, underlying image view and DialogFragment background were all cleared.

Could someone please help me understand what exactly is going on?

EDIT: I reproduced an example confirming my suspicions. When adding this exact custom view but in a DialogFragment, the underlying activity becomes visible.

enter image description here

This seems a pretty clear indicator to me that the Mode.CLEAR is somehow erasing the canvas of the views underneath as well? My guess would be the black in the first example is that of the top level view?

See Question&Answers more detail:os

与恶龙缠斗过久,自身亦成为恶龙;凝视深渊过久,深渊将回以凝视…
Welcome To Ask or Share your Answers For Others

1 Reply

0 votes
by (71.8m points)

The problem is hardware acceleration. Turn it OFF for the particular View that you are painting with CLEAR. If you're using a custom view, do this in the constructors:

if (android.os.Build.VERSION.SDK_INT >= 11) 
{
     setLayerType(View.LAYER_TYPE_SOFTWARE, null);
}

You can also call setLayerType on a view reference.


与恶龙缠斗过久,自身亦成为恶龙;凝视深渊过久,深渊将回以凝视…
OGeek|极客中国-欢迎来到极客的世界,一个免费开放的程序员编程交流平台!开放,进步,分享!让技术改变生活,让极客改变未来! Welcome to OGeek Q&A Community for programmer and developer-Open, Learning and Share
Click Here to Ask a Question

...