Android 图像调色 | 利用ColorMatrix 实现实时亮度与饱和度调节

179 阅读2分钟

在上一篇 Android | ColorMatrix 全面解析:原理、方法 中,介绍了ColorMatrix的常见方法,本文在其基础上,利用 ColorMatrix 动态调整图像亮度和饱和度,并实现滑动实时预览效果的示例。

效果演示

左图是原图,右图是可以实时调节亮度或饱和度的处理图。拖动下方 SeekBar,即可看到变化效果。

效果图

上述效果是同时调整了亮度和饱和度,还可以分别单个设置以及设置其他属性等,可以自行尝试,后面会给出完整代码示例。

实现思路

  • 使用 ColorMatrixImageView 自定义 View 扩展 AppCompatImageView
  • 利用 ColorMatrix.setSaturation() 设置饱和度
  • 利用 ColorMatrix.setScale() 或构造矩阵设置亮度
  • 使用 ColorMatrix.postConcat() 合并多个色彩矩阵,配合 ColorMatrixColorFilter 实时更新图片色彩
  • 使用协程进行防抖处理,避免滑动频繁更新造成卡顿

ColorMatrixImageView 实现代码

class ColorMatrixImageView @JvmOverloads constructor(
    context: Context,
    attrs: AttributeSet? = null,
    defStyleAttr: Int = 0
) : AppCompatImageView(context, attrs, defStyleAttr) {

    private var brightness = 1.0f // 默认亮度
    private var saturation = 1.0f // 默认饱和度

    // 防抖处理
    private var mJob: Job? = null
    private val mScope = CoroutineScope(Dispatchers.Main + SupervisorJob())

    init {
        scaleType = ScaleType.CENTER_CROP
    }

    fun setBrightness(brightness: Float) {
        this.brightness = brightness
        scheduleUpdate()
    }

    fun setSaturation(value: Float) {
        this.saturation = value
        scheduleUpdate()
    }

    fun setAdjustments(brightness: Float, saturation: Float) {
        this.saturation = saturation
        this.brightness = brightness
        scheduleUpdate()
    }

    private fun scheduleUpdate(delayMs: Long = 16L) {
        mJob?.cancel()
        mJob = mScope.launch {
            delay(delayMs)
            applyAdjustments()
        }
    }

    private fun applyAdjustments() {
        val combineMatrix = ColorMatrix()
        val saturationMatrix = ColorMatrix()
        saturationMatrix.setSaturation(saturation)

        val brightnessMatrix = ColorMatrix(
            floatArrayOf(
                brightness, 0f, 0f, 0f, 0f,
                0f, brightness, 0f, 0f, 0f,
                0f, 0f, brightness, 0f, 0f,
                0f, 0f, 0f, 1f, 0f
            )
        )

        combineMatrix.postConcat(saturationMatrix)
        combineMatrix.postConcat(brightnessMatrix)

        colorFilter = ColorMatrixColorFilter(combineMatrix)
    }

    override fun onDetachedFromWindow() {
        super.onDetachedFromWindow()
        mScope.cancel()
    }
}

XML 布局文件(部分)

<org.ninetripods.mq.study.widget.matrix.ColorMatrixImageView
    android:id="@+id/iv_color_matrix_xml_view"
    android:layout_width="150dp"
    android:layout_height="150dp"
    android:layout_marginStart="30dp"
    android:background="@color/black"
    android:scaleType="centerCrop"
    android:src="@drawable/icon_cat_h"
    android:visibility="visible"
    app:layout_constraintEnd_toEndOf="parent"
    app:layout_constraintStart_toEndOf="@+id/iv_origin"
    app:layout_constraintTop_toTopOf="parent" />

SeekBar 调节 UI 控制代码

mSeekBar.setOnSeekBarChangeListener(object : SeekBar.OnSeekBarChangeListener {
    override fun onProgressChanged(seekBar: SeekBar, progress: Int, fromUser: Boolean) {
        val barValue = progress / 20f // 0.0f ~ 5.0f

        // 只调亮度
        mIvColorMatrix.setBrightness(barValue)
        mTvProcess.text = String.format("亮度 = %.2f", barValue)

        // 只调饱和度
        // mIvColorMatrix.setSaturation(barValue)
        // mTvProcess.text = String.format("饱和度 = %.2f", barValue)

        // 同时调亮度 + 饱和度
        // mIvColorMatrix.setAdjustments(barValue, barValue)
        // mTvProcess.text = String.format("亮度 & 饱和度 = %.2f", barValue)
    }

    override fun onStartTrackingTouch(seekBar: SeekBar) {}
    override fun onStopTrackingTouch(seekBar: SeekBar) {}
})

1. 亮度(Brightness)

亮度是通过将 RGB 三通道统一放大或缩小来实现的。例如:

ColorMatrix(floatArrayOf(
    brightness, 0f, 0f, 0f, 0f,
    0f, brightness, 0f, 0f, 0f,
    0f, 0f, brightness, 0f, 0f,
    0f, 0f, 0f, 1f, 0f
))

2. 饱和度(Saturation)

饱和度的变化是通过 ColorMatrix.setSaturation(saturation) 方法内部完成的,值域通常为 0f ~ 2f,1f 表示不变。

总结

本文通过封装一个 ColorMatrixImageView,结合协程防抖与 SeekBar 滑动控制,实现了 Android 图像的动态调色处理。完整代码参见:github.com/crazyqiang/…