Paint API之—— ColorFilter(颜色过滤器)(2-3)

Paint API 之—— ColorFilter 详解(二)

在《Paint API之—— ColorFilter(颜色过滤器)(1/3)》中,我们介绍了 ColorFilter 的基础概念、三大子类(LightingColorFilter、PorterDuffColorFilter、ColorMatrixColorFilter)以及基本用法。本篇作为系列的第二部分,将深入探讨 ColorMatrixColorFilter 的高级矩阵变换,包括色调调整、对比度增强、颜色反转等常见效果的实现原理和代码示例。我们还将介绍如何结合 CanvasPath 创建复杂滤镜效果,以及性能优化技巧。内容基于 Android 官方文档和开发实践,适合希望实现高级图像处理效果的开发者。


一、ColorMatrixColorFilter 高级矩阵变换

1. 工作原理

ColorMatrixColorFilter 使用一个 4×5 矩阵对 RGBA 颜色通道进行线性变换,公式为:

[
\begin{bmatrix}
R_{out} \
G_{out} \
B_{out} \
A_{out}

\end{bmatrix}

\begin{bmatrix}
a & b & c & d & e \
f & g & h & i & j \
k & l & m & n & o \
p & q & r & s & t
\end{bmatrix}
\times
\begin{bmatrix}
R_{in} \
G_{in} \
B_{in} \
A_{in} \
1
\end{bmatrix}
]

  • 矩阵说明
  • 每行控制一个输出通道(R、G、B、A)。
  • 每列对应输入通道(R、G、B、A)或偏移量。
  • 示例:(R_{out} = a \times R_{in} + b \times G_{in} + c \times B_{in} + d \times A_{in} + e)。
  • 常见效果
  • 灰度:调整饱和度为 0。
  • 色调调整:修改 RGB 权重。
  • 对比度:缩放颜色值。
  • 颜色反转:反转 RGB 值。
  • 亮度调整:增加偏移量。
2. 常见矩阵变换公式

