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

资料

相关推荐
AD钙奶-lalala6 小时前
某车企面试备忘
android
我爱拉臭臭7 小时前
kotlin音乐app之自定义点击缩放组件Shrink Layout
android·java·kotlin
匹马夕阳8 小时前
(二十五)安卓开发一个完整的登录页面-支持密码登录和手机验证码登录
android·智能手机
吃饭了呀呀呀8 小时前
🐳 深度解析:Android 下拉选择控件优化方案——NiceSpinner 实践指南
android·java
吃饭了呀呀呀8 小时前
🐳 《Android》 安卓开发教程 - 三级地区联动
android·java·后端
_祝你今天愉快10 小时前
深入剖析Java中ThreadLocal原理
android
张力尹10 小时前
谈谈 kotlin 和 java 中的锁!你是不是在协程中使用 synchronized?
android
流浪汉kylin11 小时前
Android 斜切图片
android
PuddingSama11 小时前
Android 视图转换工具 Matrix
android·前端·面试
RichardLai8812 小时前
[Flutter学习之Dart基础] - 控制语句
android·flutter