Android 图像合成:玩转 PorterDuff.Mode 的 18 种混合模式

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蓝色区域。

资料

相关推荐
千里马学框架1 天前
一起学 Android 14:ShellTransition 屏幕旋转过程深度剖析
android·智能手机·性能优化·framework·性能·屏幕旋转·rotation
美狐美颜SDK开放平台1 天前
开发直播APP时如何接入视频美颜SDK?开发流程与注意事项
android·人工智能·计算机视觉·音视频·直播美颜sdk
AFinalStone1 天前
Android7 SystemUI源码解析(七)Keyguard锁屏模块深度解析
android·systemui
致远ccc1 天前
Google Play 上架前如何测试 App?多国家 Android 环境测试
android·app测试·googleplay·多国家应用测试
ttyyttemo1 天前
Kotlin 协程中的 Job 结构化并发与取消
android
sun0077001 天前
tbox 4g/5g切换,导致wan ip 改变,导致车机旧网络不可用。需要重启车机才行
android
其实防守也摸鱼1 天前
内网穿透与反向代理:原理、工具与实战指南
android·大数据·运维·安全·网络安全·自动化·渗透
AFinalStone1 天前
Android7 SystemUI 源码解析(四)NavigationBar 导航栏与 SystemBars
android·systemui
JMchen1 天前
属性动画原理与高级动画实现
android·kotlin·canvas
AFinalStone1 天前
Android7 SystemUI 源码解析(二)启动流程深度解析
android·systemui