以下是实现常见效果的矩阵公式和代码:

  • 灰度效果(饱和度为 0):
  • 使用亮度公式:(L = 0.299R + 0.587G + 0.114B)(ITU-R BT.601 标准)。
  • 矩阵:
    [
    \begin{bmatrix}
    0.299 & 0.587 & 0.114 & 0 & 0 \
    0.299 & 0.587 & 0.114 & 0 & 0 \
    0.299 & 0.587 & 0.114 & 0 & 0 \
    0 & 0 & 0 & 1 & 0
    \end{bmatrix}
    ]
  • 代码val matrix = ColorMatrix().apply { setSaturation(0f) // 自动生成灰度矩阵 } paint.colorFilter = ColorMatrixColorFilter(matrix)
  • 色调调整(如偏红):
  • 增强红色通道,减弱绿色和蓝色:
    [
    \begin{bmatrix}
    1.5 & 0 & 0 & 0 & 0 \
    0 & 0.5 & 0 & 0 & 0 \
    0 & 0 & 0.5 & 0 & 0 \
    0 & 0 & 0 & 1 & 0
    \end{bmatrix}
    ]
  • 代码val matrix = ColorMatrix(floatArrayOf( 1.5f, 0f, 0f, 0f, 0f, 0f, 0.5f, 0f, 0f, 0f, 0f, 0f, 0.5f, 0f, 0f, 0f, 0f, 0f, 1f, 0f )) paint.colorFilter = ColorMatrixColorFilter(matrix)
  • 对比度增强(系数 s):
  • 公式:(C_{out} = s \times (C_{in} – 0.5) + 0.5),s > 1 增强对比度。
  • 矩阵:
    [
    \begin{bmatrix}
    s & 0 & 0 & 0 & 0.5(1-s) \
    0 & s & 0 & 0 & 0.5(1-s) \
    0 & 0 & s & 0 & 0.5(1-s) \
    0 & 0 & 0 & 1 & 0
    \end{bmatrix}
    ]
  • 代码(对比度 s=2): val s = 2f val matrix = ColorMatrix(floatArrayOf( s, 0f, 0f, 0f, 0.5f * (1f - s), 0f, s, 0f, 0f, 0.5f * (1f - s), 0f, 0f, s, 0f, 0.5f * (1f - s), 0f, 0f, 0f, 1f, 0f )) paint.colorFilter = ColorMatrixColorFilter(matrix)
  • 颜色反转
  • 反转 RGB:(C_{out} = 1 – C_{in})。
  • 矩阵:
    [
    \begin{bmatrix}
    -1 & 0 & 0 & 0 & 255 \
    0 & -1 & 0 & 0 & 255 \
    0 & 0 & -1 & 0 & 255 \
    0 & 0 & 0 & 1 & 0
    \end{bmatrix}
    ]
  • 代码val matrix = ColorMatrix(floatArrayOf( -1f, 0f, 0f, 0f, 255f, 0f, -1f, 0f, 0f, 255f, 0f, 0f, -1f, 0f, 255f, 0f, 0f, 0f, 1f, 0f )) paint.colorFilter = ColorMatrixColorFilter(matrix)
  • 亮度调整(增加亮度 b):
  • 增加偏移量:
    [
    \begin{bmatrix}
    1 & 0 & 0 & 0 & b \
    0 & 1 & 0 & 0 & b \
    0 & 0 & 1 & 0 & b \
    0 & 0 & 0 & 1 & 0
    \end{bmatrix}
    ]
  • 代码(亮度 +50):
    kotlin val matrix = ColorMatrix(floatArrayOf( 1f, 0f, 0f, 0f, 50f, 0f, 1f, 0f, 0f, 50f, 0f, 0f, 1f, 0f, 50f, 0f, 0f, 0f, 1f, 0f )) paint.colorFilter = ColorMatrixColorFilter(matrix)
3. 矩阵叠加
  • 原理:多个 ColorMatrix 可通过 postConcat 叠加效果。
  • 代码
  val matrix1 = ColorMatrix().apply { setSaturation(0f) } // 灰度
  val matrix2 = ColorMatrix(floatArrayOf(
      1f, 0f, 0f, 0f, 50f,
      0f, 1f, 0f, 0f, 50f,
      0f, 0f, 1f, 0f, 50f,
      0f, 0f, 0f, 1f, 0f
  )) // 增加亮度
  matrix1.postConcat(matrix2)
  paint.colorFilter = ColorMatrixColorFilter(matrix1)
  • 效果:先灰度再增加亮度。

二、实战示例:动态滤镜切换

以下是一个完整的示例,展示如何使用 ColorMatrixColorFilter 实现灰度、色调调整、对比度增强和颜色反转效果,支持动画过渡。

1. 自定义 View(AdvancedFilterView)

绘制图片并动态切换滤镜。

import android.animation.ValueAnimator
import android.content.Context
import android.graphics.Bitmap
import android.graphics.BitmapFactory
import android.graphics.Canvas
import android.graphics.Color
import android.graphics.ColorMatrix
import android.graphics.ColorMatrixColorFilter
import android.graphics.Paint
import android.util.AttributeSet
import android.view.View
import android.view.animation.LinearInterpolator

class AdvancedFilterView(context: Context, attrs: AttributeSet?) : View(context, attrs) {
    private val paint = Paint().apply {
        isAntiAlias = true
    }
    private val bitmap = BitmapFactory.decodeResource(resources, R.drawable.sample_image) // 替换为你的图片
    private val filters = listOf(
        ColorMatrix().apply { setSaturation(0f) }, // 灰度
        ColorMatrix(floatArrayOf( // 偏红
            1.5f, 0f, 0f, 0f, 0f,
            0f, 0.5f, 0f, 0f, 0f,
            0f, 0f, 0.5f, 0f, 0f,
            0f, 0f, 0f, 1f, 0f
        )),
        ColorMatrix(floatArrayOf( // 对比度增强
            2f, 0f, 0f, 0f, -0.5f,
            0f, 2f, 0f, 0f, -0.5f,
            0f, 0f, 2f, 0f, -0.5f,
            0f, 0f, 0f, 1f, 0f
        )),
        ColorMatrix(floatArrayOf( // 颜色反转
            -1f, 0f, 0f, 0f, 255f,
            0f, -1f, 0f, 0f, 255f,
            0f, 0f, -1f, 0f, 255f,
            0f, 0f, 0f, 1f, 0f
        ))
    )
    private var currentFilterIndex = 0
    private var transitionProgress = 1f // 动画过渡值 (0-1)

    init {
        setOnClickListener {
            currentFilterIndex = (currentFilterIndex + 1) % filters.size
            startTransitionAnimation()
        }
        paint.colorFilter = ColorMatrixColorFilter(filters[currentFilterIndex])
    }

    private fun startTransitionAnimation() {
        ValueAnimator.ofFloat(0f, 1f).apply {
            duration = 1000
            interpolator = LinearInterpolator()
            addUpdateListener {
                transitionProgress = it.animatedValue as Float
                updateFilter()
                invalidate()
            }
            start()
        }
    }

    private fun updateFilter() {
        val currentMatrix = filters[currentFilterIndex]
        val matrix = ColorMatrix().apply {
            set(filters[currentFilterIndex]) // 当前滤镜
            setScale(transitionProgress, transitionProgress, transitionProgress, 1f) // 渐变过渡
        }
        paint.colorFilter = ColorMatrixColorFilter(matrix)
    }

    override fun onDraw(canvas: Canvas) {
        super.onDraw(canvas)
        // 绘制图片
        canvas.drawBitmap(bitmap, 0f, 0f, paint)

        // 显示滤镜名称
        paint.colorFilter = null
        paint.color = Color.BLACK
        paint.textSize = 40f
        val filterName = when (currentFilterIndex) {
            0 -> "Gray"
            1 -> "Red Tint"
            2 -> "High Contrast"
            3 -> "Invert"
            else -> "Unknown"
        }
        canvas.drawText(filterName, 20f, height - 40f, paint)
    }
}
  • 说明
  • 滤镜:灰度、偏红、对比度增强、颜色反转。
  • 动画:使用 ValueAnimator 实现平滑过渡。
  • Canvas:绘制图片和文字。
2. 布局文件(activity_main.xml)
<LinearLayout
    xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:gravity="center">

    <com.example.AdvancedFilterView
        android:layout_width="match_parent"
        android:layout_height="match_parent" />

</LinearLayout>
3. 主 Activity(MainActivity)
import android.os.Bundle
import androidx.appcompat.app.AppCompatActivity

class MainActivity : AppCompatActivity() {
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main)
    }
}
4. 运行效果
  • 初始显示灰度图片。
  • 点击切换滤镜(灰度 → 偏红 → 对比度 → 反转),伴随 1 秒动画过渡。
  • 底部显示当前滤镜名称。
