Android教程網
  1. 首頁
  2. Android 技術
  3. Android 手機
  4. Android 系統教程
  5. Android 游戲
 Android教程網 >> Android技術 >> 關於Android編程 >> 自定義控件三部曲之繪圖篇(十二)——Paint之setXfermode(三)

自定義控件三部曲之繪圖篇(十二)——Paint之setXfermode(三)

編輯:關於Android編程

一篇給大家講解了有關setXfermode的幾種模式,還剩最後一系列DST模式沒講,這篇文章就給大家講講這個模式的用法及實戰

一、DST相關模式

在講完了SRC相關的模式以後,我們知道SRC相關的模式,都是在相交區域優先顯示源圖像為主。
與之相對應的有DST相關的模式,在DST相關的模式中,在處理相交區域時,優先以目標圖像顯示為主。
這部分所涉及的模式有:Mode.DST、Mode.DST_IN、Mode.DST_OUT、Mode.DST_OVER、Mode.DST_ATOP

1、Mode.DST

計算公式為:[Da, Dc]
從公式中也可以看出,在處理源圖像所在區域的相交問題時,正好與Mode.SRC相反,全部以目標圖像顯示
所以示例圖像為:

 

\

 

2、Mode.DST_IN

計算公式為:[Da * Sa,Dc * Sa]
我們與Mode.SRC_IN的公式對比一下:SRC_IN:[Sa * Da, Sc * Da]
正好與SRC_IN相反,Mode.DST_IN是在相交時利用源圖像的透明度來改變目標圖像的透明度和飽和度。當源圖像透明度為0時,目標圖像就完全不顯示。
示例圖像為:

 

\

由於Mode.DST_IN的模式與SRC_IN正好是相反,所以我們利用Mode.SRC_IN實現的示例,只需要將源圖像與目標圖像對調就可以使用Mode.DST_IN來實現了。
所以大家自己來改造實現下《自定義控件三部曲之繪圖篇(十一)——Paint之setXfermode(二)》中的圖形圓角效果和圖片倒影效果,這裡就不再貼代碼了,有困難的同學可以參考文章底部源碼,源碼中有實現。
這裡我們就再來實現幾個不一樣的效果,這些效果也可以通過Mode.SRC_IN模式實現哦,大家自己可以試試

示例1、區域波紋

我們在《自定義控件三部曲之繪圖篇(六)——Path之貝賽爾曲線和手勢軌跡、水波紋效果 》中講解了水波紋效果,但這個水波紋效果卻只能是一固定在一個矩形區域,本例我們就利用xfermode來實現在不規則區域中顯示水波紋效果,效果圖如下:

 

\

這裡使用到一張圖片(text_shade.png):

\

在這張圖片中,只有文字部分是純白色的,其它區域都是透明像素。
所以再加上我們需要自己繪制的水波紋效果的圖片,這裡就有兩張圖片了,一張是水波紋效果圖,另一張是text_shade.png
那麼問題來了,如果我們使用Mode.DST_IN模式的話,誰當目標圖像,誰當源圖像呢?
這就需要分析Mode.DST_IN模式的成像原理了,在Mode.DST_IN中,源圖像所占區域計算結果圖像時,相交區域顯示的是DST目標圖像;
所以我們要最終顯示的被裁剪後的波紋圖,所以DST目標圖像就應該是波紋圖。
所以源碼如下:

public class CircleWave_DSTIN extends View {
    private Paint mPaint;
    private Path mPath;
    private int mItemWaveLength = 1000;
    private int dx;

    private Bitmap BmpSRC,BmpDST;
    public CircleWave_DSTIN(Context context, AttributeSet attrs) {
        super(context, attrs);
        mPath = new Path();
        mPaint = new Paint();
        mPaint.setColor(Color.GREEN);
        mPaint.setStyle(Paint.Style.FILL_AND_STROKE);

        BmpSRC = BitmapFactory.decodeResource(getResources(),R.drawable.text_shade,null);
        BmpDST = Bitmap.createBitmap(BmpSRC.getWidth(), BmpSRC.getHeight(), Bitmap.Config.ARGB_8888);

        startAnim();
    }

