Paint API之—— Xfermode与PorterDuff详解(三)
Paint API 之—— Xfermode 与 PorterDuff 详解(三)
在上篇《Paint API之—— Xfermode与PorterDuff详解(二)》中,我们探讨了高级模式的应用(如 MULTIPLY、SCREEN、OVERLAY 和 ADD)和性能优化技巧。本篇作为系列的第三部分,将全面详解 PorterDuff 的所有 18 种模式,包括每个模式的公式、Alpha 处理、组合逻辑和实际效果。通过工具调用,我提取了官方文档中的完整描述,并补充了示例和可视化解释。同时,提供一个综合实战示例(图片滤镜效果),以及与 WebView 和 Socket 的集成场景。内容基于 Android 官方文档,帮助开发者系统掌握 PorterDuff 的所有模式。
一、PorterDuff 所有 18 种模式详解
PorterDuff 模式基于像素级计算,公式通常为:
- Alpha 通道:(\alpha_{out} = SRC \alpha \times F_s + DST \alpha \times F_d)
- 颜色通道:(C_{out} = (SRC C \times SRC \alpha \times F_s + DST C \times DST \alpha \times F_d) / \alpha_{out})
其中 (F_s) 和 (F_d) 是模式特定的因子(0 到 1)。
以下是所有 18 种模式的列表,包括描述、公式(简化版)、Alpha 处理、组合方式和效果示例。模式按官方枚举顺序排列,可视化假设 SRC 为蓝色圆形,DST 为红色矩形。
模式名称 | 描述 | 公式 (Alpha) | 公式 (Color) | Alpha 处理方式 | 组合逻辑 | 效果示例 |
---|---|---|---|---|---|---|
ADD | 将源像素添加到目标像素,并饱和结果。 | (\alpha_{out} = \min(1, \alpha_{src} + \alpha_{dst})) | (C_{out} = \min(1, C_{src} + C_{dst})) | Alpha 相加并饱和(不超过 1)。 | SRC + DST,亮度增加。 | 圆形和矩形颜色叠加变亮,可能过曝。 |
CLEAR | 将源覆盖的目标像素清零为 0。 | (\alpha_{out} = 0) | (C_{out} = 0) | Alpha 设置为 0,完全透明。 | 擦除 DST。 | 矩形被圆形覆盖的部分变为透明。 |
DARKEN | 保留源和目标像素的最小组件。 | (\alpha_{out} = \alpha_{src} + \alpha_{dst} – \alpha_{src} \times \alpha_{dst}) | (C_{out} = (1 – \alpha_{dst}) \times C_{src} + (1 – \alpha_{src}) \times C_{dst} + \min(C_{src}, C_{dst})) | 使用 over 公式混合 Alpha。 | 取 SRC 和 DST 的较暗颜色。 | 混合区域变暗,增强阴影效果。 |
DST | 丢弃源像素,保留目标不变。 | (\alpha_{out} = \alpha_{dst}) | (C_{out} = C_{dst}) | Alpha 直接取 DST。 | 忽略 SRC,保留 DST。 | 仅显示矩形,圆形被忽略。 |
DST_ATOP | 丢弃未被源覆盖的目标像素,将剩余目标像素绘制在源像素上。 | (\alpha_{out} = \alpha_{src}) | (C_{out} = \alpha_{src} \times C_{dst} + (1 – \alpha_{dst}) \times C_{src}) | 输出 Alpha 取 SRC。 | DST 在 SRC 上,但仅 SRC 覆盖区域。 | 矩形在圆形覆盖区域显示。 |
DST_IN | 保留覆盖源像素的目标像素,丢弃剩余源和目标像素。 | (\alpha_{out} = \alpha_{src} \times \alpha_{dst}) | (C_{out} = C_{dst} \times \alpha_{src}) | Alpha 为 SRC 和 DST 的乘积。 | 仅保留 SRC 和 DST 交集,以 DST 颜色。 | 矩形仅在圆形覆盖区域显示。 |
DST_OUT | 保留未被源覆盖的目标像素,丢弃被源覆盖的目标像素,丢弃所有源像素。 | (\alpha_{out} = (1 – \alpha_{src}) \times \alpha_{dst}) | (C_{out} = (1 – \alpha_{src}) \times C_{dst}) | Alpha 为 DST 减 SRC 覆盖。 | DST 减 SRC 覆盖区域。 | 矩形被圆形覆盖的部分消失。 |
DST_OVER | 将源像素绘制在目标像素后面。 | (\alpha_{out} = \alpha_{dst} + (1 – \alpha_{dst}) \times \alpha_{src}) | (C_{out} = C_{dst} + (1 – \alpha_{dst}) \times C_{src}) | Alpha 为 DST + SRC 未覆盖部分。 | SRC 在 DST 下面。 | 圆形在矩形下面显示。 |
LIGHTEN | 保留源和目标像素的最大组件。 | (\alpha_{out} = \alpha_{src} + \alpha_{dst} – \alpha_{src} \times \alpha_{dst}) | (C_{out} = (1 – \alpha_{dst}) \times C_{src} + (1 – \alpha_{src}) \times C_{dst} + \max(C_{src}, C_{dst})) | 使用 over 公式混合 Alpha。 | 取 SRC 和 DST 的较亮颜色。 | 混合区域变亮,增强高光效果。 |
MULTIPLY | 将源像素乘以目标像素。 | (\alpha_{out} = \alpha_{src} \times \alpha_{dst}) | (C_{out} = C_{src} \times C_{dst}) | Alpha 为乘积。 | 逐通道乘法,产生更暗颜色。 | 混合区域变暗,如阴影叠加。 |
OVERLAY | 根据目标亮度选择 MULTIPLY 或 SCREEN 模式。 | (\alpha_{out} = \alpha_{src} + \alpha_{dst} – \alpha_{src} \times \alpha_{dst}) | (C_{out} = (1 – \alpha_{dst}) \times C_{src} + (1 – \alpha_{src}) \times C_{dst} + 2 \times C_{dst} \times C_{src}) (for dark DST) or equivalent for light. | 使用 over 公式混合 Alpha。 | 增强对比度,暗区暗化、亮区亮化。 | 混合区域对比增强,如纹理叠加。 |
SCREEN | 将源和目标像素的反相乘法。 | (\alpha_{out} = \alpha_{src} + \alpha_{dst} – \alpha_{src} \times \alpha_{dst}) | (C_{out} = C_{src} + C_{dst} – C_{src} \times C_{dst}) | 使用 over 公式混合 Alpha。 | 逐通道反相乘法,产生更亮颜色。 | 混合区域变亮,如高光效果。 |
SRC | 用源像素替换目标像素。 | (\alpha_{out} = \alpha_{src}) | (C_{out} = C_{src}) | Alpha 取 SRC。 | 完全覆盖 DST。 | 仅显示圆形,矩形被替换。 |
SRC_ATOP | 将源像素绘制在目标像素上,丢弃未覆盖源的目标像素。 | (\alpha_{out} = \alpha_{dst}) | (C_{out} = \alpha_{dst} \times C_{src} + (1 – \alpha_{dst}) \times C_{dst}) | 输出 Alpha 取 DST。 | SRC 在 DST 上,但仅 DST 覆盖区域。 | 圆形在矩形覆盖区域显示。 |
SRC_IN | 保留覆盖源像素的源像素,丢弃剩余源和目标像素。 | (\alpha_{out} = \alpha_{src} \times \alpha_{dst}) | (C_{out} = C_{src} \times \alpha_{dst}) | Alpha 为乘积。 | 仅保留 SRC 和 DST 交集,以 SRC 颜色。 | 圆形仅在矩形覆盖区域显示。 |
SRC_OUT | 保留未被目标覆盖的源像素,丢弃源覆盖的目标像素,丢弃所有目标像素。 | (\alpha_{out} = (1 – \alpha_{dst}) \times \alpha_{src}) | (C_{out} = (1 – \alpha_{dst}) \times C_{src}) | Alpha 为 SRC 减 DST 覆盖。 | SRC 减 DST 覆盖区域。 | 圆形被矩形覆盖的部分消失。 |
SRC_OVER | 将源像素绘制在目标像素上。 | (\alpha_{out} = \alpha_{src} + (1 – \alpha_{src}) \times \alpha_{dst}) | (C_{out} = C_{src} + (1 – \alpha_{src}) \times C_{dst}) | Alpha 为 SRC + DST 未覆盖部分。 | SRC 在 DST 上(默认混合)。 | 圆形叠加在矩形上。 |
XOR | 源和目标像素的异或操作。 | (\alpha_{out} = (1 – \alpha_{dst}) \times \alpha_{src} + (1 – \alpha_{src}) \times \alpha_{dst}) | (C_{out} = (1 – \alpha_{dst}) \times C_{src} + (1 – \alpha_{src}) \times C_{dst}) | Alpha 为未覆盖部分的和。 | SRC 和 DST 非交集部分。 | 圆形和矩形的非交集显示。 |
- 注意:
- 所有模式对 Alpha 通道有不同处理,确保 SRC 和 DST 具有透明度。
- Deprecated 模式:无(但 AvoidXfermode 和 PixelXorXfermode 已废弃,不在此 18 种内)。
- API 变化:API 1+ 支持基本模式,API 11+ 添加 ADD 等高级模式。
二、实战示例:图片滤镜和动态混合
以下是一个完整的示例,展示使用 PorterDuffXfermode 实现图片滤镜效果(动态切换 MULTIPLY 和 SCREEN 模式),并通过动画过渡。
1. 自定义 View(FilterView)
加载两张图片,应用 Xfermode 混合,支持点击切换模式。
import android.animation.ValueAnimator
import android.content.Context
import android.graphics.Bitmap
import android.graphics.BitmapFactory
import android.graphics.Canvas
import android.graphics.Paint
import android.graphics.PorterDuff
import android.graphics.PorterDuffXfermode
import android.util.AttributeSet
import android.view.View
import android.view.animation.LinearInterpolator
class FilterView(context: Context, attrs: AttributeSet?) : View(context, attrs) {
private val paint = Paint().apply {
isAntiAlias = true
}
private val srcBitmap = BitmapFactory.decodeResource(resources, R.drawable.src_texture) // SRC 纹理图片
private val dstBitmap = BitmapFactory.decodeResource(resources, R.drawable.dst_photo) // DST 照片
private val modes = arrayOf(PorterDuff.Mode.MULTIPLY, PorterDuff.Mode.SCREEN)
private var currentModeIndex = 0
private var alpha = 255f // 用于动画过渡 Alpha
init {
setOnClickListener {
currentModeIndex = (currentModeIndex + 1) % modes.size
startAlphaAnimation() // 触发动画过渡
}
}
private fun startAlphaAnimation() {
ValueAnimator.ofFloat(0f, 255f).apply {
duration = 1000
interpolator = LinearInterpolator()
addUpdateListener {
alpha = it.animatedValue as Float
invalidate()
}
start()
}
}
override fun onDraw(canvas: Canvas) {
super.onDraw(canvas)
// 创建临时 Bitmap
val output = Bitmap.createBitmap(dstBitmap.width, dstBitmap.height, Bitmap.Config.ARGB_8888)
val tempCanvas = Canvas(output)
// 绘制 DST (照片)
tempCanvas.drawBitmap(dstBitmap, 0f, 0f, paint)
// 设置当前模式
paint.xfermode = PorterDuffXfermode(modes[currentModeIndex])
// 绘制 SRC (纹理),动态 Alpha
paint.alpha = alpha.toInt()
tempCanvas.drawBitmap(srcBitmap, 0f, 0f, paint)
paint.alpha = 255 // 重置 Alpha
paint.xfermode = null
// 绘制结果到主 Canvas
canvas.drawBitmap(output, 0f, 0f, paint)
output.recycle()
}
}
- 说明:
- MULTIPLY:纹理暗化照片。
- SCREEN:纹理亮化照片。
- 动画:使用 ValueAnimator 动态调整 SRC Alpha,实现平滑过渡。
- 临时 Bitmap:确保 Xfermode 独立计算。
2. 布局中使用
<com.example.FilterView
android:layout_width="match_parent"
android:layout_height="match_parent" />
3. 运行效果
- 初始显示照片叠加纹理(MULTIPLY 模式)。
- 点击切换到 SCREEN 模式,伴随 Alpha 动画过渡。
- 效果:图片滤镜切换,类似 Instagram 滤镜。
4. 扩展:结合 Canvas 和 Path
使用 Path 绘制形状,并应用 OVERLAY 模式合成。
override fun onDraw(canvas: Canvas) {
super.onDraw(canvas)
val output = Bitmap.createBitmap(dstBitmap.width, dstBitmap.height, Bitmap.Config.ARGB_8888)
val tempCanvas = Canvas(output)
// 绘制 DST
tempCanvas.drawBitmap(dstBitmap, 0f, 0f, paint)
// 定义 Path 形状 (三角形)
val path = Path().apply {
moveTo(0f, 0f)
lineTo(dstBitmap.width.toFloat(), 0f)
lineTo(dstBitmap.width / 2f, dstBitmap.height.toFloat())
close()
}
// 设置 OVERLAY 模式
paint.xfermode = PorterDuffXfermode(PorterDuff.Mode.OVERLAY)
// 绘制 SRC 路径
tempCanvas.drawPath(path, paint.apply { color = Color.GREEN })
paint.xfermode = null
canvas.drawBitmap(output, 0f, 0f, paint)
output.recycle()
}
- 效果:照片上叠加绿色三角形(OVERLAY 模式),增强对比。
四、常见问题及注意事项
- 性能问题:
- 原因:高级模式(如 OVERLAY)计算复杂,大图像慢。
- 解决:使用小尺寸 Bitmap、动画优化、Profiler 监控。
- 兼容性:
- API 差异:所有模式在 API 1+ 支持,但效果在低版本可能不一致。
- Android 4.4+:优化渲染,支持硬件加速。
- WebView 注意:渲染为 Bitmap,使用 ARGB_8888 格式。
- 内存管理:
- 临时 Bitmap:及时
recycle()
。 - OOM 风险:采样图片:
kotlin val options = BitmapFactory.Options().apply { inSampleSize = 2 } BitmapFactory.decodeResource(resources, R.drawable.image, options)
- 错误处理:
- Socket 错误:捕获 IOException。
- 绘制异常:确保 SRC/DST 尺寸匹配。
- 效果优化:
- 多模式叠加:使用 LayerDrawable 结合多个 Xfermode。
- 动画:动态 Alpha 或模式切换:
kotlin ValueAnimator.ofFloat(0f, 1f).apply { addUpdateListener { // 动态混合 invalidate() } }.start()
五、学习建议与实践
- 学习路径:
- 掌握所有 18 种模式的效果和公式。
- 实现图片滤镜和动态切换。
- 学习性能监控和优化。
- 扩展到 WebView 和 Socket。
- 实践项目:
- 简单项目:实现图片滤镜切换。
- 进阶项目:使用 OVERLAY 添加水印动画。
- 高级项目:通过 Socket 传输纹理,客户端 Xfermode 合成。
- 调试工具:
- Layout Inspector:检查合成效果。
- Profiler:监控性能。
- Wireshark:调试 Socket。
- Chrome DevTools:调试 WebView。
- 推荐资源:
- Xfermode 文档:https://developer.android.com/reference/android/graphics/Xfermode
- PorterDuff.Mode:https://developer.android.com/reference/android/graphics/PorterDuff.Mode
- 性能优化:https://developer.android.com/topic/performance/vitals/render
七、后续系列预告
本篇为详解系列(三),聚焦所有模式和高级应用。后续系列将深入探讨:
- 详解(四):Xfermode 与 Shader、Path 的组合,以及自定义 Xfermode。
如果需要代码示例(如 LIGHTEN 模式高光、Socket 动态合成)或系列(四)的预览,请告诉我!