美文网首页Android常用功能
Android——PorterDuffXfermode

Android——PorterDuffXfermode

作者: 海晨忆 | 来源:发表于2018-03-28 20:19 被阅读20次

    个人博客:haichenyi.com。感谢关注

    简介

      PorterDuffXfermode是什么鬼?个人理解,简单的来讲就是做两个Bitmap操作的,什么操作呢?有裁剪,合并等等,有16种图形混合模式。先举一个简单的例子,我们在慢慢讲:

    /**
     * Author: 海晨忆
     * Date: 2018/3/28
     * Desc:
     */
    public class MyCustomView extends View {
      private int width = 300;
      private int height = 300;
      private Bitmap dstBmp;
      private Bitmap srcBmp;
      private Paint mPaint;
    
      public MyCustomView(Context context) {
        this(context, null);
      }
    
      public MyCustomView(Context context, @Nullable AttributeSet attrs) {
        this(context, attrs, 0);
      }
    
      public MyCustomView(Context context, @Nullable AttributeSet attrs, int defStyleAttr) {
        super(context, attrs, defStyleAttr);
        initView();
      }
    
      private void initView() {
        setLayerType(View.LAYER_TYPE_SOFTWARE, null);
        srcBmp = makeSrc(width, height);
        dstBmp = makeDst(width, height);
        mPaint = new Paint();
      }
    
      @Override
      protected void onDraw(Canvas canvas) {
        super.onDraw(canvas);
        canvas.translate(getWidth() / 2 - width / 2, getHeight() / 2 - height / 2);
        canvas.drawColor(Color.BLUE);
        int layerID = canvas.saveLayer(0, 0, width * 2, height * 2, mPaint, Canvas.ALL_SAVE_FLAG);
        canvas.drawBitmap(dstBmp, 0, 0, mPaint);
        @SuppressLint("DrawAllocation") PorterDuffXfermode xfermode = new PorterDuffXfermode(PorterDuff.Mode.SRC_IN);
        mPaint.setXfermode(xfermode);
        canvas.drawBitmap(srcBmp, width / 2, height / 2, mPaint);
        mPaint.setXfermode(null);
        canvas.restoreToCount(layerID);
      }
    
      private Bitmap makeDst(int w, int h) {
        Bitmap dst = Bitmap.createBitmap(w, h, Bitmap.Config.ARGB_8888);
        Canvas mCanvas = new Canvas(dst);
        Paint mPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
        mPaint.setColor(Color.RED);
        mCanvas.drawOval(new RectF(0, 0, w, h), mPaint);
        return dst;
      }
    
      private Bitmap makeSrc(int w, int h) {
        Bitmap src = Bitmap.createBitmap(w, h, Bitmap.Config.ARGB_8888);
        Canvas mCanvas = new Canvas(src);
        Paint mPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
        mPaint.setColor(Color.YELLOW);
        mCanvas.drawRect(0, 0, w, h, mPaint);
        return src;
      }
    }
    
    

    效果图如下:


    简介demo图.png

      上面画了一个圆形bitmap,画了一个矩形bitmap,设置了一个模式 PorterDuff.Mode.SRC_IN 就变成了上面的形状,这是怎么做到的呢?带着我们的问题,进入我们的主题。

    注意点

      为什么我要拿一个大标题来写这个呢?因为,我当时卡在这里很久,然后踩着巨人的肩膀,我才踏过去的。

    1. 首先,两个图形必须都是Bitmap,直接用Canvas画形状,做操作,是达不到效果的。重要的事情说三遍:两个图形必须都是Bitmap。两个图形必须都是Bitmap。两个图形必须都是Bitmap

    2. 其次,避免不必要的麻烦,请先关闭硬件加速。重要的事情说三遍:请先关闭硬件加速。请先关闭硬件加速。请先关闭硬件加速

    3. 然后,两个bitmap的大小最好一样。

    4. 最后,我要强调的是:先绘制的是目标图,后绘制的是源图。

      这里一直说bitmap,辣么,怎么生成这个bitmap,生成这个bitmap之后怎么画图形呢?如下代码:

    //第一步,我们先创建一个bitmap对象
    Bitmap dst = Bitmap.createBitmap(w, h, Bitmap.Config.ARGB_8888);
    //第二步,我们通过这个bitmap对象创建一个画布,
    //说白了,就是new 一个画布,把bitmap放到画布的构造方法里面
        Canvas mCanvas = new Canvas(dst);
        Paint mPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
        mPaint.setColor(Color.RED);
    //最后,在这个画布上面的所有操作,最后都是呈现在bitmap上面。
    //就像这里的,在这个画布上面画了一个椭圆,其实,最后我们的bitmap就是一个椭圆
        mCanvas.drawOval(new RectF(0, 0, w, h), mPaint);
    

      可以,bitmap会创建了,再就是我们前面说的两个bitmap,先绘制的是目标图,后绘制的是源图,一个是dst(目标图片,下层,先画),一个是src(源图片,上层,后画)。就是我们上面的自定view里面的onDraw()方法里面,用onDraw的canvas画的东西。

      我们如果不用这个xfermode模式,我们的代码应该是这样的:

    @Override
      protected void onDraw(Canvas canvas) {
        super.onDraw(canvas);
        canvas.translate(getWidth() / 2 - width / 2, getHeight() / 2 - height / 2);
        canvas.drawColor(Color.BLUE);
        canvas.drawBitmap(dstBmp, 0, 0, mPaint);
        canvas.drawBitmap(srcBmp, width / 2, height / 2, mPaint);
      }
    

      很简单的几行代码,把画布移到正中间,给画布加一个背景蓝色,先画dst,后画src,跑出来的效果图应该是下面这样的:

    注意点1.png

      我们如果加上这个xfermode模式里面的 PorterDuff.Mode.SRC_IN模式,代码如下:

    @Override
      protected void onDraw(Canvas canvas) {
        super.onDraw(canvas);
        canvas.translate(getWidth() / 2 - width / 2, getHeight() / 2 - height / 2);
        canvas.drawColor(Color.BLUE);
        int layerID = canvas.saveLayer(0, 0, width * 2, height * 2, mPaint, Canvas.ALL_SAVE_FLAG);
        canvas.drawBitmap(dstBmp, 0, 0, mPaint);
        @SuppressLint("DrawAllocation") PorterDuffXfermode xfermode = new PorterDuffXfermode(PorterDuff.Mode.SRC_IN);
        mPaint.setXfermode(xfermode);
        canvas.drawBitmap(srcBmp, width / 2, height / 2, mPaint);
        mPaint.setXfermode(null);
        canvas.restoreToCount(layerID);
      }
    

      比上面的代码,就多加了一个xfermode模式,他们要是同一个画笔,用完之后,记得要把这个模式置null这个saveLayer等会讲,先不说。跑出来的效果图,如下:

    注意点2.png

      前面,我们一直都在强调dst先画,src后画,如果调换一下,会是什么样的结果呢?代码我就不贴出来了,就把那两个drawBitmap调换一个位置,跑出来的效果图,如下:

    注意点3.png

      很明显,跟我们的预期结果不一样。这是为什么呢?带着我们的问题进入下一节。

    十六种模式和saveLayer()

    十六种模式

    名字 含义 名字 含义
    CLEAR 清除模式[0,0],即最终所有点的像素的alpha 和color 都为 0,所以画出来的效果只有白色背景 SRC 显示上层绘制图片
    DST 显示下层绘制图片 SRC_OVER 正常绘制显示,上下层绘制叠盖
    DST_OVER 上下层都显示,下层居上显示 SRC_IN 取两层绘制交集。显示上层
    DST_IN 取两层绘制交集,显示下层 SRC_OUT 取上层绘制非交集部分
    DST_OUT 取下层绘制非交集部分 SRC_ATOP 取下层非交集部分与上层交集部分
    DST_ATOP 取上层非交集部分与下层交集部分 XOR 异或:去除两图层交集部分
    DARKEN 取两图层全部区域,交集部分颜色加深 LIGHTEN 取两图层全部,点亮交集部分颜色
    MULTIPLY 取两图层交集部分叠加后颜色 SCREEN 取两图层全部区域,交集部分变为透明色

    PS:名称前面都应该有:PorterDuff.Mode ,例如:PorterDuff.Mode.CLEAR

      什么?有的看不懂什么意思?没关系,我也没指望你一次就看懂,我们先来说一说saveLayer(),且听我娓娓道来。

    saveLayer()

      这个方法是干嘛用的?保存指定区域内画布的内容。

    public int saveLayer(RectF bounds, Paint paint, int saveFlags)  
    public int saveLayer(float left, float top, float right, float bottom,Paint paint, int saveFlags) 
    

      后面的saveFlags,有6个值,我们这里用到的 Canvas.ALL_SAVE_FLAG,很明显,表示保存所有内容。我们这里如果把这个方法去掉,会是什么样的结果呢?代码就不贴出来了,就直接注释掉saveLayer的两行代码。还是先画dst,后画src,跑出来的效果图如下:

    去掉saveLayer().png

    我们先规定两点:

    1. 先画dst,也就是目标图像,是一个圆形。
    2. 后画src,也就是源图像,是一个矩形。
    代码名称1 顺序 中文名称 形状
    dst 先画 目标图像 红色圆形
    src 后画 源图像 黄色矩形

      规定好之后,我们再来说一说这个 SRC_IN,我们前面说了:取两层绘制交集。显示上层。首先IN是取交集部分,OUT是取非交集部分。这个就是说最后显示的图形,他们的交集部分,显示src,也就是矩形的颜色,也就是黄色。先画的dst,他自然在src的上层。所以,显示dst的形状,两者交集部分显示src的颜色。可以看下图:

    saveLayer.png

      我们再来说说saveLayer的绘制流程:如上图所示,它会创建一个全新图名的bitmap,大小跟你前面指定的保存区域相同,然后,绘制的图形会保存在这个全新透明的bitmap上面,最后把这个透明的bitmap画在画布上面。

      辣么,没有savelayer()方法的绘制流程呢?如下图:

    no_saveLayer.png

    他是直接作用在画布上面的。

    常用的PorterDuffXfermode模式介绍

    SRC模式

      只保留源图像的 alpha 和 color ,所以绘制出来只有源图,有时候会感觉分不清先绘制的是源图还是后绘制的是源图,这个时候可以这么记,先绘制的是目标图,不管任何时候,一定要做一个有目标的人,目标在前!(未达到我们的预期效果,感觉有问题)

    DST模式

      只显示目标图片,也就是只显示红色的圆形。

    DST.png

    SRC_OVER模式

      在目标图片顶部绘制源图像,从命名上也可以看出来就是把源图像绘制在上方,也就是把黄色长方形,画在红色圆形的上面。效果图如下:

    SRC_OVER.png

    DST_OVER模式

      把目标图像绘制在上方。与前一个相反,把红色圆形画在长方形上面。效果图如下:

    DST_OVER.png

    SRC_IN模式

      在两者相交的地方绘制源图像,并且绘制的效果会受到目标图像对应地方透明度的影响。

      有点绕,我分成几段讲出来:

      也就是说,两者相交的位置,显示源图像,也就是黄色的矩形,目标图像的透明度为0,然后, 源图像的其他位置的透明度  会跟源图像与目标图像相交的地方  的目标图像的透明度一样。

      有点绕。其实,就是说源图像的其他地方隐藏,就显示相交的位置。效果图如下:

    SRC_IN.png

    DST_IN模式

      跟前面刚好对应,在两者相交的地方绘制目标图像,并且,绘制效果会受到源图像对应地方透明度的影响。我们最是绘制目标图像。效果图如下:

    DST_IN.png

    SRC_OUT模式

      在不相交的地方绘制源图像,相交处根据目标alpha进行过滤,目标色完全不透明时则完全过滤,完全透明则不过滤;

    SRC_OUT.png

    DST_OUT模式

      同样,可以类比SRC_OUT , 在不相交的地方绘制目标图像,相交处根据源图像alpha进行过滤,完全不透明处则完全过滤,完全透明则不过滤;

    DST_OUT.png

    太多了,后面就不写了,用的也比较少。

    用途,加上上一篇的贝赛尔曲线的水波纹。很明显,我就想做如下效果:

    圆形水波纹.gif

    这个圆只是一种,这只是一个demo,这个圆,你可以换成任意的形状。你知道水波纹用贝赛尔曲线怎么做,知道了,PorterDuffXfermode这个模式,两张图片是怎么切割。像这样的,还不就是一个道理。随手拈来。如下图:

    自定义背景.gif

    这个水波纹的,已经全部封装好了。任意改变背景图片。项目链接

    相关文章

      网友评论

        本文标题:Android——PorterDuffXfermode

        本文链接:https://www.haomeiwen.com/subject/wyiucftx.html