5. 结合 Canvas 和 Path

绘制心形并应用滤镜。

override fun onDraw(canvas: Canvas) {
    super.onDraw(canvas)
    // 绘制心形
    val path = Path().apply {
        moveTo(width / 2f, height / 4f)
        cubicTo(width / 2f, 0f, 0f, height / 4f, width / 4f, height / 2f)
        cubicTo(width / 2f, height * 3 / 4f, width / 2f, height * 3 / 4f, width / 2f, height * 3 / 4f)
        cubicTo(width / 2f, height * 3 / 4f, width.toFloat(), height / 4f, width * 3 / 4f, height / 2f)
        cubicTo(width / 2f, height * 3 / 4f, width / 2f, height / 4f, width / 2f, height / 4f)
    }
    paint.colorFilter = filters[currentFilterIndex]
    paint.color = Color.BLUE
    canvas.drawPath(path, paint)

    // 显示滤镜名称
    paint.colorFilter = null
    paint.color = Color.BLACK
    paint.textSize = 40f
    val filterName = when (currentFilterIndex) {
        0 -> "Gray"
        1 -> "Red Tint"
        2 -> "High Contrast"
        3 -> "Invert"
        else -> "Unknown"
    }
    canvas.drawText(filterName, 20f, height - 40f, paint)
}
  • 效果:蓝色心形应用不同滤镜,点击切换。