    @Override
    protected void onDraw(Canvas canvas) {
        super.onDraw(canvas);

        generageWavePath();

        //先清空bitmap上的圖像,然後再畫上Path
        Canvas c = new Canvas(BmpDST);
        c.drawColor(Color.BLACK, PorterDuff.Mode.CLEAR);
        c.drawPath(mPath,mPaint);

        canvas.drawBitmap(BmpSRC,0,0,mPaint);
        int layerId = canvas.saveLayer(0, 0, getWidth(), getHeight(), null, Canvas.ALL_SAVE_FLAG);
        canvas.drawBitmap(BmpDST,0,0,mPaint);
        mPaint.setXfermode(new PorterDuffXfermode(PorterDuff.Mode.DST_IN));
        canvas.drawBitmap(BmpSRC,0,0,mPaint);
        mPaint.setXfermode(null);
        canvas.restoreToCount(layerId);
    }

    /**
     * 生成此時的Path
     */
    private void generageWavePath(){
        mPath.reset();
        int originY = BmpSRC.getHeight()/2;
        int halfWaveLen = mItemWaveLength/2;
        mPath.moveTo(-mItemWaveLength+dx,originY);
        for (int i = -mItemWaveLength;i<=getWidth()+mItemWaveLength;i+=mItemWaveLength){
            mPath.rQuadTo(halfWaveLen/2,-50,halfWaveLen,0);
            mPath.rQuadTo(halfWaveLen/2,50,halfWaveLen,0);
        }
        mPath.lineTo(BmpSRC.getWidth(),BmpSRC.getHeight());
        mPath.lineTo(0,BmpSRC.getHeight());
        mPath.close();
    }

    public void startAnim(){
        ValueAnimator animator = ValueAnimator.ofInt(0,mItemWaveLength);
        animator.setDuration(2000);
        animator.setRepeatCount(ValueAnimator.INFINITE);
        animator.setInterpolator(new LinearInterpolator());
        animator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
            @Override
            public void onAnimationUpdate(ValueAnimator animation) {
                dx = (int)animation.getAnimatedValue();
                postInvalidate();
            }
        });
        animator.start();
    }
}
這裡涉及到利用貝賽爾曲線生成波浪線的知識,有關波浪線的代碼就一概而過了,核心放在xfermode上。
首先看初始化:
BmpSRC = BitmapFactory.decodeResource(getResources(),R.drawable.text_shade,null);
BmpDST = Bitmap.createBitmap(BmpSRC.getWidth(), BmpSRC.getHeight(), Bitmap.Config.ARGB_8888);
在初始化時,生成了兩張圖像,正如我們所說BmpDST是要顯示的波浪線,所以我們新建一個空白圖像,用以畫即將生成的波浪線;而BmpSRC則用來顯示源圖。
然後再看onDraw()部分:
generageWavePath();

//先清空bitmap上的圖像,然後再畫上Path
Canvas c = new Canvas(BmpDST);
c.drawColor(Color.BLACK, PorterDuff.Mode.CLEAR);
c.drawPath(mPath,mPaint);
這一段是將波浪線的Path畫到BmpDST圖像上。
首先是利用generageWavePath()生成當前的波浪線path,然後是利用PorterDuff.Mode.CLEAR模式將BmpDST圖像清空:
c.drawColor(Color.BLACK, PorterDuff.Mode.CLEAR);
有關使用PorterDuff.Mode.CLEAR來清空圖像的知識,後面在講解PorterDuff.Mode.CLEAR時會細講,這裡知道即可。
為什麼我們要首先清空BmpDST的圖像呢,因為在這個圖像之前已經畫上了上一次的波浪線的圖像了,我們如果不將圖像清空,那這兩幅波紋線不就重疊了麼。
在清空BmpDST之後,再在上面畫上path:
c.drawPath(mPath,mPaint);
在生成BmpDST之後,我們就可以使用xfermode了:
//先畫上SRC圖像來顯示完整的文字
canvas.drawBitmap(BmpSRC,0,0,mPaint);
int layerId = canvas.saveLayer(0, 0, getWidth(), getHeight(), null, Canvas.ALL_SAVE_FLAG);
canvas.drawBitmap(BmpDST,0,0,mPaint);
mPaint.setXfermode(new PorterDuffXfermode(PorterDuff.Mode.DST_IN));
canvas.drawBitmap(BmpSRC,0,0,mPaint);
mPaint.setXfermode(null);
canvas.restoreToCount(layerId);
有些同學可能會問,為什麼在saveLayer前,先要畫一遍BmpSRC呢,這是因為我們在使用Mode.DST_IN時,除了相交區域以外,其它區域都會因為有空白像素而消失不見了,如果我們不加canvas.drawBitmap(BmpSRC,0,0,mPaint);效果圖將是這樣的:
\
看到問題所在了吧,如果僅僅這樣顯示,用戶根本看不出來這是個啥字,所以我們需要提前把完整的字母繪制上去,來顯示完整的文字樣式;
源碼在文章底部給出

