个人博客: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;
}
}
效果图如下:
上面画了一个圆形bitmap,画了一个矩形bitmap,设置了一个模式 PorterDuff.Mode.SRC_IN 就变成了上面的形状,这是怎么做到的呢?带着我们的问题,进入我们的主题。
注意点
为什么我要拿一个大标题来写这个呢?因为,我当时卡在这里很久,然后踩着巨人的肩膀,我才踏过去的。
首先,两个图形必须都是Bitmap,直接用Canvas画形状,做操作,是达不到效果的。重要的事情说三遍:两个图形必须都是Bitmap。两个图形必须都是Bitmap。两个图形必须都是Bitmap
其次,避免不必要的麻烦,请先关闭硬件加速。重要的事情说三遍:请先关闭硬件加速。请先关闭硬件加速。请先关闭硬件加速
然后,两个bitmap的大小最好一样。
最后,我要强调的是:先绘制的是目标图,后绘制的是源图。
这里一直说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,跑出来的效果图应该是下面这样的:
我们如果加上这个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等会讲,先不说。跑出来的效果图,如下:
前面,我们一直都在强调dst先画,src后画,如果调换一下,会是什么样的结果呢?代码我就不贴出来了,就把那两个drawBitmap调换一个位置,跑出来的效果图,如下:
很明显,跟我们的预期结果不一样。这是为什么呢?带着我们的问题进入下一节。
十六种模式和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,跑出来的效果图如下:
我们先规定两点:
- 先画dst,也就是目标图像,是一个圆形。
- 后画src,也就是源图像,是一个矩形。
代码名称1 | 顺序 | 中文名称 | 形状 |
---|---|---|---|
dst | 先画 | 目标图像 | 红色圆形 |
src | 后画 | 源图像 | 黄色矩形 |
规定好之后,我们再来说一说这个 SRC_IN,我们前面说了:取两层绘制交集。显示上层。首先IN是取交集部分,OUT是取非交集部分。这个就是说最后显示的图形,他们的交集部分,显示src,也就是矩形的颜色,也就是黄色。先画的dst,他自然在src的上层。所以,显示dst的形状,两者交集部分显示src的颜色。可以看下图:
我们再来说说saveLayer的绘制流程:如上图所示,它会创建一个全新图名的bitmap,大小跟你前面指定的保存区域相同,然后,绘制的图形会保存在这个全新透明的bitmap上面,最后把这个透明的bitmap画在画布上面。
辣么,没有savelayer()方法的绘制流程呢?如下图:
他是直接作用在画布上面的。
常用的PorterDuffXfermode模式介绍
SRC模式
只保留源图像的 alpha 和 color ,所以绘制出来只有源图,有时候会感觉分不清先绘制的是源图还是后绘制的是源图,这个时候可以这么记,先绘制的是目标图,不管任何时候,一定要做一个有目标的人,目标在前!(未达到我们的预期效果,感觉有问题)
DST模式
只显示目标图片,也就是只显示红色的圆形。
SRC_OVER模式
在目标图片顶部绘制源图像,从命名上也可以看出来就是把源图像绘制在上方,也就是把黄色长方形,画在红色圆形的上面。效果图如下:
DST_OVER模式
把目标图像绘制在上方。与前一个相反,把红色圆形画在长方形上面。效果图如下:
SRC_IN模式
在两者相交的地方绘制源图像,并且绘制的效果会受到目标图像对应地方透明度的影响。
有点绕,我分成几段讲出来:
也就是说,两者相交的位置,显示源图像,也就是黄色的矩形,目标图像的透明度为0,然后, 源图像的其他位置的透明度 会跟源图像与目标图像相交的地方 的目标图像的透明度一样。
有点绕。其实,就是说源图像的其他地方隐藏,就显示相交的位置。效果图如下:
DST_IN模式
跟前面刚好对应,在两者相交的地方绘制目标图像,并且,绘制效果会受到源图像对应地方透明度的影响。我们最是绘制目标图像。效果图如下:
SRC_OUT模式
在不相交的地方绘制源图像,相交处根据目标alpha进行过滤,目标色完全不透明时则完全过滤,完全透明则不过滤;
DST_OUT模式
同样,可以类比SRC_OUT , 在不相交的地方绘制目标图像,相交处根据源图像alpha进行过滤,完全不透明处则完全过滤,完全透明则不过滤;
太多了,后面就不写了,用的也比较少。
用途,加上上一篇的贝赛尔曲线的水波纹。很明显,我就想做如下效果:
这个圆只是一种,这只是一个demo,这个圆,你可以换成任意的形状。你知道水波纹用贝赛尔曲线怎么做,知道了,PorterDuffXfermode这个模式,两张图片是怎么切割。像这样的,还不就是一个道理。随手拈来。如下图:
这个水波纹的,已经全部封装好了。任意改变背景图片。项目链接