三、性能优化技巧

  1. 小尺寸 Bitmap
  • 缩放图片减少计算:
    kotlin val scaledBitmap = Bitmap.createScaledBitmap(bitmap, 200, 200, true)
  1. 硬件加速
  • 确保启用:
    xml <application android:hardwareAccelerated="true">
  • 复杂矩阵可能回退到软件渲染,测试性能。
  1. 异步处理
  • 大图处理移到后台:
    kotlin GlobalScope.launch(Dispatchers.IO) { val filteredBitmap = applyFilter(bitmap) runOnUiThread { imageView.setImageBitmap(filteredBitmap) } }
  1. 缓存结果
  • 使用 LruCache 存储滤镜结果:
    kotlin val cache = LruCache<String, Bitmap>(10 * 1024 * 1024) // 10MB cache.put("filter_${currentFilterIndex}", filteredBitmap)
  1. 监控工具
  • Android Profiler:检查渲染时间和内存。
  • Logcat:记录矩阵参数:
    kotlin Log.d("ColorFilter", "Matrix: ${filters[currentFilterIndex].matrix.joinToString()}")

四、常见问题及注意事项

  1. 性能问题
  • 原因:复杂矩阵变换对大图消耗高。
  • 解决:缩放图片、异步处理、Profiler 监控。
  1. 兼容性
  • API 支持:ColorMatrixColorFilter 在 API 1+ 可用。
  • 低版本:测试复杂矩阵效果。
  1. 内存管理
  • 回收 Bitmap
    kotlin if (!bitmap.isRecycled) bitmap.recycle()
  • OOM 预防:采样图片:
    kotlin val options = BitmapFactory.Options().apply { inSampleSize = 2 }
  1. 错误处理
  • 矩阵错误:确保 4×5 矩阵格式正确。
  • 绘制异常:检查 Paint 和 Canvas 初始化。
  1. 效果优化
  • 动态调整:动画过渡滤镜参数:
    kotlin ValueAnimator.ofFloat(0f, 2f).apply { addUpdateListener { val matrix = ColorMatrix(floatArrayOf( it.animatedValue as Float, 0f, 0f, 0f, -0.5f * (1f - it.animatedValue), 0f, it.animatedValue, 0f, 0f, -0.5f * (1f - it.animatedValue), 0f, 0f, it.animatedValue, 0f, -0.5f * (1f - it.animatedValue), 0f, 0f, 0f, 1f, 0f )) paint.colorFilter = ColorMatrixColorFilter(matrix) invalidate() } start() }

五、总结(详解二)

  • ColorMatrixColorFilter:使用 4×5 矩阵实现复杂颜色变换。
  • 常见效果:灰度、色调调整、对比度增强、颜色反转、亮度调整。
  • 实战示例:动态切换滤镜,结合 Path 绘制心形。
  • 优化:小尺寸 Bitmap、硬件加速、异步处理、缓存。
  • 注意:性能监控、内存管理、矩阵正确性。

Paint API 之—— ColorFilter 详解(三)

本篇作为系列的第三部分,将聚焦 ColorFilter 的动态动画效果WebView、Socket 集成,展示如何通过动画实现平滑滤镜过渡,以及如何将滤镜效果应用于网络场景(如通过 Socket 传输滤镜参数,或在 WebView 中显示)。我们还将提供高级实战示例和进一步的优化建议。