2、心電圖

下面我們再來實現一個心電圖的動畫,效果圖如下:

 

\

很明顯,正規的心電圖應該是利用Path把當前的實時的點連接起來,我這裡只是一張圖片(hearmap.png)通過使用動畫來實現的

\

中間是一條心電圖線,其余位置都是透明像素;大家先想想我們要怎麼利用這張圖片實現上面的動畫呢?
利用Mode.DST_IN模式,由於在這個模式中,相交區域優先顯示目標圖像,所以我們這裡需要顯示心電圖,所以心電圖就是目標圖像。
那麼問題來了,源圖像是啥?
由於我們需要從右向左逐漸顯示心電圖圖像,所以我們源圖像就是自建的空白圖像,在這個圖像中,繪制一個矩形,逐漸增大矩形的區域,即相交區域也會跟著增大,由於相交區域會顯示出目標圖像,顯示出來的結果就是心電圖的動畫,代碼如下:

 

public class HeartMap extends View {

    private Paint mPaint;
    private int mItemWaveLength = 0;
    private int dx=0;

    private Bitmap BmpSRC,BmpDST;
    public HeartMap(Context context, AttributeSet attrs) {
        super(context, attrs);

        mPaint = new Paint();
        mPaint.setColor(Color.RED);

        BmpDST = BitmapFactory.decodeResource(getResources(),R.drawable.heartmap,null);
        BmpSRC = Bitmap.createBitmap(BmpDST.getWidth(), BmpDST.getHeight(), Bitmap.Config.ARGB_8888);

        mItemWaveLength = BmpDST.getWidth();
        startAnim();
    }

    @Override
    protected void onDraw(Canvas canvas) {
        super.onDraw(canvas);

        Canvas c = new Canvas(BmpSRC);
        //清空bitmap
        c.drawColor(Color.BLACK, PorterDuff.Mode.CLEAR);
        //畫上矩形
        c.drawRect(BmpDST.getWidth() - dx,0,BmpDST.getWidth(),BmpDST.getHeight(),mPaint);

        //模式合成
        int layerId = canvas.saveLayer(0, 0, getWidth(), getHeight(), null, Canvas.ALL_SAVE_FLAG);
        canvas.drawBitmap(BmpDST,0,0,mPaint);
        mPaint.setXfermode(new PorterDuffXfermode(PorterDuff.Mode.DST_IN));
        canvas.drawBitmap(BmpSRC,0,0,mPaint);
        mPaint.setXfermode(null);
        canvas.restoreToCount(layerId);
    }


    public void startAnim(){
        ValueAnimator animator = ValueAnimator.ofInt(0,mItemWaveLength);
        animator.setDuration(6000);
        animator.setRepeatCount(ValueAnimator.INFINITE);
        animator.setInterpolator(new LinearInterpolator());
        animator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
            @Override
            public void onAnimationUpdate(ValueAnimator animation) {
                dx = (int)animation.getAnimatedValue();
                postInvalidate();
            }
        });
        animator.start();
    }
}
這段代碼難度不大,就不再講了,我們再來舉個例子。

