自定义控件三部曲之绘图篇(十二)——Paint之setXfermode(三)

版权声明:本文为博主原创文章,未经博主允许不得转载。 https://blog.csdn.net/harvic880925/article/details/51288006

前言:几乎忘了昨日的种种 开始又敢作梦

系列文章:

Android自定义控件三部曲文章索引:http://blog.csdn.net/harvic880925/article/details/50995268


一篇给大家讲解了有关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();
    }
}
这里涉及到利用贝赛尔曲线生成波浪线的知识,请参考 《自定义控件三部曲之绘图篇(六)——Path之贝赛尔曲线和手势轨迹、水波纹效果 》,有关波浪线的代码就一概而过了,核心放在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的知识比较有难度,大家需要仔细揣摩下


如果本文有帮到你,记得加关注哦

源码下载地址:http://download.csdn.net/detail/harvic880925/9507446

请大家尊重原创者版权,转载请标明出处:http://blog.csdn.net/harvic880925/article/details/51288006 谢谢

如果你喜欢我的文章,那么你将会更喜欢我的微信公众号,将定期推送博主最新文章与收集干货分享给大家(一周一次)






猜你喜欢

转载自blog.csdn.net/harvic880925/article/details/51288006