一、动态滤镜动画

1. 实现原理
  • 使用 ValueAnimatorObjectAnimator 动态调整 ColorMatrix 参数。
  • 常见动画效果:
  • 渐变灰度(饱和度 1 → 0)。
  • 色调渐变(RGB 权重变化)。
  • 对比度平滑过渡。
2. 代码示例:灰度动画

动态调整图片从彩色到灰度。

import android.animation.ValueAnimator
import android.content.Context
import android.graphics.Bitmap
import android.graphics.BitmapFactory
import android.graphics.Canvas
import android.graphics.Color
import android.graphics.ColorMatrix
import android.graphics.ColorMatrixColorFilter
import android.graphics.Paint
import android.util.AttributeSet
import android.view.View
import android.view.animation.LinearInterpolator

class AnimatedFilterView(context: Context, attrs: AttributeSet?) : View(context, attrs) {
    private val paint = Paint().apply {
        isAntiAlias = true
    }
    private val bitmap = BitmapFactory.decodeResource(resources, R.drawable.sample_image)
    private var saturation = 1f // 饱和度 (0-1)

    init {
        setOnClickListener {
            val targetSaturation = if (saturation == 1f) 0f else 1f
            ValueAnimator.ofFloat(saturation, targetSaturation).apply {
                duration = 1000
                interpolator = LinearInterpolator()
                addUpdateListener {
                    saturation = it.animatedValue as Float
                    val matrix = ColorMatrix().apply { setSaturation(saturation) }
                    paint.colorFilter = ColorMatrixColorFilter(matrix)
                    invalidate()
                }
                start()
            }
        }
    }

    override fun onDraw(canvas: Canvas) {
        super.onDraw(canvas)
        canvas.drawBitmap(bitmap, 0f, 0f, paint)

        // 显示饱和度
        paint.colorFilter = null
        paint.color = Color.BLACK
        paint.textSize = 40f
        canvas.drawText("Saturation: ${"%.2f".format(saturation)}", 20f, height - 40f, paint)
    }
}
  • 效果:点击切换彩色和灰度,1 秒动画过渡。
3. 结合 Path

绘制动画心形滤镜。

override fun onDraw(canvas: Canvas) {
    super.onDraw(canvas)
    val path = Path().apply {
        moveTo(width / 2f, height / 4f)
        cubicTo(width / 2f, 0f, 0f, height / 4f, width / 4f, height / 2f)
        cubicTo(width / 2f, height * 3 / 4f, width / 2f, height * 3 / 4f, width / 2f, height * 3 / 4f)
        cubicTo(width / 2f, height * 3 / 4f, width.toFloat(), height / 4f, width * 3 / 4f, height / 2f)
        cubicTo(width / 2f, height * 3 / 4f, width / 2f, height / 4f, width / 2f, height / 4f)
    }
    paint.color = Color.BLUE
    canvas.drawPath(path, paint)

    // 显示饱和度
    paint.colorFilter = null
    paint.color = Color.BLACK
    paint.textSize = 40f
    canvas.drawText("Saturation: ${"%.2f".format(saturation)}", 20f, height - 40f, paint)
}

二、结合 WebView 和 Socket

1. WebView 集成
  • 场景:将滤镜效果渲染为 Bitmap,在 WebView 中显示。
  • 代码
  val bitmap = Bitmap.createBitmap(width, height, Bitmap.Config.ARGB_8888)
  val canvas = Canvas(bitmap)
  draw(canvas) // 调用 onDraw
  val stream = ByteArrayOutputStream()
  bitmap.compress(Bitmap.CompressFormat.PNG, 100, stream)
  val base64 = android.util.Base64.encodeToString(stream.toByteArray(), android.util.Base64.DEFAULT)
  webView.loadUrl("javascript:displayImage('data:image/png;base64,$base64')")
  • JavaScript
  function displayImage(base64) {
      document.getElementById('filterImage').src = base64;
  }
  • 布局
  <WebView
      android:id="@+id/webView"
      android:layout_width="match_parent"
      android:layout_height="match_parent" />
