Xfermode 的简单使用
不是很难的知识点,但还是花了点时间才理解。
简单用例
Xfermode(transfer mode)是 Android 开发中用于实现图形混合效果的一种技术,于android.graphics
包中的PorterDuffXfermode
类中实现,主要用于处理位图(Bitmap)和绘制操作(如绘制图形、文字等)之间的像素混合。Xfermode 基于 Porter-Duff 混合算法,该算法是由 Thomas Porter 和 Tom Duff 于 1984 年提出的一种图形混合算法,可以实现多种不同的混合模式。
Xfermode 提供了 16 种不同的混合模式,全部都在PorterDuff.Mode
枚举中定义,包括:
- CLEAR:清除所有像素,即结果为透明。
- SRC:显示源图像,覆盖目标图像。
- DST:显示目标图像,忽略源图像。
- SRC_OVER:源图像覆盖在目标图像之上。
- DST_OVER:目标图像覆盖在源图像之上。
- SRC_IN:只显示源图像和目标图像相交的部分。
- DST_IN:只显示目标图像和源图像相交的部分。
- SRC_OUT:只显示源图像和目标图像不相交的部分。
- DST_OUT:只显示目标图像和源图像不相交的部分。
- SRC_ATOP:源图像覆盖在目标图像相交的部分,保留目标图像不相交的部分。
- DST_ATOP:目标图像覆盖在源图像相交的部分,保留源图像不相交的部分。
- XOR:显示源图像和目标图像不相交的部分,忽略相交部分。
- DARKEN:显示源图像和目标图像中较暗的像素。
- LIGHTEN:显示源图像和目标图像中较亮的像素。
- MULTIPLY:源图像和目标图像的颜色值相乘,结果为更暗的颜色。
- SCREEN:源图像和目标图像的颜色值进行反向相乘,结果为更亮的颜色。
更详细的介绍可以查看官方文档:https://developer.android.com/reference/android/graphics/PorterDuff.Mode
要使用 Xfermode,需要创建一个 PorterDuffXfermode 对象,并将其赋值给一个 Paint 对象的xfermode
属性。例如:
1 |
|
然后在绘制时,使用带有该 Paint 对象的 Canvas 绘制方法,就可以实现相应的混合效果。不过需要注意的是,在使用 Xfermode 时,通常需要用到离屏缓冲。利用离屏缓冲,可以将某个 View 或图形对象在屏幕外部的缓冲区进行绘制,然后再将绘制结果显示到屏幕上。离屏绘制可以用于实现各种高级图形效果,如阴影、圆角、蒙版等。接下来看一个将椭圆和某张矩形图片混合的用例(可以理解为矩形的头像被裁切成圆形展示):
1 |
|
这就是 Xfermode 的一个简单用法,注释很详细,除了要知道saveLayer()
很消耗性能以外似乎没有什么需要记录的。代码运行后会将准备好的矩形图片和椭圆混合,并将结果显示在屏幕中央。
理解误区
在上面的用例中,使用到了一张图片和椭圆进行混合,并且图片和图形的坐标是一致的,同时图片的面积比图形大(椭圆成为了矩形的内切圆)。在这样的情况下,Xfermode 确实达到了理想中的效果。
接下来来看另一个用例,现在要在使用PorterDuff.Mode.SRC_IN
的情况下,将一个矩形(正方形)和椭圆(圆形)进行混合,来达到官方文档中 SRC_IN 的效果:
1 |
|
上面的代码在不使用 Xfermode 的情况下会把两个图形完整地画出,同时圆的左下四分之一的部分将会与正方形重叠,也就是说,一切正常。然而在使用PorterDuff.Mode.SRC_IN
模式后,却并不能得到官方文档中对应的 Source In 的效果,反而是得到了 Source Atop 的效果……具体原因个人理解如下:
- 实际上,官方文档中用例所使用的并不是两个图形,而是两张大小相同、绘制位置也相同的图片(图片中包含了图形和透明区域)。
- 混合结果与谁是 Source image 和谁是 Destination image 有关——在设置 Paint 对象的
xfermode
属性之前绘制的是 Destination image ,相应的,之后的就是 Source image 。
以 SRC_IN 为例,其官方解释为:
Keeps the source pixels that cover the destination pixels, discards the remaining source and destination pixels.
先来看上半句话,大致意思是保留源与目标重叠的像素——在这里就是圆的左下四分之一的部分(在结果中可以看到这一部分确实是属于矩形的深红色);然后是下半句话,抛弃源和目标剩下的像素。源剩下的像素被抛弃了好理解,因为矩形除了重叠的部分以外的都消失了,但是圆(目标)还剩下四分之三的像素保留在那里呢……所以这里只能是理解成非重叠的目标像素不在混合的计算范围内了。
这时候如果把canvas.drawOval(ovalBounds, paint)
和canvas.drawRect(rectBounds, paint)
这两行代码调换一下位置,也就是先画矩形再画椭圆,就又会出现不一样的结果,这是因为它们之间源和目标的身份互换了。不过只要结合上面的理论,会发现这样的结果其实也还是说得通。
再来一个例子,假设在原来先画椭圆再画矩形的基础上,把canvas.drawRect(rectBounds, paint)
改成canvas.drawRect(ovalBounds, paint)
,也就是将矩形绘制在和椭圆相同的位置,这时候圆又会成为这个矩形的内切圆,在SRC_IN
的情况下,就又会得到一开始把矩形图片裁切成圆形的效果。
总的来说,图形混合最终的效果主要和以下 3 点有关:
- Paint 对象的
xfermode
属性设置了什么值。 - 两张图像哪张是 Destination image 哪张是 Source image 。
- 两张图像的重叠部分。