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

Paint API 之—— ColorFilter 详解(三)

在《Paint API之—— ColorFilter(颜色过滤器)(1/3)》中,我们介绍了 ColorFilter 的基础概念和三大子类(LightingColorFilter、PorterDuffColorFilter、ColorMatrixColorFilter)的基本用法;在《详解(二)》中,我们深入探讨了 ColorMatrixColorFilter 的高级矩阵变换(灰度、色调调整、对比度增强、颜色反转)以及动态动画效果。本篇作为系列的第三部分,将聚焦 ColorFilter 的高级动画效果与 WebView 和 Socket 的集成,以及在复杂场景中的应用(如实时滤镜、远程控制)。我们还将提供综合实战示例,展示如何结合 CanvasPath动画 实现动态滤镜切换,并通过 Socket 传输滤镜参数,或在 WebView 中显示滤镜效果。内容基于 Android 官方文档和开发实践,旨在帮助开发者实现生产级图像处理功能。


一、动态滤镜动画效果

1. 实现原理
  • 使用 ValueAnimatorObjectAnimator 动态调整 ColorFilter 参数(如饱和度、色调、亮度),实现平滑过渡。
  • 结合 ColorMatrix 的矩阵运算,支持复杂效果(如渐变灰度、动态色调调整)。
  • 动画可作用于 BitmapPathText,增强 UI 交互性。
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)
    private var brightness = 0f // 亮度偏移 (0-100)

    init {
        setOnClickListener {
            val targetSaturation = if (saturation == 1f) 0f else 1f
            val targetBrightness = if (brightness == 0f) 100f else 0f
            ValueAnimator.ofFloat(0f, 1f).apply {
                duration = 1000
                interpolator = LinearInterpolator()
                addUpdateListener {
                    val progress = it.animatedValue as Float
                    saturation = saturation + (targetSaturation - saturation) * progress
                    brightness = brightness + (targetBrightness - brightness) * progress
                    updateFilter()
                    invalidate()
                }
                start()
            }
        }
    }

    private fun updateFilter() {
        val matrix = ColorMatrix().apply {
            setSaturation(saturation)
            postConcat(ColorMatrix(floatArrayOf(
                1f, 0f, 0f, 0f, brightness,
                0f, 1f, 0f, 0f, brightness,
                0f, 0f, 1f, 0f, brightness,
                0f, 0f, 0f, 1f, 0f
            )))
        }
        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
        canvas.drawText("Saturation: ${"%.2f".format(saturation)}", 20f, height - 80f, paint)
        canvas.drawText("Brightness: ${"%.2f".format(brightness)}", 20f, height - 40f, paint)
    }
}
  • 说明
  • 动画:同时调整饱和度和亮度,1 秒过渡。
  • ColorMatrix:结合 setSaturation 和亮度矩阵。
  • 效果:点击切换彩色/灰度+亮度,模拟夜间模式。
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 - 80f, paint)
    canvas.drawText("Brightness: ${"%.2f".format(brightness)}", 20f, height - 40f, paint)
}
  • 效果:蓝色心形从彩色变为灰度并增加亮度,点击切换。

二、WebView 和 Socket 集成

1. WebView 集成
  • 场景:将滤镜效果渲染为 Bitmap,通过 WebView 显示,适合网页交互。
  • 代码
  import android.webkit.WebView
  import java.io.ByteArrayOutputStream

  fun renderToWebView(webView: 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')")
      bitmap.recycle()
  }
  • JavaScript
  function displayImage(base64) {
      document.getElementById('filterImage').src = base64;
  }
  • 布局
  <WebView
      android:id="@+id/webView"
      android:layout_width="match_parent"
      android:layout_height="match_parent" />
  • 调用
  findViewById<WebView>(R.id.webView).apply {
      settings.javaScriptEnabled = true
      renderToWebView(this)
  }
  • 注意
  • 使用 ARGB_8888 格式支持透明度。
  • 优化 Bitmap 大小以降低内存占用。
2. Socket 集成
  • 场景:通过 TCP Socket 传输滤镜参数(如饱和度、亮度),客户端实时应用,适合远程控制或多人协作。
  • 客户端代码
  import kotlinx.coroutines.Dispatchers
  import kotlinx.coroutines.GlobalScope
  import kotlinx.coroutines.launch
  import java.io.BufferedReader
  import java.io.InputStreamReader
  import java.net.Socket

  fun updateFilterFromSocket() {
      GlobalScope.launch(Dispatchers.IO) {
          try {
              Socket("192.168.1.100", 12345).use { socket ->
                  val input = BufferedReader(InputStreamReader(socket.getInputStream()))
                  val params = input.readLine().split(",")
                  val newSaturation = params[0].toFloat()
                  val newBrightness = params[1].toFloat()
                  runOnUiThread {
                      ValueAnimator.ofFloat(0f, 1f).apply {
                          duration = 1000
                          addUpdateListener {
                              val progress = it.animatedValue as Float
                              saturation = saturation + (newSaturation - saturation) * progress
                              brightness = brightness + (newBrightness - brightness) * progress
                              updateFilter()
                              invalidate()
                          }
                          start()
                      }
                  }
              }
          } 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,100") // 发送饱和度 0.5,亮度 100
              clientSocket.close()
          }.start()
      }
  }
  • 权限
  <uses-permission android:name="android.permission.INTERNET" />
  • 效果:客户端接收参数,平滑过渡到新滤镜。

三、综合实战示例:实时滤镜控制

以下是一个综合示例,结合动态动画、WebView 和 Socket,实现实时滤镜切换。

1. 自定义 View(RealTimeFilterView)

支持本地动画和远程参数更新。

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
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.GlobalScope
import kotlinx.coroutines.launch
import java.io.BufferedReader
import java.io.InputStreamReader
import java.net.Socket