2. Socket 集成
  • 场景:通过 TCP Socket 传输滤镜参数(如饱和度),客户端应用。
  • 客户端代码
  fun updateFilterFromSocket() {
      GlobalScope.launch(Dispatchers.IO) {
          try {
              Socket("192.168.1.100", 12345).use { socket ->
                  val input = BufferedReader(InputStreamReader(socket.getInputStream()))
                  val saturation = input.readLine().toFloat()
                  runOnUiThread {
                      val matrix = ColorMatrix().apply { setSaturation(saturation) }
                      paint.colorFilter = ColorMatrixColorFilter(matrix)
                      invalidate()
                  }
              }
          } catch (e: Exception) {
              Log.e("Socket", "错误: ${e.message}")
          }
      }
  }
  • 服务器代码(示例):
  import java.io.PrintWriter
  import java.net.ServerSocket

  fun startServer() {
      val serverSocket = ServerSocket(12345)
      while (true) {
          val clientSocket = serverSocket.accept()
          Thread {
              val out = PrintWriter(clientSocket.getOutputStream(), true)
              out.println("0.5") // 发送饱和度 0.5
              clientSocket.close()
          }.start()
      }
  }
  • 权限
  <uses-permission android:name="android.permission.INTERNET" />

三、常见问题及注意事项

  1. 性能问题
  • 原因:矩阵计算对大图消耗高。
  • 解决:缩放图片、异步处理、Profiler 监控。
  1. 兼容性
  • API 支持:所有 ColorFilter 在 API 1+ 可用。
  • WebView:确保 ARGB_8888 格式。
  • 低版本:测试复杂矩阵效果。
  1. 内存管理
  • 回收 Bitmap
    kotlin if (!bitmap.isRecycled) bitmap.recycle()
  • OOM 预防:采样图片。
  1. 错误处理
  • Socket 错误
    kotlin try { socket.connect() } catch (e: IOException) { Log.e("Socket", "连接失败: ${e.message}") }
  • 矩阵错误:检查矩阵维度。
  1. 效果优化
  • 动画优化:避免频繁重绘:
    kotlin invalidate(rect) // 局部重绘
  • 多滤镜叠加:使用 postConcat 组合矩阵。

四、学习建议与实践

  1. 学习路径
  • 掌握 ColorMatrix 的矩阵公式。
  • 实现灰度、色调、对比度等效果。
  • 学习动画和网络集成。
  • 优化性能和内存。
  1. 实践项目
  • 简单项目:实现灰度动画。
  • 进阶项目:动态色调调整,结合 Path。
  • 高级项目:通过 Socket 实时更新滤镜。
  1. 调试工具
  • Layout Inspector:检查滤镜效果。
  • Profiler:监控性能。
  • Wireshark:调试 Socket。
  1. 推荐资源
  • ColorMatrix 文档:https://developer.android.com/reference/android/graphics/ColorMatrix
  • 动画文档:https://developer.android.com/guide/topics/graphics/prop-animation
  • 性能优化:https://developer.android.com/topic/performance/vitals/render

五、总结(详解三)

  • 动态动画:使用 ValueAnimator 实现灰度、色调等平滑过渡。
  • WebView 集成:渲染滤镜为 Bitmap,显示在 WebView。
  • Socket 集成:通过 TCP 传输滤镜参数,实时更新。
  • 优化:小尺寸 Bitmap、硬件加速、异步处理。
  • 注意:性能监控、内存管理、兼容性测试。

如果需要更详细的代码(如复杂矩阵变换、Socket 多参数传输)或进一步问题解答,请告诉我!

类似文章

发表回复

您的邮箱地址不会被公开。 必填项已用 * 标注