
Android-GPUImage Sample模块源码解析
Android-GPUImage是对标iOS GPUImage的Android端GPU滤镜库,其Sample模块是学习库使用的核心入口,涵盖「图片编辑」和「相机实时滤镜」两大核心场景。本文将从目录结构、启动入口、构建配置三个维度,解析Sample模块的整体架构设计。
Sample模块目录结构解析
Sample模块的目录树如下(核心文件已标注):
android-gpuimage/sample/
├── build.gradle // 模块构建配置
├── proguard-project.txt // ProGuard混淆规则
└── src/main/
├── AndroidManifest.xml // 权限与页面配置
├── java/jp/co/cyberagent/android/gpuimage/sample/
│ ├── GPUImageFilterTools.kt // 滤镜选择器+滤镜创建工具
│ ├── activity/ // 核心页面
│ │ ├── MainActivity.kt // 入口页面:跳转相册/相机场景
│ │ ├── GalleryActivity.kt // 图片编辑场景:选图+滤镜+保存
│ │ └── CameraActivity.kt // 相机场景:实时滤镜+拍照+相机切换
│ └── utils/ // 工具类
│ ├── Camera1Loader.kt // 低版本相机(<Android 5.0)适配
│ ├── Camera2Loader.kt // 高版本相机(≥Android 5.0)适配
│ ├── CameraLoader.kt // 相机适配抽象层
│ ├── ImageExt.kt // 图片扩展(本文暂不涉及)
│ └── ViewExt.kt // View布局监听工具
└── res/ // 资源文件(布局、字符串等)
各核心文件的职责:
| 文件/目录 | 核心作用 |
|---|---|
| GPUImageFilterTools.kt | 封装滤镜列表、弹窗选择器、滤镜实例化(如Sepia、Sobel边缘检测等) |
| MainActivity.kt | 应用入口,处理相机/存储权限申请,跳转Gallery/Camera页面 |
| GalleryActivity.kt | 图片编辑核心页面:选图、滤镜切换、参数调整、保存处理后的图片 |
| CameraActivity.kt | 相机实时滤镜核心页面:相机预览、实时渲染、前后置切换、拍照保存 |
| Camera1/2Loader.kt | 分版本封装相机预览逻辑,解耦Camera1/Camera2 API差异 |
| ViewExt.kt | 简化View布局监听(如doOnLayout),解决GPUImageView尺寸初始化时机问题 |
MainActivity
MainActivity是Sample的启动页,核心逻辑是「权限校验 + 页面跳转」,我们逐行解析关键代码:
1. 页面初始化与点击事件
kotlin
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
// 跳转图片编辑页面(GalleryActivity)
findViewById<View>(R.id.button_gallery).setOnClickListener {
startActivity(Intent(this, GalleryActivity::class.java))
}
// 跳转相机实时滤镜页面(CameraActivity)
findViewById<View>(R.id.button_camera).setOnClickListener {
if (!hasCameraPermission() || !hasStoragePermission()) {
// 权限未授予时申请:相机+存储
ActivityCompat.requestPermissions(
this,
arrayOf(Manifest.permission.CAMERA, Manifest.permission.WRITE_EXTERNAL_STORAGE),
REQUEST_CAMERA
)
} else {
startActivity(Intent(this, CameraActivity::class.java))
}
}
}
- 「相册按钮」直接跳转GalleryActivity(无需权限,相册选图权限由系统相册处理);
- 「相机按钮」需先校验
CAMERA和WRITE_EXTERNAL_STORAGE权限,未授予则触发申请。
2. 权限申请回调
kotlin
override fun onRequestPermissionsResult(
requestCode: Int,
permissions: Array<String>,
grantResults: IntArray
) {
if (requestCode == REQUEST_CAMERA && grantResults.size == 2
&& grantResults[0] == PackageManager.PERMISSION_GRANTED
&& grantResults[1] == PackageManager.PERMISSION_GRANTED
) {
// 双权限授予后跳转相机页面
startActivity(Intent(this, CameraActivity::class.java))
} else {
super.onRequestPermissionsResult(requestCode, permissions, grantResults)
}
}
- 仅当「相机 + 存储」双权限都授予时,才跳转CameraActivity;
- 权限拒绝则不处理(符合Android权限最佳实践)。
3. 权限校验工具方法
kotlin
private fun hasCameraPermission(): Boolean {
return ContextCompat.checkSelfPermission(
this,
Manifest.permission.CAMERA
) == PackageManager.PERMISSION_GRANTED
}
private fun hasStoragePermission(): Boolean {
return ContextCompat.checkSelfPermission(
this,
Manifest.permission.WRITE_EXTERNAL_STORAGE
) == PackageManager.PERMISSION_GRANTED
}
- 封装权限校验逻辑,提高代码复用性;
- 注意:Android 10+已废弃
WRITE_EXTERNAL_STORAGE强制申请,Sample未做高版本适配,实际开发需补充MediaStore保存逻辑。
Sample模块构建与配置解析
1. build.gradle配置
groovy
apply plugin: 'com.android.application'
apply plugin: 'kotlin-android-extensions'
apply plugin: 'kotlin-android'
android {
compileSdkVersion COMPILE_SDK_VERSION as int
defaultConfig {
minSdkVersion MIN_SDK_VERSION as int
targetSdkVersion TARGET_SDK_VERSION as int
versionCode = VERSION_CODE as int
versionName = VERSION_NAME
}
buildTypes {
release {
minifyEnabled true
shrinkResources true
proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
}
}
}
dependencies {
implementation project(':library') // 依赖核心库模块
implementation 'androidx.appcompat:appcompat:1.2.0'
implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk7:$kotlin_version"
}
- 核心依赖:
project(':library')关联Android-GPUImage的核心库,是Sample能使用滤镜API的基础; - 构建类型:Release包开启混淆和资源压缩,符合生产环境要求。
2. AndroidManifest.xml配置
xml
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="jp.co.cyberagent.android.gpuimage.sample">
<!-- 核心权限 -->
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
<uses-permission android:name="android.permission.CAMERA" />
<!-- 相机硬件特性声明 -->
<uses-feature android:name="android.hardware.camera" />
<uses-feature android:name="android.hardware.camera.autofocus" android:required="false" />
<application ...>
<activity android:name=".activity.MainActivity">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
</activity>
<activity android:name=".activity.GalleryActivity" />
<activity
android:name=".activity.CameraActivity"
android:screenOrientation="portrait" // 强制竖屏,简化旋转适配
android:theme="@style/AppTheme.NoActionBar" />
</application>
</manifest>
- 权限声明:明确申请相机和存储权限;
- 相机页面强制竖屏:避免横屏导致的预览方向错乱,降低适配成本;
- 硬件特性:声明依赖相机,但自动对焦为非必需(兼容低配设备)。
3. ProGuard混淆规则(proguard-project.txt)
Sample的混淆规则仅保留基础配置,核心原则是「不混淆Shader相关类、不混淆相机/OpenGL核心API」:
txt
# 无额外keep规则,依赖默认的proguard-android.txt
# 实际项目中需补充:
# -keep class jp.co.cyberagent.android.gpuimage.** { *; }
# 避免滤镜类、GPUImageView被混淆
GalleryActivity
GalleryActivity是Android-GPUImage Sample中「图片编辑」场景的核心页面,完整覆盖「选图 → 滤镜切换 → 参数调整 → 保存图片」全流程。
GalleryActivity的核心业务流程:
启动相册选择器 → 选择图片(返回Uri)→ 加载图片到GPUImageView →
选择滤镜(如Sepia、Sobel边缘检测)→ 调整滤镜参数(SeekBar)→
保存处理后的图片到相册
页面初始化与UI交互逻辑
1. 核心变量与布局初始化
kotlin
class GalleryActivity : AppCompatActivity() {
// 滤镜参数调整器:封装不同滤镜的参数范围映射
private var filterAdjuster: FilterAdjuster? = null
// GPUImageView:核心渲染视图(封装OpenGL渲染、滤镜应用)
private val gpuImageView: GPUImageView by lazy { findViewById<GPUImageView>(R.id.gpuimage) }
// 参数调整SeekBar:控制滤镜强度(如对比度、亮度)
private val seekBar: SeekBar by lazy { findViewById<SeekBar>(R.id.seekBar) }
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_gallery)
// 1. SeekBar监听:实时调整滤镜参数
seekBar.setOnSeekBarChangeListener(object : OnSeekBarChangeListener {
override fun onProgressChanged(seekBar: SeekBar?, progress: Int, fromUser: Boolean) {
filterAdjuster?.adjust(progress) // 调整参数
gpuImageView.requestRender() // 触发重新渲染
}
override fun onStartTrackingTouch(seekBar: SeekBar?) {}
override fun onStopTrackingTouch(seekBar: SeekBar?) {}
})
// 2. 选择滤镜按钮:弹出滤镜选择弹窗
findViewById<View>(R.id.button_choose_filter).setOnClickListener {
GPUImageFilterTools.showDialog(this) { filter ->
switchFilterTo(filter) // 切换滤镜
gpuImageView.requestRender()
}
}
// 3. 保存图片按钮:保存处理后的图片
findViewById<View>(R.id.button_save).setOnClickListener { saveImage() }
// 4. 启动相册选择器
startPhotoPicker()
}
}
核心变量说明:
GPUImageView:库提供的自定义View,封装了OpenGL上下文、纹理渲染、滤镜应用等核心逻辑;FilterAdjuster:Sample封装的工具类,适配不同滤镜的参数范围(如0-100的SeekBar进度映射为滤镜所需的0.0-2.0对比度);SeekBar:用于实时调整滤镜参数,进度变化时触发requestRender()重新渲染。
2. 启动相册选择器
kotlin
private fun startPhotoPicker() {
val photoPickerIntent = Intent(Intent.ACTION_PICK)
photoPickerIntent.type = "image\/*" // 选择所有图片类型
startActivityForResult(photoPickerIntent, REQUEST_PICK_IMAGE)
}
override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) {
when (requestCode) {
REQUEST_PICK_IMAGE -> if (resultCode == RESULT_OK) {
// 相册选图成功:加载图片到GPUImageView
gpuImageView.setImage(data!!.data)
} else {
finish() // 取消选图则关闭页面
}
else -> super.onActivityResult(requestCode, resultCode, data)
}
}
- 通过
Intent.ACTION_PICK调用系统相册,返回图片Uri; - 核心API:
gpuImageView.setImage(Uri)------ 加载图片并渲染到View(注意:该方法内部会处理图片解码,建议在子线程调用,Sample未做优化,实际开发需补充)。
滤镜切换与参数调整核心逻辑
1. 滤镜选择工具:GPUImageFilterTools
GPUImageFilterTools是Sample封装的滤镜管理工具,核心功能:
- 封装所有支持的滤镜列表(如Contrast、Sepia、Sobel Edge Detection等);
- 弹出AlertDialog供用户选择滤镜;
- 根据选择的类型创建对应滤镜实例(如
GPUImageSepiaToneFilter())。
关键代码片段:
kotlin
object GPUImageFilterTools {
fun showDialog(context: Context, listener: (filter: GPUImageFilter) -> Unit) {
val filters = FilterList().apply {
addFilter("Contrast", FilterType.CONTRAST)
addFilter("Sepia", FilterType.SEPIA)
addFilter("Sobel Edge Detection", FilterType.SOBEL_EDGE_DETECTION)
// 更多滤镜...
}
val builder = AlertDialog.Builder(context)
builder.setTitle("Choose a filter")
builder.setItems(filters.names.toTypedArray()) { _, item ->
// 创建选中的滤镜实例并回调
listener(createFilterForType(context, filters.filters[item]))
}
builder.create().show()
}
// 根据类型创建滤镜实例
private fun createFilterForType(context: Context, type: FilterType): GPUImageFilter {
return when (type) {
FilterType.CONTRAST -> GPUImageContrastFilter(2.0f)
FilterType.SEPIA -> GPUImageSepiaToneFilter()
FilterType.SOBEL_EDGE_DETECTION -> GPUImageSobelEdgeDetectionFilter()
// 其他滤镜实例化逻辑...
}
}
}
2. 切换滤镜:switchFilterTo
kotlin
private fun switchFilterTo(filter: GPUImageFilter) {
// 仅当滤镜类型变化时切换(避免重复创建)
if (gpuImageView.filter == null || gpuImageView.filter.javaClass != filter.javaClass) {
gpuImageView.filter = filter // 应用新滤镜
filterAdjuster = FilterAdjuster(filter) // 绑定参数调整器
// 判断滤镜是否支持参数调整(如Invert反转滤镜无参数,隐藏SeekBar)
if (filterAdjuster!!.canAdjust()) {
seekBar.visibility = View.VISIBLE
filterAdjuster!!.adjust(seekBar.progress) // 初始化参数
} else {
seekBar.visibility = View.GONE
}
}
}
核心逻辑:
- 滤镜切换核心API:
gpuImageView.filter = filter; FilterAdjuster.canAdjust():判断滤镜是否有可调整参数(如Sepia有浓度参数,Invert无),动态控制SeekBar显隐;- 避免重复创建:仅当滤镜类型变化时才重新绑定调整器,优化性能。
图片保存逻辑
kotlin
private fun saveImage() {
val fileName = System.currentTimeMillis().toString() + ".jpg"
// 保存图片到相册:GPUImage/Sample目录下
gpuImageView.saveToPictures("GPUImage", fileName) { uri ->
Toast.makeText(this, "Saved: " + uri.toString(), Toast.LENGTH_SHORT).show()
}
}
核心API:gpuImageView.saveToPictures(folderName, fileName, callback)
- 内部逻辑:触发OpenGL渲染,将渲染后的纹理数据编码为JPEG,保存到
DCIM/GPUImage/目录; - 回调参数
uri:保存后的图片Uri,可用于预览或分享; - 注意:Android 10+需适配Scoped Storage,Sample的
saveToPictures已兼容,但需确保应用有存储权限。
关键细节与扩展点
1. 性能优化点
-
gpuImageView.setImage(Uri):内部在主线程解码图片,大图片会导致ANR,建议封装到子线程:kotlinlifecycleScope.launch(Dispatchers.IO) { gpuImageView.setImage(uri) withContext(Dispatchers.Main) { gpuImageView.requestRender() } } -
requestRender():仅在参数变化时触发,避免频繁渲染(Sample已做到)。
2. FilterAdjuster核心原理
FilterAdjuster封装了不同滤镜的参数范围映射,例如对比度滤镜的调整逻辑:
kotlin
class FilterAdjuster(private val filter: GPUImageFilter) {
fun adjust(progress: Int) {
when (filter) {
is GPUImageContrastFilter -> {
// 将0-100的进度映射为0.0-4.0的对比度(默认1.0)
val contrast = progress / 50.0f
filter.setContrast(contrast)
}
// 其他滤镜的参数映射逻辑...
}
}
}
CameraActivity
CameraActivity是Android-GPUImage Sample中「相机实时滤镜」的核心页面,相比静态图片编辑,该场景需解决「相机适配、实时预览、旋转矫正、滤镜渲染」四大核心问题。
CameraActivity的核心业务流程:
页面初始化 → 相机适配(Camera1/Camera2)→ 绑定GPUImageView →
相机预览帧回调 → 实时渲染滤镜 → 滤镜切换/参数调整 →
拍照保存/切换前后置相机
核心变量与页面初始化
1. 核心变量定义
kotlin
class CameraActivity : AppCompatActivity() {
// 核心渲染视图:实时渲染相机预览帧
private val gpuImageView: GPUImageView by lazy { findViewById<GPUImageView>(R.id.surfaceView) }
// 滤镜参数调整SeekBar
private val seekBar: SeekBar by lazy { findViewById<SeekBar>(R.id.seekBar) }
// 相机适配器:根据系统版本选择Camera1/Camera2
private val cameraLoader: CameraLoader by lazy {
if (Build.VERSION.SDK_INT < 21) {
Camera1Loader(this) // Android 5.0以下
} else {
Camera2Loader(this) // Android 5.0及以上
}
}
// 滤镜参数调整器
private var filterAdjuster: FilterAdjuster? = null
// ...
}
核心设计亮点:
CameraLoader抽象层:解耦Camera1和Camera2 API差异,上层无需关注具体相机实现;GPUImageView:配置为「连续渲染模式」,适配实时预览场景。
2. 页面初始化核心逻辑
kotlin
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_camera)
// 1. SeekBar参数调整监听(同GalleryActivity)
seekBar.setOnSeekBarChangeListener(object : OnSeekBarChangeListener {
override fun onProgressChanged(seekBar: SeekBar, progress: Int, fromUser: Boolean) {
filterAdjuster?.adjust(progress)
}
override fun onStartTrackingTouch(seekBar: SeekBar) {}
override fun onStopTrackingTouch(seekBar: SeekBar) {}
})
// 2. 选择滤镜按钮(同GalleryActivity)
findViewById<View>(R.id.button_choose_filter).setOnClickListener {
GPUImageFilterTools.showDialog(this) { filter -> switchFilterTo(filter) }
}
// 3. 拍照按钮:保存当前预览帧
findViewById<View>(R.id.button_capture).setOnClickListener {
saveSnapshot()
}
// 4. 切换前后置相机按钮
findViewById<View>(R.id.img_switch_camera).run {
if (!cameraLoader.hasMultipleCamera()) {
visibility = View.GONE // 无多相机则隐藏
}
setOnClickListener {
cameraLoader.switchCamera() // 切换相机
// 矫正预览旋转角度
gpuImageView.setRotation(getRotation(cameraLoader.getCameraOrientation()))
}
}
// 5. 相机预览帧回调:将相机数据传入GPUImageView
cameraLoader.setOnPreviewFrameListener { data, width, height ->
gpuImageView.updatePreviewFrame(data, width, height)
}
// 6. GPUImageView配置:旋转矫正 + 连续渲染
gpuImageView.setRotation(getRotation(cameraLoader.getCameraOrientation()))
gpuImageView.setRenderMode(GPUImageView.RENDERMODE_CONTINUOUSLY)
}
核心配置说明:
RENDERMODE_CONTINUOUSLY:GPUImageView的连续渲染模式(实时滤镜必需,静态图片用RENDERMODE_WHEN_DIRTY);setRotation:矫正相机预览方向(不同相机/设备的预览角度不同,需适配);setOnPreviewFrameListener:相机预览帧回调,将YUV数据传入GPUImageView进行渲染。
相机适配核心:CameraLoader抽象层
Android系统从5.0(API 21)开始引入Camera2 API,替代老旧的Camera1 API。
Sample通过CameraLoader抽象层解耦两者差异:
1. CameraLoader接口定义(核心方法)
kotlin
interface CameraLoader {
fun onResume(width: Int, height: Int) // 相机启动
fun onPause() // 相机关闭
fun switchCamera() // 切换前后置
fun hasMultipleCamera(): Boolean // 是否有多个相机(前后置)
fun getCameraOrientation(): Int // 获取相机预览方向
fun setOnPreviewFrameListener(listener: (data: ByteArray, width: Int, height: Int) -> Unit)
}
2. Camera2Loader核心逻辑(关键片段)
kotlin
override fun onConfigured(session: CameraCaptureSession) {
cameraInstance ?: return
captureSession = session
val builder = cameraInstance!!.createCaptureRequest(CameraDevice.TEMPLATE_PREVIEW)
builder.addTarget(imageReader!!.surface) // 绑定预览Surface
try {
// 启动连续预览
session.setRepeatingRequest(builder.build(), null, null)
} catch (e: CameraAccessException) {
Log.e(TAG, "Failed to start camera preview", e)
}
}
- Camera2通过
CameraCaptureSession实现连续预览,将预览数据输出到ImageReader的Surface; ImageReader监听预览帧,将YUV数据回调给CameraLoader,最终传入gpuImageView.updatePreviewFrame。
3. 相机生命周期管理
kotlin
override fun onResume() {
super.onResume()
// 等待GPUImageView布局完成后启动相机(避免尺寸未初始化导致预览变形)
gpuImageView.doOnLayout {
cameraLoader.onResume(it.width, it.height)
}
}
override fun onPause() {
cameraLoader.onPause() // 停止相机预览,释放资源
super.onPause()
}
核心细节:
doOnLayout:通过ViewExt.kt的工具方法,确保GPUImageView尺寸初始化完成后再启动相机,避免预览比例错误;- 生命周期对齐:
onResume启动相机,onPause释放相机,符合Android最佳实践。
实时滤镜渲染核心逻辑
1. 预览帧更新
kotlin
cameraLoader.setOnPreviewFrameListener { data, width, height ->
gpuImageView.updatePreviewFrame(data, width, height)
}
updatePreviewFrame:将相机输出的YUV数据转换为OpenGL纹理,结合当前滤镜进行渲染;- 内部逻辑:YUV → RGB转换(通过原生库优化)→ 绑定到GPU纹理 → 应用滤镜Shader → 渲染到GPUImageView。
2. 滤镜切换(与GalleryActivity的差异)
kotlin
private fun switchFilterTo(filter: GPUImageFilter) {
if (gpuImageView.filter == null || gpuImageView.filter!!.javaClass != filter.javaClass) {
gpuImageView.filter = filter // 切换滤镜
filterAdjuster = FilterAdjuster(filter)
filterAdjuster?.adjust(seekBar.progress) // 调整参数
}
}
与GalleryActivity的差异:
- 无需调用
requestRender():因为GPUImageView设置为RENDERMODE_CONTINUOUSLY,会自动连续渲染; - 实时性:滤镜切换/参数调整后,预览画面立即更新,无延迟。
3. 拍照保存(快照)
kotlin
private fun saveSnapshot() {
val folderName = "GPUImage"
val fileName = System.currentTimeMillis().toString() + ".jpg"
// 保存当前渲染帧为图片
gpuImageView.saveToPictures(folderName, fileName) {
Toast.makeText(this, "$folderName/$fileName saved", Toast.LENGTH_SHORT).show()
}
}
saveToPictures在相机场景中,会截取当前GPUImageView的渲染帧(带滤镜),保存为图片;- 逻辑与GalleryActivity一致,但数据源是相机预览帧而非静态图片。
旋转适配核心逻辑
kotlin
private fun getRotation(orientation: Int): Rotation {
return when (orientation) {
90 -> Rotation.ROTATION_90
180 -> Rotation.ROTATION_180
270 -> Rotation.ROTATION_270
else -> Rotation.NORMAL
}
}
- 相机预览的原始方向可能是90/180/270度(不同设备/相机不同);
gpuImageView.setRotation:通过旋转纹理坐标,矫正预览画面方向,避免画面倒置。
关键扩展与优化点
1. 性能优化
- 滤镜选择:避免频繁创建滤镜实例(Sample已做判断,仅类型变化时创建);
- 渲染模式:非实时场景(如拍照后)可切换为
RENDERMODE_WHEN_DIRTY,降低GPU功耗; - 相机分辨率:CameraLoader可添加分辨率选择逻辑,避免高分辨率导致的性能卡顿。
2. 兼容性适配
- Android 13+权限:
CAMERA权限无需再申请WRITE_EXTERNAL_STORAGE,Sample需补充适配; - 相机2兼容性:部分设备Camera2实现有bug,可降级为Camera1;
- 纹理类型:GPUImageView支持
SurfaceView/TextureView,可通过XML属性gpuimage_surface_type配置,TextureView适配性更好但性能略低。
