1. 程式人生 > >【Android】解析Paint類中Xfermode的使用

【Android】解析Paint類中Xfermode的使用

Paint類提供了setXfermode(Xfermode xfermode)方法,Xfermode指明瞭原影象和目標影象的結合方式。談到Xfermode就不得不談它的派生類PorterDuffXfermode,PorterDuffXfermode類只有一個建構函式如下:

public PorterDuffXfermode (PorterDuff.Mode mode)

建立PorterDuffXfermode例項,需要指定porter-duff模式。porter-duff實際上是Thomas Porter和Tom Duff的簡寫,他們在1984年發表的“Compositing Digital Images”文章中指出了該項發現。PorterDuff.Mode是一個列舉類,下面是不同模式的結合圖:

每種模式的結合原理,官方文件已經說得非常清楚了。圖象組合實現的程式碼:

Paint paint = new Paint();
canvas.drawBitmap(destinationImage, 0, 0, paint);

PorterDuff.Mode mode = // choose a mode
paint.setXfermode(new PorterDuffXfermode(mode));

canvas.drawBitmap(sourceImage, 0, 0, paint);

接下來筆者將會展示這些模式的實現,希望能夠對你所有幫助。眾所周知,Bitmap中儲存著Canvas中繪製的資料。為了更好的展示Xfermode功能,我們首先應該建立一張透明的畫布層Canvas,然後在該畫布層上完成一些列的porter-duff模式,最後將該畫布層中的資料Bitmap繪製到螢幕層的畫布中。

//set hardware layer
setLayerType(View.LAYER_TYPE_HARDWARE, null);

Bitmap separate_bitmap=Bitmap.createBitmap(screenWidth,screenHeight,Config.ARGB_8888);
Canvas separate_canvas=new Cavnas(bitmap);

separate_canvas.drawBitmap(destinationImage,0,0,paint);

PorterDuff.Mode mode=//choose a mode
paint.setXfermode(new
PorterDuffXfermode(mode)); separate_canvas.drawBitmap(sourceImage,0,0,paint); //retore to software layer setLayerType(View.LAYER_TYPE_SOFTWARE,null); //drawing separate_bitmap to base canvas canvas.drawBitmap(separate_bitmap, 0, 0, paint);

首先用separate_bitmap建立一個separate_canvas,因為separate_bitmap上沒有任何資料,所以separate_canvas這時候是完全透明的,這時候我們可以在separate_canvas上順利地完成Xfermode的操作。之所以不在螢幕canvas上的進行這些操作,這是因為螢幕canvas不是無色的和透明的,預設是白色和不透明的,也就是說如果它的Bitmap在設定為Config.ARGB_8888的情況下,那麼ARGB的值就分別是255、255、255、255,這顯然將會對Xfermode的合成造成影響。如果這樣的影響是在你的預期範圍內的話,可以考慮直接繪製到螢幕canvas上。然後將separate_canvas已經繪製完的separate_bitmap資料繪製螢幕canvas的bitmap中。

canvas.drawBitmap(separate_bitmap, 0, 0, paint);

有一點不得不提就是hardware

//set hardware layer
setLayerType(View.LAYER_TYPE_HARDWARE, null);

//do Xfermode combine.
....

//retore to software layer
setLayerType(View.LAYER_TYPE_SOFTWARE,null);

檢視預設的圖層型別不是hardware,而是software。設定圖層型別型別為hardware,就會開啟硬體加速,在渲染圖形時就會強制使用android硬體渲染通道。在AndroidManifest.xml進行如下配置也會開啟硬體加速:

android:hardwareAccelerated="true"

hardware layers對於複雜圖形樹的繪製更快、更高效。不僅僅是Xfermode,任何需要進行開發複雜檢視或是快速動畫,筆者都強烈建議開啟此硬體加速。Canvas還提供了方法saveLayer,利用這個方法可以達到分層開發,它的原理圖:但是筆者不建議在開發中使用saveLayer這個方法,尤其在複製介面或是動畫中,因為saveLayer是一個非常“昂貴”的方法,它通常會佔用更多的資源。activity_main.xml