3、不規則波紋

上面我們實現的波紋效果都是規則的,如果我們想實現如下圖這樣的不規則波紋要怎麼辦呢?
\

 

在這裡我們需要用到兩張圖:
一張圓形遮罩(circle_shape.png)
\

一張不規則的波浪圖

\

想必到這裡,可能很多同學都知道要怎麼做了
就是在圓形遮罩上繪制不斷移動的不規則的波浪圖。
代碼如下:

 

public class IrregularWaveView extends View {

    private Paint mPaint;
    private int mItemWaveLength = 0;
    private int dx=0;

    private Bitmap BmpSRC,BmpDST;

    public IrregularWaveView(Context context, AttributeSet attrs) {
        super(context, attrs);
        mPaint = new Paint();

        BmpDST = BitmapFactory.decodeResource(getResources(),R.drawable.wave_bg,null);
        BmpSRC = BitmapFactory.decodeResource(getResources(),R.drawable.circle_shape,null);
        mItemWaveLength = BmpDST.getWidth();

        startAnim();
    }


    @Override
    protected void onDraw(Canvas canvas) {
        super.onDraw(canvas);

        //先畫上圓形
        canvas.drawBitmap(BmpSRC,0,0,mPaint);
        //再畫上結果
        int layerId = canvas.saveLayer(0, 0, getWidth(), getHeight(), null, Canvas.ALL_SAVE_FLAG);
        canvas.drawBitmap(BmpDST,new Rect(dx,0,dx+BmpSRC.getWidth(),BmpSRC.getHeight()),new Rect(0,0,BmpSRC.getWidth(),BmpSRC.getHeight()),mPaint);
        mPaint.setXfermode(new PorterDuffXfermode(PorterDuff.Mode.DST_IN));
        canvas.drawBitmap(BmpSRC,0,0,mPaint);
        mPaint.setXfermode(null);
        canvas.restoreToCount(layerId);
    }


    public void startAnim(){
        ValueAnimator animator = ValueAnimator.ofInt(0,mItemWaveLength);
        animator.setDuration(4000);
        animator.setRepeatCount(ValueAnimator.INFINITE);
        animator.setInterpolator(new LinearInterpolator());
        animator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
            @Override
            public void onAnimationUpdate(ValueAnimator animation) {
                dx = (int)animation.getAnimatedValue();
                postInvalidate();
            }
        });
        animator.start();
    }
}
首先看動畫部分:
public void startAnim(){
    ValueAnimator animator = ValueAnimator.ofInt(0,mItemWaveLength);
    animator.setDuration(4000);
    animator.setRepeatCount(ValueAnimator.INFINITE);
    animator.setInterpolator(new LinearInterpolator());
    animator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
        @Override
        public void onAnimationUpdate(ValueAnimator animation) {
            dx = (int)animation.getAnimatedValue();
            postInvalidate();
        }
    });
    animator.start();
}
其中:
mItemWaveLength = BmpDST.getWidth();
在這裡生成了一個ValueAnimator動畫,動畫的值從0到波浪圖的總長,實時的值保存在dx變量中。
再看onDraw函數
protected void onDraw(Canvas canvas) {
    super.onDraw(canvas);

    //先畫上圓形
    canvas.drawBitmap(BmpSRC,0,0,mPaint);
    //再畫上計算結果
    int layerId = canvas.saveLayer(0, 0, getWidth(), getHeight(), null, Canvas.ALL_SAVE_FLAG);
    canvas.drawBitmap(BmpDST,new Rect(dx,0,dx+BmpSRC.getWidth(),BmpSRC.getHeight()),new Rect(0,0,BmpSRC.getWidth(),BmpSRC.getHeight()),mPaint);
    mPaint.setXfermode(new PorterDuffXfermode(PorterDuff.Mode.DST_IN));
    canvas.drawBitmap(BmpSRC,0,0,mPaint);
    mPaint.setXfermode(null);
    canvas.restoreToCount(layerId);
}
首先,是畫了個BmpSRC所表示的原形圖:
canvas.drawBitmap(BmpSRC,0,0,mPaint);
與“示例1、區域波紋”的原因一樣,我們需要先畫上圓形圖,不然就看不出來整體的樣式是什麼樣的。
然後在使用xfermode時,最難的應該是畫BmpDST的這句:
canvas.drawBitmap(BmpDST,new Rect(dx,0,dx+BmpSRC.getWidth(),BmpSRC.getHeight()),new Rect(0,0,BmpSRC.getWidth(),BmpSRC.getHeight()),mPaint);
它的意思就是截取波浪圖上new Rect(dx,0,dx+BmpSRC.getWidth(),BmpSRC.getHeight())這個矩形位置,將其畫在BmpSRC的位置:new Rect(0,0,BmpSRC.getWidth(),BmpSRC.getHeight())
好了,這部分也就到這了
源碼在文章底部給出

