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

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

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 202,056评论 5 474
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 84,842评论 2 378
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 148,938评论 0 335
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,296评论 1 272
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,292评论 5 363
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,413评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,824评论 3 393
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,493评论 0 256
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,686评论 1 295
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,502评论 2 318
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,553评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,281评论 4 318
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,820评论 3 305
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,873评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,109评论 1 258
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 42,699评论 2 348
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,257评论 2 341

推荐阅读更多精彩内容