<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:paddingBottom="@dimen/activity_vertical_margin"
    android:paddingLeft="@dimen/activity_horizontal_margin"
    android:paddingRight="@dimen/activity_horizontal_margin"
    android:paddingTop="@dimen/activity_vertical_margin"
    tools:context=".MainActivity" >

    <ImageView
        android:id="@+id/image"
        android:background="@android:drawable/edit_text"
        android:src="@drawable/ic_launcher"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:layout_centerHorizontal="true"
        android:layout_alignParentTop="true"/>
    
    <ScrollView
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:layout_below="@id/image"
         >
    <RadioGroup
        android:id="@+id/radioGroup1"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:orientation="vertical" >
        <RadioButton
            android:id="@+id/radio0"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:checked="true"
            android:text="Src" />
        <RadioButton
            android:id="@+id/radio1"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:text="Dst" />
        <RadioButton
            android:id="@+id/radio2"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:text="SrcOver" />
        <RadioButton
            android:id="@+id/radio3"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:text="DstOver" />
        <RadioButton
            android:id="@+id/radio4"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:text="SrcIn" />
        <RadioButton
            android:id="@+id/radio5"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:text="DstIn" />
        <RadioButton
            android:id="@+id/radio6"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:text="SrcOut" />
        <RadioButton
            android:id="@+id/radio7"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:text="DstOut" />
        <RadioButton
            android:id="@+id/radio8"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:text="SrcATop" />
        <RadioButton
            android:id="@+id/radio9"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:text="DstATop" />
        <RadioButton
            android:id="@+id/radio10"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:text="Xor" />
        <RadioButton
            android:id="@+id/radio11"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:text="Darken" />
        <RadioButton
            android:id="@+id/radio12"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:text="Lighten" />
        <RadioButton
            android:id="@+id/radio13"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:text="Multiply" />
        <RadioButton
            android:id="@+id/radio14"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:text="Screen" />
        <RadioButton
            android:id="@+id/radio15"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:text="Clear" />
    </RadioGroup>
    </ScrollView>
</RelativeLayout>

MainActivity.java

public class MainActivity extends Activity{
    private int screenWidth=0;
    private int screenHeight=0;
    private PorterDuff.Mode porterduffmodel=null;
    private ImageView imageView=null;
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        
        //initial porter-duff mode is Src
        porterduffmodel=PorterDuff.Mode.SRC;
        
        Point point=new Point();
        getWindowManager().getDefaultDisplay().getSize(point);
        //width of screen
        screenWidth=point.x;
        //height of screen
        screenHeight=point.y;
        
        
        imageView= ((ImageView)findViewById(R.id.image));
        setXfermodeImage(imageView);
        
        //choose src
        findViewById(R.id.radio0).setOnClickListener(new OnClickListener() {
            @Override
            public void onClick(View arg0) {
                porterduffmodel=PorterDuff.Mode.SRC;
                setXfermodeImage(imageView);
            }
        });
        
        //choose Dst
        findViewById(R.id.radio1).setOnClickListener(new OnClickListener() {
            @Override
            public void onClick(View arg0) {
                porterduffmodel=PorterDuff.Mode.DST;
                setXfermodeImage(imageView);
            }
        });
        
        //choose SrcOver
        findViewById(R.id.radio2).setOnClickListener(new OnClickListener() {
            @Override
            public void onClick(View arg0) {
                porterduffmodel=PorterDuff.Mode.SRC_OVER;
                setXfermodeImage(imageView);
            }
        });
        
        //choose DstOver
        findViewById(R.id.radio3).setOnClickListener(new OnClickListener() {
            @Override
            public void onClick(View arg0) {
                porterduffmodel=PorterDuff.Mode.DST_OVER;
                setXfermodeImage(imageView);
            }
        });
        
        //choose SrcIn
        findViewById(R.id.radio4).setOnClickListener(new OnClickListener() {
            @Override
            public void onClick(View arg0) {
                porterduffmodel=PorterDuff.Mode.SRC_IN;
                setXfermodeImage(imageView);
            }
        });
        
        //choose DstIn
        findViewById(R.id.radio5).setOnClickListener(new OnClickListener() {
            @Override
            public void onClick(View arg0) {
                porterduffmodel=PorterDuff.Mode.DST_IN;
                setXfermodeImage(imageView);
            }
        });
        
        //choose SrcOut
        findViewById(R.id.radio6).setOnClickListener(new OnClickListener() {
            @Override
            public void onClick(View arg0) {
                porterduffmodel=PorterDuff.Mode.SRC_OUT;
                setXfermodeImage(imageView);
            }
        });
        
        //choose DstOut
        findViewById(R.id.radio7).setOnClickListener(new OnClickListener() {
            @Override
            public void onClick(View arg0) {
                porterduffmodel=PorterDuff.Mode.DST_OUT;
                setXfermodeImage(imageView);
            }
        });
        
        //choose SrcATop
        findViewById(R.id.radio8).setOnClickListener(new OnClickListener() {
            @Override
            public void onClick(View arg0) {
                porterduffmodel=PorterDuff.Mode.SRC_ATOP;
                setXfermodeImage(imageView);
            }
        });
        