3、Mode.DST_OUT

計算公式為:[Da * (1 - Sa), Dc * (1 - Sa)]
同樣,我們拿這個公式與Mode.SRC_OUT對比一下,Mode.SRC_OUT:[Sa * (1 - Da), Sc * (1 - Da)]
可以看出Mode.SRC_OUT是利用目標圖像的透明度的補值來改變源圖像的透明度和飽和度。而Mode.DST_OUT反過來,是通過源圖像的透明度補值來改變目標圖像的透明度和飽和度。
簡單來說,在Mode.DST_OUT模式下,就是相交區域顯示的是目標圖像,目標圖像的透明度和飽和度與源圖像的透明度相反,當源圖像透明底是100%時,則相交區域為空值。當源圖像透明度為0時,則完全顯示目標圖像。非相交區域完全顯示目標圖像。
示例圖像為:
\
有些同學對這個結果可能感覺很奇怪,我們來分析一下,上篇中我們提到在xfermode的示例圖像中,我們主要需要關注兩點:
\
圖中編號1的相交區域:在DST_OUT模式下,由於源圖像的透明度是100%,所以計算後的結果圖像在這個區域是空像素。
圖中編號2的非相交區域:在DST_OUT模式下,這個區域的源圖像透明度仍為100%,所以計算後的結果圖像在這個區域仍是空像素。
所以我們做下簡單的總結,當源圖像區域透明度為100%時,所在區域計算結果為透明像素,當源圖像的區域透明時,計算結果就是目標圖像;
這與SRC_OUT模式的結果正好相反,在SRC_OUT模式下,當目標圖像區域透明度為100%時,所在區域計算結果為透明像素,當目標圖像的區域透明時,計算結果就是源圖像;
所以,在上篇中,使用SRC_OUT模式實現的橡皮擦效果和刮刮卡效果都是可以使用DST_OUT模式實現的,只需要將SRC和DST所對應的圖像翻轉一下就可以了;
這裡就不再實現了,大家自己來試試吧。

4、Mode.DST_OVER

計算公式為:[Sa + (1 - Sa)*Da, Rc = Dc + (1 - Da)*Sc]
同樣先寫Mode.SRC_OVER對比一下,SRC_OVER:[Sa + (1 - Sa)*Da, Rc = Sc + (1 - Sa)*Dc]
所以它們的效果就是在SRC模式中以顯示SRC圖像為主變成了以顯示DST圖像為主。從SRC模式中的使用目標圖像控制結果圖像的透明度和飽和度變成了由源圖像控件結果圖像的透明度和飽和度。
示例圖像為:
\

 

 

5、Mode.DST_ATOP

計算公式為:[Sa, Sa * Dc + Sc * (1 - Da)]
示例圖像為:

 

\

