Android——PorterDuffXfermode

个人博客: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

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

猜你喜欢

转载自blog.csdn.net/qq_27634797/article/details/79733314