        //choose DstATop
        findViewById(R.id.radio9).setOnClickListener(new OnClickListener() {
            @Override
            public void onClick(View arg0) {
                porterduffmodel=PorterDuff.Mode.DST_ATOP;
                setXfermodeImage(imageView);
            }
        });
        
        //choose Xor
        findViewById(R.id.radio10).setOnClickListener(new OnClickListener() {
            @Override
            public void onClick(View arg0) {
                porterduffmodel=PorterDuff.Mode.XOR;
                setXfermodeImage(imageView);
            }
        });
        
        //choose Darken
        findViewById(R.id.radio11).setOnClickListener(new OnClickListener() {
            @Override
            public void onClick(View arg0) {
                porterduffmodel=PorterDuff.Mode.DARKEN;
                setXfermodeImage(imageView);
            }
        });
        
        //choose lighten
        findViewById(R.id.radio12).setOnClickListener(new OnClickListener() {
            @Override
            public void onClick(View arg0) {
                porterduffmodel=PorterDuff.Mode.LIGHTEN;
                setXfermodeImage(imageView);
            }
        });
        
        //choose multiply
        findViewById(R.id.radio13).setOnClickListener(new OnClickListener() {
            @Override
            public void onClick(View arg0) {
                porterduffmodel=PorterDuff.Mode.MULTIPLY;
                setXfermodeImage(imageView);
            }
        });
        
        //choose screen
        findViewById(R.id.radio14).setOnClickListener(new OnClickListener() {
            @Override
            public void onClick(View arg0) {
                porterduffmodel=PorterDuff.Mode.SCREEN;
                setXfermodeImage(imageView);
            }
        });
        
        //clear
        findViewById(R.id.radio15).setOnClickListener(new OnClickListener() {
            @Override
            public void onClick(View arg0) {
                porterduffmodel=PorterDuff.Mode.CLEAR;
                setXfermodeImage(imageView);
            }
        });
    }
    
    private void setXfermodeImage(ImageView imageView){
        //open hardware accelerate rendering, it's software rendering default.
        imageView.setLayerType(View.LAYER_TYPE_HARDWARE, null);
        
        Bitmap separate_bitmap=Bitmap.createBitmap(screenWidth, screenHeight/2, Config.ARGB_8888);
        Canvas separate_canvas=new Canvas(separate_bitmap);
        
        
        Bitmap destination_bitmap=Bitmap.createBitmap(screenWidth, screenHeight/2, Config.ARGB_8888);
        //create destination canvas
        Canvas destination_canvas=new Canvas(destination_bitmap);
        Paint destination_paint=new Paint();
        destination_paint.setColor(Color.YELLOW);
        destination_paint.setStyle(Style.FILL);
        //drawing destination circle to destination canvas
        destination_canvas.drawCircle(screenWidth/2,screenHeight/4,screenHeight/8,destination_paint);
        
        
        Bitmap source_bitmap=Bitmap.createBitmap(screenWidth, screenHeight/2, Config.ARGB_8888);
        //create source canvas
        Canvas source_canvas=new Canvas(source_bitmap);
        Paint source_paint=new Paint();
        source_paint.setColor(Color.BLUE);
        source_paint.setStyle(Style.FILL);
        //drawing source rectangle to source canvas
        source_canvas.drawRect(screenWidth/8, screenHeight/4, screenWidth/2, 7*screenHeight/16, source_paint);
        
        
        Paint separate_paint=new Paint();
        separate_canvas.drawBitmap(destination_bitmap, 0, 0, separate_paint);
        separate_paint.setXfermode(new PorterDuffXfermode(porterduffmodel));
        separate_canvas.drawBitmap(source_bitmap, 0, 0,separate_paint);
        
        //set separate_bitmap to target view
        imageView.setImageBitmap(separate_bitmap);
        
        //retrieve layer type to software layer
        imageView.setLayerType(ImageView.LAYER_TYPE_SOFTWARE, null);
    }
}

效果圖:

利用這些功能,我們可以輕易的完成一些較為複雜的功能。

接下來筆者實現一個繪製圓形頭像的功能,DstIn只會繪製目標圖形中與源影象相交的部分。因此我們只需要設定源影象為圓圈,目標影象設定為圖片,然後再使用DstIn便可以完成顯示圓頭像的功能了。

public class MainActivity extends Activity {
    int screenWidth=0;
    int screenHeight=0;
    
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        Point point=new Point();
        getWindowManager().getDefaultDisplay().getSize(point);
        //initialize screen width
        screenWidth=point.x;
        //initialize screen height
        screenHeight=point.y;
        