由於在SRC中,我們知道了Mode.SRC_ATOP與MODE.SRC_IN的區別:
一般而言SRC_ATOP是可以和SRC_IN通用的,但SRC_ATOP所產生的效果圖在目標圖的透明度不是0或100%的時候,會比SRC_IN模式產生的圖像更亮些;
我們再來對比下DST中的兩個模式與SRC中的這兩個模式中公式中區別:
SRC_IN: [Sa * Da, Sc * Da]
SRC_ATOP:[Da, Sc * Da + (1 - Sa) * Dc]
DST_IN:[Da * Sa , Dc * Sa ]
DST_ATOP:[Sa, Sa * Dc + Sc * (1 - Da)]
從公式中可以看到,在SRC模式中,以顯示源圖像為主,透明度和飽和度利用Da來調節
而在DST模式中,以顯示目標圖像為主,透明度和飽和度利用Sa來調節
所以Mode.DST_ATOP與Mode.DST_IN的關系也是:
一般而言DST_ATOP是可以和DST_IN通用的,但DST_ATOP所產生的效果圖在源圖像的透明度不是0或100%的時候,會比DST_IN模式產生的圖像更亮些;
同樣,大家也可以使用Mode.DST_ATOP實現上篇文章中Mode.SRC_ATOP的兩個示例:圓角效果和圖片倒影,這裡就不再講了

到這裡有關DST相關模式都講完了,我們總結一下:
1、DST相關模式是完全可以使用SRC對應的模式來實現的,只不過需要將目標圖像和源圖像對調一下即可。
2、在SRC模式中,是以顯示源圖像為主,通過目標圖像的透明度來調節計算結果的透明度和飽和度,而在DST模式中,是以顯示目標圖像為主,通過源圖像的透明度來調節計算結果的透明度和飽和度。

二、其它模式

除了顏色疊加系列模式,SRC系列模式和DST系列模式以外,另外還有兩個模式Mode.CLEAR和Mode.XOR,下面我們就來看看它們的用法

1、Mode.CLEAR

計算公式:[0, 0]
示例圖像:
\

 

前面我們做清空圖像的時候用過這個方法,從公式中可以看到,計算結果直接就是[0,0]即空像素。也就是說,源圖像所在區域都會變成空像素!
這樣就起到了清空源圖像所在區域圖像的功能了。上面示例中已經存在這個Mode的用法,這裡就不再舉例了。

2、Mode.XOR

計算公式為:[Sa + Da - Sa*Da,Sc*(1 - Da) + Dc*(1 - Sa) + min(Sc, Dc)]
示例圖像為:

 

\

單從示例圖像中,好像是異或的功能,即將源圖像中除了相交區域以外的部分做為結果。但仔細看看公式,其實並沒有這麼簡單。
首先看公式中透明度部分:Sa + Da - Sa*Da,就是將目標圖像和源圖像的透明度相加,然後減去它們的乘積,所以計算結果的透明度會增大(即比目標圖像和源圖像都大,當其中一個圖像的透明度為1時,那結果圖像的透明度肯定是1)
然後再看顏色值部分:Sc*(1 - Da) + Dc*(1 - Sa) + min(Sc, Dc);表示源圖像和目標圖像分別以自己的透明度的補值乘以對方的顏色值,然後相加得到結果。最後再加上Sc, Dc中的最小值。
這個模式太過復雜,在實際應用中應用也比較少,目前沒想到有哪些示例,大家有用到的,可以跟我說哦。

在實際應用中,我們可以從下面三個方面來決定使用哪一個模式:
1、首先,目標圖像和源圖像混合,需不需要生成顏色的疊加特效,如果需要疊加特效則從顏色疊加相關模式中選擇,有Mode.ADD(飽和度相加)、Mode.DARKEN(變暗),Mode.LIGHTEN(變亮)、Mode.MULTIPLY(正片疊底)、Mode.OVERLAY(疊加),Mode.SCREEN(濾色)
2、當不需要特效,而需要根據某一張圖像的透明像素來裁剪時,就需要使用SRC相關模式或DST相關模式了。由於SRC相關模式與DST相關模式是相通的,唯一不同的是決定當前哪個是目標圖像和源圖像;
3、當需要清空圖像時,使用Mode.CLEAR
這篇文章到這裡就結束啦,有關xfermode的知識比較有難度,大家需要仔細揣摩下

 

  1. 上一頁:
  2. 下一頁:
熱門文章
閱讀排行版
Copyright © Android教程網 All Rights Reserved