class RealTimeFilterView(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
    private var brightness = 0f

    init {
        setOnClickListener {
            updateFilterFromSocket()
        }
        updateFilter()
    }

    private fun updateFilter() {
        val matrix = ColorMatrix().apply {
            setSaturation(saturation)
            postConcat(ColorMatrix(floatArrayOf(
                1f, 0f, 0f, 0f, brightness,
                0f, 1f, 0f, 0f, brightness,
                0f, 0f, 1f, 0f, brightness,
                0f, 0f, 0f, 1f, 0f
            )))
        }
        paint.colorFilter = ColorMatrixColorFilter(matrix)
    }

    private fun updateFilterFromSocket() {
        GlobalScope.launch(Dispatchers.IO) {
            try {
                Socket("192.168.1.100", 12345).use { socket ->
                    val input = BufferedReader(InputStreamReader(socket.getInputStream()))
                    val params = input.readLine().split(",")
                    val targetSaturation = params[0].toFloat()
                    val targetBrightness = params[1].toFloat()
                    runOnUiThread {
                        ValueAnimator.ofFloat(0f, 1f).apply {
                            duration = 1000
                            interpolator = LinearInterpolator()
                            addUpdateListener {
                                val progress = it.animatedValue as Float
                                saturation = saturation + (targetSaturation - saturation) * progress
                                brightness = brightness + (targetBrightness - brightness) * progress
                                updateFilter()
                                invalidate()
                            }
                            start()
                        }
                    }
                }
            } catch (e: Exception) {
                Log.e("Socket", "错误: ${e.message}")
            }
        }
    }

    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 - 80f, paint)
        canvas.drawText("Brightness: ${"%.2f".format(brightness)}", 20f, height - 40f, paint)
    }
}
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.RealTimeFilterView
        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. 运行效果
  • 初始显示彩色图片。
  • 点击触发 Socket 通信,接收新参数(如饱和度 0.5,亮度 100)。
  • 图片以 1 秒动画过渡到新滤镜效果,显示当前参数。
5. WebView 集成

在 Activity 中添加 WebView 显示:

import android.webkit.WebView
import android.widget.LinearLayout
import java.io.ByteArrayOutputStream

class MainActivity : AppCompatActivity() {
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main)
        val filterView = findViewById<RealTimeFilterView>(R.id.filterView)
        val webView = findViewById<WebView>(R.id.webView)
        webView.settings.javaScriptEnabled = true
        filterView.setOnClickListener {
            filterView.updateFilterFromSocket()
            val bitmap = Bitmap.createBitmap(filterView.width, filterView.height, Bitmap.Config.ARGB_8888)
            val canvas = Canvas(bitmap)
            filterView.draw(canvas)
            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')")
            bitmap.recycle()
        }
    }
}
  • 布局修改
<LinearLayout
    xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:orientation="vertical">

    <com.example.RealTimeFilterView
        android:id="@+id/filterView"
        android:layout_width="match_parent"
        android:layout_height="0dp"
        android:layout_weight="1" />

    <WebView
        android:id="@+id/webView"
        android:layout_width="match_parent"
        android:layout_height="0dp"
        android:layout_weight="1" />

</LinearLayout>
  • 效果:滤镜效果同步显示在 WebView 中。

四、常见问题及注意事项

  1. 性能问题
  • 原因:复杂矩阵计算和 Socket 通信可能导致延迟。
  • 解决
    • 缩放图片:
      kotlin val scaledBitmap = Bitmap.createScaledBitmap(bitmap, 200, 200, true)
    • 异步处理:
      kotlin GlobalScope.launch(Dispatchers.IO) { /* 滤镜计算 */ }
    • 局部重绘:
      kotlin invalidate(rect)
  1. 兼容性
  • API 支持:ColorFilter 在 API 1+ 可用。
  • WebView:确保 ARGB_8888 格式。
  • 低版本:测试 Socket 和动画效果。
  1. 内存管理
  • 回收 Bitmap
    kotlin if (!bitmap.isRecycled) bitmap.recycle()
  • OOM 预防:采样图片:
    kotlin val options = BitmapFactory.Options().apply { inSampleSize = 2 }
  1. 错误处理
  • Socket 错误
    kotlin try { socket.connect() } catch (e: IOException) { Log.e("Socket", "连接失败: ${e.message}") }
  • 矩阵错误:验证 4×5 矩阵格式。
  1. 效果优化
  • 动画优化:减少重绘频率,使用 LinearInterpolator 确保平滑。
  • 缓存:存储滤镜结果:
    kotlin val cache = LruCache<String, Bitmap>(10 * 1024 * 1024)

五、学习建议与实践

  1. 学习路径
  • 掌握动态动画(饱和度、亮度过渡)。
  • 实现 WebView 和 Socket 集成。
  • 优化性能和内存。
  • 探索多滤镜组合。
  1. 实践项目
  • 简单项目:实现灰度动画。
  • 进阶项目:通过 Socket 控制滤镜。
  • 高级项目:实时滤镜协作系统,结合 WebView 显示。
  1. 调试工具
  • Layout Inspector:检查滤镜效果。
  • Profiler:监控性能。
  • Wireshark:调试 Socket。
  • Chrome DevTools:调试 WebView。
  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,在网页显示。
  • Socket 集成:实时传输滤镜参数,客户端动画更新。
  • 实战示例:结合动画、WebView 和 Socket 实现实时滤镜控制。
  • 优化:小尺寸 Bitmap、硬件加速、异步处理、缓存。
  • 注意:性能监控、内存管理、兼容性测试。

如果需要更详细的代码(如多参数动画、复杂 Socket 协议)或进一步问题解答,请告诉我!

类似文章

发表回复

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