        setContentView(new MyView(this));
    }
    class MyView extends View{
        Bitmap destination_bitmap=null;
        public MyView(Context context){
            super(context);
            destination_bitmap=BitmapFactory.decodeResource(getResources(), R.drawable.scene);
        }
        @Override
        protected void onDraw(Canvas canvas) {
            super.onDraw(canvas);            
            
            //create an empty separate_bitmap and a separate_canvas
            Bitmap separate_bitmap=Bitmap.createBitmap(screenWidth, screenHeight, Config.ARGB_8888);
            Canvas separate_canvas=new Canvas(separate_bitmap);
            
            
            //create an empty source canvas
            Bitmap source_bitmap=Bitmap.createBitmap(screenWidth, screenHeight, Config.ARGB_8888);
            Canvas source_canvas=new Canvas(source_bitmap);
            //drawing a circle on source_bitmap
            Paint source_paint=new Paint();
            source_paint.setStyle(Style.FILL);
            source_canvas.drawCircle(screenWidth/2,screenHeight/4,200,source_paint);
            
            
            Paint paint=new Paint();
            //drawing destination_bitmap to separate_canvas
            separate_canvas.drawBitmap(destination_bitmap, 0, 0, paint);
            //set porter-duff mode is DST_IN
            paint.setXfermode(new PorterDuffXfermode(PorterDuff.Mode.DST_IN));
            //drawing source_bitmap to separate_canvas
            separate_canvas.drawBitmap(source_bitmap, 0, 0, paint);
                        
            //drawing separate_bitmap to screen canvas
            canvas.drawBitmap(separate_bitmap, 0, 0, new Paint());
            
        }
    }
}

效果圖:

最後筆者簡單展示一下,如何利用DST_OUT模式,實現刮刮樂的功能。

public class MainActivity extends Activity {
    int screenWidth=0;
    int screenHeight=0;
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        Point point=new Point();
        getWindowManager().getDefaultDisplay().getSize(point);
        //screen width
        screenWidth=point.x;
        //screen height
        screenHeight=point.y;
        
        setContentView(new MyView(this));
    }
    class MyView extends View{
        private Bitmap resultBitmap=null;
        private Bitmap showBitmap=null;
        private Bitmap tempBitmap=null;
        private Canvas tempCanvas=null;
        private float start_x = 0;
        private float start_y = 0;
         private float end_x=0;
         private float end_y=0;
         private Paint paint=null;
         private Path path=null;
        public MyView(Context context){
            super(context);
            //create result bitmap with R.drawable.result which representing the answer.
            resultBitmap = BitmapFactory.decodeResource(getResources(), R.drawable.result);
            //create show bitmap with R.drawable.show which used to cover answer.
            showBitmap = BitmapFactory.decodeResource(getResources(), R.drawable.show);
            
            tempBitmap=Bitmap.createBitmap(screenWidth, screenHeight, Config.ARGB_8888);
            tempCanvas=new Canvas(tempBitmap);
            tempCanvas.drawBitmap(showBitmap, 0, 0, new Paint());
            
            path=new Path();
            paint=new Paint();
            paint.setStrokeWidth(20);
            paint.setStyle(Style.STROKE);
            paint.setXfermode(new PorterDuffXfermode(PorterDuff.Mode.DST_OUT));
            paint.setStrokeCap(Paint.Cap.ROUND);
        }
        @Override
        protected void onDraw(Canvas canvas) {
            super.onDraw(canvas);
            canvas.drawBitmap(resultBitmap, 0, 0,new Paint());
            
            canvas.drawBitmap(tempBitmap, 0, 0, new Paint());
        }
        
        @Override
        public boolean onTouchEvent(MotionEvent event) {
            if(event.getAction()==MotionEvent.ACTION_DOWN){
                start_x=event.getX();
                start_y=event.getY();
                path.moveTo(start_x, start_y);//setting start point
            }else if(
                    event.getAction()==MotionEvent.ACTION_MOVE
                    ||
                    event.getAction()==MotionEvent.ACTION_UP){
                end_x=event.getX();
                end_y=event.getY();
                path.lineTo(end_x,end_y);
                tempCanvas.drawPath(path,paint);
                start_x=end_x;
                start_y=end_y;
                
                postInvalidate();
            }
            return true;
        }
    }
}

效果圖:

這篇Blog並未詳細的講解每種模式的詳細合成模式過程,關於這些讀者可以到Google官方文件查閱。最後,希望這篇可以對你所有幫助。