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

资料

相关推荐
祖国的好青年10 小时前
VS Code 搭建 React Native 开发环境(Windows 实战指南)
android·windows·react native·react.js
黄林晴10 小时前
警惕!AGP 9.2 别只改版本号,R8 规则与构建链路全线收紧
android·gradle
小米渣的逆袭11 小时前
Android ADB 完全使用指南
android·adb
儿歌八万首11 小时前
Jetpack Compose Canvas 进阶:结合 animateFloatAsState 让自定义图形动起来
android·动画·compose
zhangphil12 小时前
Android Page 3 Flow读sql数据库媒体文件,Kotlin
android·kotlin
神探小白牙12 小时前
echarts,3d堆叠图
android·3d·echarts
李白的天不白12 小时前
如何项目发布到github上
android·vue.js
summerkissyou198712 小时前
Android-RTC、NTP 和 System Time(系统时间)
android
小书房12 小时前
Kotlin使用体验及理解1
android·开发语言·kotlin
撩得Android一次心动13 小时前
Android Navigation 组件全面讲解
android·jetpack·navigation