PorterDuff.Mode 详解
PorterDuff.Mode 是 Android 提供的一组用于图像混合的模式枚举类,定义了两张图像(源图像 Source 和目标图像 Destination)的像素如何进行合成的规则。它主要用于 Paint 和 Canvas 等类来实现图像的绘制和效果处理。
kotlin
Paint.setXfermode(Xfermode xfermode)
PorterDuff 的命名来自一篇经典的计算机图形学论文,由两位研究者 Thomas Porter 和 Tom Duff 于 1984 年发表,论文标题为 "Compositing Digital Images"。在这篇论文中,他们提出了12 个图像合成的数学模型,即 Porter-Duff 算子(Porter-Duff Operators),后来这些模型被广泛应用于计算机图形学中。由于 Porter 和 Duff 的工作仅仅关注源和目标的 alpha 通道的影响,因此原始论文中描述的 12 个操作符在这里被称为 alpha 合成模式。
为方便起见,此类还提供了几种混合模式,这些模式同样定义了合成源和目标的结果,但不受 Alpha 通道的限制。这些混合模式不是由 Porter 和 Duff 定义的,但为了方便起见,已包含在此类中。
PorterDuff 模式的组成与核心概念
Porter-Duff 模型的核心是基于两张图像:Source (SRC)要绘制的新图像 以及 Destination (DST)已经存在的背景图像。每个像素的最终颜色是由源图像和目标图像的颜色值,以及透明度(Alpha 值)决定的。主要涉及以下三个重要参数:
- Source color (Cs):源图像像素颜色。
- Source alpha (As):源图像像素的透明度。
- Destination color (Cd):目标图像像素颜色。
- Destination alpha (Ad):目标图像像素的透明度。
通过这四个参数,可以计算出最终的颜色值(Result)。以下是常用的 PorterDuff.Mode 模式列表和效果解释:
kotlin
//Alpha合成效果:
PorterDuffXfermode(PorterDuff.Mode.CLEAR), // DST区域被清空,不绘制任何东西,要闭硬件加速,否则无效
PorterDuffXfermode(PorterDuff.Mode.SRC), // 显示SRC图像,完全覆盖DST图像
PorterDuffXfermode(PorterDuff.Mode.DST), // 显示DST图像,不显示SRC图像。
PorterDuffXfermode(PorterDuff.Mode.SRC_OVER), // SRC图像覆盖DST图像
PorterDuffXfermode(PorterDuff.Mode.DST_OVER), // DST图像覆盖SRC图像
PorterDuffXfermode(PorterDuff.Mode.SRC_IN), // 取交集部分的SRC图像
PorterDuffXfermode(PorterDuff.Mode.DST_IN), // 取交集部分的DST图像
PorterDuffXfermode(PorterDuff.Mode.SRC_OUT), // SRC图像的非交集区域显示,DST图像被移除
PorterDuffXfermode(PorterDuff.Mode.DST_OUT), // DST图像的非交集区域显示,SRC图像被移除
PorterDuffXfermode(PorterDuff.Mode.SRC_ATOP), //取交集部分的SRC + 非交集部分的DST
PorterDuffXfermode(PorterDuff.Mode.DST_ATOP), //取交集部分的DST + 非交集部分的SRC
PorterDuffXfermode(PorterDuff.Mode.XOR), // 显示SRC图像与DST图像不重叠的部分,交集区域被移除。
//混合特效:注意要闭硬件加速,否则无效
PorterDuffXfermode(PorterDuff.Mode.DARKEN), // 相交区域变暗。
PorterDuffXfermode(PorterDuff.Mode.LIGHTEN), // 相交区域变亮。
PorterDuffXfermode(PorterDuff.Mode.MULTIPLY), // 显示重合的图像,SRC图像和DST图像的颜色值相乘,颜色合并
PorterDuffXfermode(PorterDuff.Mode.SCREEN), // SRC图像和DST图像的颜色值叠加,得到更亮的颜色
PorterDuffXfermode(PorterDuff.Mode.ADD), //饱和度叠加,SRC图像和DST图像的颜色值相加,颜色变得更亮
PorterDuffXfermode(PorterDuff.Mode.OVERLAY), //结合 MULTIPLY 和 SCREEN 模式,根据背景的亮度进行混合
是不是觉得Alpha合成的效果记不住?一会交集一会非交集的,一会取DST一会取SRC的,其实可以通过后面的关键字进行判断:
- IN:取指定层的交集部分,如:SRC_IN 表示取交集部分的SRC图像
- OUT:取指定层的非交集部分,如:DST_OUT 表示取非交集的DST图像部分
- OVER:取指定层在上层显示,如:SRC_OVER表示SRC图像覆盖DST图像
- XOR:取上下两层的非交集部分显示
- ATOP:取指定层的交集部分和非指定层的非交集部分,如: DST_ATOP表示取交集部分的DST + 非交集部分的SRC
通过这些模式,可以轻松实现复杂的图像合成效果,无需手动编写复杂的混合算法。这些模式提供了一种抽象化的方式,能够快速满足各种渲染需求。
使用示例
Google官方提供了使用示例如下,其中蓝色是SRC,红色是DST:
先绘制DST,设置Xfermode,最后绘制SRC,固定模式如下:
kotlin
Paint paint = new Paint();
canvas.drawBitmap(destinationImage, 0, 0, paint);
PorterDuff.Mode mode = // choose a mode
paint.setXfermode(new PorterDuffXfermode(mode));
canvas.drawBitmap(sourceImage, 0, 0, paint);
执行结果:
我们按照上述模式也来实现一次:
kotlin
class XfermodeView @JvmOverloads constructor(
context: Context,
attrs: AttributeSet? = null,
defStyleAttr: Int = 0
) : View(context, attrs, defStyleAttr) {
private var mPaint: Paint
internal var mItemSize = 0f
internal var mItemHorizontalOffset = 0f
internal var mItemVerticalOffset = 0f
internal var mCircleRadius = 0f
internal var mRectSize = 0f
internal var mCircleColor = -0x33bc//黄色
internal var mRectColor = -0x995501//蓝色
internal var mTextSize = 25f
init {
setLayerType(LAYER_TYPE_SOFTWARE, null) //关闭硬件加速器
mPaint = Paint(Paint.ANTI_ALIAS_FLAG).apply {
textSize = mTextSize
textAlign = Paint.Align.CENTER
strokeWidth = 2f
}
}
override fun onSizeChanged(w: Int, h: Int, oldw: Int, oldh: Int) {
super.onSizeChanged(w, h, oldw, oldh)
mItemSize = w / 4.5f
mItemHorizontalOffset = mItemSize / 6
mItemVerticalOffset = mItemSize * 0.426f
mCircleRadius = mItemSize / 3
mRectSize = mItemSize * 0.6f
}
override fun onDraw(canvas: Canvas) {
super.onDraw(canvas)
//设置背景色
//canvas.drawARGB(255, 139, 197, 186);
for (row in 0..4) {
for (column in 0..3) {
if (row == 4 && column in 2..3) return
canvas.save()
//保存当前图层
val layer = canvas.saveLayer(0f, 0f, width.toFloat(), height.toFloat(), null)
mPaint.xfermode = null
val index = row * 4 + column
val translateX = (mItemSize + mItemHorizontalOffset) * column
val translateY = (mItemSize + mItemVerticalOffset) * row
canvas.translate(translateX, translateY)
//画文字
val text = sLabels[index]
mPaint.color = Color.BLACK
val textXOffset = mItemSize / 2
val textYOffset = mTextSize + (mItemVerticalOffset - mTextSize) / 2
canvas.drawText(text, textXOffset, textYOffset, mPaint)
canvas.translate(0f, mItemVerticalOffset)
//画边框
mPaint.style = Paint.Style.STROKE
mPaint.color = -0x1000000
canvas.drawRect(2f, 2f, mItemSize - 2, mItemSize - 2, mPaint)
mPaint.style = Paint.Style.FILL
//1、画圆(DST)
mPaint.color = mCircleColor
val left = mCircleRadius + 3
val top = mCircleRadius + 3
canvas.drawCircle(left, top, mCircleRadius, mPaint)
//2、设置xfermode
mPaint.xfermode = sModes[index]
//3、画矩形(SRC)
mPaint.color = mRectColor
val rectRight = mCircleRadius + mRectSize
val rectBottom = mCircleRadius + mRectSize
canvas.drawRect(left, top, rectRight, rectBottom, mPaint)
//4、清空xfermode
mPaint.xfermode = null
canvas.restoreToCount(layer)
}
}
}
companion object {
/**
* NOTE:先绘制DST(下层) 后绘制SRC(上层)
*/
private val sModes = arrayOf<Xfermode>(
//Alpha合成效果:
/**
* IN:取指定层的交集部分,如:SRC_IN 表示取交集部分的SRC图像
* OUT:取指定层的非交集部分,如:DST_OUT 表示取非交集的DST图像部分
* OVER:取指定层在上层显示,如:SRC_OVER表示SRC图像覆盖DST图像
* XOR:取上下两层的非交集部分显示
* ATOP:取指定层的交集部分和非指定层的非交集部分,如: DST_ATOP表示取交集部分的DST + 非交集部分的SRC
*/
PorterDuffXfermode(PorterDuff.Mode.CLEAR), // DST区域被清空,不绘制任何东西,要闭硬件加速,否则无效
PorterDuffXfermode(PorterDuff.Mode.SRC), // 显示SRC图像,完全覆盖DST图像
PorterDuffXfermode(PorterDuff.Mode.DST), // 显示DST图像,不显示SRC图像。
PorterDuffXfermode(PorterDuff.Mode.SRC_OVER), // SRC图像覆盖DST图像
PorterDuffXfermode(PorterDuff.Mode.DST_OVER), // DST图像覆盖SRC图像
PorterDuffXfermode(PorterDuff.Mode.SRC_IN), // 取交集部分的SRC图像
PorterDuffXfermode(PorterDuff.Mode.DST_IN), // 取交集部分的DST图像
PorterDuffXfermode(PorterDuff.Mode.SRC_OUT), // SRC图像的非交集区域显示,DST图像被移除
PorterDuffXfermode(PorterDuff.Mode.DST_OUT), // DST图像的非交集区域显示,SRC图像被移除
PorterDuffXfermode(PorterDuff.Mode.SRC_ATOP), //取交集部分的SRC + 非交集部分的DST
PorterDuffXfermode(PorterDuff.Mode.DST_ATOP), //取交集部分的DST + 非交集部分的SRC
PorterDuffXfermode(PorterDuff.Mode.XOR), // 显示SRC图像与DST图像不重叠的部分,交集区域被移除。
//混合特效:注意要闭硬件加速,否则无效
PorterDuffXfermode(PorterDuff.Mode.DARKEN), // 相交区域变暗。
PorterDuffXfermode(PorterDuff.Mode.LIGHTEN), // 相交区域变亮。
PorterDuffXfermode(PorterDuff.Mode.MULTIPLY), // 显示重合的图像,SRC图像和DST图像的颜色值相乘,颜色合并
PorterDuffXfermode(PorterDuff.Mode.SCREEN), // SRC图像和DST图像的颜色值叠加,得到更亮的颜色
PorterDuffXfermode(PorterDuff.Mode.ADD), //饱和度叠加,SRC图像和DST图像的颜色值相加,颜色变得更亮
PorterDuffXfermode(PorterDuff.Mode.OVERLAY), //结合 MULTIPLY 和 SCREEN 模式,根据背景的亮度进行混合
)
private val sLabels = arrayOf(
"Clear",
"Src",
"Dst",
"SrcOver",
"DstOver",
"SrcIn",
"DstIn",
"SrcOut",
"DstOut",
"SrcATop",
"DstATop",
"Xor",
"Darken",
"Lighten",
"Multiply",
"Screen",
"Add",
"Overlay"
)
}
}
在XML中使用:
kotlin
<org.ninetripods.mq.study.fragment.XfermodeView xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent" />
执行结果:

可以看到跟官方的结果图还不太一样,以CLEAR为例,我们实现的效果:

官方实现的效果:

为什么会这样呢?其实是SRC、DST所设置的Rect范围不一样导致的。官方设置的SRC、DST范围是整个外部的大矩形区域,所以CLEAR模式会影响整个矩形;而我们设置的SRC、DST区域分别为蓝色、红色区域,最终的CLEAR模式只影响SRC蓝色区域。
资料
- PorterDuff.Mode:developer.android.com/reference/a...